diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index a6afa12afa4..6377183ef4f 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -48,6 +48,7 @@ from esphome.yaml_util import load_yaml from . import defines as df, helpers, lv_validation as lvalid, widgets from .automation import focused_widgets, layers_to_code, lvgl_update, refreshed_widgets +from .defines import CONF_ALIGN_TO_LAMBDA_ID from .encoders import ( ENCODERS_CONFIG, encoders_to_code, @@ -69,8 +70,16 @@ from .schemas import ( ) from .styles import styles_to_code, theme_to_code from .touchscreens import touchscreen_schema, touchscreens_to_code -from .trigger import add_on_boot_triggers, generate_triggers -from .types import IdleTrigger, PlainTrigger, lv_font_t, lv_group_t, lv_style_t, lvgl_ns +from .trigger import add_on_boot_triggers, generate_align_tos, generate_triggers +from .types import ( + IdleTrigger, + PlainTrigger, + lv_font_t, + lv_group_t, + lv_lambda_t, + lv_style_t, + lvgl_ns, +) from .widgets import ( LvScrActType, Widget, @@ -345,6 +354,7 @@ async def to_code(configs): Widget.widgets_completed = True async with LvContext(): await generate_triggers() + await generate_align_tos(configs[0]) for config in configs: lv_component = await cg.get_variable(config[CONF_ID]) await generate_page_triggers(config) @@ -458,6 +468,7 @@ LVGL_SCHEMA = cv.All( .extend( { cv.GenerateID(CONF_ID): cv.declare_id(LvglComponent), + cv.GenerateID(CONF_ALIGN_TO_LAMBDA_ID): cv.declare_id(lv_lambda_t), cv.GenerateID(df.CONF_DISPLAYS): display_schema, cv.Optional(CONF_COLOR_DEPTH, default=16): cv.one_of(16), cv.Optional( diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 0a53d886693..72345ca98e1 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -504,6 +504,7 @@ CONF_ACCEPTED_CHARS = "accepted_chars" CONF_ADJUSTABLE = "adjustable" CONF_ALIGN = "align" CONF_ALIGN_TO = "align_to" +CONF_ALIGN_TO_LAMBDA_ID = "align_to_lambda_id" CONF_ANGLE_RANGE = "angle_range" CONF_ANIMATED = "animated" CONF_ANIMATION = "animation" diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 8de82d50c01..21d1e0d4176 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -128,10 +128,19 @@ class LvPageType : public Parented { bool skip; }; -using LvLambdaType = std::function; -using set_value_lambda_t = std::function; using event_callback_t = void(lv_event_t *); -using text_lambda_t = std::function; + +class LvLambdaComponent : public Component { + public: + LvLambdaComponent(void (*callback)()) : callback_(callback) {} + + void setup() override { this->callback_(); } + // execute after the LvglComponent is setup + float get_setup_priority() const override { return setup_priority::PROCESSOR - 5; } + + protected: + void (*callback_)(); +}; template class ObjUpdateAction : public Action { public: diff --git a/esphome/components/lvgl/trigger.py b/esphome/components/lvgl/trigger.py index 077ff06bb75..54309cdf891 100644 --- a/esphome/components/lvgl/trigger.py +++ b/esphome/components/lvgl/trigger.py @@ -8,10 +8,13 @@ from esphome.const import ( CONF_X, CONF_Y, ) +from esphome.cpp_generator import new_Pvariable +from esphome.cpp_helpers import register_component from .defines import ( CONF_ALIGN, CONF_ALIGN_TO, + CONF_ALIGN_TO_LAMBDA_ID, DIRECTIONS, LV_EVENT_MAP, LV_EVENT_TRIGGERS, @@ -89,14 +92,32 @@ async def generate_triggers(): await add_on_boot_triggers(w.config.get(CONF_ON_BOOT, ())) - # Generate align to directives while we're here - if align_to := w.config.get(CONF_ALIGN_TO): + +async def generate_align_tos(config: dict): + """ + Called once, with a full lvgl configuration to emit deferred align_to actions as a component + that executes after the LVGL setup. This is required since align_to actions are not recalculated on layout changes + and so must be applied after the display is properly laid out. + :param config: + :return: + """ + align_tos = tuple( + w for w in widget_map.values() if w.config and CONF_ALIGN_TO in w.config + ) + if align_tos: + async with LambdaContext(where="align_to") as context: + for w in align_tos: + align_to = w.config[CONF_ALIGN_TO] target = widget_map[align_to[CONF_ID]].obj align = literal(align_to[CONF_ALIGN]) x = align_to[CONF_X] y = align_to[CONF_Y] lv.obj_align_to(w.obj, target, align, x, y) + action_id = config[CONF_ALIGN_TO_LAMBDA_ID] + var = new_Pvariable(action_id, await context.get_lambda()) + await register_component(var, {}) + async def add_trigger(conf, w, *events, is_selected=None): is_selected = is_selected or w.is_selected() diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index 8343a542a9f..686e4292679 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -1,7 +1,7 @@ from esphome import automation, codegen as cg from esphome.const import CONF_TEXT, CONF_VALUE from esphome.cpp_generator import MockObj -from esphome.cpp_types import esphome_ns +from esphome.cpp_types import Component, esphome_ns from .defines import lvgl_ns @@ -51,7 +51,7 @@ IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template()) ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action) LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition) LvglAction = lvgl_ns.class_("LvglAction", automation.Action) -lv_lambda_t = lvgl_ns.class_("LvLambdaType") +lv_lambda_t = lvgl_ns.class_("LvLambdaComponent", Component) LvCompound = lvgl_ns.class_("LvCompound") lv_font_t = cg.global_ns.class_("lv_font_t") lv_style_t = cg.global_ns.struct("lv_style_t") diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index b168578a98c..821476a72b8 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -288,7 +288,9 @@ lvgl: - label: text: "Hello shiny day" text_color: 0xFFFFFF - align: bottom_mid + align_to: + id: hello_label + align: OUT_LEFT_TOP - label: id: setup_lambda_label # Test lambda in widget property during setup (LvContext)