mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[lvgl] Implement canvas widget (#8504)
This commit is contained in:
		| @@ -39,14 +39,13 @@ from .lvcode import LvContext, LvglComponent, lvgl_static | |||||||
| from .schemas import ( | from .schemas import ( | ||||||
|     DISP_BG_SCHEMA, |     DISP_BG_SCHEMA, | ||||||
|     FLEX_OBJ_SCHEMA, |     FLEX_OBJ_SCHEMA, | ||||||
|  |     FULL_STYLE_SCHEMA, | ||||||
|     GRID_CELL_SCHEMA, |     GRID_CELL_SCHEMA, | ||||||
|     LAYOUT_SCHEMAS, |     LAYOUT_SCHEMAS, | ||||||
|     STYLE_SCHEMA, |  | ||||||
|     WIDGET_TYPES, |     WIDGET_TYPES, | ||||||
|     any_widget_schema, |     any_widget_schema, | ||||||
|     container_schema, |     container_schema, | ||||||
|     create_modify_schema, |     create_modify_schema, | ||||||
|     grid_alignments, |  | ||||||
|     obj_schema, |     obj_schema, | ||||||
| ) | ) | ||||||
| from .styles import add_top_layer, styles_to_code, theme_to_code | from .styles import add_top_layer, styles_to_code, theme_to_code | ||||||
| @@ -74,6 +73,7 @@ from .widgets.animimg import animimg_spec | |||||||
| from .widgets.arc import arc_spec | from .widgets.arc import arc_spec | ||||||
| from .widgets.button import button_spec | from .widgets.button import button_spec | ||||||
| from .widgets.buttonmatrix import buttonmatrix_spec | from .widgets.buttonmatrix import buttonmatrix_spec | ||||||
|  | from .widgets.canvas import canvas_spec | ||||||
| from .widgets.checkbox import checkbox_spec | from .widgets.checkbox import checkbox_spec | ||||||
| from .widgets.dropdown import dropdown_spec | from .widgets.dropdown import dropdown_spec | ||||||
| from .widgets.img import img_spec | from .widgets.img import img_spec | ||||||
| @@ -126,6 +126,7 @@ for w_type in ( | |||||||
|     keyboard_spec, |     keyboard_spec, | ||||||
|     tileview_spec, |     tileview_spec, | ||||||
|     qr_code_spec, |     qr_code_spec, | ||||||
|  |     canvas_spec, | ||||||
| ): | ): | ||||||
|     WIDGET_TYPES[w_type.name] = w_type |     WIDGET_TYPES[w_type.name] = w_type | ||||||
|  |  | ||||||
| @@ -421,15 +422,8 @@ LVGL_SCHEMA = cv.All( | |||||||
|                     "big_endian", "little_endian" |                     "big_endian", "little_endian" | ||||||
|                 ), |                 ), | ||||||
|                 cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list( |                 cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list( | ||||||
|                     cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)}) |                     cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)}).extend( | ||||||
|                     .extend(STYLE_SCHEMA) |                         FULL_STYLE_SCHEMA | ||||||
|                     .extend( |  | ||||||
|                         { |  | ||||||
|                             cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, |  | ||||||
|                             cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments, |  | ||||||
|                             cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, |  | ||||||
|                             cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, |  | ||||||
|                         } |  | ||||||
|                     ) |                     ) | ||||||
|                 ), |                 ), | ||||||
|                 cv.Optional(CONF_ON_IDLE): validate_automation( |                 cv.Optional(CONF_ON_IDLE): validate_automation( | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ def add_define(macro, value="1"): | |||||||
|     lv_defines[macro] = value |     lv_defines[macro] = value | ||||||
|  |  | ||||||
|  |  | ||||||
| def literal(arg): | def literal(arg) -> MockObj: | ||||||
|     if isinstance(arg, str): |     if isinstance(arg, str): | ||||||
|         return MockObj(arg) |         return MockObj(arg) | ||||||
|     return arg |     return arg | ||||||
|   | |||||||
| @@ -254,11 +254,27 @@ def pixels_or_percent_validator(value): | |||||||
| pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal) | pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal) | ||||||
|  |  | ||||||
|  |  | ||||||
| def zoom(value): | def pixels_validator(value): | ||||||
|  |     if isinstance(value, str) and value.lower().endswith("px"): | ||||||
|  |         value = value[:-2] | ||||||
|  |     return cv.positive_int(value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | pixels = LValidator(pixels_validator, uint32, retmapper=literal) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def zoom_validator(value): | ||||||
|     value = cv.float_range(0.1, 10.0)(value) |     value = cv.float_range(0.1, 10.0)(value) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def zoom_retmapper(value): | ||||||
|     return int(value * 256) |     return int(value * 256) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | zoom = LValidator(zoom_validator, uint32, retmapper=zoom_retmapper) | ||||||
|  |  | ||||||
|  |  | ||||||
| def angle(value): | def angle(value): | ||||||
|     """ |     """ | ||||||
|     Validation for an angle in degrees, converted to an integer representing 0.1deg units |     Validation for an angle in degrees, converted to an integer representing 0.1deg units | ||||||
| @@ -286,14 +302,6 @@ def size_validator(value): | |||||||
| size = LValidator(size_validator, uint32, retmapper=literal) | size = LValidator(size_validator, uint32, retmapper=literal) | ||||||
|  |  | ||||||
|  |  | ||||||
| def pixels_validator(value): |  | ||||||
|     if isinstance(value, str) and value.lower().endswith("px"): |  | ||||||
|         return cv.int_(value[:-2]) |  | ||||||
|     return cv.int_(value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| pixels = LValidator(pixels_validator, uint32, retmapper=literal) |  | ||||||
|  |  | ||||||
| radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") | radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -206,11 +206,16 @@ class LocalVariable(MockObj): | |||||||
|  |  | ||||||
|     def __enter__(self): |     def __enter__(self): | ||||||
|         CodeContext.start_block() |         CodeContext.start_block() | ||||||
|         CodeContext.append( |  | ||||||
|             VariableDeclarationExpression(self.base.type, self.modifier, self.base.id) |  | ||||||
|         ) |  | ||||||
|         if self.rhs is not None: |         if self.rhs is not None: | ||||||
|             CodeContext.append(AssignmentExpression(None, "", self.base, self.rhs)) |             CodeContext.append( | ||||||
|  |                 AssignmentExpression(self.base.type, self.modifier, self.base, self.rhs) | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             CodeContext.append( | ||||||
|  |                 VariableDeclarationExpression( | ||||||
|  |                     self.base.type, self.modifier, self.base.id | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         return MockObj(self.base) |         return MockObj(self.base) | ||||||
|  |  | ||||||
|     def __exit__(self, *args): |     def __exit__(self, *args): | ||||||
|   | |||||||
| @@ -63,6 +63,11 @@ inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) | |||||||
| inline void lv_obj_set_style_bg_img_src(lv_obj_t *obj, esphome::image::Image *image, lv_style_selector_t selector) { | inline void lv_obj_set_style_bg_img_src(lv_obj_t *obj, esphome::image::Image *image, lv_style_selector_t selector) { | ||||||
|   lv_obj_set_style_bg_img_src(obj, image->get_lv_img_dsc(), selector); |   lv_obj_set_style_bg_img_src(obj, image->get_lv_img_dsc(), selector); | ||||||
| } | } | ||||||
|  | inline void lv_canvas_draw_img(lv_obj_t *canvas, lv_coord_t x, lv_coord_t y, image::Image *image, | ||||||
|  |                                lv_draw_img_dsc_t *dsc) { | ||||||
|  |   lv_canvas_draw_img(canvas, x, y, image->get_lv_img_dsc(), dsc); | ||||||
|  | } | ||||||
|  |  | ||||||
| #ifdef USE_LVGL_METER | #ifdef USE_LVGL_METER | ||||||
| inline lv_meter_indicator_t *lv_meter_add_needle_img(lv_obj_t *obj, lv_meter_scale_t *scale, esphome::image::Image *src, | inline lv_meter_indicator_t *lv_meter_add_needle_img(lv_obj_t *obj, lv_meter_scale_t *scale, esphome::image::Image *src, | ||||||
|                                                      lv_coord_t pivot_x, lv_coord_t pivot_y) { |                                                      lv_coord_t pivot_x, lv_coord_t pivot_y) { | ||||||
|   | |||||||
| @@ -87,31 +87,29 @@ ENCODER_SCHEMA = cv.Schema( | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | POINT_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_X): cv.templatable(cv.int_), | ||||||
|  |         cv.Required(CONF_Y): cv.templatable(cv.int_), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| def point_shorthand(value): |  | ||||||
|  | def point_schema(value): | ||||||
|     """ |     """ | ||||||
|     A shorthand for a point in the form of x,y |     A shorthand for a point in the form of x,y | ||||||
|     :param value: The value to check |     :param value: The value to check | ||||||
|     :return: The value as a tuple of x,y |     :return: The value as a tuple of x,y | ||||||
|     """ |     """ | ||||||
|     if isinstance(value, str): |     if isinstance(value, dict): | ||||||
|         try: |         return POINT_SCHEMA(value) | ||||||
|             x, y = map(int, value.split(",")) |     try: | ||||||
|             return {CONF_X: x, CONF_Y: y} |         x, y = map(int, value.split(",")) | ||||||
|         except ValueError: |         return {CONF_X: x, CONF_Y: y} | ||||||
|             pass |     except ValueError: | ||||||
|     raise cv.Invalid("Invalid point format, should be <x_value>, <y_value>") |         pass | ||||||
|  |     # not raising this in the catch block because pylint doesn't like it | ||||||
|  |     raise cv.Invalid("Invalid point format, should be <x_int>, <y_int>") | ||||||
| POINT_SCHEMA = cv.Any( |  | ||||||
|     cv.Schema( |  | ||||||
|         { |  | ||||||
|             cv.Required(CONF_X): cv.templatable(cv.int_), |  | ||||||
|             cv.Required(CONF_Y): cv.templatable(cv.int_), |  | ||||||
|         } |  | ||||||
|     ), |  | ||||||
|     point_shorthand, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # All LVGL styles and their validators | # All LVGL styles and their validators | ||||||
| @@ -132,6 +130,7 @@ STYLE_PROPS = { | |||||||
|     "bg_image_recolor": lvalid.lv_color, |     "bg_image_recolor": lvalid.lv_color, | ||||||
|     "bg_image_recolor_opa": lvalid.opacity, |     "bg_image_recolor_opa": lvalid.opacity, | ||||||
|     "bg_image_src": lvalid.lv_image, |     "bg_image_src": lvalid.lv_image, | ||||||
|  |     "bg_image_tiled": lvalid.lv_bool, | ||||||
|     "bg_main_stop": lvalid.stop_value, |     "bg_main_stop": lvalid.stop_value, | ||||||
|     "bg_opa": lvalid.opacity, |     "bg_opa": lvalid.opacity, | ||||||
|     "border_color": lvalid.lv_color, |     "border_color": lvalid.lv_color, | ||||||
| @@ -146,9 +145,9 @@ STYLE_PROPS = { | |||||||
|     "height": lvalid.size, |     "height": lvalid.size, | ||||||
|     "image_recolor": lvalid.lv_color, |     "image_recolor": lvalid.lv_color, | ||||||
|     "image_recolor_opa": lvalid.opacity, |     "image_recolor_opa": lvalid.opacity, | ||||||
|     "line_width": cv.positive_int, |     "line_width": lvalid.lv_positive_int, | ||||||
|     "line_dash_width": cv.positive_int, |     "line_dash_width": lvalid.lv_positive_int, | ||||||
|     "line_dash_gap": cv.positive_int, |     "line_dash_gap": lvalid.lv_positive_int, | ||||||
|     "line_rounded": lvalid.lv_bool, |     "line_rounded": lvalid.lv_bool, | ||||||
|     "line_color": lvalid.lv_color, |     "line_color": lvalid.lv_color, | ||||||
|     "opa": lvalid.opacity, |     "opa": lvalid.opacity, | ||||||
| @@ -176,8 +175,8 @@ STYLE_PROPS = { | |||||||
|         "LV_TEXT_DECOR_", "NONE", "UNDERLINE", "STRIKETHROUGH" |         "LV_TEXT_DECOR_", "NONE", "UNDERLINE", "STRIKETHROUGH" | ||||||
|     ).several_of, |     ).several_of, | ||||||
|     "text_font": lv_font, |     "text_font": lv_font, | ||||||
|     "text_letter_space": cv.positive_int, |     "text_letter_space": lvalid.lv_positive_int, | ||||||
|     "text_line_space": cv.positive_int, |     "text_line_space": lvalid.lv_positive_int, | ||||||
|     "text_opa": lvalid.opacity, |     "text_opa": lvalid.opacity, | ||||||
|     "transform_angle": lvalid.lv_angle, |     "transform_angle": lvalid.lv_angle, | ||||||
|     "transform_height": lvalid.pixels_or_percent, |     "transform_height": lvalid.pixels_or_percent, | ||||||
| @@ -201,10 +200,15 @@ STYLE_REMAP = { | |||||||
|     "bg_image_recolor": "bg_img_recolor", |     "bg_image_recolor": "bg_img_recolor", | ||||||
|     "bg_image_recolor_opa": "bg_img_recolor_opa", |     "bg_image_recolor_opa": "bg_img_recolor_opa", | ||||||
|     "bg_image_src": "bg_img_src", |     "bg_image_src": "bg_img_src", | ||||||
|  |     "bg_image_tiled": "bg_img_tiled", | ||||||
|     "image_recolor": "img_recolor", |     "image_recolor": "img_recolor", | ||||||
|     "image_recolor_opa": "img_recolor_opa", |     "image_recolor_opa": "img_recolor_opa", | ||||||
| } | } | ||||||
|  |  | ||||||
|  | cell_alignments = df.LV_CELL_ALIGNMENTS.one_of | ||||||
|  | grid_alignments = df.LV_GRID_ALIGNMENTS.one_of | ||||||
|  | flex_alignments = df.LV_FLEX_ALIGNMENTS.one_of | ||||||
|  |  | ||||||
| # Complete object style schema | # Complete object style schema | ||||||
| STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend( | STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend( | ||||||
|     { |     { | ||||||
| @@ -215,6 +219,16 @@ STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).ex | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | # Also allow widget specific properties for use in style definitions | ||||||
|  | FULL_STYLE_SCHEMA = STYLE_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, | ||||||
|  |         cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments, | ||||||
|  |         cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, | ||||||
|  |         cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| # Object states. Top level properties apply to MAIN | # Object states. Top level properties apply to MAIN | ||||||
| STATE_SCHEMA = cv.Schema( | STATE_SCHEMA = cv.Schema( | ||||||
|     {cv.Optional(state): STYLE_SCHEMA for state in df.STATES} |     {cv.Optional(state): STYLE_SCHEMA for state in df.STATES} | ||||||
| @@ -346,10 +360,6 @@ grid_spec = cv.Any( | |||||||
|     lvalid.size, df.LvConstant("LV_GRID_", "CONTENT").one_of, grid_free_space |     lvalid.size, df.LvConstant("LV_GRID_", "CONTENT").one_of, grid_free_space | ||||||
| ) | ) | ||||||
|  |  | ||||||
| cell_alignments = df.LV_CELL_ALIGNMENTS.one_of |  | ||||||
| grid_alignments = df.LV_GRID_ALIGNMENTS.one_of |  | ||||||
| flex_alignments = df.LV_FLEX_ALIGNMENTS.one_of |  | ||||||
|  |  | ||||||
| LAYOUT_SCHEMA = { | LAYOUT_SCHEMA = { | ||||||
|     cv.Optional(df.CONF_LAYOUT): cv.typed_schema( |     cv.Optional(df.CONF_LAYOUT): cv.typed_schema( | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
|  | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
| from esphome.core import ID | from esphome.core import ID | ||||||
| from esphome.cpp_generator import MockObj | from esphome.cpp_generator import MockObj | ||||||
| @@ -12,25 +14,54 @@ from .defines import ( | |||||||
| ) | ) | ||||||
| from .helpers import add_lv_use | from .helpers import add_lv_use | ||||||
| from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable | from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable | ||||||
| from .schemas import ALL_STYLES, STYLE_REMAP | from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP | ||||||
| from .types import lv_lambda_t, lv_obj_t, lv_obj_t_ptr | from .types import ObjUpdateAction, lv_lambda_t, lv_obj_t, lv_obj_t_ptr, lv_style_t | ||||||
| from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map | from .widgets import ( | ||||||
|  |     Widget, | ||||||
|  |     add_widgets, | ||||||
|  |     set_obj_properties, | ||||||
|  |     theme_widget_map, | ||||||
|  |     wait_for_widgets, | ||||||
|  | ) | ||||||
| from .widgets.obj import obj_spec | from .widgets.obj import obj_spec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def style_set(svar, style): | ||||||
|  |     for prop, validator in ALL_STYLES.items(): | ||||||
|  |         if (value := style.get(prop)) is not None: | ||||||
|  |             if isinstance(validator, LValidator): | ||||||
|  |                 value = await validator.process(value) | ||||||
|  |             if isinstance(value, list): | ||||||
|  |                 value = "|".join(value) | ||||||
|  |             remapped_prop = STYLE_REMAP.get(prop, prop) | ||||||
|  |             lv.call(f"style_set_{remapped_prop}", svar, literal(value)) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def styles_to_code(config): | async def styles_to_code(config): | ||||||
|     """Convert styles to C__ code.""" |     """Convert styles to C__ code.""" | ||||||
|     for style in config.get(CONF_STYLE_DEFINITIONS, ()): |     for style in config.get(CONF_STYLE_DEFINITIONS, ()): | ||||||
|         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(): |         await style_set(svar, style) | ||||||
|             if (value := style.get(prop)) is not None: |  | ||||||
|                 if isinstance(validator, LValidator): |  | ||||||
|                     value = await validator.process(value) | @automation.register_action( | ||||||
|                 if isinstance(value, list): |     "lvgl.style.update", | ||||||
|                     value = "|".join(value) |     ObjUpdateAction, | ||||||
|                 remapped_prop = STYLE_REMAP.get(prop, prop) |     FULL_STYLE_SCHEMA.extend( | ||||||
|                 lv.call(f"style_set_{remapped_prop}", svar, literal(value)) |         { | ||||||
|  |             cv.Required(CONF_ID): cv.use_id(lv_style_t), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def style_update_to_code(config, action_id, template_arg, args): | ||||||
|  |     await wait_for_widgets() | ||||||
|  |     style = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     async with LambdaContext(parameters=args, where=action_id) as context: | ||||||
|  |         await style_set(style, config) | ||||||
|  |  | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| async def theme_to_code(config): | async def theme_to_code(config): | ||||||
|   | |||||||
							
								
								
									
										403
									
								
								esphome/components/lvgl/widgets/canvas.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										403
									
								
								esphome/components/lvgl/widgets/canvas.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,403 @@ | |||||||
|  | from esphome import automation, codegen as cg, config_validation as cv | ||||||
|  | from esphome.components.display_menu_base import CONF_LABEL | ||||||
|  | from esphome.const import CONF_COLOR, CONF_HEIGHT, CONF_ID, CONF_TEXT, CONF_WIDTH | ||||||
|  | from esphome.cpp_generator import Literal, MockObj | ||||||
|  |  | ||||||
|  | from ..automation import action_to_code | ||||||
|  | from ..defines import ( | ||||||
|  |     CONF_END_ANGLE, | ||||||
|  |     CONF_MAIN, | ||||||
|  |     CONF_OPA, | ||||||
|  |     CONF_PIVOT_X, | ||||||
|  |     CONF_PIVOT_Y, | ||||||
|  |     CONF_POINTS, | ||||||
|  |     CONF_SRC, | ||||||
|  |     CONF_START_ANGLE, | ||||||
|  |     CONF_X, | ||||||
|  |     CONF_Y, | ||||||
|  |     literal, | ||||||
|  | ) | ||||||
|  | from ..lv_validation import ( | ||||||
|  |     lv_angle, | ||||||
|  |     lv_bool, | ||||||
|  |     lv_color, | ||||||
|  |     lv_image, | ||||||
|  |     lv_text, | ||||||
|  |     opacity, | ||||||
|  |     pixels, | ||||||
|  |     size, | ||||||
|  | ) | ||||||
|  | from ..lvcode import LocalVariable, lv, lv_assign | ||||||
|  | from ..schemas import STYLE_PROPS, STYLE_REMAP, TEXT_SCHEMA, point_schema | ||||||
|  | from ..types import LvType, ObjUpdateAction, WidgetType | ||||||
|  | from . import Widget, get_widgets | ||||||
|  | from .line import lv_point_t, process_coord | ||||||
|  |  | ||||||
|  | CONF_CANVAS = "canvas" | ||||||
|  | CONF_BUFFER_ID = "buffer_id" | ||||||
|  | CONF_MAX_WIDTH = "max_width" | ||||||
|  | CONF_TRANSPARENT = "transparent" | ||||||
|  | CONF_RADIUS = "radius" | ||||||
|  |  | ||||||
|  | lv_canvas_t = LvType("lv_canvas_t") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CanvasType(WidgetType): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__( | ||||||
|  |             CONF_CANVAS, | ||||||
|  |             lv_canvas_t, | ||||||
|  |             (CONF_MAIN,), | ||||||
|  |             cv.Schema( | ||||||
|  |                 { | ||||||
|  |                     cv.Required(CONF_WIDTH): size, | ||||||
|  |                     cv.Required(CONF_HEIGHT): size, | ||||||
|  |                     cv.Optional(CONF_TRANSPARENT, default=False): cv.boolean, | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def get_uses(self): | ||||||
|  |         return "img", CONF_LABEL | ||||||
|  |  | ||||||
|  |     async def to_code(self, w: Widget, config): | ||||||
|  |         width = config[CONF_WIDTH] | ||||||
|  |         height = config[CONF_HEIGHT] | ||||||
|  |         use_alpha = "_ALPHA" if config[CONF_TRANSPARENT] else "" | ||||||
|  |         lv.canvas_set_buffer( | ||||||
|  |             w.obj, | ||||||
|  |             lv.custom_mem_alloc( | ||||||
|  |                 literal(f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})") | ||||||
|  |             ), | ||||||
|  |             width, | ||||||
|  |             height, | ||||||
|  |             literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | canvas_spec = CanvasType() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.fill", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), | ||||||
|  |             cv.Required(CONF_COLOR): lv_color, | ||||||
|  |             cv.Optional(CONF_OPA, default="COVER"): opacity, | ||||||
|  |         }, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def canvas_fill(config, action_id, template_arg, args): | ||||||
|  |     widget = await get_widgets(config) | ||||||
|  |     color = await lv_color.process(config[CONF_COLOR]) | ||||||
|  |     opa = await opacity.process(config[CONF_OPA]) | ||||||
|  |  | ||||||
|  |     async def do_fill(w: Widget): | ||||||
|  |         lv.canvas_fill_bg(w.obj, color, opa) | ||||||
|  |  | ||||||
|  |     return await action_to_code(widget, do_fill, action_id, template_arg, args) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.set_pixels", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), | ||||||
|  |             cv.Required(CONF_COLOR): lv_color, | ||||||
|  |             cv.Optional(CONF_OPA): opacity, | ||||||
|  |             cv.Required(CONF_POINTS): cv.ensure_list(point_schema), | ||||||
|  |         }, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def canvas_set_pixel(config, action_id, template_arg, args): | ||||||
|  |     widget = await get_widgets(config) | ||||||
|  |     color = await lv_color.process(config[CONF_COLOR]) | ||||||
|  |     opa = await opacity.process(config.get(CONF_OPA)) | ||||||
|  |     points = [ | ||||||
|  |         ( | ||||||
|  |             await pixels.process(p[CONF_X]), | ||||||
|  |             await pixels.process(p[CONF_Y]), | ||||||
|  |         ) | ||||||
|  |         for p in config[CONF_POINTS] | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     async def do_set_pixels(w: Widget): | ||||||
|  |         if isinstance(color, MockObj): | ||||||
|  |             for point in points: | ||||||
|  |                 x, y = point | ||||||
|  |                 lv.canvas_set_px_color(w.obj, x, y, color) | ||||||
|  |         else: | ||||||
|  |             with LocalVariable("color", "lv_color_t", color, modifier="") as color_var: | ||||||
|  |                 for point in points: | ||||||
|  |                     x, y = point | ||||||
|  |                     lv.canvas_set_px_color(w.obj, x, y, color_var) | ||||||
|  |         if opa: | ||||||
|  |             if isinstance(opa, Literal): | ||||||
|  |                 for point in points: | ||||||
|  |                     x, y = point | ||||||
|  |                     lv.canvas_set_px_opa(w.obj, x, y, opa) | ||||||
|  |             else: | ||||||
|  |                 with LocalVariable("opa", "lv_opa_t", opa, modifier="") as opa_var: | ||||||
|  |                     for point in points: | ||||||
|  |                         x, y = point | ||||||
|  |                         lv.canvas_set_px_opa(w.obj, x, y, opa_var) | ||||||
|  |  | ||||||
|  |     return await action_to_code(widget, do_set_pixels, action_id, template_arg, args) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DRAW_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), | ||||||
|  |         cv.Required(CONF_X): pixels, | ||||||
|  |         cv.Required(CONF_Y): pixels, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  | DRAW_OPA_SCHEMA = DRAW_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.Optional(CONF_OPA): opacity, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def draw_to_code(config, dsc_type, props, do_draw, action_id, template_arg, args): | ||||||
|  |     widget = await get_widgets(config) | ||||||
|  |     x = await pixels.process(config.get(CONF_X)) | ||||||
|  |     y = await pixels.process(config.get(CONF_Y)) | ||||||
|  |  | ||||||
|  |     async def action_func(w: Widget): | ||||||
|  |         with LocalVariable("dsc", f"lv_draw_{dsc_type}_dsc_t", modifier="") as dsc: | ||||||
|  |             dsc_addr = literal(f"&{dsc}") | ||||||
|  |             lv.call(f"draw_{dsc_type}_dsc_init", dsc_addr) | ||||||
|  |             if CONF_OPA in config: | ||||||
|  |                 opa = await opacity.process(config[CONF_OPA]) | ||||||
|  |                 lv_assign(dsc.opa, opa) | ||||||
|  |             for prop, validator in props.items(): | ||||||
|  |                 if prop in config: | ||||||
|  |                     value = await validator.process(config[prop]) | ||||||
|  |                     mapped_prop = STYLE_REMAP.get(prop, prop) | ||||||
|  |                     lv_assign(getattr(dsc, mapped_prop), value) | ||||||
|  |             await do_draw(w, x, y, dsc_addr) | ||||||
|  |  | ||||||
|  |     return await action_to_code(widget, action_func, action_id, template_arg, args) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | RECT_PROPS = { | ||||||
|  |     p: STYLE_PROPS[p] | ||||||
|  |     for p in ( | ||||||
|  |         "radius", | ||||||
|  |         "bg_opa", | ||||||
|  |         "bg_color", | ||||||
|  |         "bg_grad", | ||||||
|  |         "border_color", | ||||||
|  |         "border_width", | ||||||
|  |         "border_opa", | ||||||
|  |         "outline_color", | ||||||
|  |         "outline_width", | ||||||
|  |         "outline_pad", | ||||||
|  |         "outline_opa", | ||||||
|  |         "shadow_color", | ||||||
|  |         "shadow_width", | ||||||
|  |         "shadow_ofs_x", | ||||||
|  |         "shadow_ofs_y", | ||||||
|  |         "shadow_spread", | ||||||
|  |         "shadow_opa", | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.draw_rectangle", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     DRAW_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_WIDTH): cv.templatable(cv.int_), | ||||||
|  |             cv.Required(CONF_HEIGHT): cv.templatable(cv.int_), | ||||||
|  |         }, | ||||||
|  |     ).extend({cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}), | ||||||
|  | ) | ||||||
|  | async def canvas_draw_rect(config, action_id, template_arg, args): | ||||||
|  |     width = await pixels.process(config[CONF_WIDTH]) | ||||||
|  |     height = await pixels.process(config[CONF_HEIGHT]) | ||||||
|  |  | ||||||
|  |     async def do_draw_rect(w: Widget, x, y, dsc_addr): | ||||||
|  |         lv.canvas_draw_rect(w.obj, x, y, width, height, dsc_addr) | ||||||
|  |  | ||||||
|  |     return await draw_to_code( | ||||||
|  |         config, "rect", RECT_PROPS, do_draw_rect, action_id, template_arg, args | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TEXT_PROPS = { | ||||||
|  |     p: STYLE_PROPS[f"text_{p}"] | ||||||
|  |     for p in ( | ||||||
|  |         "font", | ||||||
|  |         "color", | ||||||
|  |         # "sel_color", | ||||||
|  |         # "sel_bg_color", | ||||||
|  |         "line_space", | ||||||
|  |         "letter_space", | ||||||
|  |         "align", | ||||||
|  |         "decor", | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.draw_text", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     TEXT_SCHEMA.extend(DRAW_OPA_SCHEMA) | ||||||
|  |     .extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_MAX_WIDTH): cv.templatable(cv.int_), | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  |     .extend({cv.Optional(prop): STYLE_PROPS[f"text_{prop}"] for prop in TEXT_PROPS}), | ||||||
|  | ) | ||||||
|  | async def canvas_draw_text(config, action_id, template_arg, args): | ||||||
|  |     text = await lv_text.process(config[CONF_TEXT]) | ||||||
|  |     max_width = await pixels.process(config[CONF_MAX_WIDTH]) | ||||||
|  |  | ||||||
|  |     async def do_draw_text(w: Widget, x, y, dsc_addr): | ||||||
|  |         lv.canvas_draw_text(w.obj, x, y, max_width, dsc_addr, text) | ||||||
|  |  | ||||||
|  |     return await draw_to_code( | ||||||
|  |         config, "label", TEXT_PROPS, do_draw_text, action_id, template_arg, args | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | IMG_PROPS = { | ||||||
|  |     "angle": STYLE_PROPS["transform_angle"], | ||||||
|  |     "zoom": STYLE_PROPS["transform_zoom"], | ||||||
|  |     "recolor": STYLE_PROPS["image_recolor"], | ||||||
|  |     "recolor_opa": STYLE_PROPS["image_recolor_opa"], | ||||||
|  |     "opa": STYLE_PROPS["opa"], | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.draw_image", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     DRAW_OPA_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_SRC): lv_image, | ||||||
|  |             cv.Optional(CONF_PIVOT_X, default=0): pixels, | ||||||
|  |             cv.Optional(CONF_PIVOT_Y, default=0): pixels, | ||||||
|  |         }, | ||||||
|  |     ).extend({cv.Optional(prop): validator for prop, validator in IMG_PROPS.items()}), | ||||||
|  | ) | ||||||
|  | async def canvas_draw_image(config, action_id, template_arg, args): | ||||||
|  |     src = await lv_image.process(config[CONF_SRC]) | ||||||
|  |     pivot_x = await pixels.process(config[CONF_PIVOT_X]) | ||||||
|  |     pivot_y = await pixels.process(config[CONF_PIVOT_Y]) | ||||||
|  |  | ||||||
|  |     async def do_draw_image(w: Widget, x, y, dsc_addr): | ||||||
|  |         dsc = MockObj(f"(*{dsc_addr})") | ||||||
|  |         if pivot_x or pivot_y: | ||||||
|  |             # pylint :disable=no-member | ||||||
|  |             lv_assign(dsc.pivot, literal(f"{{{pivot_x}, {pivot_y}}}")) | ||||||
|  |         lv.canvas_draw_img(w.obj, x, y, src, dsc_addr) | ||||||
|  |  | ||||||
|  |     return await draw_to_code( | ||||||
|  |         config, "img", IMG_PROPS, do_draw_image, action_id, template_arg, args | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | LINE_PROPS = { | ||||||
|  |     "width": STYLE_PROPS["line_width"], | ||||||
|  |     "color": STYLE_PROPS["line_color"], | ||||||
|  |     "dash-width": STYLE_PROPS["line_dash_width"], | ||||||
|  |     "dash-gap": STYLE_PROPS["line_dash_gap"], | ||||||
|  |     "round_start": lv_bool, | ||||||
|  |     "round_end": lv_bool, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.draw_line", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), | ||||||
|  |             cv.Optional(CONF_OPA): opacity, | ||||||
|  |             cv.Required(CONF_POINTS): cv.ensure_list(point_schema), | ||||||
|  |         }, | ||||||
|  |     ).extend({cv.Optional(prop): validator for prop, validator in LINE_PROPS.items()}), | ||||||
|  | ) | ||||||
|  | async def canvas_draw_line(config, action_id, template_arg, args): | ||||||
|  |     points = [ | ||||||
|  |         [await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])] | ||||||
|  |         for p in config[CONF_POINTS] | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     async def do_draw_line(w: Widget, x, y, dsc_addr): | ||||||
|  |         with LocalVariable( | ||||||
|  |             "points", cg.std_vector.template(lv_point_t), points, modifier="" | ||||||
|  |         ) as points_var: | ||||||
|  |             lv.canvas_draw_line(w.obj, points_var.data(), points_var.size(), dsc_addr) | ||||||
|  |  | ||||||
|  |     return await draw_to_code( | ||||||
|  |         config, "line", LINE_PROPS, do_draw_line, action_id, template_arg, args | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.draw_polygon", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t), | ||||||
|  |             cv.Required(CONF_POINTS): cv.ensure_list(point_schema), | ||||||
|  |         }, | ||||||
|  |     ).extend({cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}), | ||||||
|  | ) | ||||||
|  | async def canvas_draw_polygon(config, action_id, template_arg, args): | ||||||
|  |     points = [ | ||||||
|  |         [await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])] | ||||||
|  |         for p in config[CONF_POINTS] | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     async def do_draw_polygon(w: Widget, x, y, dsc_addr): | ||||||
|  |         with LocalVariable( | ||||||
|  |             "points", cg.std_vector.template(lv_point_t), points, modifier="" | ||||||
|  |         ) as points_var: | ||||||
|  |             lv.canvas_draw_polygon( | ||||||
|  |                 w.obj, points_var.data(), points_var.size(), dsc_addr | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     return await draw_to_code( | ||||||
|  |         config, "rect", RECT_PROPS, do_draw_polygon, action_id, template_arg, args | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ARC_PROPS = { | ||||||
|  |     "width": STYLE_PROPS["arc_width"], | ||||||
|  |     "color": STYLE_PROPS["arc_color"], | ||||||
|  |     "rounded": STYLE_PROPS["arc_rounded"], | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "lvgl.canvas.draw_arc", | ||||||
|  |     ObjUpdateAction, | ||||||
|  |     DRAW_OPA_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_RADIUS): pixels, | ||||||
|  |             cv.Required(CONF_START_ANGLE): lv_angle, | ||||||
|  |             cv.Required(CONF_END_ANGLE): lv_angle, | ||||||
|  |         } | ||||||
|  |     ).extend({cv.Optional(prop): validator for prop, validator in ARC_PROPS.items()}), | ||||||
|  | ) | ||||||
|  | async def canvas_draw_arc(config, action_id, template_arg, args): | ||||||
|  |     radius = await size.process(config[CONF_RADIUS]) | ||||||
|  |     start_angle = await lv_angle.process(config[CONF_START_ANGLE]) | ||||||
|  |     end_angle = await lv_angle.process(config[CONF_END_ANGLE]) | ||||||
|  |  | ||||||
|  |     async def do_draw_arc(w: Widget, x, y, dsc_addr): | ||||||
|  |         lv.canvas_draw_arc(w.obj, x, y, radius, start_angle, end_angle, dsc_addr) | ||||||
|  |  | ||||||
|  |     return await draw_to_code( | ||||||
|  |         config, "arc", ARC_PROPS, do_draw_arc, action_id, template_arg, args | ||||||
|  |     ) | ||||||
| @@ -4,7 +4,7 @@ from esphome.core import Lambda | |||||||
|  |  | ||||||
| from ..defines import CONF_MAIN, CONF_X, CONF_Y, call_lambda | from ..defines import CONF_MAIN, CONF_X, CONF_Y, call_lambda | ||||||
| from ..lvcode import lv_add | from ..lvcode import lv_add | ||||||
| from ..schemas import POINT_SCHEMA | from ..schemas import point_schema | ||||||
| from ..types import LvCompound, LvType | from ..types import LvCompound, LvType | ||||||
| from . import Widget, WidgetType | from . import Widget, WidgetType | ||||||
|  |  | ||||||
| @@ -16,14 +16,14 @@ lv_point_t = cg.global_ns.struct("lv_point_t") | |||||||
|  |  | ||||||
|  |  | ||||||
| LINE_SCHEMA = { | LINE_SCHEMA = { | ||||||
|     cv.Required(CONF_POINTS): cv.ensure_list(POINT_SCHEMA), |     cv.Required(CONF_POINTS): cv.ensure_list(point_schema), | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| async def process_coord(coord): | async def process_coord(coord): | ||||||
|     if isinstance(coord, Lambda): |     if isinstance(coord, Lambda): | ||||||
|         coord = call_lambda( |         coord = call_lambda( | ||||||
|             await cg.process_lambda(coord, (), return_type="lv_coord_t") |             await cg.process_lambda(coord, [], return_type="lv_coord_t") | ||||||
|         ) |         ) | ||||||
|         if not coord.endswith("()"): |         if not coord.endswith("()"): | ||||||
|             coord = f"static_cast<lv_coord_t>({coord})" |             coord = f"static_cast<lv_coord_t>({coord})" | ||||||
|   | |||||||
| @@ -130,6 +130,10 @@ lvgl: | |||||||
|           on_click: |           on_click: | ||||||
|             then: |             then: | ||||||
|               - lvgl.widget.hide: message_box |               - lvgl.widget.hide: message_box | ||||||
|  |               - lvgl.style.update: | ||||||
|  |                   id: style_test | ||||||
|  |                   bg_color: blue | ||||||
|  |                   bg_opa: !lambda return 0.5; | ||||||
|     - id: simple_msgbox |     - id: simple_msgbox | ||||||
|       title: Simple |       title: Simple | ||||||
|  |  | ||||||
| @@ -510,6 +514,110 @@ lvgl: | |||||||
|  |  | ||||||
|     - id: page2 |     - id: page2 | ||||||
|       widgets: |       widgets: | ||||||
|  |         - canvas: | ||||||
|  |             id: canvas_id | ||||||
|  |             align: center | ||||||
|  |             width: 400 | ||||||
|  |             height: 400 | ||||||
|  |             transparent: true | ||||||
|  |             on_boot: | ||||||
|  |               - lvgl.canvas.fill: | ||||||
|  |                   color: blue | ||||||
|  |                   opa: 50% | ||||||
|  |               - lvgl.canvas.draw_rectangle: | ||||||
|  |                   x: 20 | ||||||
|  |                   y: 20 | ||||||
|  |                   width: 150 | ||||||
|  |                   height: 150 | ||||||
|  |                   bg_color: green | ||||||
|  |                   bg_opa: cover | ||||||
|  |                   radius: 5 | ||||||
|  |                   border_color: black | ||||||
|  |                   border_width: 4 | ||||||
|  |                   border_opa: 80% | ||||||
|  |                   shadow_color: black | ||||||
|  |                   shadow_width: 10 | ||||||
|  |                   shadow_ofs_x: 5 | ||||||
|  |                   shadow_ofs_y: 5 | ||||||
|  |                   shadow_spread: 4 | ||||||
|  |                   shadow_opa: cover | ||||||
|  |                   outline_color: red | ||||||
|  |                   outline_width: 4 | ||||||
|  |                   outline_pad: 4 | ||||||
|  |                   outline_opa: cover | ||||||
|  |               - lvgl.canvas.set_pixels: | ||||||
|  |                   color: red | ||||||
|  |                   points: | ||||||
|  |                     - x: 100 | ||||||
|  |                       y: 100 | ||||||
|  |                     - 100,101 | ||||||
|  |                     - 100,102 | ||||||
|  |                     - 100,103 | ||||||
|  |                     - 100,104 | ||||||
|  |               - lvgl.canvas.set_pixels: | ||||||
|  |                   opa: 50% | ||||||
|  |                   color: !lambda return lv_color_make(255,255,255); | ||||||
|  |                   points: | ||||||
|  |                     - x: !lambda return random_uint32() % 200; | ||||||
|  |                       y: !lambda return random_uint32() % 200; | ||||||
|  |                     - 121,120 | ||||||
|  |                     - 122,120 | ||||||
|  |                     - 123,120 | ||||||
|  |                     - 124,120 | ||||||
|  |                     - 125,120 | ||||||
|  |  | ||||||
|  |               - lvgl.canvas.draw_text: | ||||||
|  |                   x: 100 | ||||||
|  |                   y: 100 | ||||||
|  |                   font: montserrat_18 | ||||||
|  |                   color: white | ||||||
|  |                   opa: cover | ||||||
|  |                   decor: underline | ||||||
|  |                   letter_space: 1 | ||||||
|  |                   line_space: 2 | ||||||
|  |                   text: Canvas Text | ||||||
|  |                   align: center | ||||||
|  |                   max_width: 150 | ||||||
|  |               - lvgl.canvas.draw_image: | ||||||
|  |                   src: cat_image | ||||||
|  |                   x: 100 | ||||||
|  |                   y: 100 | ||||||
|  |                   angle: 90 | ||||||
|  |                   zoom: 2.0 | ||||||
|  |                   pivot_x: 25 | ||||||
|  |                   pivot_y: 25 | ||||||
|  |               - lvgl.canvas.draw_line: | ||||||
|  |                   color: blue | ||||||
|  |                   width: 4 | ||||||
|  |                   round_end: true | ||||||
|  |                   round_start: false | ||||||
|  |                   points: | ||||||
|  |                     - 50,50 | ||||||
|  |                     - 50, 200 | ||||||
|  |                     - 200, 200 | ||||||
|  |                     - 200, 50 | ||||||
|  |                     - 50,50 | ||||||
|  |               - lvgl.canvas.draw_polygon: | ||||||
|  |                   bg_color: teal | ||||||
|  |                   border_color: white | ||||||
|  |                   border_width: 2 | ||||||
|  |                   border_opa: cover | ||||||
|  |                   points: | ||||||
|  |                     - 150,150 | ||||||
|  |                     - 150, 300 | ||||||
|  |                     - 300, 300 | ||||||
|  |                     - 350, 250 | ||||||
|  |               - lvgl.canvas.draw_arc: | ||||||
|  |                   x: 200 | ||||||
|  |                   y: 200 | ||||||
|  |                   radius: 40 | ||||||
|  |                   opa: 50% | ||||||
|  |                   color: purple | ||||||
|  |                   width: 6 | ||||||
|  |                   rounded: true | ||||||
|  |                   start_angle: 10 | ||||||
|  |                   end_angle: !lambda return 900; | ||||||
|  |  | ||||||
|         - qrcode: |         - qrcode: | ||||||
|             id: lv_qr |             id: lv_qr | ||||||
|             align: left_mid |             align: left_mid | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user