mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	[lvgl] Stage 5 (#7191)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -24,10 +24,13 @@ from . import defines as df, helpers, lv_validation as lvalid | |||||||
| from .animimg import animimg_spec | from .animimg import animimg_spec | ||||||
| from .arc import arc_spec | from .arc import arc_spec | ||||||
| from .automation import disp_update, update_to_code | 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 .checkbox import checkbox_spec | ||||||
| from .defines import CONF_SKIP | from .defines import CONF_SKIP | ||||||
|  | from .dropdown import dropdown_spec | ||||||
| from .img import img_spec | from .img import img_spec | ||||||
|  | from .keyboard import keyboard_spec | ||||||
| from .label import label_spec | from .label import label_spec | ||||||
| from .led import led_spec | from .led import led_spec | ||||||
| from .line import line_spec | from .line import line_spec | ||||||
| @@ -35,8 +38,11 @@ from .lv_bar import bar_spec | |||||||
| from .lv_switch import switch_spec | from .lv_switch import switch_spec | ||||||
| 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 | ||||||
|  | from .meter import meter_spec | ||||||
|  | from .msgbox import MSGBOX_SCHEMA, msgboxes_to_code | ||||||
| from .obj import obj_spec | from .obj import obj_spec | ||||||
| from .page import add_pages, page_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 .rotary_encoders import ROTARY_ENCODER_CONFIG, rotary_encoders_to_code | ||||||
| from .schemas import ( | from .schemas import ( | ||||||
|     DISP_BG_SCHEMA, |     DISP_BG_SCHEMA, | ||||||
| @@ -52,8 +58,12 @@ from .schemas import ( | |||||||
|     obj_schema, |     obj_schema, | ||||||
| ) | ) | ||||||
| from .slider import slider_spec | from .slider import slider_spec | ||||||
|  | from .spinbox import spinbox_spec | ||||||
| from .spinner import spinner_spec | from .spinner import spinner_spec | ||||||
| from .styles import add_top_layer, styles_to_code, theme_to_code | 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 .touchscreens import touchscreen_schema, touchscreens_to_code | ||||||
| from .trigger import generate_triggers | from .trigger import generate_triggers | ||||||
| from .types import ( | from .types import ( | ||||||
| @@ -75,7 +85,7 @@ LOGGER = logging.getLogger(__name__) | |||||||
| for w_type in ( | for w_type in ( | ||||||
|     label_spec, |     label_spec, | ||||||
|     obj_spec, |     obj_spec, | ||||||
|     btn_spec, |     button_spec, | ||||||
|     bar_spec, |     bar_spec, | ||||||
|     slider_spec, |     slider_spec, | ||||||
|     arc_spec, |     arc_spec, | ||||||
| @@ -86,6 +96,15 @@ for w_type in ( | |||||||
|     checkbox_spec, |     checkbox_spec, | ||||||
|     img_spec, |     img_spec, | ||||||
|     switch_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 |     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_widgets(lv_scr_act, config) | ||||||
|         await add_pages(lv_component, config) |         await add_pages(lv_component, config) | ||||||
|         await add_top_layer(config) |         await add_top_layer(config) | ||||||
|  |         await msgboxes_to_code(config) | ||||||
|         await disp_update(f"{lv_component}->get_disp()", config) |         await disp_update(f"{lv_component}->get_disp()", config) | ||||||
|         Widget.set_completed() |         Widget.set_completed() | ||||||
|         await generate_triggers(lv_component) |         await generate_triggers(lv_component) | ||||||
| @@ -308,6 +328,7 @@ CONFIG_SCHEMA = ( | |||||||
|             cv.Exclusive(CONF_PAGES, CONF_PAGES): cv.ensure_list( |             cv.Exclusive(CONF_PAGES, CONF_PAGES): cv.ensure_list( | ||||||
|                 container_schema(page_spec) |                 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_PAGE_WRAP, default=True): lv_bool, | ||||||
|             cv.Optional(df.CONF_TOP_LAYER): container_schema(obj_spec), |             cv.Optional(df.CONF_TOP_LAYER): container_schema(obj_spec), | ||||||
|             cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color, |             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.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_DURATION, CONF_ID | 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 .automation import action_to_code | ||||||
| from .defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC | from .defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC | ||||||
| from .helpers import lvgl_components_required | 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: |     if CONF_DISP_BG_COLOR not in config and CONF_DISP_BG_IMAGE not in config: | ||||||
|         return |         return | ||||||
|     with LocalVariable("lv_disp_tmp", lv_disp_t, literal(disp)) as disp_temp: |     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)) |             lv.disp_set_bg_color(disp_temp, await lv_color.process(bg_color)) | ||||||
|         if bg_image := config.get(CONF_DISP_BG_IMAGE): |         if bg_image := config.get(CONF_DISP_BG_IMAGE): | ||||||
|             lv.disp_set_bg_image(disp_temp, await lv_image.process(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 .defines import CONF_MAIN | ||||||
| from .types import LvBoolean, WidgetType | 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): |     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): |     def get_uses(self): | ||||||
|         return ("btn",) |         return ("btn",) | ||||||
| @@ -17,4 +17,4 @@ class BtnType(WidgetType): | |||||||
|         return [] |         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): |     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)) |             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") | ARC_MODES = LvConstant("LV_ARC_MODE_", "NORMAL", "REVERSE", "SYMMETRICAL") | ||||||
| BAR_MODES = LvConstant("LV_BAR_MODE_", "NORMAL", "SYMMETRICAL", "RANGE") | BAR_MODES = LvConstant("LV_BAR_MODE_", "NORMAL", "SYMMETRICAL", "RANGE") | ||||||
|  |  | ||||||
| BTNMATRIX_CTRLS = LvConstant( | BUTTONMATRIX_CTRLS = LvConstant( | ||||||
|     "LV_BTNMATRIX_CTRL_", |     "LV_BTNMATRIX_CTRL_", | ||||||
|     "HIDDEN", |     "HIDDEN", | ||||||
|     "NO_REPEAT", |     "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): |     async def to_code(self, w: Widget, config): | ||||||
|         if src := config.get(CONF_SRC): |         if src := config.get(CONF_SRC): | ||||||
|             lv.img_set_src(w.obj, await lv_image.process(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_x = config[CONF_PIVOT_X] | ||||||
|             pivot_y = config[CONF_PIVOT_Y] |             pivot_y = config[CONF_PIVOT_Y] | ||||||
|             lv.img_set_pivot(w.obj, pivot_x, pivot_y) |             lv.img_set_pivot(w.obj, pivot_x, pivot_y) | ||||||
|             lv.img_set_angle(w.obj, cf_angle) |             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) |             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) |             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) |             lv.img_set_offset_y(w.obj, offset) | ||||||
|         if CONF_ANTIALIAS in config: |         if CONF_ANTIALIAS in config: | ||||||
|             lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS]) |             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) |         super().__init__(CONF_LED, LvType("lv_led_t"), (CONF_MAIN,), LED_SCHEMA) | ||||||
|  |  | ||||||
|     async def to_code(self, w: Widget, config): |     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)) |             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)) |             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 | #endif  // USE_LVGL_ROTARY_ENCODER | ||||||
|  |  | ||||||
| #ifdef USE_LVGL_BUTTONMATRIX | #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); |   LvCompound::set_obj(lv_obj); | ||||||
|   lv_obj_add_event_cb( |   lv_obj_add_event_cb( | ||||||
|       lv_obj, |       lv_obj, | ||||||
|       [](lv_event_t *event) { |       [](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) |         if (self->key_callback_.size() == 0) | ||||||
|           return; |           return; | ||||||
|         auto key_idx = lv_btnmatrix_get_selected_btn(self->obj); |         auto key_idx = lv_btnmatrix_get_selected_btn(self->obj); | ||||||
|   | |||||||
| @@ -246,7 +246,7 @@ class LVEncoderListener : public Parented<LvglComponent> { | |||||||
| }; | }; | ||||||
| #endif  // USE_LVGL_ROTARY_ENCODER | #endif  // USE_LVGL_ROTARY_ENCODER | ||||||
| #ifdef USE_LVGL_BUTTONMATRIX | #ifdef USE_LVGL_BUTTONMATRIX | ||||||
| class LvBtnmatrixType : public key_provider::KeyProvider, public LvCompound { | class LvButtonMatrixType : public key_provider::KeyProvider, public LvCompound { | ||||||
|  public: |  public: | ||||||
|   void set_obj(lv_obj_t *lv_obj) override; |   void set_obj(lv_obj_t *lv_obj) override; | ||||||
|   uint16_t get_selected() { return lv_btnmatrix_get_selected_btn(this->obj); } |   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]) |         svar = cg.new_Pvariable(style[CONF_ID]) | ||||||
|         lv.style_init(svar) |         lv.style_init(svar) | ||||||
|         for prop, validator in ALL_STYLES.items(): |         for prop, validator in ALL_STYLES.items(): | ||||||
|             if value := style.get(prop): |             if (value := style.get(prop)) is not None: | ||||||
|                 if isinstance(validator, LValidator): |                 if isinstance(validator, LValidator): | ||||||
|                     value = await validator.process(value) |                     value = await validator.process(value) | ||||||
|                 if isinstance(value, list): |                 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()}")) |         lv_obj.set_layout(w.obj, literal(f"LV_LAYOUT_{layout_type.upper()}")) | ||||||
|         if layout_type == TYPE_GRID: |         if layout_type == TYPE_GRID: | ||||||
|             wid = config[CONF_ID] |             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_id = ID(f"{wid}_row_dsc", is_declaration=True, type=lv_coord_t) | ||||||
|             row_array = cg.static_const_array(row_id, cg.RawExpression(rows)) |             row_array = cg.static_const_array(row_id, cg.RawExpression(rows)) | ||||||
|             w.set_style("grid_row_dsc_array", row_array, 0) |             w.set_style("grid_row_dsc_array", row_array, 0) | ||||||
|             columns = ( |             columns = [str(x) for x in layout[CONF_GRID_COLUMNS]] | ||||||
|                 "{" + ",".join(layout[CONF_GRID_COLUMNS]) + ", LV_GRID_TEMPLATE_LAST}" |             columns = "{" + ",".join(columns) + ", LV_GRID_TEMPLATE_LAST}" | ||||||
|             ) |  | ||||||
|             column_id = ID(f"{wid}_column_dsc", is_declaration=True, type=lv_coord_t) |             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)) |             column_array = cg.static_const_array(column_id, cg.RawExpression(columns)) | ||||||
|             w.set_style("grid_column_dsc_array", column_array, 0) |             w.set_style("grid_column_dsc_array", column_array, 0) | ||||||
|   | |||||||
| @@ -39,9 +39,12 @@ | |||||||
| #define USE_LOCK | #define USE_LOCK | ||||||
| #define USE_LOGGER | #define USE_LOGGER | ||||||
| #define USE_LVGL | #define USE_LVGL | ||||||
|  | #define USE_LVGL_ANIMIMG | ||||||
| #define USE_LVGL_BINARY_SENSOR | #define USE_LVGL_BINARY_SENSOR | ||||||
|  | #define USE_LVGL_BUTTONMATRIX | ||||||
| #define USE_LVGL_FONT | #define USE_LVGL_FONT | ||||||
| #define USE_LVGL_IMAGE | #define USE_LVGL_IMAGE | ||||||
|  | #define USE_LVGL_KEYBOARD | ||||||
| #define USE_LVGL_KEY_LISTENER | #define USE_LVGL_KEY_LISTENER | ||||||
| #define USE_LVGL_TOUCHSCREEN | #define USE_LVGL_TOUCHSCREEN | ||||||
| #define USE_LVGL_ROTARY_ENCODER | #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 |       x_max: 240 | ||||||
|       y_max: 320 |       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: | lvgl: | ||||||
|   log_level: TRACE |   log_level: TRACE | ||||||
|   bg_color: light_blue |   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: |   touchscreens: | ||||||
|     - touchscreen_id: tft_touch |     - touchscreen_id: tft_touch | ||||||
|       long_press_repeat_time: 200ms |       long_press_repeat_time: 200ms | ||||||
| @@ -9,6 +56,13 @@ lvgl: | |||||||
|     - id: page1 |     - id: page1 | ||||||
|       skip: true |       skip: true | ||||||
|       widgets: |       widgets: | ||||||
|  |         - animimg: | ||||||
|  |             height: 60 | ||||||
|  |             id: anim_img | ||||||
|  |             src: [cat_image, dog_image] | ||||||
|  |             repeat_count: 10 | ||||||
|  |             duration: 1s | ||||||
|  |             auto_start: true | ||||||
|         - label: |         - label: | ||||||
|             id: hello_label |             id: hello_label | ||||||
|             text: Hello world |             text: Hello world | ||||||
| @@ -16,7 +70,9 @@ lvgl: | |||||||
|             align: center |             align: center | ||||||
|             text_font: montserrat_40 |             text_font: montserrat_40 | ||||||
|             border_post: true |             border_post: true | ||||||
|  |             on_click: | ||||||
|  |               then: | ||||||
|  |                 - lvgl.animimg.stop: anim_img | ||||||
|         - label: |         - label: | ||||||
|             text: "Hello shiny day" |             text: "Hello shiny day" | ||||||
|             text_color: 0xFFFFFF |             text_color: 0xFFFFFF | ||||||
| @@ -94,7 +150,65 @@ lvgl: | |||||||
|             width: 10px |             width: 10px | ||||||
|             x: 100 |             x: 100 | ||||||
|             y: 120 |             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: |         - button: | ||||||
|  |             id: button_button | ||||||
|             width: 20% |             width: 20% | ||||||
|             height: 10% |             height: 10% | ||||||
|             pressed: |             pressed: | ||||||
| @@ -137,6 +251,7 @@ lvgl: | |||||||
|             on_long_press_repeat: |             on_long_press_repeat: | ||||||
|               logger.log: Button clicked |               logger.log: Button clicked | ||||||
|         - led: |         - led: | ||||||
|  |             id: lv_led | ||||||
|             color: 0x00FF00 |             color: 0x00FF00 | ||||||
|             brightness: 50% |             brightness: 50% | ||||||
|             align: right_mid |             align: right_mid | ||||||
| @@ -151,6 +266,41 @@ lvgl: | |||||||
|  |  | ||||||
|     - id: page2 |     - id: page2 | ||||||
|       widgets: |       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: |         - arc: | ||||||
|             align: left_mid |             align: left_mid | ||||||
|             id: lv_arc |             id: lv_arc | ||||||
| @@ -160,7 +310,6 @@ lvgl: | |||||||
|                 - logger.log: |                 - logger.log: | ||||||
|                     format: "Arc value is %f" |                     format: "Arc value is %f" | ||||||
|                     args: [x] |                     args: [x] | ||||||
|             group: general |  | ||||||
|             scroll_on_focus: true |             scroll_on_focus: true | ||||||
|             value: 75 |             value: 75 | ||||||
|             min_value: 1 |             min_value: 1 | ||||||
| @@ -201,6 +350,7 @@ lvgl: | |||||||
|         - switch: |         - switch: | ||||||
|             align: right_mid |             align: right_mid | ||||||
|         - checkbox: |         - checkbox: | ||||||
|  |             id: checkbox_id | ||||||
|             text: Checkbox |             text: Checkbox | ||||||
|             align: bottom_right |             align: bottom_right | ||||||
|         - slider: |         - slider: | ||||||
| @@ -221,6 +371,78 @@ lvgl: | |||||||
|                 - lvgl.slider.update: |                 - lvgl.slider.update: | ||||||
|                     id: slider_id |                     id: slider_id | ||||||
|                     value: !lambda return (int)((float)rand() / RAND_MAX * 100); |                     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: | font: | ||||||
|   - file: "gfonts://Roboto" |   - file: "gfonts://Roboto" | ||||||
|     id: space16 |     id: space16 | ||||||
| @@ -230,7 +452,7 @@ image: | |||||||
|   - id: cat_image |   - id: cat_image | ||||||
|     resize: 256x48 |     resize: 256x48 | ||||||
|     file: $component_dir/logo-text.svg |     file: $component_dir/logo-text.svg | ||||||
|   - id: dog_img |   - id: dog_image | ||||||
|     file: $component_dir/logo-text.svg |     file: $component_dir/logo-text.svg | ||||||
|     resize: 256x48 |     resize: 256x48 | ||||||
|     type: TRANSPARENT_BINARY |     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.
										
									
								
							
		Reference in New Issue
	
	Block a user