mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[lvgl] Add lvgl.widget.focus action and related triggers. (#7315)
This commit is contained in:
		| @@ -21,8 +21,8 @@ from esphome.final_validate import full_config | |||||||
| from esphome.helpers import write_file_if_changed | from esphome.helpers import write_file_if_changed | ||||||
|  |  | ||||||
| from . import defines as df, helpers, lv_validation as lvalid | from . import defines as df, helpers, lv_validation as lvalid | ||||||
| from .automation import disp_update, update_to_code | from .automation import disp_update, focused_widgets, update_to_code | ||||||
| from .defines import CONF_SKIP | from .defines import CONF_ADJUSTABLE, CONF_SKIP | ||||||
| from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code | from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code | ||||||
| from .lv_validation import lv_bool, lv_images_used | from .lv_validation import lv_bool, lv_images_used | ||||||
| from .lvcode import LvContext, LvglComponent | from .lvcode import LvContext, LvglComponent | ||||||
| @@ -67,7 +67,7 @@ from .widgets.lv_bar import bar_spec | |||||||
| from .widgets.meter import meter_spec | from .widgets.meter import meter_spec | ||||||
| from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code | from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code | ||||||
| from .widgets.obj import obj_spec | from .widgets.obj import obj_spec | ||||||
| from .widgets.page import add_pages, page_spec | from .widgets.page import add_pages, generate_page_triggers, page_spec | ||||||
| from .widgets.roller import roller_spec | from .widgets.roller import roller_spec | ||||||
| from .widgets.slider import slider_spec | from .widgets.slider import slider_spec | ||||||
| from .widgets.spinbox import spinbox_spec | from .widgets.spinbox import spinbox_spec | ||||||
| @@ -182,6 +182,14 @@ def final_validation(config): | |||||||
|             raise cv.Invalid( |             raise cv.Invalid( | ||||||
|                 "Using RGBA or RGB24 in image config not compatible with LVGL", path |                 "Using RGBA or RGB24 in image config not compatible with LVGL", path | ||||||
|             ) |             ) | ||||||
|  |     for w in focused_widgets: | ||||||
|  |         path = global_config.get_path_for_id(w) | ||||||
|  |         widget_conf = global_config.get_config_for_path(path[:-1]) | ||||||
|  |         if CONF_ADJUSTABLE in widget_conf and not widget_conf[CONF_ADJUSTABLE]: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 "A non adjustable arc may not be focused", | ||||||
|  |                 path, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
| @@ -271,6 +279,7 @@ async def to_code(config): | |||||||
|     Widget.set_completed() |     Widget.set_completed() | ||||||
|     async with LvContext(lv_component): |     async with LvContext(lv_component): | ||||||
|         await generate_triggers(lv_component) |         await generate_triggers(lv_component) | ||||||
|  |         await generate_page_triggers(lv_component, config) | ||||||
|         for conf in config.get(CONF_ON_IDLE, ()): |         for conf in config.get(CONF_ON_IDLE, ()): | ||||||
|             templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32) |             templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32) | ||||||
|             idle_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, templ) |             idle_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, templ) | ||||||
|   | |||||||
| @@ -4,13 +4,15 @@ from typing import Callable | |||||||
| from esphome import automation | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ID, CONF_TIMEOUT | from esphome.const import CONF_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT | ||||||
| from esphome.cpp_generator import RawExpression | from esphome.cpp_generator import RawExpression, get_variable | ||||||
| from esphome.cpp_types import nullptr | from esphome.cpp_types import nullptr | ||||||
|  |  | ||||||
| from .defines import ( | from .defines import ( | ||||||
|     CONF_DISP_BG_COLOR, |     CONF_DISP_BG_COLOR, | ||||||
|     CONF_DISP_BG_IMAGE, |     CONF_DISP_BG_IMAGE, | ||||||
|  |     CONF_EDITING, | ||||||
|  |     CONF_FREEZE, | ||||||
|     CONF_LVGL_ID, |     CONF_LVGL_ID, | ||||||
|     CONF_SHOW_SNOW, |     CONF_SHOW_SNOW, | ||||||
|     literal, |     literal, | ||||||
| @@ -30,6 +32,7 @@ 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 | from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA | ||||||
| from .types import ( | from .types import ( | ||||||
| @@ -38,7 +41,9 @@ from .types import ( | |||||||
|     LvglCondition, |     LvglCondition, | ||||||
|     ObjUpdateAction, |     ObjUpdateAction, | ||||||
|     lv_disp_t, |     lv_disp_t, | ||||||
|  |     lv_group_t, | ||||||
|     lv_obj_t, |     lv_obj_t, | ||||||
|  |     lv_pseudo_button_t, | ||||||
| ) | ) | ||||||
| from .widgets import ( | from .widgets import ( | ||||||
|     Widget, |     Widget, | ||||||
| @@ -48,6 +53,9 @@ from .widgets import ( | |||||||
|     wait_for_widgets, |     wait_for_widgets, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | # Record widgets that are used in a focused action here | ||||||
|  | focused_widgets = set() | ||||||
|  |  | ||||||
|  |  | ||||||
| async def action_to_code( | async def action_to_code( | ||||||
|     widgets: list[Widget], |     widgets: list[Widget], | ||||||
| @@ -234,3 +242,72 @@ async def obj_show_to_code(config, action_id, template_arg, args): | |||||||
|     return await action_to_code( |     return await action_to_code( | ||||||
|         await get_widgets(config), do_show, action_id, template_arg, args |         await get_widgets(config), do_show, action_id, template_arg, args | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def focused_id(value): | ||||||
|  |     value = cv.use_id(lv_pseudo_button_t)(value) | ||||||
|  |     focused_widgets.add(value) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.widget.focus", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     cv.Any( | ||||||
|  |         cv.maybe_simple_value( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_GROUP): cv.use_id(lv_group_t), | ||||||
|  |                 cv.Required(CONF_ACTION): cv.one_of( | ||||||
|  |                     "MARK", "RESTORE", "NEXT", "PREVIOUS", upper=True | ||||||
|  |                 ), | ||||||
|  |                 cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), | ||||||
|  |                 cv.Optional(CONF_FREEZE, default=False): cv.boolean, | ||||||
|  |             }, | ||||||
|  |             key=CONF_ACTION, | ||||||
|  |         ), | ||||||
|  |         cv.maybe_simple_value( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_ID): focused_id, | ||||||
|  |                 cv.Optional(CONF_FREEZE, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_EDITING, default=False): cv.boolean, | ||||||
|  |             }, | ||||||
|  |             key=CONF_ID, | ||||||
|  |         ), | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def widget_focus(config, action_id, template_arg, args): | ||||||
|  |     widget = await get_widgets(config) | ||||||
|  |     if widget: | ||||||
|  |         widget = widget[0] | ||||||
|  |         group = static_cast( | ||||||
|  |             lv_group_t.operator("ptr"), lv_expr.obj_get_group(widget.obj) | ||||||
|  |         ) | ||||||
|  |     elif group := config.get(CONF_GROUP): | ||||||
|  |         group = await get_variable(group) | ||||||
|  |     else: | ||||||
|  |         group = lv_expr.group_get_default() | ||||||
|  |  | ||||||
|  |     async with LambdaContext(parameters=args, where=action_id) as context: | ||||||
|  |         if widget: | ||||||
|  |             lv.group_focus_freeze(group, False) | ||||||
|  |             lv.group_focus_obj(widget.obj) | ||||||
|  |             if config[CONF_EDITING]: | ||||||
|  |                 lv.group_set_editing(group, True) | ||||||
|  |         else: | ||||||
|  |             action = config[CONF_ACTION] | ||||||
|  |             lv_comp = await get_variable(config[CONF_LVGL_ID]) | ||||||
|  |             if action == "MARK": | ||||||
|  |                 context.add(lv_comp.set_focus_mark(group)) | ||||||
|  |             else: | ||||||
|  |                 lv.group_focus_freeze(group, False) | ||||||
|  |                 if action == "RESTORE": | ||||||
|  |                     context.add(lv_comp.restore_focus_mark(group)) | ||||||
|  |                 elif action == "NEXT": | ||||||
|  |                     lv.group_focus_next(group) | ||||||
|  |                 else: | ||||||
|  |                     lv.group_focus_prev(group) | ||||||
|  |  | ||||||
|  |         if config[CONF_FREEZE]: | ||||||
|  |             lv.group_focus_freeze(group, True) | ||||||
|  |         var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) | ||||||
|  |         return var | ||||||
|   | |||||||
| @@ -148,6 +148,7 @@ LV_EVENT_MAP = { | |||||||
|     "DEFOCUS": "DEFOCUSED", |     "DEFOCUS": "DEFOCUSED", | ||||||
|     "READY": "READY", |     "READY": "READY", | ||||||
|     "CANCEL": "CANCEL", |     "CANCEL": "CANCEL", | ||||||
|  |     "ALL_EVENTS": "ALL", | ||||||
| } | } | ||||||
|  |  | ||||||
| LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) | LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) | ||||||
| @@ -390,6 +391,7 @@ CONF_DEFAULT_FONT = "default_font" | |||||||
| CONF_DEFAULT_GROUP = "default_group" | CONF_DEFAULT_GROUP = "default_group" | ||||||
| CONF_DIR = "dir" | CONF_DIR = "dir" | ||||||
| CONF_DISPLAYS = "displays" | CONF_DISPLAYS = "displays" | ||||||
|  | CONF_EDITING = "editing" | ||||||
| CONF_ENCODERS = "encoders" | CONF_ENCODERS = "encoders" | ||||||
| CONF_END_ANGLE = "end_angle" | CONF_END_ANGLE = "end_angle" | ||||||
| CONF_END_VALUE = "end_value" | CONF_END_VALUE = "end_value" | ||||||
| @@ -401,6 +403,7 @@ CONF_FLEX_ALIGN_MAIN = "flex_align_main" | |||||||
| CONF_FLEX_ALIGN_CROSS = "flex_align_cross" | CONF_FLEX_ALIGN_CROSS = "flex_align_cross" | ||||||
| CONF_FLEX_ALIGN_TRACK = "flex_align_track" | CONF_FLEX_ALIGN_TRACK = "flex_align_track" | ||||||
| CONF_FLEX_GROW = "flex_grow" | CONF_FLEX_GROW = "flex_grow" | ||||||
|  | CONF_FREEZE = "freeze" | ||||||
| CONF_FULL_REFRESH = "full_refresh" | CONF_FULL_REFRESH = "full_refresh" | ||||||
| CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos" | CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos" | ||||||
| CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos" | CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos" | ||||||
| @@ -428,9 +431,9 @@ CONF_MSGBOXES = "msgboxes" | |||||||
| CONF_OBJ = "obj" | CONF_OBJ = "obj" | ||||||
| CONF_OFFSET_X = "offset_x" | CONF_OFFSET_X = "offset_x" | ||||||
| CONF_OFFSET_Y = "offset_y" | CONF_OFFSET_Y = "offset_y" | ||||||
|  | CONF_ONE_CHECKED = "one_checked" | ||||||
| CONF_ONE_LINE = "one_line" | CONF_ONE_LINE = "one_line" | ||||||
| CONF_ON_SELECT = "on_select" | CONF_ON_SELECT = "on_select" | ||||||
| CONF_ONE_CHECKED = "one_checked" |  | ||||||
| CONF_NEXT = "next" | CONF_NEXT = "next" | ||||||
| CONF_PAD_ROW = "pad_row" | CONF_PAD_ROW = "pad_row" | ||||||
| CONF_PAD_COLUMN = "pad_column" | CONF_PAD_COLUMN = "pad_column" | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ LVGL_COMP = "lv_component"  # used as a lambda argument in lvgl_comp() | |||||||
| LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent) | LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent) | ||||||
| LVGL_COMP_ARG = [(LvglComponent.operator("ptr"), LVGL_COMP)] | LVGL_COMP_ARG = [(LvglComponent.operator("ptr"), LVGL_COMP)] | ||||||
| lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr") | lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr") | ||||||
| EVENT_ARG = [(lv_event_t_ptr, "ev")] | EVENT_ARG = [(lv_event_t_ptr, "event")] | ||||||
| # Two custom events; API_EVENT is fired when an entity is updated remotely by an API interaction; | # Two custom events; API_EVENT is fired when an entity is updated remotely by an API interaction; | ||||||
| # UPDATE_EVENT is fired when an entity is programmatically updated locally. | # UPDATE_EVENT is fired when an entity is programmatically updated locally. | ||||||
| # VALUE_CHANGED is the event generated by LVGL when an entity's value changes through user interaction. | # VALUE_CHANGED is the event generated by LVGL when an entity's value changes through user interaction. | ||||||
| @@ -291,6 +291,10 @@ 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 | ||||||
|   | |||||||
| @@ -15,6 +15,60 @@ static void log_cb(const char *buf) { | |||||||
| } | } | ||||||
| #endif  // LV_USE_LOG | #endif  // LV_USE_LOG | ||||||
|  |  | ||||||
|  | static const char *const EVENT_NAMES[] = { | ||||||
|  |     "NONE", | ||||||
|  |     "PRESSED", | ||||||
|  |     "PRESSING", | ||||||
|  |     "PRESS_LOST", | ||||||
|  |     "SHORT_CLICKED", | ||||||
|  |     "LONG_PRESSED", | ||||||
|  |     "LONG_PRESSED_REPEAT", | ||||||
|  |     "CLICKED", | ||||||
|  |     "RELEASED", | ||||||
|  |     "SCROLL_BEGIN", | ||||||
|  |     "SCROLL_END", | ||||||
|  |     "SCROLL", | ||||||
|  |     "GESTURE", | ||||||
|  |     "KEY", | ||||||
|  |     "FOCUSED", | ||||||
|  |     "DEFOCUSED", | ||||||
|  |     "LEAVE", | ||||||
|  |     "HIT_TEST", | ||||||
|  |     "COVER_CHECK", | ||||||
|  |     "REFR_EXT_DRAW_SIZE", | ||||||
|  |     "DRAW_MAIN_BEGIN", | ||||||
|  |     "DRAW_MAIN", | ||||||
|  |     "DRAW_MAIN_END", | ||||||
|  |     "DRAW_POST_BEGIN", | ||||||
|  |     "DRAW_POST", | ||||||
|  |     "DRAW_POST_END", | ||||||
|  |     "DRAW_PART_BEGIN", | ||||||
|  |     "DRAW_PART_END", | ||||||
|  |     "VALUE_CHANGED", | ||||||
|  |     "INSERT", | ||||||
|  |     "REFRESH", | ||||||
|  |     "READY", | ||||||
|  |     "CANCEL", | ||||||
|  |     "DELETE", | ||||||
|  |     "CHILD_CHANGED", | ||||||
|  |     "CHILD_CREATED", | ||||||
|  |     "CHILD_DELETED", | ||||||
|  |     "SCREEN_UNLOAD_START", | ||||||
|  |     "SCREEN_LOAD_START", | ||||||
|  |     "SCREEN_LOADED", | ||||||
|  |     "SCREEN_UNLOADED", | ||||||
|  |     "SIZE_CHANGED", | ||||||
|  |     "STYLE_CHANGED", | ||||||
|  |     "LAYOUT_CHANGED", | ||||||
|  |     "GET_SELF_SIZE", | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | std::string lv_event_code_name_for(uint8_t event_code) { | ||||||
|  |   if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) { | ||||||
|  |     return EVENT_NAMES[event_code]; | ||||||
|  |   } | ||||||
|  |   return str_sprintf("%2d", event_code); | ||||||
|  | } | ||||||
| static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) { | static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) { | ||||||
|   // make sure all coordinates are even |   // make sure all coordinates are even | ||||||
|   if (area->x1 & 1) |   if (area->x1 & 1) | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ namespace lvgl { | |||||||
|  |  | ||||||
| extern lv_event_code_t lv_api_event;     // NOLINT | extern lv_event_code_t lv_api_event;     // NOLINT | ||||||
| extern lv_event_code_t lv_update_event;  // NOLINT | extern lv_event_code_t lv_update_event;  // NOLINT | ||||||
|  | extern std::string lv_event_code_name_for(uint8_t event_code); | ||||||
| extern bool lv_is_pre_initialise(); | extern bool lv_is_pre_initialise(); | ||||||
| #ifdef USE_LVGL_COLOR | #ifdef USE_LVGL_COLOR | ||||||
| inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } | inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } | ||||||
| @@ -143,6 +144,13 @@ class LvglComponent : public PollingComponent { | |||||||
|   void show_next_page(lv_scr_load_anim_t anim, uint32_t time); |   void show_next_page(lv_scr_load_anim_t anim, uint32_t time); | ||||||
|   void show_prev_page(lv_scr_load_anim_t anim, uint32_t time); |   void show_prev_page(lv_scr_load_anim_t anim, uint32_t time); | ||||||
|   void set_page_wrap(bool wrap) { this->page_wrap_ = wrap; } |   void set_page_wrap(bool wrap) { this->page_wrap_ = wrap; } | ||||||
|  |   void set_focus_mark(lv_group_t *group) { this->focus_marks_[group] = lv_group_get_focused(group); } | ||||||
|  |   void restore_focus_mark(lv_group_t *group) { | ||||||
|  |     auto *mark = this->focus_marks_[group]; | ||||||
|  |     if (mark != nullptr) { | ||||||
|  |       lv_group_focus_obj(mark); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void write_random_(); |   void write_random_(); | ||||||
| @@ -158,6 +166,7 @@ class LvglComponent : public PollingComponent { | |||||||
|   bool show_snow_{}; |   bool show_snow_{}; | ||||||
|   lv_coord_t snow_line_{}; |   lv_coord_t snow_line_{}; | ||||||
|   bool page_wrap_{true}; |   bool page_wrap_{true}; | ||||||
|  |   std::map<lv_group_t *, lv_obj_t *> focus_marks_{}; | ||||||
|  |  | ||||||
|   std::vector<std::function<void(LvglComponent *lv_component)>> init_lambdas_; |   std::vector<std::function<void(LvglComponent *lv_component)>> init_lambdas_; | ||||||
|   CallbackManager<void(uint32_t)> idle_callbacks_{}; |   CallbackManager<void(uint32_t)> idle_callbacks_{}; | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ from . import defines as df, lv_validation as lvalid | |||||||
| from .defines import CONF_TIME_FORMAT | from .defines import CONF_TIME_FORMAT | ||||||
| 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_image | from .lv_validation import lv_color, lv_font, lv_image | ||||||
| from .lvcode import LvglComponent | from .lvcode import LvglComponent, lv_event_t_ptr | ||||||
| from .types import ( | from .types import ( | ||||||
|     LVEncoderListener, |     LVEncoderListener, | ||||||
|     LvType, |     LvType, | ||||||
| @@ -215,14 +215,12 @@ def automation_schema(typ: LvType): | |||||||
|         events = df.LV_EVENT_TRIGGERS + (CONF_ON_VALUE,) |         events = df.LV_EVENT_TRIGGERS + (CONF_ON_VALUE,) | ||||||
|     else: |     else: | ||||||
|         events = df.LV_EVENT_TRIGGERS |         events = df.LV_EVENT_TRIGGERS | ||||||
|     if isinstance(typ, LvType): |     args = [typ.get_arg_type()] if isinstance(typ, LvType) else [] | ||||||
|         template = Trigger.template(typ.get_arg_type()) |     args.append(lv_event_t_ptr) | ||||||
|     else: |  | ||||||
|         template = Trigger.template() |  | ||||||
|     return { |     return { | ||||||
|         cv.Optional(event): validate_automation( |         cv.Optional(event): validate_automation( | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(template), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger.template(*args)), | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|         for event in events |         for event in events | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ from .lvcode import ( | |||||||
|     LvConditional, |     LvConditional, | ||||||
|     lv, |     lv, | ||||||
|     lv_add, |     lv_add, | ||||||
|  |     lv_event_t_ptr, | ||||||
| ) | ) | ||||||
| from .types import LV_EVENT | from .types import LV_EVENT | ||||||
| from .widgets import widget_map | from .widgets import widget_map | ||||||
| @@ -65,10 +66,10 @@ async def generate_triggers(lv_component): | |||||||
| async def add_trigger(conf, lv_component, w, *events): | async def add_trigger(conf, lv_component, w, *events): | ||||||
|     tid = conf[CONF_TRIGGER_ID] |     tid = conf[CONF_TRIGGER_ID] | ||||||
|     trigger = cg.new_Pvariable(tid) |     trigger = cg.new_Pvariable(tid) | ||||||
|     args = w.get_args() |     args = w.get_args() + [(lv_event_t_ptr, "event")] | ||||||
|     value = w.get_value() |     value = w.get_value() | ||||||
|     await automation.build_automation(trigger, args, conf) |     await automation.build_automation(trigger, args, conf) | ||||||
|     async with LambdaContext(EVENT_ARG, where=tid) as context: |     async with LambdaContext(EVENT_ARG, where=tid) as context: | ||||||
|         with LvConditional(w.is_selected()): |         with LvConditional(w.is_selected()): | ||||||
|             lv_add(trigger.trigger(value)) |             lv_add(trigger.trigger(value, literal("event"))) | ||||||
|     lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), *events)) |     lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), *events)) | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ lv_group_t = cg.global_ns.struct("lv_group_t") | |||||||
| LVTouchListener = lvgl_ns.class_("LVTouchListener") | LVTouchListener = lvgl_ns.class_("LVTouchListener") | ||||||
| LVEncoderListener = lvgl_ns.class_("LVEncoderListener") | LVEncoderListener = lvgl_ns.class_("LVEncoderListener") | ||||||
| lv_obj_t = LvType("lv_obj_t") | lv_obj_t = LvType("lv_obj_t") | ||||||
| lv_page_t = cg.global_ns.class_("LvPageType", LvCompound) | lv_page_t = LvType("LvPageType", parents=(LvCompound,)) | ||||||
| lv_img_t = LvType("lv_img_t") | lv_img_t = LvType("lv_img_t") | ||||||
|  |  | ||||||
| LV_EVENT = MockObj(base="LV_EVENT_", op="") | LV_EVENT = MockObj(base="LV_EVENT_", op="") | ||||||
|   | |||||||
| @@ -225,7 +225,7 @@ def get_widget_generator(wid): | |||||||
|         yield |         yield | ||||||
|  |  | ||||||
|  |  | ||||||
| async def get_widget_(wid: Widget): | async def get_widget_(wid): | ||||||
|     if obj := widget_map.get(wid): |     if obj := widget_map.get(wid): | ||||||
|         return obj |         return obj | ||||||
|     return await FakeAwaitable(get_widget_generator(wid)) |     return await FakeAwaitable(get_widget_generator(wid)) | ||||||
| @@ -348,8 +348,6 @@ async def set_obj_properties(w: Widget, config): | |||||||
|     if group := config.get(CONF_GROUP): |     if group := config.get(CONF_GROUP): | ||||||
|         group = await cg.get_variable(group) |         group = await cg.get_variable(group) | ||||||
|         lv.group_add_obj(group, w.obj) |         lv.group_add_obj(group, w.obj) | ||||||
|     flag_clr = set() |  | ||||||
|     flag_set = set() |  | ||||||
|     props = parts[CONF_MAIN][CONF_DEFAULT] |     props = parts[CONF_MAIN][CONF_DEFAULT] | ||||||
|     lambs = {} |     lambs = {} | ||||||
|     flag_set = set() |     flag_set = set() | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_GROUP, | ||||||
|     CONF_MAX_VALUE, |     CONF_MAX_VALUE, | ||||||
|     CONF_MIN_VALUE, |     CONF_MIN_VALUE, | ||||||
|     CONF_MODE, |     CONF_MODE, | ||||||
| @@ -20,7 +21,7 @@ from ..defines import ( | |||||||
|     literal, |     literal, | ||||||
| ) | ) | ||||||
| from ..lv_validation import angle, get_start_value, lv_float | from ..lv_validation import angle, get_start_value, lv_float | ||||||
| from ..lvcode import lv, lv_obj | from ..lvcode import lv, lv_expr, lv_obj | ||||||
| from ..types import LvNumber, NumberType | from ..types import LvNumber, NumberType | ||||||
| from . import Widget | from . import Widget | ||||||
|  |  | ||||||
| @@ -69,6 +70,9 @@ class ArcType(NumberType): | |||||||
|         if config.get(CONF_ADJUSTABLE) is False: |         if config.get(CONF_ADJUSTABLE) is False: | ||||||
|             lv_obj.remove_style(w.obj, nullptr, literal("LV_PART_KNOB")) |             lv_obj.remove_style(w.obj, nullptr, literal("LV_PART_KNOB")) | ||||||
|             w.clear_flag("LV_OBJ_FLAG_CLICKABLE") |             w.clear_flag("LV_OBJ_FLAG_CLICKABLE") | ||||||
|  |         elif CONF_GROUP not in config: | ||||||
|  |             # For some reason arc does not get automatically added to the default group | ||||||
|  |             lv.group_add_obj(lv_expr.group_get_default(), w.obj) | ||||||
|  |  | ||||||
|         value = await get_start_value(config) |         value = await get_start_value(config) | ||||||
|         if value is not None: |         if value is not None: | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| from esphome import automation, codegen as cg | from esphome import automation, codegen as cg | ||||||
|  | from esphome.automation import Trigger | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ID, CONF_PAGES, CONF_TIME | from esphome.const import CONF_ID, CONF_PAGES, CONF_TIME, CONF_TRIGGER_ID | ||||||
|  |  | ||||||
| from ..defines import ( | from ..defines import ( | ||||||
|     CONF_ANIMATION, |     CONF_ANIMATION, | ||||||
| @@ -9,12 +10,39 @@ from ..defines import ( | |||||||
|     CONF_PAGE_WRAP, |     CONF_PAGE_WRAP, | ||||||
|     CONF_SKIP, |     CONF_SKIP, | ||||||
|     LV_ANIM, |     LV_ANIM, | ||||||
|  |     literal, | ||||||
| ) | ) | ||||||
| from ..lv_validation import lv_bool, lv_milliseconds | from ..lv_validation import lv_bool, lv_milliseconds | ||||||
| from ..lvcode import LVGL_COMP_ARG, LambdaContext, add_line_marks, lv_add, lvgl_comp | from ..lvcode import ( | ||||||
|  |     EVENT_ARG, | ||||||
|  |     LVGL_COMP_ARG, | ||||||
|  |     LambdaContext, | ||||||
|  |     add_line_marks, | ||||||
|  |     lv_add, | ||||||
|  |     lvgl_comp, | ||||||
|  | ) | ||||||
| from ..schemas import LVGL_SCHEMA | from ..schemas import LVGL_SCHEMA | ||||||
| from ..types import LvglAction, lv_page_t | from ..types import LvglAction, lv_page_t | ||||||
| from . import Widget, WidgetType, add_widgets, set_obj_properties | from . import Widget, WidgetType, add_widgets, get_widgets, set_obj_properties | ||||||
|  |  | ||||||
|  | CONF_ON_LOAD = "on_load" | ||||||
|  | CONF_ON_UNLOAD = "on_unload" | ||||||
|  |  | ||||||
|  | PAGE_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Optional(CONF_SKIP, default=False): lv_bool, | ||||||
|  |         cv.Optional(CONF_ON_LOAD): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger.template()), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ON_UNLOAD): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger.template()), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PageType(WidgetType): | class PageType(WidgetType): | ||||||
| @@ -23,9 +51,8 @@ class PageType(WidgetType): | |||||||
|             CONF_PAGE, |             CONF_PAGE, | ||||||
|             lv_page_t, |             lv_page_t, | ||||||
|             (), |             (), | ||||||
|             { |             PAGE_SCHEMA, | ||||||
|                 cv.Optional(CONF_SKIP, default=False): lv_bool, |             modify_schema={}, | ||||||
|             }, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     async def to_code(self, w: Widget, config: dict): |     async def to_code(self, w: Widget, config: dict): | ||||||
| @@ -39,7 +66,6 @@ SHOW_SCHEMA = LVGL_SCHEMA.extend( | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| page_spec = PageType() | page_spec = PageType() | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -111,3 +137,21 @@ async def add_pages(lv_component, config): | |||||||
|         await set_obj_properties(page, config) |         await set_obj_properties(page, config) | ||||||
|         await set_obj_properties(page, pconf) |         await set_obj_properties(page, pconf) | ||||||
|         await add_widgets(page, pconf) |         await add_widgets(page, pconf) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def generate_page_triggers(lv_component, config): | ||||||
|  |     for pconf in config.get(CONF_PAGES, ()): | ||||||
|  |         page = (await get_widgets(pconf))[0] | ||||||
|  |         for ev in (CONF_ON_LOAD, CONF_ON_UNLOAD): | ||||||
|  |             for loaded in pconf.get(ev, ()): | ||||||
|  |                 trigger = cg.new_Pvariable(loaded[CONF_TRIGGER_ID]) | ||||||
|  |                 await automation.build_automation(trigger, [], loaded) | ||||||
|  |                 async with LambdaContext(EVENT_ARG, where=id) as context: | ||||||
|  |                     lv_add(trigger.trigger()) | ||||||
|  |                 lv_add( | ||||||
|  |                     lv_component.add_event_cb( | ||||||
|  |                         page.obj, | ||||||
|  |                         await context.get_lambda(), | ||||||
|  |                         literal(f"LV_EVENT_SCREEN_{ev[3:].upper()}_START"), | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|   | |||||||
| @@ -54,6 +54,17 @@ lvgl: | |||||||
|       long_press_time: 500ms |       long_press_time: 500ms | ||||||
|   pages: |   pages: | ||||||
|     - id: page1 |     - id: page1 | ||||||
|  |       on_load: | ||||||
|  |         - logger.log: page loaded | ||||||
|  |         - lvgl.widget.focus: | ||||||
|  |             action: restore | ||||||
|  |       on_unload: | ||||||
|  |         - logger.log: page unloaded | ||||||
|  |         - lvgl.widget.focus: mark | ||||||
|  |       on_all_events: | ||||||
|  |         logger.log: | ||||||
|  |           format: "Event %s" | ||||||
|  |           args: ['lv_event_code_name_for(event->code).c_str()'] | ||||||
|       skip: true |       skip: true | ||||||
|       layout: |       layout: | ||||||
|         type: flex |         type: flex | ||||||
| @@ -70,6 +81,10 @@ lvgl: | |||||||
|             repeat_count: 10 |             repeat_count: 10 | ||||||
|             duration: 1s |             duration: 1s | ||||||
|             auto_start: true |             auto_start: true | ||||||
|  |             on_all_events: | ||||||
|  |               logger.log: | ||||||
|  |                 format: "Event %s" | ||||||
|  |                 args: ['lv_event_code_name_for(event->code).c_str()'] | ||||||
|         - label: |         - label: | ||||||
|             id: hello_label |             id: hello_label | ||||||
|             text: Hello world |             text: Hello world | ||||||
| @@ -229,6 +244,16 @@ lvgl: | |||||||
|               - label: |               - label: | ||||||
|                   text: Button |                   text: Button | ||||||
|             on_click: |             on_click: | ||||||
|  |               - lvgl.widget.focus: spin_up | ||||||
|  |               - lvgl.widget.focus: next | ||||||
|  |               - lvgl.widget.focus: previous | ||||||
|  |               - lvgl.widget.focus: | ||||||
|  |                   action: previous | ||||||
|  |                   freeze: true | ||||||
|  |               - lvgl.widget.focus: | ||||||
|  |                   id: spin_up | ||||||
|  |                   freeze: true | ||||||
|  |                   editing: true | ||||||
|               - lvgl.label.update: |               - lvgl.label.update: | ||||||
|                   id: hello_label |                   id: hello_label | ||||||
|                   bg_color: 0x123456 |                   bg_color: 0x123456 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user