mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
[lvgl] Stage 5 (#7191)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
acaec41bb7
commit
6b141102d6
29 changed files with 1716 additions and 27 deletions
|
@ -24,10 +24,13 @@ from . import defines as df, helpers, lv_validation as lvalid
|
|||
from .animimg import animimg_spec
|
||||
from .arc import arc_spec
|
||||
from .automation import disp_update, update_to_code
|
||||
from .btn import btn_spec
|
||||
from .button import button_spec
|
||||
from .buttonmatrix import buttonmatrix_spec
|
||||
from .checkbox import checkbox_spec
|
||||
from .defines import CONF_SKIP
|
||||
from .dropdown import dropdown_spec
|
||||
from .img import img_spec
|
||||
from .keyboard import keyboard_spec
|
||||
from .label import label_spec
|
||||
from .led import led_spec
|
||||
from .line import line_spec
|
||||
|
@ -35,8 +38,11 @@ from .lv_bar import bar_spec
|
|||
from .lv_switch import switch_spec
|
||||
from .lv_validation import lv_bool, lv_images_used
|
||||
from .lvcode import LvContext, LvglComponent
|
||||
from .meter import meter_spec
|
||||
from .msgbox import MSGBOX_SCHEMA, msgboxes_to_code
|
||||
from .obj import obj_spec
|
||||
from .page import add_pages, page_spec
|
||||
from .roller import roller_spec
|
||||
from .rotary_encoders import ROTARY_ENCODER_CONFIG, rotary_encoders_to_code
|
||||
from .schemas import (
|
||||
DISP_BG_SCHEMA,
|
||||
|
@ -52,8 +58,12 @@ from .schemas import (
|
|||
obj_schema,
|
||||
)
|
||||
from .slider import slider_spec
|
||||
from .spinbox import spinbox_spec
|
||||
from .spinner import spinner_spec
|
||||
from .styles import add_top_layer, styles_to_code, theme_to_code
|
||||
from .tabview import tabview_spec
|
||||
from .textarea import textarea_spec
|
||||
from .tileview import tileview_spec
|
||||
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||
from .trigger import generate_triggers
|
||||
from .types import (
|
||||
|
@ -75,7 +85,7 @@ LOGGER = logging.getLogger(__name__)
|
|||
for w_type in (
|
||||
label_spec,
|
||||
obj_spec,
|
||||
btn_spec,
|
||||
button_spec,
|
||||
bar_spec,
|
||||
slider_spec,
|
||||
arc_spec,
|
||||
|
@ -86,6 +96,15 @@ for w_type in (
|
|||
checkbox_spec,
|
||||
img_spec,
|
||||
switch_spec,
|
||||
tabview_spec,
|
||||
buttonmatrix_spec,
|
||||
meter_spec,
|
||||
dropdown_spec,
|
||||
roller_spec,
|
||||
textarea_spec,
|
||||
spinbox_spec,
|
||||
keyboard_spec,
|
||||
tileview_spec,
|
||||
):
|
||||
WIDGET_TYPES[w_type.name] = w_type
|
||||
|
||||
|
@ -244,6 +263,7 @@ async def to_code(config):
|
|||
await add_widgets(lv_scr_act, config)
|
||||
await add_pages(lv_component, config)
|
||||
await add_top_layer(config)
|
||||
await msgboxes_to_code(config)
|
||||
await disp_update(f"{lv_component}->get_disp()", config)
|
||||
Widget.set_completed()
|
||||
await generate_triggers(lv_component)
|
||||
|
@ -308,6 +328,7 @@ CONFIG_SCHEMA = (
|
|||
cv.Exclusive(CONF_PAGES, CONF_PAGES): cv.ensure_list(
|
||||
container_schema(page_spec)
|
||||
),
|
||||
cv.Optional(df.CONF_MSGBOXES): cv.ensure_list(MSGBOX_SCHEMA),
|
||||
cv.Optional(df.CONF_PAGE_WRAP, default=True): lv_bool,
|
||||
cv.Optional(df.CONF_TOP_LAYER): container_schema(obj_spec),
|
||||
cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color,
|
||||
|
|
|
@ -2,8 +2,8 @@ from esphome import automation
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DURATION, CONF_ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ...cpp_generator import MockObj
|
||||
from .automation import action_to_code
|
||||
from .defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC
|
||||
from .helpers import lvgl_components_required
|
||||
|
|
|
@ -109,7 +109,7 @@ async def disp_update(disp, config: dict):
|
|||
if CONF_DISP_BG_COLOR not in config and CONF_DISP_BG_IMAGE not in config:
|
||||
return
|
||||
with LocalVariable("lv_disp_tmp", lv_disp_t, literal(disp)) as disp_temp:
|
||||
if bg_color := config.get(CONF_DISP_BG_COLOR):
|
||||
if (bg_color := config.get(CONF_DISP_BG_COLOR)) is not None:
|
||||
lv.disp_set_bg_color(disp_temp, await lv_color.process(bg_color))
|
||||
if bg_image := config.get(CONF_DISP_BG_IMAGE):
|
||||
lv.disp_set_bg_image(disp_temp, await lv_image.process(bg_image))
|
||||
|
|
|
@ -3,12 +3,12 @@ from esphome.const import CONF_BUTTON
|
|||
from .defines import CONF_MAIN
|
||||
from .types import LvBoolean, WidgetType
|
||||
|
||||
lv_btn_t = LvBoolean("lv_btn_t")
|
||||
lv_button_t = LvBoolean("lv_btn_t")
|
||||
|
||||
|
||||
class BtnType(WidgetType):
|
||||
class ButtonType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(CONF_BUTTON, lv_btn_t, (CONF_MAIN,), lv_name="btn")
|
||||
super().__init__(CONF_BUTTON, lv_button_t, (CONF_MAIN,), lv_name="btn")
|
||||
|
||||
def get_uses(self):
|
||||
return ("btn",)
|
||||
|
@ -17,4 +17,4 @@ class BtnType(WidgetType):
|
|||
return []
|
||||
|
||||
|
||||
btn_spec = BtnType()
|
||||
button_spec = ButtonType()
|
277
esphome/components/lvgl/buttonmatrix.py
Normal file
277
esphome/components/lvgl/buttonmatrix.py
Normal file
|
@ -0,0 +1,277 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.key_provider import KeyProvider
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_WIDTH
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from .automation import action_to_code
|
||||
from .button import lv_button_t
|
||||
from .defines import (
|
||||
BUTTONMATRIX_CTRLS,
|
||||
CONF_BUTTONS,
|
||||
CONF_CONTROL,
|
||||
CONF_ITEMS,
|
||||
CONF_KEY_CODE,
|
||||
CONF_MAIN,
|
||||
CONF_ONE_CHECKED,
|
||||
CONF_ROWS,
|
||||
CONF_SELECTED,
|
||||
CONF_TEXT,
|
||||
)
|
||||
from .helpers import lvgl_components_required
|
||||
from .lv_validation import key_code, lv_bool
|
||||
from .lvcode import lv, lv_add, lv_expr
|
||||
from .schemas import automation_schema
|
||||
from .types import (
|
||||
LV_BTNMATRIX_CTRL,
|
||||
LV_STATE,
|
||||
LvBoolean,
|
||||
LvCompound,
|
||||
LvType,
|
||||
ObjUpdateAction,
|
||||
char_ptr,
|
||||
lv_pseudo_button_t,
|
||||
)
|
||||
from .widget import Widget, WidgetType, get_widgets, widget_map
|
||||
|
||||
CONF_BUTTONMATRIX = "buttonmatrix"
|
||||
CONF_BUTTON_TEXT_LIST_ID = "button_text_list_id"
|
||||
|
||||
LvButtonMatrixButton = LvBoolean(
|
||||
str(cg.uint16),
|
||||
parents=(lv_pseudo_button_t,),
|
||||
)
|
||||
BUTTONMATRIX_BUTTON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TEXT): cv.string,
|
||||
cv.Optional(CONF_KEY_CODE): key_code,
|
||||
cv.GenerateID(): cv.declare_id(LvButtonMatrixButton),
|
||||
cv.Optional(CONF_WIDTH, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_CONTROL): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{cv.Optional(k.lower()): cv.boolean for k in BUTTONMATRIX_CTRLS.choices}
|
||||
)
|
||||
),
|
||||
}
|
||||
).extend(automation_schema(lv_button_t))
|
||||
|
||||
BUTTONMATRIX_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ONE_CHECKED, default=False): lv_bool,
|
||||
cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr),
|
||||
cv.Required(CONF_ROWS): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BUTTONS): cv.ensure_list(
|
||||
BUTTONMATRIX_BUTTON_SCHEMA
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ButtonmatrixButtonType(WidgetType):
|
||||
"""
|
||||
A pseudo-widget for the matrix buttons
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("btnmatrix_btn", LvButtonMatrixButton, (), {}, {})
|
||||
|
||||
async def to_code(self, w, config: dict):
|
||||
return []
|
||||
|
||||
|
||||
btn_btn_spec = ButtonmatrixButtonType()
|
||||
|
||||
|
||||
class MatrixButton(Widget):
|
||||
"""
|
||||
Describes a button within a button matrix.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def create_button(id, parent, config: dict, index):
|
||||
w = MatrixButton(id, parent, config, index)
|
||||
widget_map[id] = w
|
||||
return w
|
||||
|
||||
def __init__(self, id, parent: Widget, config, index):
|
||||
super().__init__(id, btn_btn_spec, config)
|
||||
self.parent = parent
|
||||
self.index = index
|
||||
self.obj = parent.obj
|
||||
|
||||
def is_selected(self):
|
||||
return self.parent.var.get_selected() == MockObj(self.var)
|
||||
|
||||
@staticmethod
|
||||
def map_ctrls(state):
|
||||
state = str(state).upper().removeprefix("LV_STATE_")
|
||||
assert state in BUTTONMATRIX_CTRLS.choices
|
||||
return getattr(LV_BTNMATRIX_CTRL, state)
|
||||
|
||||
def has_state(self, state):
|
||||
state = self.map_ctrls(state)
|
||||
return lv_expr.btnmatrix_has_btn_ctrl(self.obj, self.index, state)
|
||||
|
||||
def add_state(self, state):
|
||||
state = self.map_ctrls(state)
|
||||
return lv.btnmatrix_set_btn_ctrl(self.obj, self.index, state)
|
||||
|
||||
def clear_state(self, state):
|
||||
state = self.map_ctrls(state)
|
||||
return lv.btnmatrix_clear_btn_ctrl(self.obj, self.index, state)
|
||||
|
||||
def is_pressed(self):
|
||||
return self.is_selected() & self.parent.has_state(LV_STATE.PRESSED)
|
||||
|
||||
def is_checked(self):
|
||||
return self.has_state(LV_STATE.CHECKED)
|
||||
|
||||
def get_value(self):
|
||||
return self.is_checked()
|
||||
|
||||
def check_null(self):
|
||||
return None
|
||||
|
||||
|
||||
async def get_button_data(config, buttonmatrix: Widget):
|
||||
"""
|
||||
Process a button matrix button list
|
||||
:param config: The row list
|
||||
:param buttonmatrix: The parent variable
|
||||
:return: text array id, control list, width list
|
||||
"""
|
||||
text_list = []
|
||||
ctrl_list = []
|
||||
width_list = []
|
||||
key_list = []
|
||||
for row in config:
|
||||
for button_conf in row.get(CONF_BUTTONS) or ():
|
||||
bid = button_conf[CONF_ID]
|
||||
index = len(width_list)
|
||||
MatrixButton.create_button(bid, buttonmatrix, button_conf, index)
|
||||
cg.new_variable(bid, index)
|
||||
text_list.append(button_conf.get(CONF_TEXT) or "")
|
||||
key_list.append(button_conf.get(CONF_KEY_CODE) or 0)
|
||||
width_list.append(button_conf[CONF_WIDTH])
|
||||
ctrl = ["LV_BTNMATRIX_CTRL_CLICK_TRIG"]
|
||||
for item in button_conf.get(CONF_CONTROL, ()):
|
||||
ctrl.extend([k for k, v in item.items() if v])
|
||||
ctrl_list.append(await BUTTONMATRIX_CTRLS.process(ctrl))
|
||||
text_list.append("\n")
|
||||
text_list = text_list[:-1]
|
||||
text_list.append(cg.nullptr)
|
||||
return text_list, ctrl_list, width_list, key_list
|
||||
|
||||
|
||||
lv_buttonmatrix_t = LvType(
|
||||
"LvButtonMatrixType",
|
||||
parents=(KeyProvider, LvCompound),
|
||||
largs=[(cg.uint16, "x")],
|
||||
lvalue=lambda w: w.var.get_selected(),
|
||||
)
|
||||
|
||||
|
||||
class ButtonMatrixType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_BUTTONMATRIX,
|
||||
lv_buttonmatrix_t,
|
||||
(CONF_MAIN, CONF_ITEMS),
|
||||
BUTTONMATRIX_SCHEMA,
|
||||
{},
|
||||
lv_name="btnmatrix",
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
lvgl_components_required.add("BUTTONMATRIX")
|
||||
if CONF_ROWS not in config:
|
||||
return []
|
||||
text_list, ctrl_list, width_list, key_list = await get_button_data(
|
||||
config[CONF_ROWS], w
|
||||
)
|
||||
text_id = config[CONF_BUTTON_TEXT_LIST_ID]
|
||||
text_id = cg.static_const_array(text_id, text_list)
|
||||
lv.btnmatrix_set_map(w.obj, text_id)
|
||||
set_btn_data(w.obj, ctrl_list, width_list)
|
||||
lv.btnmatrix_set_one_checked(w.obj, config[CONF_ONE_CHECKED])
|
||||
for index, key in enumerate(key_list):
|
||||
if key != 0:
|
||||
lv_add(w.var.set_key(index, key))
|
||||
|
||||
def get_uses(self):
|
||||
return ("btnmatrix",)
|
||||
|
||||
|
||||
def set_btn_data(obj, ctrl_list, width_list):
|
||||
for index, ctrl in enumerate(ctrl_list):
|
||||
lv.btnmatrix_set_btn_ctrl(obj, index, ctrl)
|
||||
for index, width in enumerate(width_list):
|
||||
lv.btnmatrix_set_btn_width(obj, index, width)
|
||||
|
||||
|
||||
buttonmatrix_spec = ButtonMatrixType()
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.matrix.button.update",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_WIDTH): cv.positive_int,
|
||||
cv.Optional(CONF_CONTROL): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(k.lower()): cv.boolean
|
||||
for k in BUTTONMATRIX_CTRLS.choices
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Required(CONF_ID): cv.ensure_list(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LvButtonMatrixButton),
|
||||
},
|
||||
key=CONF_ID,
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_SELECTED): lv_bool,
|
||||
}
|
||||
),
|
||||
)
|
||||
async def button_update_to_code(config, action_id, template_arg, args):
|
||||
widgets = await get_widgets(config[CONF_ID])
|
||||
assert all(isinstance(w, MatrixButton) for w in widgets)
|
||||
|
||||
async def do_button_update(w: MatrixButton):
|
||||
if (width := config.get(CONF_WIDTH)) is not None:
|
||||
lv.btnmatrix_set_btn_width(w.obj, w.index, width)
|
||||
if config.get(CONF_SELECTED):
|
||||
lv.btnmatrix_set_selected_btn(w.obj, w.index)
|
||||
if controls := config.get(CONF_CONTROL):
|
||||
adds = []
|
||||
clrs = []
|
||||
for item in controls:
|
||||
adds.extend(
|
||||
[f"LV_BTNMATRIX_CTRL_{k.upper()}" for k, v in item.items() if v]
|
||||
)
|
||||
clrs.extend(
|
||||
[f"LV_BTNMATRIX_CTRL_{k.upper()}" for k, v in item.items() if not v]
|
||||
)
|
||||
if adds:
|
||||
lv.btnmatrix_set_btn_ctrl(
|
||||
w.obj, w.index, await BUTTONMATRIX_CTRLS.process(adds)
|
||||
)
|
||||
if clrs:
|
||||
lv.btnmatrix_clear_btn_ctrl(
|
||||
w.obj, w.index, await BUTTONMATRIX_CTRLS.process(clrs)
|
||||
)
|
||||
|
||||
return await action_to_code(
|
||||
widgets, do_button_update, action_id, template_arg, args
|
||||
)
|
|
@ -18,7 +18,7 @@ class CheckboxType(WidgetType):
|
|||
)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if value := config.get(CONF_TEXT):
|
||||
if (value := config.get(CONF_TEXT)) is not None:
|
||||
lv.checkbox_set_text(w.obj, await lv_text.process(value))
|
||||
|
||||
|
||||
|
|
|
@ -304,7 +304,7 @@ OBJ_FLAGS = (
|
|||
ARC_MODES = LvConstant("LV_ARC_MODE_", "NORMAL", "REVERSE", "SYMMETRICAL")
|
||||
BAR_MODES = LvConstant("LV_BAR_MODE_", "NORMAL", "SYMMETRICAL", "RANGE")
|
||||
|
||||
BTNMATRIX_CTRLS = LvConstant(
|
||||
BUTTONMATRIX_CTRLS = LvConstant(
|
||||
"LV_BTNMATRIX_CTRL_",
|
||||
"HIDDEN",
|
||||
"NO_REPEAT",
|
||||
|
|
76
esphome/components/lvgl/dropdown.py
Normal file
76
esphome/components/lvgl/dropdown.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_OPTIONS
|
||||
|
||||
from .defines import (
|
||||
CONF_DIR,
|
||||
CONF_INDICATOR,
|
||||
CONF_MAIN,
|
||||
CONF_SELECTED_INDEX,
|
||||
CONF_SYMBOL,
|
||||
DIRECTIONS,
|
||||
literal,
|
||||
)
|
||||
from .label import CONF_LABEL
|
||||
from .lv_validation import lv_int, lv_text, option_string
|
||||
from .lvcode import LocalVariable, lv, lv_expr
|
||||
from .schemas import part_schema
|
||||
from .types import LvSelect, LvType, lv_obj_t
|
||||
from .widget import Widget, WidgetType, set_obj_properties
|
||||
|
||||
CONF_DROPDOWN = "dropdown"
|
||||
CONF_DROPDOWN_LIST = "dropdown_list"
|
||||
|
||||
lv_dropdown_t = LvSelect("lv_dropdown_t")
|
||||
lv_dropdown_list_t = LvType("lv_dropdown_list_t")
|
||||
dropdown_list_spec = WidgetType(CONF_DROPDOWN_LIST, lv_dropdown_list_t, (CONF_MAIN,))
|
||||
|
||||
DROPDOWN_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_SYMBOL): lv_text,
|
||||
cv.Optional(CONF_SELECTED_INDEX): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_DIR, default="BOTTOM"): DIRECTIONS.one_of,
|
||||
cv.Optional(CONF_DROPDOWN_LIST): part_schema(dropdown_list_spec),
|
||||
}
|
||||
)
|
||||
|
||||
DROPDOWN_SCHEMA = DROPDOWN_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_OPTIONS): cv.ensure_list(option_string),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class DropdownType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_DROPDOWN,
|
||||
lv_dropdown_t,
|
||||
(CONF_MAIN, CONF_INDICATOR),
|
||||
DROPDOWN_SCHEMA,
|
||||
DROPDOWN_BASE_SCHEMA,
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if options := config.get(CONF_OPTIONS):
|
||||
text = cg.safe_exp("\n".join(options))
|
||||
lv.dropdown_set_options(w.obj, text)
|
||||
if symbol := config.get(CONF_SYMBOL):
|
||||
lv.dropdown_set_symbol(w.obj, await lv_text.process(symbol))
|
||||
if (selected := config.get(CONF_SELECTED_INDEX)) is not None:
|
||||
value = await lv_int.process(selected)
|
||||
lv.dropdown_set_selected(w.obj, value)
|
||||
if dirn := config.get(CONF_DIR):
|
||||
lv.dropdown_set_dir(w.obj, literal(dirn))
|
||||
if dlist := config.get(CONF_DROPDOWN_LIST):
|
||||
with LocalVariable(
|
||||
"dropdown_list", lv_obj_t, lv_expr.dropdown_get_list(w.obj)
|
||||
) as dlist_obj:
|
||||
dwid = Widget(dlist_obj, dropdown_list_spec, dlist)
|
||||
await set_obj_properties(dwid, dlist)
|
||||
|
||||
def get_uses(self):
|
||||
return (CONF_LABEL,)
|
||||
|
||||
|
||||
dropdown_spec = DropdownType()
|
|
@ -65,16 +65,16 @@ class ImgType(WidgetType):
|
|||
async def to_code(self, w: Widget, config):
|
||||
if src := config.get(CONF_SRC):
|
||||
lv.img_set_src(w.obj, await lv_image.process(src))
|
||||
if cf_angle := config.get(CONF_ANGLE):
|
||||
if (cf_angle := config.get(CONF_ANGLE)) is not None:
|
||||
pivot_x = config[CONF_PIVOT_X]
|
||||
pivot_y = config[CONF_PIVOT_Y]
|
||||
lv.img_set_pivot(w.obj, pivot_x, pivot_y)
|
||||
lv.img_set_angle(w.obj, cf_angle)
|
||||
if img_zoom := config.get(CONF_ZOOM):
|
||||
if (img_zoom := config.get(CONF_ZOOM)) is not None:
|
||||
lv.img_set_zoom(w.obj, img_zoom)
|
||||
if offset := config.get(CONF_OFFSET_X):
|
||||
if (offset := config.get(CONF_OFFSET_X)) is not None:
|
||||
lv.img_set_offset_x(w.obj, offset)
|
||||
if offset := config.get(CONF_OFFSET_Y):
|
||||
if (offset := config.get(CONF_OFFSET_Y)) is not None:
|
||||
lv.img_set_offset_y(w.obj, offset)
|
||||
if CONF_ANTIALIAS in config:
|
||||
lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS])
|
||||
|
|
49
esphome/components/lvgl/keyboard.py
Normal file
49
esphome/components/lvgl/keyboard.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
from esphome.components.key_provider import KeyProvider
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MODE
|
||||
from esphome.cpp_types import std_string
|
||||
|
||||
from .defines import CONF_ITEMS, CONF_MAIN, KEYBOARD_MODES, literal
|
||||
from .helpers import add_lv_use, lvgl_components_required
|
||||
from .textarea import CONF_TEXTAREA, lv_textarea_t
|
||||
from .types import LvCompound, LvType
|
||||
from .widget import Widget, WidgetType, get_widgets
|
||||
|
||||
CONF_KEYBOARD = "keyboard"
|
||||
|
||||
KEYBOARD_SCHEMA = {
|
||||
cv.Optional(CONF_MODE, default="TEXT_UPPER"): KEYBOARD_MODES.one_of,
|
||||
cv.Optional(CONF_TEXTAREA): cv.use_id(lv_textarea_t),
|
||||
}
|
||||
|
||||
lv_keyboard_t = LvType(
|
||||
"LvKeyboardType",
|
||||
parents=(KeyProvider, LvCompound),
|
||||
largs=[(std_string, "text")],
|
||||
has_on_value=True,
|
||||
lvalue=lambda w: literal(f"lv_textarea_get_text({w.obj})"),
|
||||
)
|
||||
|
||||
|
||||
class KeyboardType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_KEYBOARD,
|
||||
lv_keyboard_t,
|
||||
(CONF_MAIN, CONF_ITEMS),
|
||||
KEYBOARD_SCHEMA,
|
||||
)
|
||||
|
||||
def get_uses(self):
|
||||
return CONF_KEYBOARD, CONF_TEXTAREA
|
||||
|
||||
async def to_code(self, w: Widget, config: dict):
|
||||
lvgl_components_required.add("KEY_LISTENER")
|
||||
lvgl_components_required.add(CONF_KEYBOARD)
|
||||
add_lv_use("btnmatrix")
|
||||
await w.set_property(CONF_MODE, await KEYBOARD_MODES.process(config[CONF_MODE]))
|
||||
if ta := await get_widgets(config, CONF_TEXTAREA):
|
||||
await w.set_property(CONF_TEXTAREA, ta[0].obj)
|
||||
|
||||
|
||||
keyboard_spec = KeyboardType()
|
|
@ -20,9 +20,9 @@ class LedType(WidgetType):
|
|||
super().__init__(CONF_LED, LvType("lv_led_t"), (CONF_MAIN,), LED_SCHEMA)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if color := config.get(CONF_COLOR):
|
||||
if (color := config.get(CONF_COLOR)) is not None:
|
||||
lv.led_set_color(w.obj, await lv_color.process(color))
|
||||
if brightness := config.get(CONF_BRIGHTNESS):
|
||||
if (brightness := config.get(CONF_BRIGHTNESS)) is not None:
|
||||
lv.led_set_brightness(w.obj, await lv_brightness.process(brightness))
|
||||
|
||||
|
||||
|
|
|
@ -146,12 +146,12 @@ LVEncoderListener::LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_
|
|||
#endif // USE_LVGL_ROTARY_ENCODER
|
||||
|
||||
#ifdef USE_LVGL_BUTTONMATRIX
|
||||
void LvBtnmatrixType::set_obj(lv_obj_t *lv_obj) {
|
||||
void LvButtonMatrixType::set_obj(lv_obj_t *lv_obj) {
|
||||
LvCompound::set_obj(lv_obj);
|
||||
lv_obj_add_event_cb(
|
||||
lv_obj,
|
||||
[](lv_event_t *event) {
|
||||
auto *self = static_cast<LvBtnmatrixType *>(event->user_data);
|
||||
auto *self = static_cast<LvButtonMatrixType *>(event->user_data);
|
||||
if (self->key_callback_.size() == 0)
|
||||
return;
|
||||
auto key_idx = lv_btnmatrix_get_selected_btn(self->obj);
|
||||
|
|
|
@ -246,7 +246,7 @@ class LVEncoderListener : public Parented<LvglComponent> {
|
|||
};
|
||||
#endif // USE_LVGL_ROTARY_ENCODER
|
||||
#ifdef USE_LVGL_BUTTONMATRIX
|
||||
class LvBtnmatrixType : public key_provider::KeyProvider, public LvCompound {
|
||||
class LvButtonMatrixType : public key_provider::KeyProvider, public LvCompound {
|
||||
public:
|
||||
void set_obj(lv_obj_t *lv_obj) override;
|
||||
uint16_t get_selected() { return lv_btnmatrix_get_selected_btn(this->obj); }
|
||||
|
|
302
esphome/components/lvgl/meter.py
Normal file
302
esphome/components/lvgl/meter.py
Normal file
|
@ -0,0 +1,302 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_COLOR,
|
||||
CONF_COUNT,
|
||||
CONF_ID,
|
||||
CONF_LENGTH,
|
||||
CONF_LOCAL,
|
||||
CONF_RANGE_FROM,
|
||||
CONF_RANGE_TO,
|
||||
CONF_ROTATION,
|
||||
CONF_VALUE,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
|
||||
from .arc import CONF_ARC
|
||||
from .automation import action_to_code
|
||||
from .defines import (
|
||||
CONF_END_VALUE,
|
||||
CONF_MAIN,
|
||||
CONF_PIVOT_X,
|
||||
CONF_PIVOT_Y,
|
||||
CONF_SRC,
|
||||
CONF_START_VALUE,
|
||||
CONF_TICKS,
|
||||
)
|
||||
from .helpers import add_lv_use
|
||||
from .img import CONF_IMAGE
|
||||
from .line import CONF_LINE
|
||||
from .lv_validation import (
|
||||
angle,
|
||||
get_end_value,
|
||||
get_start_value,
|
||||
lv_bool,
|
||||
lv_color,
|
||||
lv_float,
|
||||
lv_image,
|
||||
requires_component,
|
||||
size,
|
||||
)
|
||||
from .lvcode import LocalVariable, lv, lv_assign, lv_expr
|
||||
from .obj import obj_spec
|
||||
from .types import LvType, ObjUpdateAction
|
||||
from .widget import Widget, WidgetType, get_widgets
|
||||
|
||||
CONF_ANGLE_RANGE = "angle_range"
|
||||
CONF_COLOR_END = "color_end"
|
||||
CONF_COLOR_START = "color_start"
|
||||
CONF_INDICATORS = "indicators"
|
||||
CONF_LABEL_GAP = "label_gap"
|
||||
CONF_MAJOR = "major"
|
||||
CONF_METER = "meter"
|
||||
CONF_R_MOD = "r_mod"
|
||||
CONF_SCALES = "scales"
|
||||
CONF_STRIDE = "stride"
|
||||
CONF_TICK_STYLE = "tick_style"
|
||||
|
||||
lv_meter_t = LvType("lv_meter_t")
|
||||
lv_meter_indicator_t = cg.global_ns.struct("lv_meter_indicator_t")
|
||||
lv_meter_indicator_t_ptr = lv_meter_indicator_t.operator("ptr")
|
||||
|
||||
|
||||
def pixels(value):
|
||||
"""A size in one axis in pixels"""
|
||||
if isinstance(value, str) and value.lower().endswith("px"):
|
||||
return cv.int_(value[:-2])
|
||||
return cv.int_(value)
|
||||
|
||||
|
||||
INDICATOR_LINE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_WIDTH, default=4): size,
|
||||
cv.Optional(CONF_COLOR, default=0): lv_color,
|
||||
cv.Optional(CONF_R_MOD, default=0): size,
|
||||
cv.Optional(CONF_VALUE): lv_float,
|
||||
}
|
||||
)
|
||||
INDICATOR_IMG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SRC): lv_image,
|
||||
cv.Required(CONF_PIVOT_X): pixels,
|
||||
cv.Required(CONF_PIVOT_Y): pixels,
|
||||
cv.Optional(CONF_VALUE): lv_float,
|
||||
}
|
||||
)
|
||||
INDICATOR_ARC_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_WIDTH, default=4): size,
|
||||
cv.Optional(CONF_COLOR, default=0): lv_color,
|
||||
cv.Optional(CONF_R_MOD, default=0): size,
|
||||
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
|
||||
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
|
||||
cv.Optional(CONF_END_VALUE): lv_float,
|
||||
}
|
||||
)
|
||||
INDICATOR_TICKS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_WIDTH, default=4): size,
|
||||
cv.Optional(CONF_COLOR_START, default=0): lv_color,
|
||||
cv.Optional(CONF_COLOR_END): lv_color,
|
||||
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
|
||||
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
|
||||
cv.Optional(CONF_END_VALUE): lv_float,
|
||||
cv.Optional(CONF_LOCAL, default=False): lv_bool,
|
||||
}
|
||||
)
|
||||
INDICATOR_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Exclusive(CONF_LINE, CONF_INDICATORS): INDICATOR_LINE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(lv_meter_indicator_t),
|
||||
}
|
||||
),
|
||||
cv.Exclusive(CONF_IMAGE, CONF_INDICATORS): cv.All(
|
||||
INDICATOR_IMG_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(lv_meter_indicator_t),
|
||||
}
|
||||
),
|
||||
requires_component("image"),
|
||||
),
|
||||
cv.Exclusive(CONF_ARC, CONF_INDICATORS): INDICATOR_ARC_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(lv_meter_indicator_t),
|
||||
}
|
||||
),
|
||||
cv.Exclusive(CONF_TICK_STYLE, CONF_INDICATORS): INDICATOR_TICKS_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(lv_meter_indicator_t),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
SCALE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TICKS): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_COUNT, default=12): cv.positive_int,
|
||||
cv.Optional(CONF_WIDTH, default=2): size,
|
||||
cv.Optional(CONF_LENGTH, default=10): size,
|
||||
cv.Optional(CONF_COLOR, default=0x808080): lv_color,
|
||||
cv.Optional(CONF_MAJOR): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_STRIDE, default=3): cv.positive_int,
|
||||
cv.Optional(CONF_WIDTH, default=5): size,
|
||||
cv.Optional(CONF_LENGTH, default="15%"): size,
|
||||
cv.Optional(CONF_COLOR, default=0): lv_color,
|
||||
cv.Optional(CONF_LABEL_GAP, default=4): size,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_,
|
||||
cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_,
|
||||
cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360),
|
||||
cv.Optional(CONF_ROTATION): angle,
|
||||
cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
|
||||
}
|
||||
)
|
||||
|
||||
METER_SCHEMA = {cv.Optional(CONF_SCALES): cv.ensure_list(SCALE_SCHEMA)}
|
||||
|
||||
|
||||
class MeterType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(CONF_METER, lv_meter_t, (CONF_MAIN,), METER_SCHEMA)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
"""For a meter object, create and set parameters"""
|
||||
|
||||
var = w.obj
|
||||
for scale_conf in config.get(CONF_SCALES) or ():
|
||||
rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2
|
||||
if CONF_ROTATION in scale_conf:
|
||||
rotation = scale_conf[CONF_ROTATION] // 10
|
||||
with LocalVariable(
|
||||
"meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var)
|
||||
) as meter_var:
|
||||
lv.meter_set_scale_range(
|
||||
var,
|
||||
meter_var,
|
||||
scale_conf[CONF_RANGE_FROM],
|
||||
scale_conf[CONF_RANGE_TO],
|
||||
scale_conf[CONF_ANGLE_RANGE],
|
||||
rotation,
|
||||
)
|
||||
if ticks := scale_conf.get(CONF_TICKS):
|
||||
color = await lv_color.process(ticks[CONF_COLOR])
|
||||
lv.meter_set_scale_ticks(
|
||||
var,
|
||||
meter_var,
|
||||
ticks[CONF_COUNT],
|
||||
ticks[CONF_WIDTH],
|
||||
ticks[CONF_LENGTH],
|
||||
color,
|
||||
)
|
||||
if CONF_MAJOR in ticks:
|
||||
major = ticks[CONF_MAJOR]
|
||||
color = await lv_color.process(major[CONF_COLOR])
|
||||
lv.meter_set_scale_major_ticks(
|
||||
var,
|
||||
meter_var,
|
||||
major[CONF_STRIDE],
|
||||
major[CONF_WIDTH],
|
||||
major[CONF_LENGTH],
|
||||
color,
|
||||
major[CONF_LABEL_GAP],
|
||||
)
|
||||
for indicator in scale_conf.get(CONF_INDICATORS) or ():
|
||||
(t, v) = next(iter(indicator.items()))
|
||||
iid = v[CONF_ID]
|
||||
ivar = cg.new_variable(
|
||||
iid, cg.nullptr, type_=lv_meter_indicator_t_ptr
|
||||
)
|
||||
# Enable getting the meter to which this belongs.
|
||||
wid = Widget.create(iid, var, obj_spec, v)
|
||||
wid.obj = ivar
|
||||
if t == CONF_LINE:
|
||||
color = await lv_color.process(v[CONF_COLOR])
|
||||
lv_assign(
|
||||
ivar,
|
||||
lv_expr.meter_add_needle_line(
|
||||
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD]
|
||||
),
|
||||
)
|
||||
if t == CONF_ARC:
|
||||
color = await lv_color.process(v[CONF_COLOR])
|
||||
lv_assign(
|
||||
ivar,
|
||||
lv_expr.meter_add_arc(
|
||||
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD]
|
||||
),
|
||||
)
|
||||
if t == CONF_TICK_STYLE:
|
||||
color_start = await lv_color.process(v[CONF_COLOR_START])
|
||||
color_end = await lv_color.process(
|
||||
v.get(CONF_COLOR_END) or color_start
|
||||
)
|
||||
lv_assign(
|
||||
ivar,
|
||||
lv_expr.meter_add_scale_lines(
|
||||
var,
|
||||
meter_var,
|
||||
color_start,
|
||||
color_end,
|
||||
v[CONF_LOCAL],
|
||||
v[CONF_WIDTH],
|
||||
),
|
||||
)
|
||||
if t == CONF_IMAGE:
|
||||
add_lv_use("img")
|
||||
lv_assign(
|
||||
ivar,
|
||||
lv_expr.meter_add_needle_img(
|
||||
var,
|
||||
meter_var,
|
||||
await lv_image.process(v[CONF_SRC]),
|
||||
v[CONF_PIVOT_X],
|
||||
v[CONF_PIVOT_Y],
|
||||
),
|
||||
)
|
||||
start_value = await get_start_value(v)
|
||||
end_value = await get_end_value(v)
|
||||
set_indicator_values(var, ivar, start_value, end_value)
|
||||
|
||||
|
||||
meter_spec = MeterType()
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.indicator.update",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_meter_indicator_t),
|
||||
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
|
||||
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
|
||||
cv.Optional(CONF_END_VALUE): lv_float,
|
||||
}
|
||||
),
|
||||
)
|
||||
async def indicator_update_to_code(config, action_id, template_arg, args):
|
||||
widget = await get_widgets(config)
|
||||
start_value = await get_start_value(config)
|
||||
end_value = await get_end_value(config)
|
||||
|
||||
async def set_value(w: Widget):
|
||||
set_indicator_values(w.var, w.obj, start_value, end_value)
|
||||
|
||||
return await action_to_code(widget, set_value, action_id, template_arg, args)
|
||||
|
||||
|
||||
def set_indicator_values(meter, indicator, start_value, end_value):
|
||||
if start_value is not None:
|
||||
if end_value is None:
|
||||
lv.meter_set_indicator_value(meter, indicator, start_value)
|
||||
else:
|
||||
lv.meter_set_indicator_start_value(meter, indicator, start_value)
|
||||
if end_value is not None:
|
||||
lv.meter_set_indicator_end_value(meter, indicator, end_value)
|
127
esphome/components/lvgl/msgbox.py
Normal file
127
esphome/components/lvgl/msgbox.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_BUTTON, CONF_ID
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import new_Pvariable, static_const_array
|
||||
from esphome.cpp_types import nullptr
|
||||
|
||||
from .button import button_spec
|
||||
from .buttonmatrix import (
|
||||
BUTTONMATRIX_BUTTON_SCHEMA,
|
||||
CONF_BUTTON_TEXT_LIST_ID,
|
||||
buttonmatrix_spec,
|
||||
get_button_data,
|
||||
lv_buttonmatrix_t,
|
||||
set_btn_data,
|
||||
)
|
||||
from .defines import (
|
||||
CONF_BODY,
|
||||
CONF_BUTTONS,
|
||||
CONF_CLOSE_BUTTON,
|
||||
CONF_MSGBOXES,
|
||||
CONF_TEXT,
|
||||
CONF_TITLE,
|
||||
TYPE_FLEX,
|
||||
literal,
|
||||
)
|
||||
from .helpers import add_lv_use
|
||||
from .label import CONF_LABEL
|
||||
from .lv_validation import lv_bool, lv_pct, lv_text
|
||||
from .lvcode import (
|
||||
EVENT_ARG,
|
||||
LambdaContext,
|
||||
LocalVariable,
|
||||
lv_add,
|
||||
lv_assign,
|
||||
lv_expr,
|
||||
lv_obj,
|
||||
lv_Pvariable,
|
||||
)
|
||||
from .obj import obj_spec
|
||||
from .schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema
|
||||
from .styles import TOP_LAYER
|
||||
from .types import LV_EVENT, char_ptr, lv_obj_t
|
||||
from .widget import Widget, set_obj_properties
|
||||
|
||||
CONF_MSGBOX = "msgbox"
|
||||
MSGBOX_SCHEMA = container_schema(
|
||||
obj_spec,
|
||||
STYLE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(lv_obj_t),
|
||||
cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA,
|
||||
cv.Optional(CONF_BODY): STYLED_TEXT_SCHEMA,
|
||||
cv.Optional(CONF_BUTTONS): cv.ensure_list(BUTTONMATRIX_BUTTON_SCHEMA),
|
||||
cv.Optional(CONF_CLOSE_BUTTON): lv_bool,
|
||||
cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def msgbox_to_code(conf):
|
||||
"""
|
||||
Construct a message box. This consists of a full-screen translucent background enclosing a centered container
|
||||
with an optional title, body, close button and a button matrix. And any other widgets the user cares to add
|
||||
:param conf: The config data
|
||||
:return: code to add to the init lambda
|
||||
"""
|
||||
add_lv_use(
|
||||
TYPE_FLEX,
|
||||
CONF_BUTTON,
|
||||
CONF_LABEL,
|
||||
CONF_MSGBOX,
|
||||
*buttonmatrix_spec.get_uses(),
|
||||
*button_spec.get_uses(),
|
||||
)
|
||||
mbid = conf[CONF_ID]
|
||||
outer = lv_Pvariable(lv_obj_t, mbid.id)
|
||||
btnm = new_Pvariable(
|
||||
ID(f"{mbid.id}_btnm_", is_declaration=True, type=lv_buttonmatrix_t)
|
||||
)
|
||||
msgbox = lv_Pvariable(lv_obj_t, f"{mbid.id}_msgbox")
|
||||
outer_w = Widget.create(mbid, outer, obj_spec, conf)
|
||||
btnm_widg = Widget.create(str(btnm), btnm, buttonmatrix_spec, conf)
|
||||
text_list, ctrl_list, width_list, _ = await get_button_data((conf,), btnm_widg)
|
||||
text_id = conf[CONF_BUTTON_TEXT_LIST_ID]
|
||||
text_list = static_const_array(text_id, text_list)
|
||||
if (text := conf.get(CONF_BODY)) is not None:
|
||||
text = await lv_text.process(text.get(CONF_TEXT))
|
||||
if (title := conf.get(CONF_TITLE)) is not None:
|
||||
title = await lv_text.process(title.get(CONF_TEXT))
|
||||
close_button = conf[CONF_CLOSE_BUTTON]
|
||||
lv_assign(outer, lv_expr.obj_create(TOP_LAYER))
|
||||
lv_obj.set_width(outer, lv_pct(100))
|
||||
lv_obj.set_height(outer, lv_pct(100))
|
||||
lv_obj.set_style_bg_opa(outer, 128, 0)
|
||||
lv_obj.set_style_bg_color(outer, literal("lv_color_black()"), 0)
|
||||
lv_obj.set_style_border_width(outer, 0, 0)
|
||||
lv_obj.set_style_pad_all(outer, 0, 0)
|
||||
lv_obj.set_style_radius(outer, 0, 0)
|
||||
outer_w.add_flag("LV_OBJ_FLAG_HIDDEN")
|
||||
lv_assign(
|
||||
msgbox, lv_expr.msgbox_create(outer, title, text, text_list, close_button)
|
||||
)
|
||||
lv_obj.set_style_align(msgbox, literal("LV_ALIGN_CENTER"), 0)
|
||||
lv_add(btnm.set_obj(lv_expr.msgbox_get_btns(msgbox)))
|
||||
await set_obj_properties(outer_w, conf)
|
||||
if close_button:
|
||||
async with LambdaContext(EVENT_ARG, where=mbid) as context:
|
||||
outer_w.add_flag("LV_OBJ_FLAG_HIDDEN")
|
||||
with LocalVariable(
|
||||
"close_btn_", lv_obj_t, lv_expr.msgbox_get_close_btn(msgbox)
|
||||
) as close_btn:
|
||||
lv_obj.remove_event_cb(close_btn, nullptr)
|
||||
lv_obj.add_event_cb(
|
||||
close_btn,
|
||||
await context.get_lambda(),
|
||||
LV_EVENT.CLICKED,
|
||||
nullptr,
|
||||
)
|
||||
|
||||
if len(ctrl_list) != 0 or len(width_list) != 0:
|
||||
set_btn_data(btnm.obj, ctrl_list, width_list)
|
||||
|
||||
|
||||
async def msgboxes_to_code(config):
|
||||
for conf in config.get(CONF_MSGBOXES, ()):
|
||||
await msgbox_to_code(conf)
|
77
esphome/components/lvgl/roller.py
Normal file
77
esphome/components/lvgl/roller.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MODE, CONF_OPTIONS
|
||||
|
||||
from .defines import (
|
||||
CONF_ANIMATED,
|
||||
CONF_MAIN,
|
||||
CONF_SELECTED,
|
||||
CONF_SELECTED_INDEX,
|
||||
CONF_VISIBLE_ROW_COUNT,
|
||||
ROLLER_MODES,
|
||||
literal,
|
||||
)
|
||||
from .label import CONF_LABEL
|
||||
from .lv_validation import animated, lv_int, option_string
|
||||
from .lvcode import lv
|
||||
from .types import LvSelect
|
||||
from .widget import WidgetType
|
||||
|
||||
CONF_ROLLER = "roller"
|
||||
lv_roller_t = LvSelect("lv_roller_t")
|
||||
|
||||
ROLLER_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_SELECTED_INDEX): cv.templatable(cv.int_),
|
||||
cv.Optional(CONF_VISIBLE_ROW_COUNT): lv_int,
|
||||
}
|
||||
)
|
||||
|
||||
ROLLER_SCHEMA = ROLLER_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_OPTIONS): cv.ensure_list(option_string),
|
||||
cv.Optional(CONF_MODE, default="NORMAL"): ROLLER_MODES.one_of,
|
||||
}
|
||||
)
|
||||
|
||||
ROLLER_MODIFY_SCHEMA = ROLLER_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ANIMATED, default=True): animated,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class RollerType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_ROLLER,
|
||||
lv_roller_t,
|
||||
(CONF_MAIN, CONF_SELECTED),
|
||||
ROLLER_SCHEMA,
|
||||
ROLLER_MODIFY_SCHEMA,
|
||||
)
|
||||
|
||||
async def to_code(self, w, config):
|
||||
if options := config.get(CONF_OPTIONS):
|
||||
mode = await ROLLER_MODES.process(config[CONF_MODE])
|
||||
text = cg.safe_exp("\n".join(options))
|
||||
lv.roller_set_options(w.obj, text, mode)
|
||||
animopt = literal(config.get(CONF_ANIMATED) or "LV_ANIM_OFF")
|
||||
if CONF_SELECTED_INDEX in config:
|
||||
if selected := config[CONF_SELECTED_INDEX]:
|
||||
value = await lv_int.process(selected)
|
||||
lv.roller_set_selected(w.obj, value, animopt)
|
||||
await w.set_property(
|
||||
CONF_VISIBLE_ROW_COUNT,
|
||||
await lv_int.process(config.get(CONF_VISIBLE_ROW_COUNT)),
|
||||
)
|
||||
|
||||
@property
|
||||
def animated(self):
|
||||
return True
|
||||
|
||||
def get_uses(self):
|
||||
return (CONF_LABEL,)
|
||||
|
||||
|
||||
roller_spec = RollerType()
|
178
esphome/components/lvgl/spinbox.py
Normal file
178
esphome/components/lvgl/spinbox.py
Normal file
|
@ -0,0 +1,178 @@
|
|||
from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE
|
||||
|
||||
from .automation import action_to_code, update_to_code
|
||||
from .defines import (
|
||||
CONF_CURSOR,
|
||||
CONF_DECIMAL_PLACES,
|
||||
CONF_DIGITS,
|
||||
CONF_MAIN,
|
||||
CONF_ROLLOVER,
|
||||
CONF_SCROLLBAR,
|
||||
CONF_SELECTED,
|
||||
CONF_TEXTAREA_PLACEHOLDER,
|
||||
)
|
||||
from .label import CONF_LABEL
|
||||
from .lv_validation import lv_bool, lv_float
|
||||
from .lvcode import lv
|
||||
from .textarea import CONF_TEXTAREA
|
||||
from .types import LvNumber, ObjUpdateAction
|
||||
from .widget import Widget, WidgetType, get_widgets
|
||||
|
||||
CONF_SPINBOX = "spinbox"
|
||||
|
||||
lv_spinbox_t = LvNumber("lv_spinbox_t")
|
||||
|
||||
SPIN_ACTIONS = (
|
||||
"INCREMENT",
|
||||
"DECREMENT",
|
||||
"STEP_NEXT",
|
||||
"STEP_PREV",
|
||||
"CLEAR",
|
||||
)
|
||||
|
||||
|
||||
def validate_spinbox(config):
|
||||
max_val = 2**31 - 1
|
||||
min_val = -1 - max_val
|
||||
range_from = int(config[CONF_RANGE_FROM])
|
||||
range_to = int(config[CONF_RANGE_TO])
|
||||
step = int(config[CONF_STEP])
|
||||
if (
|
||||
range_from > max_val
|
||||
or range_from < min_val
|
||||
or range_to > max_val
|
||||
or range_to < min_val
|
||||
):
|
||||
raise cv.Invalid("Range outside allowed limits")
|
||||
if step <= 0 or step >= (range_to - range_from) / 2:
|
||||
raise cv.Invalid("Invalid step value")
|
||||
if config[CONF_DIGITS] <= config[CONF_DECIMAL_PLACES]:
|
||||
raise cv.Invalid("Number of digits must exceed number of decimal places")
|
||||
return config
|
||||
|
||||
|
||||
SPINBOX_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VALUE): lv_float,
|
||||
cv.Optional(CONF_RANGE_FROM, default=0): cv.float_,
|
||||
cv.Optional(CONF_RANGE_TO, default=100): cv.float_,
|
||||
cv.Optional(CONF_DIGITS, default=4): cv.int_range(1, 10),
|
||||
cv.Optional(CONF_STEP, default=1.0): cv.positive_float,
|
||||
cv.Optional(CONF_DECIMAL_PLACES, default=0): cv.int_range(0, 6),
|
||||
cv.Optional(CONF_ROLLOVER, default=False): lv_bool,
|
||||
}
|
||||
).add_extra(validate_spinbox)
|
||||
|
||||
|
||||
SPINBOX_MODIFY_SCHEMA = {
|
||||
cv.Required(CONF_VALUE): lv_float,
|
||||
}
|
||||
|
||||
|
||||
class SpinboxType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_SPINBOX,
|
||||
lv_spinbox_t,
|
||||
(
|
||||
CONF_MAIN,
|
||||
CONF_SCROLLBAR,
|
||||
CONF_SELECTED,
|
||||
CONF_CURSOR,
|
||||
CONF_TEXTAREA_PLACEHOLDER,
|
||||
),
|
||||
SPINBOX_SCHEMA,
|
||||
SPINBOX_MODIFY_SCHEMA,
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if CONF_DIGITS in config:
|
||||
digits = config[CONF_DIGITS]
|
||||
scale = 10 ** config[CONF_DECIMAL_PLACES]
|
||||
range_from = int(config[CONF_RANGE_FROM]) * scale
|
||||
range_to = int(config[CONF_RANGE_TO]) * scale
|
||||
step = int(config[CONF_STEP]) * scale
|
||||
w.scale = scale
|
||||
w.step = step
|
||||
w.range_to = range_to
|
||||
w.range_from = range_from
|
||||
lv.spinbox_set_range(w.obj, range_from, range_to)
|
||||
await w.set_property(CONF_STEP, step)
|
||||
await w.set_property(CONF_ROLLOVER, config)
|
||||
lv.spinbox_set_digit_format(
|
||||
w.obj, digits, digits - config[CONF_DECIMAL_PLACES]
|
||||
)
|
||||
if (value := config.get(CONF_VALUE)) is not None:
|
||||
lv.spinbox_set_value(w.obj, await lv_float.process(value))
|
||||
|
||||
def get_scale(self, config):
|
||||
return 10 ** config[CONF_DECIMAL_PLACES]
|
||||
|
||||
def get_uses(self):
|
||||
return CONF_TEXTAREA, CONF_LABEL
|
||||
|
||||
def get_max(self, config: dict):
|
||||
return config[CONF_RANGE_TO]
|
||||
|
||||
def get_min(self, config: dict):
|
||||
return config[CONF_RANGE_FROM]
|
||||
|
||||
def get_step(self, config: dict):
|
||||
return config[CONF_STEP]
|
||||
|
||||
|
||||
spinbox_spec = SpinboxType()
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.spinbox.increment",
|
||||
ObjUpdateAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_spinbox_t),
|
||||
},
|
||||
key=CONF_ID,
|
||||
),
|
||||
)
|
||||
async def spinbox_increment(config, action_id, template_arg, args):
|
||||
widgets = await get_widgets(config)
|
||||
|
||||
async def do_increment(w: Widget):
|
||||
lv.spinbox_increment(w.obj)
|
||||
|
||||
return await action_to_code(widgets, do_increment, action_id, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.spinbox.decrement",
|
||||
ObjUpdateAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_spinbox_t),
|
||||
},
|
||||
key=CONF_ID,
|
||||
),
|
||||
)
|
||||
async def spinbox_decrement(config, action_id, template_arg, args):
|
||||
widgets = await get_widgets(config)
|
||||
|
||||
async def do_increment(w: Widget):
|
||||
lv.spinbox_decrement(w.obj)
|
||||
|
||||
return await action_to_code(widgets, do_increment, action_id, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.spinbox.update",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_spinbox_t),
|
||||
cv.Required(CONF_VALUE): lv_float,
|
||||
}
|
||||
),
|
||||
)
|
||||
async def spinbox_update_to_code(config, action_id, template_arg, args):
|
||||
return await update_to_code(config, action_id, template_arg, args)
|
|
@ -26,7 +26,7 @@ async def styles_to_code(config):
|
|||
svar = cg.new_Pvariable(style[CONF_ID])
|
||||
lv.style_init(svar)
|
||||
for prop, validator in ALL_STYLES.items():
|
||||
if value := style.get(prop):
|
||||
if (value := style.get(prop)) is not None:
|
||||
if isinstance(validator, LValidator):
|
||||
value = await validator.process(value)
|
||||
if isinstance(value, list):
|
||||
|
|
114
esphome/components/lvgl/tabview.py
Normal file
114
esphome/components/lvgl/tabview.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INDEX, CONF_NAME, CONF_POSITION, CONF_SIZE
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
from . import buttonmatrix_spec
|
||||
from .automation import action_to_code
|
||||
from .defines import (
|
||||
CONF_ANIMATED,
|
||||
CONF_MAIN,
|
||||
CONF_TAB_ID,
|
||||
CONF_TABS,
|
||||
DIRECTIONS,
|
||||
TYPE_FLEX,
|
||||
literal,
|
||||
)
|
||||
from .lv_validation import animated, lv_int, size
|
||||
from .lvcode import LocalVariable, lv, lv_assign, lv_expr
|
||||
from .obj import obj_spec
|
||||
from .schemas import container_schema, part_schema
|
||||
from .types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr
|
||||
from .widget import Widget, WidgetType, add_widgets, get_widgets, set_obj_properties
|
||||
|
||||
CONF_TABVIEW = "tabview"
|
||||
CONF_TAB_STYLE = "tab_style"
|
||||
|
||||
lv_tab_t = LvType("lv_obj_t")
|
||||
|
||||
TABVIEW_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TABS): cv.ensure_list(
|
||||
container_schema(
|
||||
obj_spec,
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.GenerateID(): cv.declare_id(lv_tab_t),
|
||||
},
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_TAB_STYLE): part_schema(buttonmatrix_spec),
|
||||
cv.Optional(CONF_POSITION, default="top"): DIRECTIONS.one_of,
|
||||
cv.Optional(CONF_SIZE, default="10%"): size,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TabviewType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_TABVIEW,
|
||||
LvType(
|
||||
"lv_tabview_t",
|
||||
largs=[(lv_obj_t_ptr, "tab")],
|
||||
lvalue=lambda w: lv_expr.obj_get_child(
|
||||
lv_expr.tabview_get_content(w.obj),
|
||||
lv_expr.tabview_get_tab_act(w.obj),
|
||||
),
|
||||
has_on_value=True,
|
||||
),
|
||||
parts=(CONF_MAIN,),
|
||||
schema=TABVIEW_SCHEMA,
|
||||
modify_schema={},
|
||||
)
|
||||
|
||||
def get_uses(self):
|
||||
return "btnmatrix", TYPE_FLEX
|
||||
|
||||
async def to_code(self, w: Widget, config: dict):
|
||||
for tab_conf in config[CONF_TABS]:
|
||||
w_id = tab_conf[CONF_ID]
|
||||
tab_obj = cg.Pvariable(w_id, cg.nullptr, type_=lv_tab_t)
|
||||
tab_widget = Widget.create(w_id, tab_obj, obj_spec)
|
||||
lv_assign(tab_obj, lv_expr.tabview_add_tab(w.obj, tab_conf[CONF_NAME]))
|
||||
await set_obj_properties(tab_widget, tab_conf)
|
||||
await add_widgets(tab_widget, tab_conf)
|
||||
if button_style := config.get(CONF_TAB_STYLE):
|
||||
with LocalVariable(
|
||||
"tabview_btnmatrix", lv_obj_t, rhs=lv_expr.tabview_get_tab_btns(w.obj)
|
||||
) as btnmatrix_obj:
|
||||
await set_obj_properties(Widget(btnmatrix_obj, obj_spec), button_style)
|
||||
|
||||
def obj_creator(self, parent: MockObjClass, config: dict):
|
||||
return lv_expr.call(
|
||||
"tabview_create",
|
||||
parent,
|
||||
literal(config[CONF_POSITION]),
|
||||
literal(config[CONF_SIZE]),
|
||||
)
|
||||
|
||||
|
||||
tabview_spec = TabviewType()
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.tabview.select",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(tabview_spec.w_type),
|
||||
cv.Optional(CONF_ANIMATED, default=False): animated,
|
||||
cv.Required(CONF_INDEX): lv_int,
|
||||
},
|
||||
).add_extra(cv.has_at_least_one_key(CONF_INDEX, CONF_TAB_ID)),
|
||||
)
|
||||
async def tabview_select(config, action_id, template_arg, args):
|
||||
widget = await get_widgets(config)
|
||||
index = config[CONF_INDEX]
|
||||
|
||||
async def do_select(w: Widget):
|
||||
lv.tabview_set_act(w.obj, index, literal(config[CONF_ANIMATED]))
|
||||
lv.event_send(w.obj, LV_EVENT.VALUE_CHANGED, cg.nullptr)
|
||||
|
||||
return await action_to_code(widget, do_select, action_id, template_arg, args)
|
67
esphome/components/lvgl/textarea.py
Normal file
67
esphome/components/lvgl/textarea.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MAX_LENGTH
|
||||
|
||||
from .defines import (
|
||||
CONF_ACCEPTED_CHARS,
|
||||
CONF_CURSOR,
|
||||
CONF_MAIN,
|
||||
CONF_ONE_LINE,
|
||||
CONF_PASSWORD_MODE,
|
||||
CONF_PLACEHOLDER_TEXT,
|
||||
CONF_SCROLLBAR,
|
||||
CONF_SELECTED,
|
||||
CONF_TEXT,
|
||||
CONF_TEXTAREA_PLACEHOLDER,
|
||||
)
|
||||
from .lv_validation import lv_bool, lv_int, lv_text
|
||||
from .schemas import TEXT_SCHEMA
|
||||
from .types import LvText
|
||||
from .widget import Widget, WidgetType
|
||||
|
||||
CONF_TEXTAREA = "textarea"
|
||||
|
||||
lv_textarea_t = LvText("lv_textarea_t")
|
||||
|
||||
TEXTAREA_SCHEMA = TEXT_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_PLACEHOLDER_TEXT): lv_text,
|
||||
cv.Optional(CONF_ACCEPTED_CHARS): lv_text,
|
||||
cv.Optional(CONF_ONE_LINE): lv_bool,
|
||||
cv.Optional(CONF_PASSWORD_MODE): lv_bool,
|
||||
cv.Optional(CONF_MAX_LENGTH): lv_int,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TextareaType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_TEXTAREA,
|
||||
lv_textarea_t,
|
||||
(
|
||||
CONF_MAIN,
|
||||
CONF_SCROLLBAR,
|
||||
CONF_SELECTED,
|
||||
CONF_CURSOR,
|
||||
CONF_TEXTAREA_PLACEHOLDER,
|
||||
),
|
||||
TEXTAREA_SCHEMA,
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config: dict):
|
||||
for prop in (CONF_TEXT, CONF_PLACEHOLDER_TEXT, CONF_ACCEPTED_CHARS):
|
||||
if (value := config.get(prop)) is not None:
|
||||
await w.set_property(prop, await lv_text.process(value))
|
||||
await w.set_property(
|
||||
CONF_MAX_LENGTH, await lv_int.process(config.get(CONF_MAX_LENGTH))
|
||||
)
|
||||
await w.set_property(
|
||||
CONF_PASSWORD_MODE,
|
||||
await lv_bool.process(config.get(CONF_PASSWORD_MODE)),
|
||||
)
|
||||
await w.set_property(
|
||||
CONF_ONE_LINE, await lv_bool.process(config.get(CONF_ONE_LINE))
|
||||
)
|
||||
|
||||
|
||||
textarea_spec = TextareaType()
|
128
esphome/components/lvgl/tileview.py
Normal file
128
esphome/components/lvgl/tileview.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_ON_VALUE, CONF_ROW, CONF_TRIGGER_ID
|
||||
|
||||
from .automation import action_to_code
|
||||
from .defines import (
|
||||
CONF_ANIMATED,
|
||||
CONF_COLUMN,
|
||||
CONF_DIR,
|
||||
CONF_MAIN,
|
||||
CONF_TILE_ID,
|
||||
CONF_TILES,
|
||||
TILE_DIRECTIONS,
|
||||
literal,
|
||||
)
|
||||
from .lv_validation import animated, lv_int
|
||||
from .lvcode import lv, lv_assign, lv_expr, lv_obj, lv_Pvariable
|
||||
from .obj import obj_spec
|
||||
from .schemas import container_schema
|
||||
from .types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr
|
||||
from .widget import Widget, WidgetType, add_widgets, get_widgets, set_obj_properties
|
||||
|
||||
CONF_TILEVIEW = "tileview"
|
||||
|
||||
lv_tile_t = LvType("lv_tileview_tile_t")
|
||||
|
||||
lv_tileview_t = LvType(
|
||||
"lv_tileview_t",
|
||||
largs=[(lv_obj_t_ptr, "tile")],
|
||||
lvalue=lambda w: w.get_property("tile_act"),
|
||||
)
|
||||
|
||||
tile_spec = WidgetType("lv_tileview_tile_t", lv_tile_t, (CONF_MAIN,), {})
|
||||
|
||||
TILEVIEW_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TILES): cv.ensure_list(
|
||||
container_schema(
|
||||
obj_spec,
|
||||
{
|
||||
cv.Required(CONF_ROW): lv_int,
|
||||
cv.Required(CONF_COLUMN): lv_int,
|
||||
cv.GenerateID(): cv.declare_id(lv_tile_t),
|
||||
cv.Optional(CONF_DIR, default="ALL"): TILE_DIRECTIONS.several_of,
|
||||
},
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
automation.Trigger.template(lv_obj_t_ptr)
|
||||
)
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TileviewType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
CONF_TILEVIEW,
|
||||
lv_tileview_t,
|
||||
(CONF_MAIN,),
|
||||
schema=TILEVIEW_SCHEMA,
|
||||
modify_schema={},
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config: dict):
|
||||
for tile_conf in config.get(CONF_TILES) or ():
|
||||
w_id = tile_conf[CONF_ID]
|
||||
tile_obj = lv_Pvariable(lv_obj_t, w_id)
|
||||
tile = Widget.create(w_id, tile_obj, tile_spec, tile_conf)
|
||||
dirs = tile_conf[CONF_DIR]
|
||||
if isinstance(dirs, list):
|
||||
dirs = "|".join(dirs)
|
||||
lv_assign(
|
||||
tile_obj,
|
||||
lv_expr.tileview_add_tile(
|
||||
w.obj, tile_conf[CONF_COLUMN], tile_conf[CONF_ROW], literal(dirs)
|
||||
),
|
||||
)
|
||||
await set_obj_properties(tile, tile_conf)
|
||||
await add_widgets(tile, tile_conf)
|
||||
|
||||
|
||||
tileview_spec = TileviewType()
|
||||
|
||||
|
||||
def tile_select_validate(config):
|
||||
row = CONF_ROW in config
|
||||
column = CONF_COLUMN in config
|
||||
tile = CONF_TILE_ID in config
|
||||
if tile and (row or column) or not tile and not (row and column):
|
||||
raise cv.Invalid("Specify either a tile id, or both a row and a column")
|
||||
return config
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.tileview.select",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_tileview_t),
|
||||
cv.Optional(CONF_ANIMATED, default=False): animated,
|
||||
cv.Optional(CONF_ROW): lv_int,
|
||||
cv.Optional(CONF_COLUMN): lv_int,
|
||||
cv.Optional(CONF_TILE_ID): cv.use_id(lv_tile_t),
|
||||
},
|
||||
).add_extra(tile_select_validate),
|
||||
)
|
||||
async def tileview_select(config, action_id, template_arg, args):
|
||||
widgets = await get_widgets(config)
|
||||
|
||||
async def do_select(w: Widget):
|
||||
if tile := config.get(CONF_TILE_ID):
|
||||
tile = await cg.get_variable(tile)
|
||||
lv_obj.set_tile(w.obj, tile, literal(config[CONF_ANIMATED]))
|
||||
else:
|
||||
row = await lv_int.process(config[CONF_ROW])
|
||||
column = await lv_int.process(config[CONF_COLUMN])
|
||||
lv_obj.set_tile_id(
|
||||
widgets[0].obj, column, row, literal(config[CONF_ANIMATED])
|
||||
)
|
||||
lv.event_send(w.obj, LV_EVENT.VALUE_CHANGED, cg.nullptr)
|
||||
|
||||
return await action_to_code(widgets, do_select, action_id, template_arg, args)
|
|
@ -282,13 +282,13 @@ async def set_obj_properties(w: Widget, config):
|
|||
lv_obj.set_layout(w.obj, literal(f"LV_LAYOUT_{layout_type.upper()}"))
|
||||
if layout_type == TYPE_GRID:
|
||||
wid = config[CONF_ID]
|
||||
rows = "{" + ",".join(layout[CONF_GRID_ROWS]) + ", LV_GRID_TEMPLATE_LAST}"
|
||||
rows = [str(x) for x in layout[CONF_GRID_ROWS]]
|
||||
rows = "{" + ",".join(rows) + ", LV_GRID_TEMPLATE_LAST}"
|
||||
row_id = ID(f"{wid}_row_dsc", is_declaration=True, type=lv_coord_t)
|
||||
row_array = cg.static_const_array(row_id, cg.RawExpression(rows))
|
||||
w.set_style("grid_row_dsc_array", row_array, 0)
|
||||
columns = (
|
||||
"{" + ",".join(layout[CONF_GRID_COLUMNS]) + ", LV_GRID_TEMPLATE_LAST}"
|
||||
)
|
||||
columns = [str(x) for x in layout[CONF_GRID_COLUMNS]]
|
||||
columns = "{" + ",".join(columns) + ", LV_GRID_TEMPLATE_LAST}"
|
||||
column_id = ID(f"{wid}_column_dsc", is_declaration=True, type=lv_coord_t)
|
||||
column_array = cg.static_const_array(column_id, cg.RawExpression(columns))
|
||||
w.set_style("grid_column_dsc_array", column_array, 0)
|
||||
|
|
|
@ -39,9 +39,12 @@
|
|||
#define USE_LOCK
|
||||
#define USE_LOGGER
|
||||
#define USE_LVGL
|
||||
#define USE_LVGL_ANIMIMG
|
||||
#define USE_LVGL_BINARY_SENSOR
|
||||
#define USE_LVGL_BUTTONMATRIX
|
||||
#define USE_LVGL_FONT
|
||||
#define USE_LVGL_IMAGE
|
||||
#define USE_LVGL_KEYBOARD
|
||||
#define USE_LVGL_KEY_LISTENER
|
||||
#define USE_LVGL_TOUCHSCREEN
|
||||
#define USE_LVGL_ROTARY_ENCODER
|
||||
|
|
2
tests/components/lvgl/.gitattributes
vendored
Normal file
2
tests/components/lvgl/.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.ttf -text
|
||||
|
|
@ -8,3 +8,49 @@ touchscreen:
|
|||
x_max: 240
|
||||
y_max: 320
|
||||
|
||||
font:
|
||||
- file: "$component_dir/roboto.ttf"
|
||||
id: roboto20
|
||||
size: 20
|
||||
extras:
|
||||
- file: '$component_dir/materialdesignicons-webfont.ttf'
|
||||
glyphs: [
|
||||
"\U000F004B",
|
||||
"\U0000f0ed",
|
||||
"\U000F006E",
|
||||
"\U000F012C",
|
||||
"\U000F179B",
|
||||
"\U000F0748",
|
||||
"\U000F1A1B",
|
||||
"\U000F02DC",
|
||||
"\U000F0A02",
|
||||
"\U000F035F",
|
||||
"\U000F0156",
|
||||
"\U000F0C5F",
|
||||
"\U000f0084",
|
||||
"\U000f0091",
|
||||
]
|
||||
- file: "$component_dir/helvetica.ttf"
|
||||
id: helvetica20
|
||||
- file: "$component_dir/roboto.ttf"
|
||||
id: roboto10
|
||||
size: 10
|
||||
bpp: 4
|
||||
extras:
|
||||
- file: '$component_dir/materialdesignicons-webfont.ttf'
|
||||
glyphs: [
|
||||
"\U000F004B",
|
||||
"\U0000f0ed",
|
||||
"\U000F006E",
|
||||
"\U000F012C",
|
||||
"\U000F179B",
|
||||
"\U000F0748",
|
||||
"\U000F1A1B",
|
||||
"\U000F02DC",
|
||||
"\U000F0A02",
|
||||
"\U000F035F",
|
||||
"\U000F0156",
|
||||
"\U000F0C5F",
|
||||
"\U000f0084",
|
||||
"\U000f0091",
|
||||
]
|
||||
|
|
BIN
tests/components/lvgl/helvetica.ttf
Normal file
BIN
tests/components/lvgl/helvetica.ttf
Normal file
Binary file not shown.
|
@ -1,6 +1,53 @@
|
|||
lvgl:
|
||||
log_level: TRACE
|
||||
bg_color: light_blue
|
||||
theme:
|
||||
obj:
|
||||
border_width: 1
|
||||
|
||||
style_definitions:
|
||||
- id: style_test
|
||||
bg_color: 0x2F8CD8
|
||||
- id: header_footer
|
||||
bg_color: 0x20214F
|
||||
bg_grad_color: 0x005782
|
||||
bg_grad_dir: VER
|
||||
bg_opa: cover
|
||||
border_width: 0
|
||||
radius: 0
|
||||
pad_all: 0
|
||||
pad_row: 0
|
||||
pad_column: 0
|
||||
border_color: 0x0077b3
|
||||
text_color: 0xFFFFFF
|
||||
width: 100%
|
||||
height: 30
|
||||
border_side: [left, top]
|
||||
text_decor: [underline, strikethrough]
|
||||
- id: style_line
|
||||
line_color: light_blue
|
||||
line_width: 8
|
||||
line_rounded: true
|
||||
- id: date_style
|
||||
text_font: roboto10
|
||||
align: center
|
||||
text_color: 0x000000
|
||||
bg_opa: cover
|
||||
radius: 4
|
||||
pad_all: 2
|
||||
- id: spin_button
|
||||
height: 40
|
||||
width: 40
|
||||
- id: spin_label
|
||||
align: center
|
||||
text_align: center
|
||||
text_font: space16
|
||||
- id: bdr_style
|
||||
border_color: 0x808080
|
||||
border_width: 2
|
||||
pad_all: 4
|
||||
align: center
|
||||
|
||||
touchscreens:
|
||||
- touchscreen_id: tft_touch
|
||||
long_press_repeat_time: 200ms
|
||||
|
@ -9,6 +56,13 @@ lvgl:
|
|||
- id: page1
|
||||
skip: true
|
||||
widgets:
|
||||
- animimg:
|
||||
height: 60
|
||||
id: anim_img
|
||||
src: [cat_image, dog_image]
|
||||
repeat_count: 10
|
||||
duration: 1s
|
||||
auto_start: true
|
||||
- label:
|
||||
id: hello_label
|
||||
text: Hello world
|
||||
|
@ -16,7 +70,9 @@ lvgl:
|
|||
align: center
|
||||
text_font: montserrat_40
|
||||
border_post: true
|
||||
|
||||
on_click:
|
||||
then:
|
||||
- lvgl.animimg.stop: anim_img
|
||||
- label:
|
||||
text: "Hello shiny day"
|
||||
text_color: 0xFFFFFF
|
||||
|
@ -94,7 +150,65 @@ lvgl:
|
|||
width: 10px
|
||||
x: 100
|
||||
y: 120
|
||||
- buttonmatrix:
|
||||
on_press:
|
||||
logger.log:
|
||||
format: "matrix button pressed: %d"
|
||||
args: ["x"]
|
||||
on_long_press:
|
||||
lvgl.matrix.button.update:
|
||||
id: [button_a, button_e, button_c]
|
||||
control:
|
||||
disabled: true
|
||||
on_click:
|
||||
logger.log:
|
||||
format: "matrix button clicked: %d, is button_a = %u"
|
||||
args: ["x", "id(button_a) == x"]
|
||||
items:
|
||||
checked:
|
||||
bg_color: 0xFFFF00
|
||||
id: b_matrix
|
||||
|
||||
rows:
|
||||
- buttons:
|
||||
- id: button_a
|
||||
text: home icon
|
||||
width: 2
|
||||
control:
|
||||
checkable: true
|
||||
on_value:
|
||||
logger.log:
|
||||
format: "button_a value %d"
|
||||
args: [x]
|
||||
- id: button_b
|
||||
text: B
|
||||
width: 1
|
||||
on_value:
|
||||
logger.log:
|
||||
format: "button_b value %d"
|
||||
args: [x]
|
||||
on_click:
|
||||
then:
|
||||
- lvgl.page.previous:
|
||||
control:
|
||||
hidden: false
|
||||
- buttons:
|
||||
- id: button_c
|
||||
text: C
|
||||
control:
|
||||
checkable: false
|
||||
- id: button_d
|
||||
text: menu left
|
||||
on_long_press:
|
||||
then:
|
||||
logger.log: Long pressed
|
||||
on_long_press_repeat:
|
||||
then:
|
||||
logger.log: Long pressed repeated
|
||||
- buttons:
|
||||
- id: button_e
|
||||
- button:
|
||||
id: button_button
|
||||
width: 20%
|
||||
height: 10%
|
||||
pressed:
|
||||
|
@ -137,6 +251,7 @@ lvgl:
|
|||
on_long_press_repeat:
|
||||
logger.log: Button clicked
|
||||
- led:
|
||||
id: lv_led
|
||||
color: 0x00FF00
|
||||
brightness: 50%
|
||||
align: right_mid
|
||||
|
@ -151,6 +266,41 @@ lvgl:
|
|||
|
||||
- id: page2
|
||||
widgets:
|
||||
- button:
|
||||
styles: spin_button
|
||||
id: spin_up
|
||||
on_click:
|
||||
- lvgl.spinbox.increment: spinbox_id
|
||||
widgets:
|
||||
- label:
|
||||
styles: spin_label
|
||||
text: "+"
|
||||
- spinbox:
|
||||
text_font: space16
|
||||
id: spinbox_id
|
||||
align: center
|
||||
width: 120
|
||||
range_from: -10
|
||||
range_to: 1000
|
||||
step: 5.0
|
||||
rollover: false
|
||||
digits: 6
|
||||
decimal_places: 2
|
||||
value: 15
|
||||
on_value:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Spinbox value is %f"
|
||||
args: [x]
|
||||
- button:
|
||||
styles: spin_button
|
||||
id: spin_down
|
||||
on_click:
|
||||
- lvgl.spinbox.decrement: spinbox_id
|
||||
widgets:
|
||||
- label:
|
||||
styles: spin_label
|
||||
text: "-"
|
||||
- arc:
|
||||
align: left_mid
|
||||
id: lv_arc
|
||||
|
@ -160,7 +310,6 @@ lvgl:
|
|||
- logger.log:
|
||||
format: "Arc value is %f"
|
||||
args: [x]
|
||||
group: general
|
||||
scroll_on_focus: true
|
||||
value: 75
|
||||
min_value: 1
|
||||
|
@ -201,6 +350,7 @@ lvgl:
|
|||
- switch:
|
||||
align: right_mid
|
||||
- checkbox:
|
||||
id: checkbox_id
|
||||
text: Checkbox
|
||||
align: bottom_right
|
||||
- slider:
|
||||
|
@ -221,6 +371,78 @@ lvgl:
|
|||
- lvgl.slider.update:
|
||||
id: slider_id
|
||||
value: !lambda return (int)((float)rand() / RAND_MAX * 100);
|
||||
- tabview:
|
||||
id: tabview_id
|
||||
width: 100%
|
||||
height: 80%
|
||||
position: top
|
||||
on_value:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return tab == id(tabview_tab_1);
|
||||
then:
|
||||
- logger.log: "Dog tab is now showing"
|
||||
tabs:
|
||||
- name: Dog
|
||||
id: tabview_tab_1
|
||||
border_width: 2
|
||||
border_color: 0xff0000
|
||||
width: 100%
|
||||
pad_all: 8
|
||||
layout:
|
||||
type: grid
|
||||
grid_row_align: end
|
||||
grid_rows: [25px, fr(1), content]
|
||||
grid_columns: [40, fr(1), fr(1)]
|
||||
widgets:
|
||||
- image:
|
||||
grid_cell_row_pos: 0
|
||||
grid_cell_column_pos: 0
|
||||
src: dog_image
|
||||
on_click:
|
||||
then:
|
||||
- lvgl.tabview.select:
|
||||
id: tabview_id
|
||||
index: 1
|
||||
animated: true
|
||||
- label:
|
||||
styles: bdr_style
|
||||
grid_cell_x_align: center
|
||||
grid_cell_y_align: stretch
|
||||
grid_cell_row_pos: 0
|
||||
grid_cell_column_pos: 1
|
||||
grid_cell_column_span: 1
|
||||
text: "Grid cell 0/1"
|
||||
- label:
|
||||
grid_cell_x_align: end
|
||||
styles: bdr_style
|
||||
grid_cell_row_pos: 1
|
||||
grid_cell_column_pos: 0
|
||||
text: "Grid cell 1/0"
|
||||
- label:
|
||||
styles: bdr_style
|
||||
grid_cell_row_pos: 1
|
||||
grid_cell_column_pos: 1
|
||||
text: "Grid cell 1/1"
|
||||
- label:
|
||||
id: cell_1_3
|
||||
styles: bdr_style
|
||||
grid_cell_row_pos: 1
|
||||
grid_cell_column_pos: 2
|
||||
text: "Grid cell 1/2"
|
||||
- name: Cat
|
||||
id: tabview_tab_2
|
||||
widgets:
|
||||
- image:
|
||||
src: cat_image
|
||||
on_click:
|
||||
then:
|
||||
- logger.log: Cat image clicked
|
||||
- lvgl.tabview.select:
|
||||
id: tabview_id
|
||||
index: 0
|
||||
animated: true
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: space16
|
||||
|
@ -230,7 +452,7 @@ image:
|
|||
- id: cat_image
|
||||
resize: 256x48
|
||||
file: $component_dir/logo-text.svg
|
||||
- id: dog_img
|
||||
- id: dog_image
|
||||
file: $component_dir/logo-text.svg
|
||||
resize: 256x48
|
||||
type: TRANSPARENT_BINARY
|
||||
|
|
BIN
tests/components/lvgl/materialdesignicons-webfont.ttf
Normal file
BIN
tests/components/lvgl/materialdesignicons-webfont.ttf
Normal file
Binary file not shown.
BIN
tests/components/lvgl/roboto.ttf
Normal file
BIN
tests/components/lvgl/roboto.ttf
Normal file
Binary file not shown.
Loading…
Reference in a new issue