1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-01 19:02:18 +01:00
Files
esphome/esphome/components/lvgl/widgets/canvas.py
2025-08-25 15:59:35 +12:00

414 lines
12 KiB
Python

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,
CONF_X,
CONF_Y,
)
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,
literal,
)
from ..lv_validation import (
lv_angle_degrees,
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, config)
@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, config
)
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, config
)
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_degrees,
cv.Required(CONF_END_ANGLE): lv_angle_degrees,
}
).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_degrees.process(config[CONF_START_ANGLE])
end_angle = await lv_angle_degrees.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
)