Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion esphome/components/lvgl/defines.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,9 @@ def __getattr__(self, item):
"OUT_BOTTOM",
)

LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER")
LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER", "LINEAR", "RADIAL", "CONICAL")
LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF")
LV_GRAD_EXTEND = LvConstant("LV_GRAD_EXTEND_", "PAD", "REPEAT", "REFLECT")

LV_LOG_LEVELS = {
"VERBOSE": "TRACE",
Expand Down
172 changes: 148 additions & 24 deletions esphome/components/lvgl/gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,35 @@
from esphome.core import ID
from esphome.cpp_generator import MockObj

from .defines import CONF_GRADIENTS, CONF_OPA, LV_DITHER, add_define, add_warning
from .defines import (
CONF_END_ANGLE,
CONF_GRADIENTS,
CONF_OPA,
CONF_START_ANGLE,
LV_DITHER,
LV_GRAD_EXTEND,
add_define,
add_warning,
)
from .helpers import add_lv_use
from .lv_validation import lv_color, lv_percentage, opacity
from .lv_validation import lv_color, lv_percentage, opacity, pixels_or_percent
from .lvcode import lv
from .types import lv_color_t, lv_gradient_t, lv_opa_t

CONF_STOPS = "stops"
CONF_LINEAR = "linear"
CONF_RADIAL = "radial"
CONF_CONICAL = "conical"
CONF_EXTEND = "extend"
CONF_FROM_X = "from_x"
CONF_FROM_Y = "from_y"
CONF_TO_X = "to_x"
CONF_TO_Y = "to_y"
CONF_CENTER_X = "center_x"
CONF_CENTER_Y = "center_y"
CONF_FOCAL_X = "focal_x"
CONF_FOCAL_Y = "focal_y"
CONF_FOCAL_RADIUS = "focal_radius"


def min_stops(value):
Expand All @@ -27,27 +49,90 @@ def min_stops(value):
return value


STOPS_SCHEMA = cv.All(
[
cv.Schema(
{
cv.Required(CONF_COLOR): lv_color,
cv.Optional(CONF_OPA, default=1.0): opacity,
cv.Required(CONF_POSITION): lv_percentage,
}
)
],
min_stops,
)

LINEAR_SCHEMA = cv.Schema(
{
cv.Required(CONF_FROM_X): pixels_or_percent,
cv.Required(CONF_FROM_Y): pixels_or_percent,
cv.Required(CONF_TO_X): pixels_or_percent,
cv.Required(CONF_TO_Y): pixels_or_percent,
cv.Optional(CONF_EXTEND, default="PAD"): LV_GRAD_EXTEND.one_of,
}
)

RADIAL_SCHEMA = cv.Schema(
{
cv.Required(CONF_CENTER_X): pixels_or_percent,
cv.Required(CONF_CENTER_Y): pixels_or_percent,
cv.Required(CONF_TO_X): pixels_or_percent,
cv.Required(CONF_TO_Y): pixels_or_percent,
cv.Optional(CONF_FOCAL_X): pixels_or_percent,
cv.Optional(CONF_FOCAL_Y): pixels_or_percent,
cv.Optional(CONF_FOCAL_RADIUS, default=0): cv.positive_int,
cv.Optional(CONF_EXTEND, default="PAD"): LV_GRAD_EXTEND.one_of,
}
)

CONICAL_SCHEMA = cv.Schema(
{
cv.Required(CONF_CENTER_X): pixels_or_percent,
cv.Required(CONF_CENTER_Y): pixels_or_percent,
cv.Optional(CONF_START_ANGLE, default=0): cv.int_range(0, 360),
cv.Optional(CONF_END_ANGLE, default=360): cv.int_range(0, 360),
cv.Optional(CONF_EXTEND, default="PAD"): LV_GRAD_EXTEND.one_of,
}
)


def gradient_validator(config):
direction = config[CONF_DIRECTION]
if direction == "LINEAR" and CONF_LINEAR not in config:
raise cv.Invalid("'linear' is required for LINEAR gradient direction")
if direction == "RADIAL" and CONF_RADIAL not in config:
raise cv.Invalid("'radial' is required for RADIAL gradient direction")
if direction == "CONICAL" and CONF_CONICAL not in config:
raise cv.Invalid("'conical' is required for CONICAL gradient direction")
if CONF_RADIAL in config:
radial = config[CONF_RADIAL]
has_focal_x = CONF_FOCAL_X in radial
has_focal_y = CONF_FOCAL_Y in radial
if has_focal_x != has_focal_y:
raise cv.Invalid(
"Both 'focal_x' and 'focal_y' must be specified together in 'radial'"
)
return config


GRADIENT_SCHEMA = cv.ensure_list(
cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(lv_gradient_t),
cv.Required(CONF_DIRECTION): cv.one_of(
"HOR", "HORIZONTAL", "VER", "VERTICAL", upper=True
),
cv.Optional(CONF_DITHER): LV_DITHER.one_of,
cv.Required(CONF_STOPS): cv.All(
[
cv.Schema(
{
cv.Required(CONF_COLOR): lv_color,
cv.Optional(CONF_OPA, default=1.0): opacity,
cv.Required(CONF_POSITION): lv_percentage,
}
)
],
min_stops,
),
}
cv.All(
cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(lv_gradient_t),
cv.Required(CONF_DIRECTION): cv.one_of(
"HOR", "HORIZONTAL", "VER", "VERTICAL",
"LINEAR", "RADIAL", "CONICAL",
upper=True,
),
cv.Optional(CONF_DITHER): LV_DITHER.one_of,
cv.Optional(CONF_LINEAR): LINEAR_SCHEMA,
cv.Optional(CONF_RADIAL): RADIAL_SCHEMA,
cv.Optional(CONF_CONICAL): CONICAL_SCHEMA,
cv.Required(CONF_STOPS): STOPS_SCHEMA,
}
),
gradient_validator,
)
)

Expand All @@ -64,10 +149,48 @@ async def gradients_to_code(config):
idbase = gradient[CONF_ID].id
stops = sorted(gradient[CONF_STOPS], key=itemgetter(CONF_POSITION))
max_stops = max(max_stops, len(stops))
if gradient[CONF_DIRECTION].startswith("VER"):
direction = gradient[CONF_DIRECTION]
if direction.startswith("VER"):
lv.grad_vertical_init(var)
else:
elif direction.startswith("HOR"):
lv.grad_horizontal_init(var)
elif direction == "LINEAR":
linear = gradient[CONF_LINEAR]
lv.grad_linear_init(
var,
await pixels_or_percent.process(linear[CONF_FROM_X]),
await pixels_or_percent.process(linear[CONF_FROM_Y]),
await pixels_or_percent.process(linear[CONF_TO_X]),
await pixels_or_percent.process(linear[CONF_TO_Y]),
await LV_GRAD_EXTEND.process(linear[CONF_EXTEND]),
)
elif direction == "RADIAL":
radial = gradient[CONF_RADIAL]
lv.grad_radial_init(
var,
await pixels_or_percent.process(radial[CONF_CENTER_X]),
await pixels_or_percent.process(radial[CONF_CENTER_Y]),
await pixels_or_percent.process(radial[CONF_TO_X]),
await pixels_or_percent.process(radial[CONF_TO_Y]),
await LV_GRAD_EXTEND.process(radial[CONF_EXTEND]),
)
if CONF_FOCAL_X in radial:
lv.grad_radial_set_focal(
var,
await pixels_or_percent.process(radial[CONF_FOCAL_X]),
await pixels_or_percent.process(radial[CONF_FOCAL_Y]),
radial[CONF_FOCAL_RADIUS],
)
elif direction == "CONICAL":
conical = gradient[CONF_CONICAL]
lv.grad_conical_init(
var,
await pixels_or_percent.process(conical[CONF_CENTER_X]),
await pixels_or_percent.process(conical[CONF_CENTER_Y]),
conical[CONF_START_ANGLE],
conical[CONF_END_ANGLE],
await LV_GRAD_EXTEND.process(conical[CONF_EXTEND]),
)
stop_colors = cg.static_const_array(
ID(idbase + "_colors_", type=lv_color_t),
[await lv_color.process(x[CONF_COLOR]) for x in stops],
Expand All @@ -83,3 +206,4 @@ async def gradients_to_code(config):
lv.grad_init_stops(var, stop_colors, stop_opacities, stop_positions, len(stops))

add_define("LV_GRADIENT_MAX_STOPS", max_stops)

57 changes: 57 additions & 0 deletions tests/components/lvgl/lvgl-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,63 @@ lvgl:
position: 212
- color: 0xFF0000
position: 255
- id: linear_grad
direction: LINEAR
linear:
from_x: 0%
from_y: 0%
to_x: 100%
to_y: 0%
extend: PAD
stops:
- color: 0xFF0000
position: 0
- color: 0x0000FF
position: 255
- id: radial_grad
direction: RADIAL
radial:
center_x: 50%
center_y: 50%
to_x: 100%
to_y: 50%
extend: PAD
stops:
- color: 0xFFFFFF
position: 0
- color: 0x000000
position: 255
- id: radial_focal_grad
direction: RADIAL
radial:
center_x: 50%
center_y: 50%
to_x: 100%
to_y: 50%
focal_x: 40%
focal_y: 40%
focal_radius: 10
extend: REPEAT
stops:
- color: 0xFF0000
position: 0
- color: 0x0000FF
position: 255
- id: conical_grad
direction: CONICAL
conical:
center_x: 50%
center_y: 50%
start_angle: 0
end_angle: 360
extend: PAD
stops:
- color: 0xFF0000
position: 0
- color: 0x00FF00
position: 127
- color: 0xFF0000
position: 255

style_definitions:
- id: style_test
Expand Down
Loading