diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index ef29a99ddd4..03bbaf8ddb4 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -309,6 +309,14 @@ LV_EVENT_MAP = { "STYLE_CHANGE": "STYLE_CHANGED", "TRIPLE_CLICK": "TRIPLE_CLICKED", } + +LV_PRESS_EVENTS = ("PRESS", "PRESSING", "RELEASE") + + +def is_press_event(event: str) -> bool: + return event.removeprefix("on_").upper() in LV_PRESS_EVENTS + + LV_SCREEN_EVENT_MAP = { "SCREEN_LOAD": "SCREEN_LOADED", "SCREEN_LOAD_START": "SCREEN_LOAD_START", diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index eb85faa16cd..3141c5f93c3 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -890,7 +890,21 @@ lv_color_t lv_grad_calculate_color(const lv_grad_dsc_t *dsc, int32_t pos) { int32_t offset = pos - stop1->frac; return lv_color_mix(stop2->color, stop1->color, range == 0 ? 0 : (offset * 255) / range); } -#endif +#endif // USE_LVGL_GRADIENT + +lv_point_t LvglComponent::get_touch_relative_to_obj(lv_obj_t *obj) { + auto *indev = lv_indev_get_act(); + if (indev == nullptr) { + return {INT32_MAX, INT32_MAX}; + } + lv_point_t point; + lv_indev_get_point(indev, &point); + lv_area_t coords; + lv_obj_get_coords(obj, &coords); + point.x -= coords.x1; + point.y -= coords.y1; + return point; +} static void lv_container_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj) { LV_TRACE_OBJ_CREATE("begin"); diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index be1f150affe..32bf3ccac6b 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -6,7 +6,7 @@ #endif // USE_BINARY_SENSOR #ifdef USE_IMAGE #include "esphome/components/image/image.h" -#endif // USE_LVGL_IMAGE +#endif // USE_IMAGE #ifdef USE_LVGL_ROTARY_ENCODER #include "esphome/components/rotary_encoder/rotary_encoder.h" #endif // USE_LVGL_ROTARY_ENCODER @@ -32,10 +32,10 @@ #ifdef USE_FONT #include "esphome/components/font/font.h" -#endif // USE_LVGL_FONT +#endif // USE_FONT #ifdef USE_TOUCHSCREEN #include "esphome/components/touchscreen/touchscreen.h" -#endif // USE_LVGL_TOUCHSCREEN +#endif // USE_TOUCHSCREEN #if defined(USE_LVGL_BUTTONMATRIX) || defined(USE_LVGL_KEYBOARD) #include "esphome/components/key_provider/key_provider.h" @@ -124,7 +124,8 @@ int16_t lv_get_needle_angle_for_value(lv_obj_t *obj, int32_t value); */ lv_color_t lv_grad_calculate_color(const lv_grad_dsc_t *dsc, int32_t pos); -#endif +#endif // USE_LVGL_GRADIENT + // Parent class for things that wrap an LVGL object class LvCompound { public: @@ -169,9 +170,9 @@ template class ObjUpdateAction : public Action { public: explicit ObjUpdateAction(std::function &&lamb) : lamb_(std::move(lamb)) {} + protected: void play(const Ts &...x) override { this->lamb_(x...); } - protected: std::function lamb_; }; #ifdef USE_LVGL_ANIMIMG @@ -190,6 +191,12 @@ class LvglComponent : public PollingComponent { LvglComponent(std::vector displays, float buffer_frac, bool full_refresh, int draw_rounding, bool resume_on_input, bool update_when_display_idle, RotationType rotation_type); static void static_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p); + /** + * + * @param obj A widget + * @return The position of the last indev point relative to the widget's origin. + */ + static lv_point_t get_touch_relative_to_obj(lv_obj_t *obj); float get_setup_priority() const override { return setup_priority::PROCESSOR; } void setup() override; @@ -311,9 +318,9 @@ class IdleTrigger : public Trigger<> { template class LvglAction : public Action, public Parented { public: explicit LvglAction(std::function &&lamb) : action_(std::move(lamb)) {} - void play(const Ts &...x) override { this->action_(this->parent_); } protected: + void play(const Ts &...x) override { this->action_(this->parent_); } std::function action_{}; }; diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 62117fbd328..a9427a98520 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -31,6 +31,7 @@ from .defines import ( CONF_TIME_FORMAT, LV_GRAD_DIR, get_remapped_uses, + is_press_event, ) from .helpers import CONF_IF_NAN, requires_component, validate_printf from .layout import ( @@ -46,6 +47,7 @@ from .types import ( LvType, lv_group_t, lv_obj_t, + lv_point_t, lv_pseudo_button_t, lv_style_t, ) @@ -370,13 +372,20 @@ def automation_schema(typ: LvType): if typ.has_on_value: events = events + (CONF_ON_VALUE,) args = typ.get_arg_type() - args.append(lv_event_t_ptr) + + def get_trigger_args(event): + result = args.copy() + if is_press_event(event): + result.append(lv_point_t) + result.append(lv_event_t_ptr) + return result + return { **{ cv.Optional(event): validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - Trigger.template(*args) + Trigger.template(*get_trigger_args(event)) ), } ) diff --git a/esphome/components/lvgl/trigger.py b/esphome/components/lvgl/trigger.py index f825999e8a3..b3d12ed1837 100644 --- a/esphome/components/lvgl/trigger.py +++ b/esphome/components/lvgl/trigger.py @@ -24,6 +24,7 @@ from .defines import ( LV_SCREEN_EVENT_MAP, LV_SCREEN_EVENT_TRIGGERS, SWIPE_TRIGGERS, + is_press_event, literal, ) from .lvcode import ( @@ -34,11 +35,10 @@ from .lvcode import ( LvConditional, lv, lv_add, - lv_event_t_ptr, lv_expr, lvgl_static, ) -from .types import LV_EVENT +from .types import LV_EVENT, lv_point_t from .widgets import LvScrActType, get_screen_active, widget_map @@ -133,19 +133,24 @@ def _get_event_literal(trigger: str | MockObj) -> MockObj: return literal("LV_EVENT_" + TRIGGER_MAP[trigger.upper()]) -async def add_trigger(conf, w, *events, is_selected=None): +async def add_trigger(conf, w, *events: str | MockObj, is_selected=None): is_selected = is_selected or w.is_selected() tid = conf[CONF_TRIGGER_ID] trigger = cg.new_Pvariable(tid) - args = w.get_args() + [(lv_event_t_ptr, "event")] - value = w.get_values() + args = w.get_args() + value: list = w.get_values() + if len(events) == 1 and is_press_event(str(events[0])): + # Make the touch point available for selected events + args.append((lv_point_t, "point")) + value.append(lvgl_static.get_touch_relative_to_obj(w.obj)) + args.extend(EVENT_ARG) await automation.build_automation(trigger, args, conf) async with LambdaContext(EVENT_ARG, where=tid) as context: with LvConditional(is_selected): lv_add(trigger.trigger(*value, literal("event"))) callback = await context.get_lambda() event_literals = [_get_event_literal(event) for event in events] - if isinstance(events[0], str) and events[0] in DISPLAY_TRIGGERS: + if str(events[0]) in DISPLAY_TRIGGERS: assert len(events) == 1 lv.display_add_event_cb( lv_expr.obj_get_display(w.obj), callback, event_literals[0], nullptr diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index 0c8ddfbfbd0..1872ce2d32a 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -70,6 +70,8 @@ lv_image_t = LvType("lv_image_t") lv_gradient_t = LvType("lv_grad_dsc_t") lv_event_t = LvType("lv_event_t") RotationType = lvgl_ns.enum("RotationType") +lv_point_t = cg.global_ns.struct("lv_point_t") +lv_point_precise_t = cg.global_ns.struct("lv_point_precise_t") LV_EVENT = MockObj(base="LV_EVENT_", op="") LV_STATE = MockObj(base="LV_STATE_", op="") diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 0ac4062106d..d35f84c4f26 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -366,7 +366,7 @@ class Widget: def get_args(self): if isinstance(self.type.w_type, LvType): - return self.type.w_type.args + return self.type.w_type.args.copy() return [(lv_obj_t_ptr, "obj")] def get_value(self): diff --git a/esphome/components/lvgl/widgets/canvas.py b/esphome/components/lvgl/widgets/canvas.py index 1308b82dcd8..4427a3b00eb 100644 --- a/esphome/components/lvgl/widgets/canvas.py +++ b/esphome/components/lvgl/widgets/canvas.py @@ -57,10 +57,9 @@ from ..lv_validation import ( ) from ..lvcode import LocalVariable, lv, lv_assign, lv_expr from ..schemas import STYLE_PROPS, TEXT_SCHEMA, point_schema, remap_property -from ..types import LvType, ObjUpdateAction +from ..types import LvType, ObjUpdateAction, lv_point_precise_t from . import Widget, WidgetType, get_widgets from .img import CONF_IMAGE -from .line import lv_point_precise_t CONF_CANVAS = "canvas" CONF_BUFFER_ID = "buffer_id" diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 19f421cbbd8..9d6aa7b4ad6 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -1,4 +1,3 @@ -import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_X, CONF_Y @@ -13,9 +12,6 @@ CONF_LINE = "line" CONF_POINTS = "points" CONF_POINT_LIST_ID = "point_list_id" -lv_point_t = cg.global_ns.struct("lv_point_t") -lv_point_precise_t = cg.global_ns.struct("lv_point_precise_t") - class LineType(WidgetType): def __init__(self): diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 39d7472054a..4bf5b9d494a 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -649,11 +649,15 @@ lvgl: on_scroll_begin: logger.log: Button clicked on_release: - logger.log: Button clicked + logger.log: + format: Button released at %d/%d + args: [point.x, point.y] on_long_press_repeat: logger.log: Button clicked on_pressing: - logger.log: Button pressing + logger.log: + format: Button pressing at %d/%d + args: [point.x, point.y] on_press_lost: logger.log: Button press lost on_single_click: @@ -925,6 +929,10 @@ lvgl: value: !lambda |- static float yyy = 83.0; return yyy + .8; + on_release: + logger.log: + format: Slider released at %d/%d with value %.0f + args: [point.x, point.y, x] - button: styles: spin_button id: spin_up