mirror of
https://github.com/esphome/esphome.git
synced 2024-11-14 11:08:10 +01:00
[lvgl] Add lvgl.widget.focus action and related triggers. (#7315)
Some checks are pending
CI / Create common environment (push) Waiting to run
CI / Check black (push) Blocked by required conditions
CI / Check flake8 (push) Blocked by required conditions
CI / Check pylint (push) Blocked by required conditions
CI / Check pyupgrade (push) Blocked by required conditions
CI / Run script/ci-custom (push) Blocked by required conditions
CI / Run pytest (macOS-latest, 3.11) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.10) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.9) (push) Blocked by required conditions
CI / Run pytest (windows-latest, 3.11) (push) Blocked by required conditions
CI / Check clang-format (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 IDF (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP8266 (push) Blocked by required conditions
CI / list-components (push) Blocked by required conditions
CI / Component test ${{ matrix.file }} (push) Blocked by required conditions
CI / Split components for testing into 20 groups maximum (push) Blocked by required conditions
CI / Test split components (push) Blocked by required conditions
CI / CI Status (push) Blocked by required conditions
YAML lint / yamllint (push) Waiting to run
Some checks are pending
CI / Create common environment (push) Waiting to run
CI / Check black (push) Blocked by required conditions
CI / Check flake8 (push) Blocked by required conditions
CI / Check pylint (push) Blocked by required conditions
CI / Check pyupgrade (push) Blocked by required conditions
CI / Run script/ci-custom (push) Blocked by required conditions
CI / Run pytest (macOS-latest, 3.11) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.10) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.9) (push) Blocked by required conditions
CI / Run pytest (windows-latest, 3.11) (push) Blocked by required conditions
CI / Check clang-format (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 IDF (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP8266 (push) Blocked by required conditions
CI / list-components (push) Blocked by required conditions
CI / Component test ${{ matrix.file }} (push) Blocked by required conditions
CI / Split components for testing into 20 groups maximum (push) Blocked by required conditions
CI / Test split components (push) Blocked by required conditions
CI / CI Status (push) Blocked by required conditions
YAML lint / yamllint (push) Waiting to run
This commit is contained in:
parent
458a8970b6
commit
d6df466237
13 changed files with 253 additions and 27 deletions
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue