[lvgl] Revise code generation to allow early widget creation (#7611)
Some checks failed
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 IDF (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP8266 (push) Blocked by required conditions
CI / list-components (push) Blocked by required conditions
CI / Run script/ci-custom (push) Blocked by required conditions
CI / Create common environment (push) Waiting to run
CI / Check black (push) Blocked by required conditions
CI / Check flake8 (push) Blocked by required conditions
CI / Check pylint (push) Blocked by required conditions
CI / Check pyupgrade (push) Blocked by required conditions
CI / Run pytest (push) Blocked by required conditions
CI / Check clang-format (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Blocked by required conditions
CI / Component test (push) Blocked by required conditions
CI / Split components for testing into 20 groups maximum (push) Blocked by required conditions
CI / Test split components (push) Blocked by required conditions
CI / CI Status (push) Blocked by required conditions
YAML lint / yamllint (push) Waiting to run
CI for docker images / Build docker containers (push) Has been cancelled

This commit is contained in:
Clyde Stubbs 2024-10-17 13:20:19 +11:00 committed by GitHub
parent f490585f66
commit 8bbe4efded
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 155 additions and 132 deletions

View file

@ -22,7 +22,7 @@ from esphome.helpers import write_file_if_changed
from . import defines as df, helpers, lv_validation as lvalid from . import defines as df, helpers, lv_validation as lvalid
from .automation import disp_update, focused_widgets, update_to_code from .automation import disp_update, focused_widgets, update_to_code
from .defines import CONF_WIDGETS, add_define from .defines import add_define
from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code
from .gradient import GRADIENT_SCHEMA, gradients_to_code from .gradient import GRADIENT_SCHEMA, gradients_to_code
from .hello_world import get_hello_world from .hello_world import get_hello_world
@ -54,7 +54,7 @@ from .types import (
lv_style_t, lv_style_t,
lvgl_ns, lvgl_ns,
) )
from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties, styles_used from .widgets import Widget, add_widgets, get_scr_act, set_obj_properties, styles_used
from .widgets.animimg import animimg_spec 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
@ -186,7 +186,7 @@ def final_validation(config):
async def to_code(config): async def to_code(config):
cg.add_library("lvgl/lvgl", "8.4.0") cg.add_library("lvgl/lvgl", "8.4.0")
CORE.add_define("USE_LVGL") cg.add_define("USE_LVGL")
# suppress default enabling of extra widgets # suppress default enabling of extra widgets
add_define("_LV_KCONFIG_PRESENT") add_define("_LV_KCONFIG_PRESENT")
# Always enable - lots of things use it. # Always enable - lots of things use it.
@ -200,7 +200,13 @@ async def to_code(config):
add_define("LV_MEM_CUSTOM_REALLOC", "lv_custom_mem_realloc") add_define("LV_MEM_CUSTOM_REALLOC", "lv_custom_mem_realloc")
add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"') add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"')
add_define("LV_LOG_LEVEL", f"LV_LOG_LEVEL_{config[df.CONF_LOG_LEVEL]}") add_define(
"LV_LOG_LEVEL", f"LV_LOG_LEVEL_{df.LV_LOG_LEVELS[config[df.CONF_LOG_LEVEL]]}"
)
cg.add_define(
"LVGL_LOG_LEVEL",
cg.RawExpression(f"ESPHOME_LOG_LEVEL_{config[df.CONF_LOG_LEVEL]}"),
)
add_define("LV_COLOR_DEPTH", config[df.CONF_COLOR_DEPTH]) add_define("LV_COLOR_DEPTH", config[df.CONF_COLOR_DEPTH])
for font in helpers.lv_fonts_used: for font in helpers.lv_fonts_used:
add_define(f"LV_FONT_{font.upper()}") add_define(f"LV_FONT_{font.upper()}")
@ -214,15 +220,9 @@ async def to_code(config):
"LV_COLOR_CHROMA_KEY", "LV_COLOR_CHROMA_KEY",
await lvalid.lv_color.process(config[df.CONF_TRANSPARENCY_KEY]), await lvalid.lv_color.process(config[df.CONF_TRANSPARENCY_KEY]),
) )
CORE.add_build_flag("-Isrc") cg.add_build_flag("-Isrc")
cg.add_global(lvgl_ns.using) cg.add_global(lvgl_ns.using)
lv_component = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(lv_component, config)
Widget.create(config[CONF_ID], lv_component, obj_spec, config)
for display in config[df.CONF_DISPLAYS]:
cg.add(lv_component.add_display(await cg.get_variable(display)))
frac = config[CONF_BUFFER_SIZE] frac = config[CONF_BUFFER_SIZE]
if frac >= 0.75: if frac >= 0.75:
frac = 1 frac = 1
@ -232,10 +232,17 @@ async def to_code(config):
frac = 4 frac = 4
else: else:
frac = 8 frac = 8
cg.add(lv_component.set_buffer_frac(int(frac))) displays = [await cg.get_variable(display) for display in config[df.CONF_DISPLAYS]]
cg.add(lv_component.set_full_refresh(config[df.CONF_FULL_REFRESH])) lv_component = cg.new_Pvariable(
cg.add(lv_component.set_draw_rounding(config[df.CONF_DRAW_ROUNDING])) config[CONF_ID],
cg.add(lv_component.set_resume_on_input(config[df.CONF_RESUME_ON_INPUT])) displays,
frac,
config[df.CONF_FULL_REFRESH],
config[df.CONF_DRAW_ROUNDING],
config[df.CONF_RESUME_ON_INPUT],
)
await cg.register_component(lv_component, config)
Widget.create(config[CONF_ID], lv_component, obj_spec, config)
for font in helpers.esphome_fonts_used: for font in helpers.esphome_fonts_used:
await cg.get_variable(font) await cg.get_variable(font)
@ -257,6 +264,7 @@ async def to_code(config):
else: else:
add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font)) add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font))
lv_scr_act = get_scr_act(lv_component)
async with LvContext(lv_component): async with LvContext(lv_component):
await touchscreens_to_code(lv_component, config) await touchscreens_to_code(lv_component, config)
await encoders_to_code(lv_component, config) await encoders_to_code(lv_component, config)
@ -266,11 +274,9 @@ async def to_code(config):
await set_obj_properties(lv_scr_act, config) await set_obj_properties(lv_scr_act, 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(lv_component, config)
await msgboxes_to_code(config) await msgboxes_to_code(lv_component, config)
await disp_update(f"{lv_component}->get_disp()", config) await disp_update(lv_component.get_disp(), config)
# At this point only the setup code should be generated
assert LvContext.added_lambda_count == 1
# Set this directly since we are limited in how many methods can be added to the Widget class. # Set this directly since we are limited in how many methods can be added to the Widget class.
Widget.widgets_completed = True Widget.widgets_completed = True
async with LvContext(lv_component): async with LvContext(lv_component):
@ -291,15 +297,15 @@ async def to_code(config):
await build_automation(resume_trigger, [], conf) await build_automation(resume_trigger, [], conf)
for comp in helpers.lvgl_components_required: for comp in helpers.lvgl_components_required:
CORE.add_define(f"USE_LVGL_{comp.upper()}") cg.add_define(f"USE_LVGL_{comp.upper()}")
if "transform_angle" in styles_used: if "transform_angle" in styles_used:
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()}")
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())
CORE.add_build_flag("-DLV_CONF_H=1") cg.add_build_flag("-DLV_CONF_H=1")
CORE.add_build_flag(f'-DLV_CONF_PATH="{LV_CONF_FILENAME}"') cg.add_build_flag(f'-DLV_CONF_PATH="{LV_CONF_FILENAME}"')
def display_schema(config): def display_schema(config):
@ -308,9 +314,9 @@ def display_schema(config):
def add_hello_world(config): def add_hello_world(config):
if CONF_WIDGETS not in config and CONF_PAGES not in config: if df.CONF_WIDGETS not in config and CONF_PAGES not in config:
LOGGER.info("No pages or widgets configured, creating default hello_world page") LOGGER.info("No pages or widgets configured, creating default hello_world page")
config[CONF_WIDGETS] = cv.ensure_list(WIDGET_SCHEMA)(get_hello_world()) config[df.CONF_WIDGETS] = cv.ensure_list(WIDGET_SCHEMA)(get_hello_world())
return config return config
@ -329,7 +335,7 @@ CONFIG_SCHEMA = (
cv.Optional(df.CONF_DRAW_ROUNDING, default=2): cv.positive_int, cv.Optional(df.CONF_DRAW_ROUNDING, default=2): cv.positive_int,
cv.Optional(CONF_BUFFER_SIZE, default="100%"): cv.percentage, cv.Optional(CONF_BUFFER_SIZE, default="100%"): cv.percentage,
cv.Optional(df.CONF_LOG_LEVEL, default="WARN"): cv.one_of( cv.Optional(df.CONF_LOG_LEVEL, default="WARN"): cv.one_of(
*df.LOG_LEVELS, upper=True *df.LV_LOG_LEVELS, upper=True
), ),
cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of( cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of(
"big_endian", "little_endian" "big_endian", "little_endian"

View file

@ -5,7 +5,7 @@ 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_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT from esphome.const import CONF_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT
from esphome.cpp_generator import RawExpression, get_variable from esphome.cpp_generator import get_variable
from esphome.cpp_types import nullptr from esphome.cpp_types import nullptr
from .defines import ( from .defines import (
@ -23,7 +23,6 @@ from .lvcode import (
UPDATE_EVENT, UPDATE_EVENT,
LambdaContext, LambdaContext,
LocalVariable, LocalVariable,
LvConditional,
LvglComponent, LvglComponent,
ReturnStatement, ReturnStatement,
add_line_marks, add_line_marks,
@ -47,8 +46,8 @@ from .types import (
) )
from .widgets import ( from .widgets import (
Widget, Widget,
get_scr_act,
get_widgets, get_widgets,
lv_scr_act,
set_obj_properties, set_obj_properties,
wait_for_widgets, wait_for_widgets,
) )
@ -66,8 +65,6 @@ async def action_to_code(
): ):
await wait_for_widgets() await wait_for_widgets()
async with LambdaContext(parameters=args, where=action_id) as context: async with LambdaContext(parameters=args, where=action_id) as context:
with LvConditional(lv_expr.is_pre_initialise()):
context.add(RawExpression("return"))
for widget in widgets: for widget in widgets:
await action(widget) await action(widget)
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
@ -126,7 +123,7 @@ async def lvgl_is_idle(config, condition_id, template_arg, args):
async def disp_update(disp, config: dict): 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, disp) as disp_temp:
if (bg_color := config.get(CONF_DISP_BG_COLOR)) is not None: 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):
@ -136,15 +133,24 @@ async def disp_update(disp, config: dict):
@automation.register_action( @automation.register_action(
"lvgl.widget.redraw", "lvgl.widget.redraw",
ObjUpdateAction, ObjUpdateAction,
cv.Any(
cv.maybe_simple_value(
{
cv.Required(CONF_ID): cv.use_id(lv_obj_t),
cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent),
},
key=CONF_ID,
),
cv.Schema( cv.Schema(
{ {
cv.Optional(CONF_ID): cv.use_id(lv_obj_t),
cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent),
} }
), ),
),
) )
async def obj_invalidate_to_code(config, action_id, template_arg, args): async def obj_invalidate_to_code(config, action_id, template_arg, args):
widgets = await get_widgets(config) or [lv_scr_act] lv_comp = await cg.get_variable(config[CONF_LVGL_ID])
widgets = await get_widgets(config) or [get_scr_act(lv_comp)]
async def do_invalidate(widget: Widget): async def do_invalidate(widget: Widget):
lv_obj.invalidate(widget.obj) lv_obj.invalidate(widget.obj)
@ -164,7 +170,7 @@ async def obj_invalidate_to_code(config, action_id, template_arg, args):
async def lvgl_update_to_code(config, action_id, template_arg, args): async def lvgl_update_to_code(config, action_id, template_arg, args):
widgets = await get_widgets(config) widgets = await get_widgets(config)
w = widgets[0] w = widgets[0]
disp = f"{w.obj}->get_disp()" disp = literal(f"{w.obj}->get_disp()")
async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context: async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context:
await disp_update(disp, config) await disp_update(disp, config)
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())

View file

@ -189,14 +189,14 @@ LV_ANIM = LvConstant(
LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER") LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER")
LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF") LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF")
LOG_LEVELS = ( LV_LOG_LEVELS = {
"TRACE", "VERBOSE": "TRACE",
"INFO", "DEBUG": "TRACE",
"WARN", "INFO": "INFO",
"ERROR", "WARN": "WARN",
"USER", "ERROR": "ERROR",
"NONE", "NONE": "NONE",
) }
LV_LONG_MODES = LvConstant( LV_LONG_MODES = LvConstant(
"LV_LABEL_LONG_", "LV_LABEL_LONG_",

View file

@ -183,17 +183,11 @@ class LvContext(LambdaContext):
super().__init__(parameters=self.args) super().__init__(parameters=self.args)
self.lv_component = lv_component self.lv_component = lv_component
async def add_init_lambda(self):
if self.code_list:
cg.add(self.lv_component.add_init_lambda(await self.get_lambda()))
LvContext.added_lambda_count += 1
async def __aexit__(self, exc_type, exc_val, exc_tb): async def __aexit__(self, exc_type, exc_val, exc_tb):
await super().__aexit__(exc_type, exc_val, exc_tb) await super().__aexit__(exc_type, exc_val, exc_tb)
await self.add_init_lambda()
def add(self, expression: Union[Expression, Statement]): def add(self, expression: Union[Expression, Statement]):
self.code_list.append(self.indented_statement(expression)) cg.add(expression)
return expression return expression
def __call__(self, *args): def __call__(self, *args):

View file

@ -11,12 +11,6 @@ namespace esphome {
namespace lvgl { namespace lvgl {
static const char *const TAG = "lvgl"; static const char *const TAG = "lvgl";
#if LV_USE_LOG
static void log_cb(const char *buf) {
esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, TAG, 0, "%.*s", (int) strlen(buf) - 1, buf);
}
#endif // LV_USE_LOG
static const char *const EVENT_NAMES[] = { static const char *const EVENT_NAMES[] = {
"NONE", "NONE",
"PRESSED", "PRESSED",
@ -383,26 +377,48 @@ void LvglComponent::write_random_() {
} }
} }
void LvglComponent::setup() { /**
ESP_LOGCONFIG(TAG, "LVGL Setup starts"); * @class LvglComponent
#if LV_USE_LOG * @brief Component for rendering LVGL.
lv_log_register_print_cb(log_cb); *
#endif * This component renders LVGL widgets on a display. Some initialisation must be done in the constructor
* since LVGL needs to be initialised before any widgets can be created.
*
* @param displays a list of displays to render onto. All displays must have the same
* resolution.
* @param buffer_frac the fraction of the display resolution to use for the LVGL
* draw buffer. A higher value will make animations smoother but
* also increase memory usage.
* @param full_refresh if true, the display will be fully refreshed on every frame.
* If false, only changed areas will be updated.
* @param draw_rounding the rounding to use when drawing. A value of 1 will draw
* without any rounding, a value of 2 will round to the nearest
* multiple of 2, and so on.
* @param resume_on_input if true, this component will resume rendering when the user
* presses a key or clicks on the screen.
*/
LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buffer_frac, bool full_refresh,
int draw_rounding, bool resume_on_input)
: draw_rounding(draw_rounding),
displays_(std::move(displays)),
buffer_frac_(buffer_frac),
full_refresh_(full_refresh),
resume_on_input_(resume_on_input) {
lv_init(); lv_init();
lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id()); lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id());
lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id()); lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id());
auto *display = this->displays_[0]; auto *display = this->displays_[0];
size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_; size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_;
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8; auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
auto *buf = lv_custom_mem_alloc(buf_bytes); // NOLINT this->rotation = display->get_rotation();
if (buf == nullptr) { if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
ESP_LOGE(TAG, "Malloc failed to allocate %zu bytes", buf_bytes); if (this->rotate_buf_ == nullptr)
#endif
this->mark_failed();
this->status_set_error("Memory allocation failure");
return; return;
} }
auto *buf = lv_custom_mem_alloc(buf_bytes); // NOLINT
if (buf == nullptr)
return;
lv_disp_draw_buf_init(&this->draw_buf_, buf, nullptr, buffer_pixels); lv_disp_draw_buf_init(&this->draw_buf_, buf, nullptr, buffer_pixels);
lv_disp_drv_init(&this->disp_drv_); lv_disp_drv_init(&this->disp_drv_);
this->disp_drv_.draw_buf = &this->draw_buf_; this->disp_drv_.draw_buf = &this->draw_buf_;
@ -410,18 +426,7 @@ void LvglComponent::setup() {
this->disp_drv_.full_refresh = this->full_refresh_; this->disp_drv_.full_refresh = this->full_refresh_;
this->disp_drv_.flush_cb = static_flush_cb; this->disp_drv_.flush_cb = static_flush_cb;
this->disp_drv_.rounder_cb = rounder_cb; this->disp_drv_.rounder_cb = rounder_cb;
this->rotation = display->get_rotation(); // reset the display rotation since we will handle all rotations
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
if (this->rotate_buf_ == nullptr) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR
ESP_LOGE(TAG, "Malloc failed to allocate %zu bytes", buf_bytes);
#endif
this->mark_failed();
this->status_set_error("Memory allocation failure");
return;
}
}
display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES); display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);
switch (this->rotation) { switch (this->rotation) {
default: default:
@ -435,12 +440,30 @@ void LvglComponent::setup() {
break; break;
} }
this->disp_ = lv_disp_drv_register(&this->disp_drv_); this->disp_ = lv_disp_drv_register(&this->disp_drv_);
for (const auto &v : this->init_lambdas_) }
v(this);
void LvglComponent::setup() {
if (this->draw_buf_.buf1 == nullptr) {
this->mark_failed();
this->status_set_error("Memory allocation failure");
return;
}
ESP_LOGCONFIG(TAG, "LVGL Setup starts");
#if LV_USE_LOG
lv_log_register_print_cb([](const char *buf) {
auto next = strchr(buf, ')');
if (next != nullptr)
buf = next + 1;
while (isspace(*buf))
buf++;
esp_log_printf_(LVGL_LOG_LEVEL, TAG, 0, "%.*s", (int) strlen(buf) - 1, buf);
});
#endif
this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0); this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0);
lv_disp_trig_activity(this->disp_); lv_disp_trig_activity(this->disp_);
ESP_LOGCONFIG(TAG, "LVGL Setup complete"); ESP_LOGCONFIG(TAG, "LVGL Setup complete");
} }
void LvglComponent::update() { void LvglComponent::update() {
// update indicators // update indicators
if (this->paused_) { if (this->paused_) {
@ -455,13 +478,6 @@ void LvglComponent::loop() {
} }
lv_timer_handler_run_in_period(5); lv_timer_handler_run_in_period(5);
} }
bool lv_is_pre_initialise() {
if (!lv_is_initialized()) {
ESP_LOGE(TAG, "LVGL call before component is initialised");
return true;
}
return false;
}
#ifdef USE_LVGL_ANIMIMG #ifdef USE_LVGL_ANIMIMG
void lv_animimg_stop(lv_obj_t *obj) { void lv_animimg_stop(lv_obj_t *obj) {

View file

@ -39,7 +39,6 @@ namespace lvgl {
extern lv_event_code_t lv_api_event; // NOLINT extern lv_event_code_t lv_api_event; // NOLINT
extern lv_event_code_t lv_update_event; // NOLINT extern lv_event_code_t lv_update_event; // NOLINT
extern std::string lv_event_code_name_for(uint8_t event_code); extern std::string lv_event_code_name_for(uint8_t event_code);
extern bool lv_is_pre_initialise();
#if LV_COLOR_DEPTH == 16 #if LV_COLOR_DEPTH == 16
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565; static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565;
#elif LV_COLOR_DEPTH == 32 #elif LV_COLOR_DEPTH == 32
@ -108,6 +107,8 @@ class LvglComponent : public PollingComponent {
constexpr static const char *const TAG = "lvgl"; constexpr static const char *const TAG = "lvgl";
public: public:
LvglComponent(std::vector<display::Display *> displays, float buffer_frac, bool full_refresh, int draw_rounding,
bool resume_on_input);
static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
float get_setup_priority() const override { return setup_priority::PROCESSOR; } float get_setup_priority() const override { return setup_priority::PROCESSOR; }
@ -118,13 +119,10 @@ class LvglComponent : public PollingComponent {
this->idle_callbacks_.add(std::move(callback)); this->idle_callbacks_.add(std::move(callback));
} }
void add_on_pause_callback(std::function<void(bool)> &&callback) { this->pause_callbacks_.add(std::move(callback)); } void add_on_pause_callback(std::function<void(bool)> &&callback) { this->pause_callbacks_.add(std::move(callback)); }
void add_display(display::Display *display) { this->displays_.push_back(display); }
void add_init_lambda(const std::function<void(LvglComponent *)> &lamb) { this->init_lambdas_.push_back(lamb); }
void dump_config() override; void dump_config() override;
void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; }
bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; } bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; }
void set_buffer_frac(size_t frac) { this->buffer_frac_ = frac; }
lv_disp_t *get_disp() { return this->disp_; } lv_disp_t *get_disp() { return this->disp_; }
lv_obj_t *get_scr_act() { return lv_disp_get_scr_act(this->disp_); }
// Pause or resume the display. // Pause or resume the display.
// @param paused If true, pause the display. If false, resume the display. // @param paused If true, pause the display. If false, resume the display.
// @param show_snow If true, show the snow effect when paused. // @param show_snow If true, show the snow effect when paused.
@ -155,17 +153,19 @@ class LvglComponent : public PollingComponent {
} }
// rounding factor to align bounds of update area when drawing // rounding factor to align bounds of update area when drawing
size_t draw_rounding{2}; size_t draw_rounding{2};
void set_draw_rounding(size_t rounding) { this->draw_rounding = rounding; }
void set_resume_on_input(bool resume_on_input) { this->resume_on_input_ = resume_on_input; }
// if set to true, the bounds of the update area will always start at 0,0
display::DisplayRotation rotation{display::DISPLAY_ROTATION_0_DEGREES}; display::DisplayRotation rotation{display::DISPLAY_ROTATION_0_DEGREES};
protected: protected:
void write_random_(); void write_random_();
void draw_buffer_(const lv_area_t *area, lv_color_t *ptr); void draw_buffer_(const lv_area_t *area, lv_color_t *ptr);
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
std::vector<display::Display *> displays_{}; std::vector<display::Display *> displays_{};
size_t buffer_frac_{1};
bool full_refresh_{};
bool resume_on_input_{};
lv_disp_draw_buf_t draw_buf_{}; lv_disp_draw_buf_t draw_buf_{};
lv_disp_drv_t disp_drv_{}; lv_disp_drv_t disp_drv_{};
lv_disp_t *disp_{}; lv_disp_t *disp_{};
@ -174,14 +174,10 @@ class LvglComponent : public PollingComponent {
size_t current_page_{0}; size_t current_page_{0};
bool show_snow_{}; bool show_snow_{};
bool page_wrap_{true}; bool page_wrap_{true};
bool resume_on_input_{};
std::map<lv_group_t *, lv_obj_t *> focus_marks_{}; std::map<lv_group_t *, lv_obj_t *> focus_marks_{};
std::vector<std::function<void(LvglComponent *lv_component)>> init_lambdas_;
CallbackManager<void(uint32_t)> idle_callbacks_{}; CallbackManager<void(uint32_t)> idle_callbacks_{};
CallbackManager<void(bool)> pause_callbacks_{}; CallbackManager<void(bool)> pause_callbacks_{};
size_t buffer_frac_{1};
bool full_refresh_{};
lv_color_t *rotate_buf_{}; lv_color_t *rotate_buf_{};
}; };

View file

@ -17,8 +17,6 @@ from .types import lv_lambda_t, lv_obj_t, lv_obj_t_ptr
from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map
from .widgets.obj import obj_spec from .widgets.obj import obj_spec
TOP_LAYER = literal("lv_disp_get_layer_top(lv_component->get_disp())")
async def styles_to_code(config): async def styles_to_code(config):
"""Convert styles to C__ code.""" """Convert styles to C__ code."""
@ -51,9 +49,10 @@ async def theme_to_code(config):
lv_assign(apply, await context.get_lambda()) lv_assign(apply, await context.get_lambda())
async def add_top_layer(config): async def add_top_layer(lv_component, config):
top_layer = lv.disp_get_layer_top(lv_component.get_disp())
if top_conf := config.get(CONF_TOP_LAYER): if top_conf := config.get(CONF_TOP_LAYER):
with LocalVariable("top_layer", lv_obj_t, TOP_LAYER) as top_layer_obj: with LocalVariable("top_layer", lv_obj_t, top_layer) as top_layer_obj:
top_w = Widget(top_layer_obj, obj_spec, top_conf) top_w = Widget(top_layer_obj, obj_spec, top_conf)
await set_obj_properties(top_w, top_conf) await set_obj_properties(top_w, top_conf)
await add_widgets(top_w, top_conf) await add_widgets(top_w, top_conf)

View file

@ -55,18 +55,6 @@ theme_widget_map = {}
styles_used = set() styles_used = set()
class LvScrActType(WidgetType):
"""
A "widget" representing the active screen.
"""
def __init__(self):
super().__init__("lv_scr_act()", lv_obj_t, ())
async def to_code(self, w, config: dict):
return []
class Widget: class Widget:
""" """
Represents a Widget. Represents a Widget.
@ -221,6 +209,25 @@ class Widget:
widget_map: dict[Any, Widget] = {} widget_map: dict[Any, Widget] = {}
class LvScrActType(WidgetType):
"""
A "widget" representing the active screen.
"""
def __init__(self):
super().__init__("lv_scr_act()", lv_obj_t, ())
async def to_code(self, w, config: dict):
return []
lv_scr_act_spec = LvScrActType()
def get_scr_act(lv_comp: MockObj) -> Widget:
return Widget.create(None, lv_comp.get_scr_act(), lv_scr_act_spec, {})
def get_widget_generator(wid): def get_widget_generator(wid):
""" """
Used to wait for a widget during code generation. Used to wait for a widget during code generation.
@ -451,7 +458,3 @@ async def widget_to_code(w_cnfig, w_type: WidgetType, parent):
await set_obj_properties(w, w_cnfig) await set_obj_properties(w, w_cnfig)
await add_widgets(w, w_cnfig) await add_widgets(w, w_cnfig)
await spec.to_code(w, w_cnfig) await spec.to_code(w, w_cnfig)
lv_scr_act_spec = LvScrActType()
lv_scr_act = Widget.create(None, literal("lv_scr_act()"), lv_scr_act_spec, {})

View file

@ -20,6 +20,7 @@ from ..lvcode import (
EVENT_ARG, EVENT_ARG,
LambdaContext, LambdaContext,
LocalVariable, LocalVariable,
lv,
lv_add, lv_add,
lv_assign, lv_assign,
lv_expr, lv_expr,
@ -27,7 +28,6 @@ from ..lvcode import (
lv_Pvariable, lv_Pvariable,
) )
from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
from ..styles import TOP_LAYER
from ..types import LV_EVENT, char_ptr, lv_obj_t from ..types import LV_EVENT, char_ptr, lv_obj_t
from . import Widget, set_obj_properties from . import Widget, set_obj_properties
from .button import button_spec from .button import button_spec
@ -59,7 +59,7 @@ MSGBOX_SCHEMA = container_schema(
) )
async def msgbox_to_code(conf): async def msgbox_to_code(top_layer, conf):
""" """
Construct a message box. This consists of a full-screen translucent background enclosing a centered container 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 with an optional title, body, close button and a button matrix. And any other widgets the user cares to add
@ -101,7 +101,7 @@ async def msgbox_to_code(conf):
text = await lv_text.process(conf[CONF_BODY].get(CONF_TEXT, "")) text = await lv_text.process(conf[CONF_BODY].get(CONF_TEXT, ""))
title = await lv_text.process(conf[CONF_TITLE].get(CONF_TEXT, "")) title = await lv_text.process(conf[CONF_TITLE].get(CONF_TEXT, ""))
close_button = conf[CONF_CLOSE_BUTTON] close_button = conf[CONF_CLOSE_BUTTON]
lv_assign(outer, lv_expr.obj_create(TOP_LAYER)) lv_assign(outer, lv_expr.obj_create(top_layer))
lv_obj.set_width(outer, lv_pct(100)) lv_obj.set_width(outer, lv_pct(100))
lv_obj.set_height(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_opa(outer, 128, 0)
@ -141,6 +141,7 @@ async def msgbox_to_code(conf):
set_btn_data(buttonmatrix.obj, ctrl_list, width_list) set_btn_data(buttonmatrix.obj, ctrl_list, width_list)
async def msgboxes_to_code(config): async def msgboxes_to_code(lv_component, config):
top_layer = lv.disp_get_layer_top(lv_component.get_disp())
for conf in config.get(CONF_MSGBOXES, ()): for conf in config.get(CONF_MSGBOXES, ()):
await msgbox_to_code(conf) await msgbox_to_code(top_layer, conf)

View file

@ -12,12 +12,12 @@ substitutions:
arrow_down: "\U000F004B" arrow_down: "\U000F004B"
lvgl: lvgl:
log_level: debug
resume_on_input: true resume_on_input: true
on_pause: on_pause:
logger.log: LVGL is Paused logger.log: LVGL is Paused
on_resume: on_resume:
logger.log: LVGL has resumed logger.log: LVGL has resumed
log_level: TRACE
bg_color: light_blue bg_color: light_blue
disp_bg_color: color_id disp_bg_color: color_id
disp_bg_image: cat_image disp_bg_image: cat_image
@ -125,6 +125,8 @@ lvgl:
on_unload: on_unload:
- logger.log: page unloaded - logger.log: page unloaded
- lvgl.widget.focus: mark - lvgl.widget.focus: mark
- lvgl.widget.redraw: hello_label
- lvgl.widget.redraw:
on_all_events: on_all_events:
logger.log: logger.log:
format: "Event %s" format: "Event %s"