mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[lvgl] Make line points templatable (#8502)
This commit is contained in:
		| @@ -375,6 +375,7 @@ async def to_code(configs): | |||||||
|         add_define("LV_COLOR_SCREEN_TRANSP", "1") |         add_define("LV_COLOR_SCREEN_TRANSP", "1") | ||||||
|     for use in helpers.lv_uses: |     for use in helpers.lv_uses: | ||||||
|         add_define(f"LV_USE_{use.upper()}") |         add_define(f"LV_USE_{use.upper()}") | ||||||
|  |         cg.add_define(f"USE_LVGL_{use.upper()}") | ||||||
|     lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME) |     lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME) | ||||||
|     write_file_if_changed(lv_conf_h_file, generate_lv_conf_h()) |     write_file_if_changed(lv_conf_h_file, generate_lv_conf_h()) | ||||||
|     cg.add_build_flag("-DLV_CONF_H=1") |     cg.add_build_flag("-DLV_CONF_H=1") | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ from .defines import ( | |||||||
|     CONF_SHOW_SNOW, |     CONF_SHOW_SNOW, | ||||||
|     PARTS, |     PARTS, | ||||||
|     literal, |     literal, | ||||||
|  |     static_cast, | ||||||
| ) | ) | ||||||
| from .lv_validation import lv_bool, lv_color, lv_image, opacity | from .lv_validation import lv_bool, lv_color, lv_image, opacity | ||||||
| from .lvcode import ( | from .lvcode import ( | ||||||
| @@ -32,7 +33,6 @@ from .lvcode import ( | |||||||
|     lv_expr, |     lv_expr, | ||||||
|     lv_obj, |     lv_obj, | ||||||
|     lvgl_comp, |     lvgl_comp, | ||||||
|     static_cast, |  | ||||||
| ) | ) | ||||||
| from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema | from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema | ||||||
| from .types import ( | from .types import ( | ||||||
|   | |||||||
| @@ -35,6 +35,10 @@ def literal(arg): | |||||||
|     return arg |     return arg | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def static_cast(type, value): | ||||||
|  |     return literal(f"static_cast<{type}>({value})") | ||||||
|  |  | ||||||
|  |  | ||||||
| def call_lambda(lamb: LambdaExpression): | def call_lambda(lamb: LambdaExpression): | ||||||
|     expr = lamb.content.strip() |     expr = lamb.content.strip() | ||||||
|     if expr.startswith("return") and expr.endswith(";"): |     if expr.startswith("return") and expr.endswith(";"): | ||||||
|   | |||||||
| @@ -285,10 +285,6 @@ class LvExpr(MockLv): | |||||||
|         pass |         pass | ||||||
|  |  | ||||||
|  |  | ||||||
| def static_cast(type, value): |  | ||||||
|     return literal(f"static_cast<{type}>({value})") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Top level mock for generic lv_ calls to be recorded | # Top level mock for generic lv_ calls to be recorded | ||||||
| lv = MockLv("lv_") | lv = MockLv("lv_") | ||||||
| # Just generate an expression | # Just generate an expression | ||||||
|   | |||||||
| @@ -90,6 +90,7 @@ inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images | |||||||
| // Parent class for things that wrap an LVGL object | // Parent class for things that wrap an LVGL object | ||||||
| class LvCompound { | class LvCompound { | ||||||
|  public: |  public: | ||||||
|  |   virtual ~LvCompound() = default; | ||||||
|   virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; } |   virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; } | ||||||
|   lv_obj_t *obj{}; |   lv_obj_t *obj{}; | ||||||
| }; | }; | ||||||
| @@ -330,6 +331,19 @@ class LVEncoderListener : public Parented<LvglComponent> { | |||||||
| }; | }; | ||||||
| #endif  //  USE_LVGL_KEY_LISTENER | #endif  //  USE_LVGL_KEY_LISTENER | ||||||
|  |  | ||||||
|  | #ifdef USE_LVGL_LINE | ||||||
|  | class LvLineType : public LvCompound { | ||||||
|  |  public: | ||||||
|  |   std::vector<lv_point_t> get_points() { return this->points_; } | ||||||
|  |   void set_points(std::vector<lv_point_t> points) { | ||||||
|  |     this->points_ = std::move(points); | ||||||
|  |     lv_line_set_points(this->obj, this->points_.data(), this->points_.size()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::vector<lv_point_t> points_{}; | ||||||
|  | }; | ||||||
|  | #endif | ||||||
| #if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER) | #if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER) | ||||||
| class LvSelectable : public LvCompound { | class LvSelectable : public LvCompound { | ||||||
|  public: |  public: | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ from esphome.core.config import StartupTrigger | |||||||
| from esphome.schema_extractors import SCHEMA_EXTRACT | from esphome.schema_extractors import SCHEMA_EXTRACT | ||||||
|  |  | ||||||
| from . import defines as df, lv_validation as lvalid | from . import defines as df, lv_validation as lvalid | ||||||
| from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR | from .defines import CONF_TIME_FORMAT, CONF_X, CONF_Y, LV_GRAD_DIR | ||||||
| from .helpers import add_lv_use, requires_component, validate_printf | from .helpers import add_lv_use, requires_component, validate_printf | ||||||
| from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity | from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity | ||||||
| from .lvcode import LvglComponent, lv_event_t_ptr | from .lvcode import LvglComponent, lv_event_t_ptr | ||||||
| @@ -87,6 +87,33 @@ ENCODER_SCHEMA = cv.Schema( | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def point_shorthand(value): | ||||||
|  |     """ | ||||||
|  |     A shorthand for a point in the form of x,y | ||||||
|  |     :param value: The value to check | ||||||
|  |     :return: The value as a tuple of x,y | ||||||
|  |     """ | ||||||
|  |     if isinstance(value, str): | ||||||
|  |         try: | ||||||
|  |             x, y = map(int, value.split(",")) | ||||||
|  |             return {CONF_X: x, CONF_Y: y} | ||||||
|  |         except ValueError: | ||||||
|  |             pass | ||||||
|  |     raise cv.Invalid("Invalid point format, should be <x_value>, <y_value>") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | ||||||
| STYLE_PROPS = { | STYLE_PROPS = { | ||||||
|     "align": df.CHILD_ALIGNMENTS.one_of, |     "align": df.CHILD_ALIGNMENTS.one_of, | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| import functools |  | ||||||
|  |  | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | from esphome.core import Lambda | ||||||
|  |  | ||||||
| from ..defines import CONF_MAIN | from ..defines import CONF_MAIN, CONF_X, CONF_Y, call_lambda | ||||||
| from ..lvcode import lv | from ..lvcode import lv_add | ||||||
| from ..types import LvType | from ..schemas import POINT_SCHEMA | ||||||
|  | from ..types import LvCompound, LvType | ||||||
| from . import Widget, WidgetType | from . import Widget, WidgetType | ||||||
|  |  | ||||||
| CONF_LINE = "line" | CONF_LINE = "line" | ||||||
| @@ -15,47 +15,37 @@ CONF_POINT_LIST_ID = "point_list_id" | |||||||
| lv_point_t = cg.global_ns.struct("lv_point_t") | lv_point_t = cg.global_ns.struct("lv_point_t") | ||||||
|  |  | ||||||
|  |  | ||||||
| def point_list(il): |  | ||||||
|     il = cv.string(il) |  | ||||||
|     nl = il.replace(" ", "").split(",") |  | ||||||
|     return [int(n) for n in nl] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def cv_point_list(value): |  | ||||||
|     if not isinstance(value, list): |  | ||||||
|         raise cv.Invalid("List of points required") |  | ||||||
|     values = [point_list(v) for v in value] |  | ||||||
|     if not functools.reduce(lambda f, v: f and len(v) == 2, values, True): |  | ||||||
|         raise cv.Invalid("Points must be a list of x,y integer pairs") |  | ||||||
|     return values |  | ||||||
|  |  | ||||||
|  |  | ||||||
| LINE_SCHEMA = { | LINE_SCHEMA = { | ||||||
|     cv.Required(CONF_POINTS): cv_point_list, |     cv.Required(CONF_POINTS): cv.ensure_list(POINT_SCHEMA), | ||||||
|     cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), |  | ||||||
| } | } | ||||||
|  |  | ||||||
| LINE_MODIFY_SCHEMA = { |  | ||||||
|     cv.Optional(CONF_POINTS): cv_point_list, | async def process_coord(coord): | ||||||
|     cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), |     if isinstance(coord, Lambda): | ||||||
| } |         coord = call_lambda( | ||||||
|  |             await cg.process_lambda(coord, (), return_type="lv_coord_t") | ||||||
|  |         ) | ||||||
|  |         if not coord.endswith("()"): | ||||||
|  |             coord = f"static_cast<lv_coord_t>({coord})" | ||||||
|  |         return cg.RawExpression(coord) | ||||||
|  |     return cg.safe_exp(coord) | ||||||
|  |  | ||||||
|  |  | ||||||
| class LineType(WidgetType): | class LineType(WidgetType): | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__( |         super().__init__( | ||||||
|             CONF_LINE, |             CONF_LINE, | ||||||
|             LvType("lv_line_t"), |             LvType("LvLineType", parents=(LvCompound,)), | ||||||
|             (CONF_MAIN,), |             (CONF_MAIN,), | ||||||
|             LINE_SCHEMA, |             LINE_SCHEMA, | ||||||
|             modify_schema=LINE_MODIFY_SCHEMA, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     async def to_code(self, w: Widget, config): |     async def to_code(self, w: Widget, config): | ||||||
|         """For a line object, create and add the points""" |         points = [ | ||||||
|         if data := config.get(CONF_POINTS): |             [await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])] | ||||||
|             points = cg.static_const_array(config[CONF_POINT_LIST_ID], data) |             for p in config[CONF_POINTS] | ||||||
|             lv.line_set_points(w.obj, points, len(data)) |         ] | ||||||
|  |         lv_add(w.var.set_points(points)) | ||||||
|  |  | ||||||
|  |  | ||||||
| line_spec = LineType() | line_spec = LineType() | ||||||
|   | |||||||
| @@ -614,6 +614,8 @@ lvgl: | |||||||
|             align: center |             align: center | ||||||
|             points: |             points: | ||||||
|               - 5, 5 |               - 5, 5 | ||||||
|  |               - x: !lambda return random_uint32() % 100; | ||||||
|  |                 y: !lambda return random_uint32() % 100; | ||||||
|               - 70, 70 |               - 70, 70 | ||||||
|               - 120, 10 |               - 120, 10 | ||||||
|               - 180, 60 |               - 180, 60 | ||||||
| @@ -622,6 +624,14 @@ lvgl: | |||||||
|               - lvgl.line.update: |               - lvgl.line.update: | ||||||
|                   id: lv_line_id |                   id: lv_line_id | ||||||
|                   line_color: 0xFFFF |                   line_color: 0xFFFF | ||||||
|  |                   points: | ||||||
|  |                     - 5, 5 | ||||||
|  |                     - x: !lambda return random_uint32() % 100; | ||||||
|  |                       y: !lambda return random_uint32() % 100; | ||||||
|  |                     - 70, 70 | ||||||
|  |                     - 120, 10 | ||||||
|  |                     - 180, 60 | ||||||
|  |                     - 240, 10 | ||||||
|               - lvgl.page.next: |               - lvgl.page.next: | ||||||
|         - switch: |         - switch: | ||||||
|             align: right_mid |             align: right_mid | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user