[lvgl] Make line points templatable (#8502)

This commit is contained in:
Clyde Stubbs 2025-04-09 09:03:29 +10:00 committed by GitHub
parent 1c72fd4674
commit 6240bfff97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 80 additions and 38 deletions

View File

@ -375,6 +375,7 @@ async def to_code(configs):
add_define("LV_COLOR_SCREEN_TRANSP", "1")
for use in helpers.lv_uses:
add_define(f"LV_USE_{use.upper()}")
cg.add_define(f"USE_LVGL_{use.upper()}")
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
cg.add_build_flag("-DLV_CONF_H=1")

View File

@ -17,6 +17,7 @@ from .defines import (
CONF_SHOW_SNOW,
PARTS,
literal,
static_cast,
)
from .lv_validation import lv_bool, lv_color, lv_image, opacity
from .lvcode import (
@ -32,7 +33,6 @@ from .lvcode import (
lv_expr,
lv_obj,
lvgl_comp,
static_cast,
)
from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema
from .types import (

View File

@ -35,6 +35,10 @@ def literal(arg):
return arg
def static_cast(type, value):
return literal(f"static_cast<{type}>({value})")
def call_lambda(lamb: LambdaExpression):
expr = lamb.content.strip()
if expr.startswith("return") and expr.endswith(";"):

View File

@ -285,10 +285,6 @@ class LvExpr(MockLv):
pass
def static_cast(type, value):
return literal(f"static_cast<{type}>({value})")
# Top level mock for generic lv_ calls to be recorded
lv = MockLv("lv_")
# Just generate an expression

View File

@ -90,6 +90,7 @@ inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images
// Parent class for things that wrap an LVGL object
class LvCompound {
public:
virtual ~LvCompound() = default;
virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; }
lv_obj_t *obj{};
};
@ -330,6 +331,19 @@ class LVEncoderListener : public Parented<LvglComponent> {
};
#endif // USE_LVGL_KEY_LISTENER
#ifdef USE_LVGL_LINE
class LvLineType : public LvCompound {
public:
std::vector<lv_point_t> get_points() { return this->points_; }
void set_points(std::vector<lv_point_t> points) {
this->points_ = std::move(points);
lv_line_set_points(this->obj, this->points_.data(), this->points_.size());
}
protected:
std::vector<lv_point_t> points_{};
};
#endif
#if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER)
class LvSelectable : public LvCompound {
public:

View File

@ -19,7 +19,7 @@ from esphome.core.config import StartupTrigger
from esphome.schema_extractors import SCHEMA_EXTRACT
from . import defines as df, lv_validation as lvalid
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
from .defines import CONF_TIME_FORMAT, CONF_X, CONF_Y, LV_GRAD_DIR
from .helpers import add_lv_use, requires_component, validate_printf
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
from .lvcode import LvglComponent, lv_event_t_ptr
@ -87,6 +87,33 @@ ENCODER_SCHEMA = cv.Schema(
}
)
def point_shorthand(value):
"""
A shorthand for a point in the form of x,y
:param value: The value to check
:return: The value as a tuple of x,y
"""
if isinstance(value, str):
try:
x, y = map(int, value.split(","))
return {CONF_X: x, CONF_Y: y}
except ValueError:
pass
raise cv.Invalid("Invalid point format, should be <x_value>, <y_value>")
POINT_SCHEMA = cv.Any(
cv.Schema(
{
cv.Required(CONF_X): cv.templatable(cv.int_),
cv.Required(CONF_Y): cv.templatable(cv.int_),
}
),
point_shorthand,
)
# All LVGL styles and their validators
STYLE_PROPS = {
"align": df.CHILD_ALIGNMENTS.one_of,

View File

@ -1,11 +1,11 @@
import functools
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import Lambda
from ..defines import CONF_MAIN
from ..lvcode import lv
from ..types import LvType
from ..defines import CONF_MAIN, CONF_X, CONF_Y, call_lambda
from ..lvcode import lv_add
from ..schemas import POINT_SCHEMA
from ..types import LvCompound, LvType
from . import Widget, WidgetType
CONF_LINE = "line"
@ -15,47 +15,37 @@ CONF_POINT_LIST_ID = "point_list_id"
lv_point_t = cg.global_ns.struct("lv_point_t")
def point_list(il):
il = cv.string(il)
nl = il.replace(" ", "").split(",")
return [int(n) for n in nl]
def cv_point_list(value):
if not isinstance(value, list):
raise cv.Invalid("List of points required")
values = [point_list(v) for v in value]
if not functools.reduce(lambda f, v: f and len(v) == 2, values, True):
raise cv.Invalid("Points must be a list of x,y integer pairs")
return values
LINE_SCHEMA = {
cv.Required(CONF_POINTS): cv_point_list,
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
cv.Required(CONF_POINTS): cv.ensure_list(POINT_SCHEMA),
}
LINE_MODIFY_SCHEMA = {
cv.Optional(CONF_POINTS): cv_point_list,
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
}
async def process_coord(coord):
if isinstance(coord, Lambda):
coord = call_lambda(
await cg.process_lambda(coord, (), return_type="lv_coord_t")
)
if not coord.endswith("()"):
coord = f"static_cast<lv_coord_t>({coord})"
return cg.RawExpression(coord)
return cg.safe_exp(coord)
class LineType(WidgetType):
def __init__(self):
super().__init__(
CONF_LINE,
LvType("lv_line_t"),
LvType("LvLineType", parents=(LvCompound,)),
(CONF_MAIN,),
LINE_SCHEMA,
modify_schema=LINE_MODIFY_SCHEMA,
)
async def to_code(self, w: Widget, config):
"""For a line object, create and add the points"""
if data := config.get(CONF_POINTS):
points = cg.static_const_array(config[CONF_POINT_LIST_ID], data)
lv.line_set_points(w.obj, points, len(data))
points = [
[await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
for p in config[CONF_POINTS]
]
lv_add(w.var.set_points(points))
line_spec = LineType()

View File

@ -614,6 +614,8 @@ lvgl:
align: center
points:
- 5, 5
- x: !lambda return random_uint32() % 100;
y: !lambda return random_uint32() % 100;
- 70, 70
- 120, 10
- 180, 60
@ -622,6 +624,14 @@ lvgl:
- lvgl.line.update:
id: lv_line_id
line_color: 0xFFFF
points:
- 5, 5
- x: !lambda return random_uint32() % 100;
y: !lambda return random_uint32() % 100;
- 70, 70
- 120, 10
- 180, 60
- 240, 10
- lvgl.page.next:
- switch:
align: right_mid