mirror of
https://github.com/esphome/esphome.git
synced 2025-09-06 13:22:19 +01:00
[lvgl] base implementation (#7116)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
260
esphome/components/lvgl/schemas.py
Normal file
260
esphome/components/lvgl/schemas.py
Normal file
@@ -0,0 +1,260 @@
|
||||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_ARGS, CONF_FORMAT, CONF_ID, CONF_STATE, CONF_TYPE
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT
|
||||
|
||||
from . import defines as df, lv_validation as lvalid, types as ty
|
||||
from .defines import WIDGET_PARTS
|
||||
from .helpers import (
|
||||
REQUIRED_COMPONENTS,
|
||||
add_lv_use,
|
||||
requires_component,
|
||||
validate_printf,
|
||||
)
|
||||
from .lv_validation import lv_font
|
||||
from .types import WIDGET_TYPES, get_widget_type
|
||||
|
||||
# A schema for text properties
|
||||
TEXT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(df.CONF_TEXT): cv.Any(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(
|
||||
cv.lambda_
|
||||
),
|
||||
},
|
||||
),
|
||||
validate_printf,
|
||||
),
|
||||
lvalid.lv_text,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
# All LVGL styles and their validators
|
||||
STYLE_PROPS = {
|
||||
"align": df.CHILD_ALIGNMENTS.one_of,
|
||||
"arc_opa": lvalid.opacity,
|
||||
"arc_color": lvalid.lv_color,
|
||||
"arc_rounded": lvalid.lv_bool,
|
||||
"arc_width": cv.positive_int,
|
||||
"anim_time": lvalid.lv_milliseconds,
|
||||
"bg_color": lvalid.lv_color,
|
||||
"bg_grad_color": lvalid.lv_color,
|
||||
"bg_dither_mode": df.LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF").one_of,
|
||||
"bg_grad_dir": df.LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER").one_of,
|
||||
"bg_grad_stop": lvalid.stop_value,
|
||||
"bg_img_opa": lvalid.opacity,
|
||||
"bg_img_recolor": lvalid.lv_color,
|
||||
"bg_img_recolor_opa": lvalid.opacity,
|
||||
"bg_main_stop": lvalid.stop_value,
|
||||
"bg_opa": lvalid.opacity,
|
||||
"border_color": lvalid.lv_color,
|
||||
"border_opa": lvalid.opacity,
|
||||
"border_post": lvalid.lv_bool,
|
||||
"border_side": df.LvConstant(
|
||||
"LV_BORDER_SIDE_", "NONE", "TOP", "BOTTOM", "LEFT", "RIGHT", "INTERNAL"
|
||||
).several_of,
|
||||
"border_width": cv.positive_int,
|
||||
"clip_corner": lvalid.lv_bool,
|
||||
"height": lvalid.size,
|
||||
"img_recolor": lvalid.lv_color,
|
||||
"img_recolor_opa": lvalid.opacity,
|
||||
"line_width": cv.positive_int,
|
||||
"line_dash_width": cv.positive_int,
|
||||
"line_dash_gap": cv.positive_int,
|
||||
"line_rounded": lvalid.lv_bool,
|
||||
"line_color": lvalid.lv_color,
|
||||
"opa": lvalid.opacity,
|
||||
"opa_layered": lvalid.opacity,
|
||||
"outline_color": lvalid.lv_color,
|
||||
"outline_opa": lvalid.opacity,
|
||||
"outline_pad": lvalid.size,
|
||||
"outline_width": lvalid.size,
|
||||
"pad_all": lvalid.size,
|
||||
"pad_bottom": lvalid.size,
|
||||
"pad_column": lvalid.size,
|
||||
"pad_left": lvalid.size,
|
||||
"pad_right": lvalid.size,
|
||||
"pad_row": lvalid.size,
|
||||
"pad_top": lvalid.size,
|
||||
"shadow_color": lvalid.lv_color,
|
||||
"shadow_ofs_x": cv.int_,
|
||||
"shadow_ofs_y": cv.int_,
|
||||
"shadow_opa": lvalid.opacity,
|
||||
"shadow_spread": cv.int_,
|
||||
"shadow_width": cv.positive_int,
|
||||
"text_align": df.LvConstant(
|
||||
"LV_TEXT_ALIGN_", "LEFT", "CENTER", "RIGHT", "AUTO"
|
||||
).one_of,
|
||||
"text_color": lvalid.lv_color,
|
||||
"text_decor": df.LvConstant(
|
||||
"LV_TEXT_DECOR_", "NONE", "UNDERLINE", "STRIKETHROUGH"
|
||||
).several_of,
|
||||
"text_font": lv_font,
|
||||
"text_letter_space": cv.positive_int,
|
||||
"text_line_space": cv.positive_int,
|
||||
"text_opa": lvalid.opacity,
|
||||
"transform_angle": lvalid.angle,
|
||||
"transform_height": lvalid.pixels_or_percent,
|
||||
"transform_pivot_x": lvalid.pixels_or_percent,
|
||||
"transform_pivot_y": lvalid.pixels_or_percent,
|
||||
"transform_zoom": lvalid.zoom,
|
||||
"translate_x": lvalid.pixels_or_percent,
|
||||
"translate_y": lvalid.pixels_or_percent,
|
||||
"max_height": lvalid.pixels_or_percent,
|
||||
"max_width": lvalid.pixels_or_percent,
|
||||
"min_height": lvalid.pixels_or_percent,
|
||||
"min_width": lvalid.pixels_or_percent,
|
||||
"radius": cv.Any(lvalid.size, df.LvConstant("LV_RADIUS_", "CIRCLE").one_of),
|
||||
"width": lvalid.size,
|
||||
"x": lvalid.pixels_or_percent,
|
||||
"y": lvalid.pixels_or_percent,
|
||||
}
|
||||
|
||||
# Complete object style schema
|
||||
STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend(
|
||||
{
|
||||
cv.Optional(df.CONF_SCROLLBAR_MODE): df.LvConstant(
|
||||
"LV_SCROLLBAR_MODE_", "OFF", "ON", "ACTIVE", "AUTO"
|
||||
).one_of,
|
||||
}
|
||||
)
|
||||
|
||||
# Object states. Top level properties apply to MAIN
|
||||
STATE_SCHEMA = cv.Schema(
|
||||
{cv.Optional(state): STYLE_SCHEMA for state in df.STATES}
|
||||
).extend(STYLE_SCHEMA)
|
||||
# Setting object states
|
||||
SET_STATE_SCHEMA = cv.Schema(
|
||||
{cv.Optional(state): lvalid.lv_bool for state in df.STATES}
|
||||
)
|
||||
# Setting object flags
|
||||
FLAG_SCHEMA = cv.Schema({cv.Optional(flag): cv.boolean for flag in df.OBJ_FLAGS})
|
||||
FLAG_LIST = cv.ensure_list(df.LvConstant("LV_OBJ_FLAG_", *df.OBJ_FLAGS).one_of)
|
||||
|
||||
|
||||
def part_schema(widget_type):
|
||||
"""
|
||||
Generate a schema for the various parts (e.g. main:, indicator:) of a widget type
|
||||
:param widget_type: The type of widget to generate for
|
||||
:return:
|
||||
"""
|
||||
parts = WIDGET_PARTS.get(widget_type)
|
||||
if parts is None:
|
||||
parts = (df.CONF_MAIN,)
|
||||
return cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}).extend(
|
||||
STATE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
def obj_schema(widget_type: str):
|
||||
"""
|
||||
Create a schema for a widget type itself i.e. no allowance for children
|
||||
:param widget_type:
|
||||
:return:
|
||||
"""
|
||||
return (
|
||||
part_schema(widget_type)
|
||||
.extend(FLAG_SCHEMA)
|
||||
.extend(ALIGN_TO_SCHEMA)
|
||||
.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_STATE): SET_STATE_SCHEMA,
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
ALIGN_TO_SCHEMA = {
|
||||
cv.Optional(df.CONF_ALIGN_TO): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(ty.lv_obj_t),
|
||||
cv.Required(df.CONF_ALIGN): df.ALIGN_ALIGNMENTS.one_of,
|
||||
cv.Optional(df.CONF_X, default=0): lvalid.pixels_or_percent,
|
||||
cv.Optional(df.CONF_Y, default=0): lvalid.pixels_or_percent,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
# A style schema that can include text
|
||||
STYLED_TEXT_SCHEMA = cv.maybe_simple_value(
|
||||
STYLE_SCHEMA.extend(TEXT_SCHEMA), key=df.CONF_TEXT
|
||||
)
|
||||
|
||||
|
||||
ALL_STYLES = {
|
||||
**STYLE_PROPS,
|
||||
}
|
||||
|
||||
|
||||
def container_validator(schema, widget_type):
|
||||
"""
|
||||
Create a validator for a container given the widget type
|
||||
:param schema: Base schema to extend
|
||||
:param widget_type:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def validator(value):
|
||||
result = schema
|
||||
if w_sch := WIDGET_TYPES[widget_type].schema:
|
||||
result = result.extend(w_sch)
|
||||
if value and (layout := value.get(df.CONF_LAYOUT)):
|
||||
if not isinstance(layout, dict):
|
||||
raise cv.Invalid("Layout value must be a dict")
|
||||
ltype = layout.get(CONF_TYPE)
|
||||
add_lv_use(ltype)
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return result
|
||||
return result(value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def container_schema(widget_type, extras=None):
|
||||
"""
|
||||
Create a schema for a container widget of a given type. All obj properties are available, plus
|
||||
the extras passed in, plus any defined for the specific widget being specified.
|
||||
:param widget_type: The widget type, e.g. "img"
|
||||
:param extras: Additional options to be made available, e.g. layout properties for children
|
||||
:return: The schema for this type of widget.
|
||||
"""
|
||||
lv_type = get_widget_type(widget_type)
|
||||
schema = obj_schema(widget_type).extend({cv.GenerateID(): cv.declare_id(lv_type)})
|
||||
if extras:
|
||||
schema = schema.extend(extras)
|
||||
# Delayed evaluation for recursion
|
||||
return container_validator(schema, widget_type)
|
||||
|
||||
|
||||
def widget_schema(widget_type, extras=None):
|
||||
"""
|
||||
Create a schema for a given widget type
|
||||
:param widget_type: The name of the widget
|
||||
:param extras:
|
||||
:return:
|
||||
"""
|
||||
validator = container_schema(widget_type, extras=extras)
|
||||
if required := REQUIRED_COMPONENTS.get(widget_type):
|
||||
validator = cv.All(validator, requires_component(required))
|
||||
return cv.Exclusive(widget_type, df.CONF_WIDGETS), validator
|
||||
|
||||
|
||||
# All widget schemas must be defined before this is called.
|
||||
|
||||
|
||||
def any_widget_schema(extras=None):
|
||||
"""
|
||||
Generate schemas for all possible LVGL widgets. This is what implements the ability to have a list of any kind of
|
||||
widget under the widgets: key.
|
||||
|
||||
:param extras: Additional schema to be applied to each generated one
|
||||
:return:
|
||||
"""
|
||||
return cv.Any(dict(widget_schema(wt, extras) for wt in WIDGET_PARTS))
|
Reference in New Issue
Block a user