[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") add_define("LV_COLOR_SCREEN_TRANSP", "1")
for use in helpers.lv_uses: for use in helpers.lv_uses:
add_define(f"LV_USE_{use.upper()}") 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) lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h()) write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
cg.add_build_flag("-DLV_CONF_H=1") cg.add_build_flag("-DLV_CONF_H=1")

View File

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

View File

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

View File

@ -285,10 +285,6 @@ class LvExpr(MockLv):
pass pass
def static_cast(type, value):
return literal(f"static_cast<{type}>({value})")
# Top level mock for generic lv_ calls to be recorded # Top level mock for generic lv_ calls to be recorded
lv = MockLv("lv_") lv = MockLv("lv_")
# Just generate an expression # 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 // Parent class for things that wrap an LVGL object
class LvCompound { class LvCompound {
public: public:
virtual ~LvCompound() = default;
virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; } virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; }
lv_obj_t *obj{}; lv_obj_t *obj{};
}; };
@ -330,6 +331,19 @@ class LVEncoderListener : public Parented<LvglComponent> {
}; };
#endif // USE_LVGL_KEY_LISTENER #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) #if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER)
class LvSelectable : public LvCompound { class LvSelectable : public LvCompound {
public: public:

View File

@ -19,7 +19,7 @@ from esphome.core.config import StartupTrigger
from esphome.schema_extractors import SCHEMA_EXTRACT from esphome.schema_extractors import SCHEMA_EXTRACT
from . import defines as df, lv_validation as lvalid 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 .helpers import add_lv_use, requires_component, validate_printf
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
from .lvcode import LvglComponent, lv_event_t_ptr 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 # All LVGL styles and their validators
STYLE_PROPS = { STYLE_PROPS = {
"align": df.CHILD_ALIGNMENTS.one_of, "align": df.CHILD_ALIGNMENTS.one_of,

View File

@ -1,11 +1,11 @@
import functools
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.core import Lambda
from ..defines import CONF_MAIN from ..defines import CONF_MAIN, CONF_X, CONF_Y, call_lambda
from ..lvcode import lv from ..lvcode import lv_add
from ..types import LvType from ..schemas import POINT_SCHEMA
from ..types import LvCompound, LvType
from . import Widget, WidgetType from . import Widget, WidgetType
CONF_LINE = "line" CONF_LINE = "line"
@ -15,47 +15,37 @@ CONF_POINT_LIST_ID = "point_list_id"
lv_point_t = cg.global_ns.struct("lv_point_t") 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 = { LINE_SCHEMA = {
cv.Required(CONF_POINTS): cv_point_list, cv.Required(CONF_POINTS): cv.ensure_list(POINT_SCHEMA),
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
} }
LINE_MODIFY_SCHEMA = {
cv.Optional(CONF_POINTS): cv_point_list, async def process_coord(coord):
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), 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): class LineType(WidgetType):
def __init__(self): def __init__(self):
super().__init__( super().__init__(
CONF_LINE, CONF_LINE,
LvType("lv_line_t"), LvType("LvLineType", parents=(LvCompound,)),
(CONF_MAIN,), (CONF_MAIN,),
LINE_SCHEMA, LINE_SCHEMA,
modify_schema=LINE_MODIFY_SCHEMA,
) )
async def to_code(self, w: Widget, config): async def to_code(self, w: Widget, config):
"""For a line object, create and add the points""" points = [
if data := config.get(CONF_POINTS): [await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
points = cg.static_const_array(config[CONF_POINT_LIST_ID], data) for p in config[CONF_POINTS]
lv.line_set_points(w.obj, points, len(data)) ]
lv_add(w.var.set_points(points))
line_spec = LineType() line_spec = LineType()

View File

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