mirror of
https://github.com/esphome/esphome.git
synced 2026-01-09 19:50:49 -07:00
[lvgl] Allow setting text directly on a button (#11964)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
@@ -108,7 +108,7 @@ LV_CONF_H_FORMAT = """\
|
||||
|
||||
|
||||
def generate_lv_conf_h():
|
||||
definitions = [as_macro(m, v) for m, v in df.lv_defines.items()]
|
||||
definitions = [as_macro(m, v) for m, v in df.get_data(df.KEY_LV_DEFINES).items()]
|
||||
definitions.sort()
|
||||
return LV_CONF_H_FORMAT.format("\n".join(definitions))
|
||||
|
||||
@@ -140,11 +140,11 @@ def multi_conf_validate(configs: list[dict]):
|
||||
)
|
||||
|
||||
|
||||
def final_validation(configs):
|
||||
if len(configs) != 1:
|
||||
multi_conf_validate(configs)
|
||||
def final_validation(config_list):
|
||||
if len(config_list) != 1:
|
||||
multi_conf_validate(config_list)
|
||||
global_config = full_config.get()
|
||||
for config in configs:
|
||||
for config in config_list:
|
||||
if (pages := config.get(CONF_PAGES)) and all(p[df.CONF_SKIP] for p in pages):
|
||||
raise cv.Invalid("At least one page must not be skipped")
|
||||
for display_id in config[df.CONF_DISPLAYS]:
|
||||
@@ -190,6 +190,14 @@ def final_validation(configs):
|
||||
raise cv.Invalid(
|
||||
f"Widget '{w}' does not have any dynamic properties to refresh",
|
||||
)
|
||||
# Do per-widget type final validation for update actions
|
||||
for widget_type, update_configs in df.get_data(df.KEY_UPDATED_WIDGETS).items():
|
||||
for conf in update_configs:
|
||||
for id_conf in conf.get(CONF_ID, ()):
|
||||
name = id_conf[CONF_ID]
|
||||
path = global_config.get_path_for_id(name)
|
||||
widget_conf = global_config.get_config_for_path(path[:-1])
|
||||
widget_type.final_validate(name, conf, widget_conf, path[1:])
|
||||
|
||||
|
||||
async def to_code(configs):
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
from esphome.core import ID, Lambda
|
||||
from esphome.core import CORE, ID, Lambda
|
||||
from esphome.cpp_generator import LambdaExpression, MockObj
|
||||
from esphome.cpp_types import uint32
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
@@ -20,11 +20,27 @@ from .helpers import requires_component
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
lvgl_ns = cg.esphome_ns.namespace("lvgl")
|
||||
|
||||
lv_defines = {} # Dict of #defines to provide as build flags
|
||||
DOMAIN = "lvgl"
|
||||
KEY_LV_DEFINES = "lv_defines"
|
||||
KEY_UPDATED_WIDGETS = "updated_widgets"
|
||||
|
||||
|
||||
def get_data(key, default=None):
|
||||
"""
|
||||
Get a data structure from the global data store by key
|
||||
:param key: A key for the data
|
||||
:param default: The default data - the default is an empty dict
|
||||
:return:
|
||||
"""
|
||||
return CORE.data.setdefault(DOMAIN, {}).setdefault(
|
||||
key, default if default is not None else {}
|
||||
)
|
||||
|
||||
|
||||
def add_define(macro, value="1"):
|
||||
if macro in lv_defines and lv_defines[macro] != value:
|
||||
lv_defines = get_data(KEY_LV_DEFINES)
|
||||
value = str(value)
|
||||
if lv_defines.setdefault(macro, value) != value:
|
||||
LOGGER.error(
|
||||
"Redefinition of %s - was %s now %s", macro, lv_defines[macro], value
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.automation import Trigger, validate_automation
|
||||
from esphome.components.time import RealTimeClock
|
||||
@@ -311,19 +313,36 @@ def automation_schema(typ: LvType):
|
||||
}
|
||||
|
||||
|
||||
def base_update_schema(widget_type, parts):
|
||||
def _update_widget(widget_type: WidgetType) -> Callable[[dict], dict]:
|
||||
"""
|
||||
Create a schema for updating a widgets style properties, states and flags
|
||||
During validation of update actions, create a map of action types to affected widgets
|
||||
for use in final validation.
|
||||
:param widget_type:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def validator(value: dict) -> dict:
|
||||
df.get_data(df.KEY_UPDATED_WIDGETS).setdefault(widget_type, []).append(value)
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def base_update_schema(widget_type: WidgetType | LvType, parts):
|
||||
"""
|
||||
Create a schema for updating a widget's style properties, states and flags.
|
||||
:param widget_type: The type of the ID
|
||||
:param parts: The allowable parts to specify
|
||||
:return:
|
||||
"""
|
||||
return part_schema(parts).extend(
|
||||
|
||||
w_type = widget_type.w_type if isinstance(widget_type, WidgetType) else widget_type
|
||||
schema = part_schema(parts).extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.ensure_list(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(widget_type),
|
||||
cv.Required(CONF_ID): cv.use_id(w_type),
|
||||
},
|
||||
key=CONF_ID,
|
||||
)
|
||||
@@ -332,11 +351,9 @@ def base_update_schema(widget_type, parts):
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def create_modify_schema(widget_type):
|
||||
return base_update_schema(widget_type.w_type, widget_type.parts).extend(
|
||||
widget_type.modify_schema
|
||||
)
|
||||
if isinstance(widget_type, WidgetType):
|
||||
schema.add_extra(_update_widget(widget_type))
|
||||
return schema
|
||||
|
||||
|
||||
def obj_schema(widget_type: WidgetType):
|
||||
|
||||
@@ -152,18 +152,18 @@ class WidgetType:
|
||||
|
||||
# Local import to avoid circular import
|
||||
from .automation import update_to_code
|
||||
from .schemas import WIDGET_TYPES, create_modify_schema
|
||||
from .schemas import WIDGET_TYPES, base_update_schema
|
||||
|
||||
if not is_mock:
|
||||
if self.name in WIDGET_TYPES:
|
||||
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
|
||||
WIDGET_TYPES[self.name] = self
|
||||
|
||||
# Register the update action automatically
|
||||
# Register the update action automatically, adding widget-specific properties
|
||||
register_action(
|
||||
f"lvgl.{self.name}.update",
|
||||
ObjUpdateAction,
|
||||
create_modify_schema(self),
|
||||
base_update_schema(self, self.parts).extend(self.modify_schema),
|
||||
)(update_to_code)
|
||||
|
||||
@property
|
||||
@@ -182,7 +182,6 @@ class WidgetType:
|
||||
Generate code for a given widget
|
||||
:param w: The widget
|
||||
:param config: Its configuration
|
||||
:return: Generated code as a list of text lines
|
||||
"""
|
||||
|
||||
async def obj_creator(self, parent: MockObjClass, config: dict):
|
||||
@@ -228,6 +227,15 @@ class WidgetType:
|
||||
"""
|
||||
return value
|
||||
|
||||
def final_validate(self, widget, update_config, widget_config, path):
|
||||
"""
|
||||
Allow final validation for a given widget type update action
|
||||
:param widget: A widget
|
||||
:param update_config: The configuration for the update action
|
||||
:param widget_config: The configuration for the widget itself
|
||||
:param path: The path to the widget, for error reporting
|
||||
"""
|
||||
|
||||
|
||||
class NumberType(WidgetType):
|
||||
def get_max(self, config: dict):
|
||||
|
||||
@@ -1,20 +1,52 @@
|
||||
from esphome.const import CONF_BUTTON
|
||||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_BUTTON, CONF_TEXT
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..defines import CONF_MAIN
|
||||
from ..defines import CONF_MAIN, CONF_WIDGETS
|
||||
from ..helpers import add_lv_use
|
||||
from ..lv_validation import lv_text
|
||||
from ..lvcode import lv, lv_expr
|
||||
from ..schemas import TEXT_SCHEMA
|
||||
from ..types import LvBoolean, WidgetType
|
||||
from . import Widget
|
||||
from .label import label_spec
|
||||
|
||||
lv_button_t = LvBoolean("lv_btn_t")
|
||||
|
||||
|
||||
class ButtonType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(CONF_BUTTON, lv_button_t, (CONF_MAIN,), lv_name="btn")
|
||||
super().__init__(
|
||||
CONF_BUTTON, lv_button_t, (CONF_MAIN,), schema=TEXT_SCHEMA, lv_name="btn"
|
||||
)
|
||||
|
||||
def validate(self, value):
|
||||
if CONF_TEXT in value:
|
||||
if CONF_WIDGETS in value:
|
||||
raise cv.Invalid("Cannot use both text and widgets in a button")
|
||||
add_lv_use("label")
|
||||
return value
|
||||
|
||||
def get_uses(self):
|
||||
return ("btn",)
|
||||
|
||||
async def to_code(self, w, config):
|
||||
return []
|
||||
def on_create(self, var: MockObj, config: dict):
|
||||
if CONF_TEXT in config:
|
||||
lv.label_create(var)
|
||||
return var
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if text := config.get(CONF_TEXT):
|
||||
label_widget = Widget.create(
|
||||
None, lv_expr.obj_get_child(w.obj, 0), label_spec
|
||||
)
|
||||
await label_widget.set_property(CONF_TEXT, await lv_text.process(text))
|
||||
|
||||
def final_validate(self, widget, update_config, widget_config, path):
|
||||
if CONF_TEXT in update_config and CONF_TEXT not in widget_config:
|
||||
raise cv.Invalid(
|
||||
"Button must have 'text:' configured to allow updating text", path
|
||||
)
|
||||
|
||||
|
||||
button_spec = ButtonType()
|
||||
|
||||
@@ -426,6 +426,14 @@ lvgl:
|
||||
logger.log: Long pressed repeated
|
||||
- buttons:
|
||||
- id: button_e
|
||||
- button:
|
||||
id: button_with_text
|
||||
text: Button
|
||||
on_click:
|
||||
lvgl.button.update:
|
||||
id: button_with_text
|
||||
text: Clicked
|
||||
|
||||
- button:
|
||||
layout: 2x1
|
||||
id: button_button
|
||||
|
||||
Reference in New Issue
Block a user