1
0
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:
Clyde Stubbs
2024-07-25 09:12:04 +10:00
committed by GitHub
parent 75635956cd
commit 23ffc3ddfb
21 changed files with 2359 additions and 0 deletions

View 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))