12 static const char *
const TAG =
"lvgl";
14 static const char *
const EVENT_NAMES[] = {
21 "LONG_PRESSED_REPEAT",
52 "SCREEN_UNLOAD_START",
63 if (event_code <
sizeof(EVENT_NAMES) /
sizeof(EVENT_NAMES[0])) {
64 return EVENT_NAMES[event_code];
69 static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
73 auto *comp =
static_cast<LvglComponent *
>(disp_drv->user_data);
76 area->x1 = area->x1 / draw_rounding * draw_rounding;
77 area->y1 = area->y1 / draw_rounding * draw_rounding;
79 area->x2 = (area->x2 + draw_rounding) / draw_rounding * draw_rounding - 1;
80 area->y2 = (area->y2 + draw_rounding) / draw_rounding * draw_rounding - 1;
86 ESP_LOGCONFIG(TAG,
"LVGL:");
87 ESP_LOGCONFIG(TAG,
" Display width/height: %d x %d", this->
disp_drv_.hor_res, this->disp_drv_.ver_res);
88 ESP_LOGCONFIG(TAG,
" Rotation: %d", this->
rotation);
89 ESP_LOGCONFIG(TAG,
" Draw rounding: %d", (
int) this->
draw_rounding);
94 if (!paused && lv_scr_act() !=
nullptr) {
95 lv_disp_trig_activity(this->
disp_);
96 lv_obj_invalidate(lv_scr_act());
103 lv_update_event =
static_cast<lv_event_code_t
>(lv_event_register_id());
104 lv_api_event =
static_cast<lv_event_code_t
>(lv_event_register_id());
107 lv_obj_add_event_cb(obj, callback, event,
nullptr);
110 lv_event_code_t event2) {
115 lv_event_code_t event2, lv_event_code_t event3) {
121 this->
pages_.push_back(page);
125 if (index >= this->
pages_.size())
147 auto width = lv_area_get_width(area);
148 auto height = lv_area_get_height(area);
154 for (lv_coord_t
x = height;
x-- != 0;) {
155 for (lv_coord_t
y = 0;
y != width;
y++) {
156 dst[
y * height +
x] = *ptr++;
160 x1 = this->
disp_drv_.ver_res - area->y1 - height;
162 height = lv_area_get_width(area);
166 for (lv_coord_t
y = height;
y-- != 0;) {
167 for (lv_coord_t
x = width;
x-- != 0;) {
168 dst[
y * width +
x] = *ptr++;
171 x1 = this->
disp_drv_.hor_res - x1 - width;
172 y1 = this->
disp_drv_.ver_res - y1 - height;
176 for (lv_coord_t
x = 0;
x != height;
x++) {
177 for (lv_coord_t
y = width;
y-- != 0;) {
178 dst[
y * height +
x] = *ptr++;
182 y1 = this->
disp_drv_.hor_res - area->x1 - width;
184 height = lv_area_get_width(area);
192 ESP_LOGV(TAG,
"draw buffer x1=%d, y1=%d, width=%d, height=%d", x1, y1, width, height);
202 ESP_LOGVV(TAG,
"flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area),
203 lv_area_get_height(area), (
int) (
millis() - now));
205 lv_disp_flush_ready(disp_drv);
225 #ifdef USE_LVGL_TOUCHSCREEN 227 this->set_parent(parent);
228 lv_indev_drv_init(&this->drv_);
229 this->drv_.disp = parent->
get_disp();
230 this->drv_.long_press_repeat_time = long_press_repeat_time;
231 this->drv_.long_press_time = long_press_time;
232 this->drv_.type = LV_INDEV_TYPE_POINTER;
233 this->drv_.user_data =
this;
234 this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
236 if (
l->touch_pressed_) {
237 data->point.x =
l->touch_point_.x;
238 data->point.y =
l->touch_point_.y;
239 data->state = LV_INDEV_STATE_PRESSED;
241 data->state = LV_INDEV_STATE_RELEASED;
247 this->touch_pressed_ = !this->parent_->is_paused() && !tpoints.empty();
248 if (this->touch_pressed_)
249 this->touch_point_ = tpoints[0];
251 #endif // USE_LVGL_TOUCHSCREEN 253 #ifdef USE_LVGL_KEY_LISTENER 255 lv_indev_drv_init(&this->drv_);
256 this->drv_.type =
type;
257 this->drv_.user_data =
this;
258 this->drv_.long_press_time = lpt;
259 this->drv_.long_press_repeat_time = lprt;
260 this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
262 data->state =
l->pressed_ ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
264 data->enc_diff = (int16_t) (
l->count_ -
l->last_count_);
265 l->last_count_ =
l->count_;
266 data->continue_reading =
false;
269 #endif // USE_LVGL_KEY_LISTENER 271 #if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER) 273 auto selected = this->get_selected_index();
274 if (selected >= this->options_.size())
276 return this->options_[selected];
279 static std::string join_string(std::vector<std::string>
options) {
280 return std::accumulate(
281 options.begin(), options.end(), std::string(),
282 [](
const std::string &a,
const std::string &b) -> std::string {
return a + (!a.empty() ?
"\n" :
"") + b; });
286 auto index = std::find(this->options_.begin(), this->options_.end(), text);
287 if (index != this->options_.end()) {
288 this->set_selected_index(index - this->options_.begin(), anim);
289 lv_event_send(this->obj, lv_api_event,
nullptr);
294 auto index = this->get_selected_index();
295 if (index >= options.size())
296 index = options.size() - 1;
297 this->options_ = std::move(options);
298 this->set_option_string(join_string(this->options_).c_str());
299 lv_event_send(this->obj, LV_EVENT_REFRESH,
nullptr);
300 this->set_selected_index(index, LV_ANIM_OFF);
302 #endif // USE_LVGL_DROPDOWN || LV_USE_ROLLER 304 #ifdef USE_LVGL_BUTTONMATRIX 309 [](lv_event_t *event) {
311 if (self->key_callback_.size() == 0)
313 auto key_idx = lv_btnmatrix_get_selected_btn(self->obj);
314 if (key_idx == LV_BTNMATRIX_BTN_NONE)
316 if (self->key_map_.count(key_idx) != 0) {
317 self->send_key_(self->key_map_[key_idx]);
320 const auto *str = lv_btnmatrix_get_btn_text(self->obj, key_idx);
321 auto len = strlen(str);
323 self->send_key_(*str++);
325 LV_EVENT_PRESSED,
this);
327 #endif // USE_LVGL_BUTTONMATRIX 329 #ifdef USE_LVGL_KEYBOARD 330 static const char *
const KB_SPECIAL_KEYS[] = {
339 [](lv_event_t *event) {
341 if (self->key_callback_.size() == 0)
344 auto key_idx = lv_btnmatrix_get_selected_btn(self->obj);
345 if (key_idx == LV_BTNMATRIX_BTN_NONE)
347 const char *txt = lv_btnmatrix_get_btn_text(self->obj, key_idx);
350 for (
const auto *kb_special_key : KB_SPECIAL_KEYS) {
351 if (strcmp(txt, kb_special_key) == 0)
355 self->send_key_(*txt++);
357 LV_EVENT_PRESSED,
this);
359 #endif // USE_LVGL_KEYBOARD 362 int iterations = 6 - lv_disp_get_inactive_time(this->disp_) / 60000;
365 while (iterations-- != 0) {
367 col = col / this->draw_rounding * this->draw_rounding;
369 row = row / this->draw_rounding * this->draw_rounding;
370 auto size = (
random_uint32() % 32) / this->draw_rounding * this->draw_rounding - 1;
374 area.x2 = col + size;
375 area.y2 = row + size;
376 if (area.x2 >= this->disp_drv_.hor_res)
377 area.x2 = this->disp_drv_.hor_res - 1;
378 if (area.y2 >= this->disp_drv_.ver_res)
379 area.y2 = this->disp_drv_.ver_res - 1;
381 size_t line_len = lv_area_get_width(&area) * lv_area_get_height(&area) / 2;
382 for (
size_t i = 0; i != line_len; i++) {
385 this->draw_buffer_(&area, (lv_color_t *) this->draw_buf_.buf1);
410 int draw_rounding,
bool resume_on_input)
411 : draw_rounding(draw_rounding),
412 displays_(
std::move(displays)),
413 buffer_frac_(buffer_frac),
414 full_refresh_(full_refresh),
415 resume_on_input_(resume_on_input) {
417 size_t buffer_pixels = display->get_width() * display->get_height() / this->
buffer_frac_;
418 auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
419 this->
rotation = display->get_rotation();
428 lv_disp_draw_buf_init(&this->
draw_buf_, buf,
nullptr, buffer_pixels);
435 this->
disp_drv_.hor_res = (lv_coord_t) display->get_width();
436 this->
disp_drv_.ver_res = (lv_coord_t) display->get_height();
446 ESP_LOGCONFIG(TAG,
"LVGL Setup starts");
448 lv_log_register_print_cb([](
const char *buf) {
449 auto next = strchr(buf,
')');
452 while (isspace(*buf))
454 esp_log_printf_(LVGL_LOG_LEVEL, TAG, 0,
"%.*s", (
int) strlen(buf) - 1, buf);
460 this->
show_page(0, LV_SCR_LOAD_ANIM_NONE, 0);
461 lv_disp_trig_activity(this->
disp_);
462 ESP_LOGCONFIG(TAG,
"LVGL Setup complete");
477 lv_timer_handler_run_in_period(5);
480 #ifdef USE_LVGL_ANIMIMG 482 auto *animg = (lv_animimg_t *) obj;
483 int32_t duration = animg->anim.time;
484 lv_animimg_set_duration(obj, 0);
485 lv_animimg_start(obj);
486 lv_animimg_set_duration(obj, duration);
497 #if defined(USE_HOST) || defined(USE_RP2040) || defined(USE_ESP8266) 499 auto *ptr = malloc(size);
500 if (ptr ==
nullptr) {
501 #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR 502 esphome::ESP_LOGE(esphome::lvgl::TAG,
"Failed to allocate %zu bytes", size);
510 static unsigned cap_bits = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
514 ptr = heap_caps_malloc(size, cap_bits);
515 if (ptr ==
nullptr) {
516 cap_bits = MALLOC_CAP_8BIT;
517 ptr = heap_caps_malloc(size, cap_bits);
519 if (ptr ==
nullptr) {
520 #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR 521 esphome::ESP_LOGE(esphome::lvgl::TAG,
"Failed to allocate %zu bytes", size);
525 #ifdef ESPHOME_LOG_HAS_VERBOSE 526 esphome::ESP_LOGV(esphome::lvgl::TAG,
"allocate %zu - > %p", size, ptr);
532 #ifdef ESPHOME_LOG_HAS_VERBOSE 533 esphome::ESP_LOGV(esphome::lvgl::TAG,
"free %p", ptr);
541 #ifdef ESPHOME_LOG_HAS_VERBOSE 542 esphome::ESP_LOGV(esphome::lvgl::TAG,
"realloc %p: %zu", ptr, size);
544 return heap_caps_realloc(ptr, size, cap_bits);
static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event)
std::string lv_event_code_name_for(uint8_t event_code)
void add_on_idle_callback(std::function< void(uint32_t)> &&callback)
void set_paused(bool paused, bool show_snow)
std::vector< TouchPoint > TouchPoints_t
void * lv_custom_mem_alloc(size_t size)
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
CallbackManager< void(bool)> pause_callbacks_
Component for rendering LVGL.
void dump_config() override
LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt)
void draw_buffer_(const lv_area_t *area, lv_color_t *ptr)
void(_lv_event_t *) event_callback_t
LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent)
uint32_t IRAM_ATTR HOT millis()
std::string get_selected_text()
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
void set_selected_text(const std::string &text, lv_anim_enable_t anim)
TemplatableValue< bool > paused_
void lv_custom_mem_free(void *ptr)
lv_event_code_t lv_update_event
lv_event_code_t lv_api_event
void update(const touchscreen::TouchPoints_t &tpoints) override
virtual void set_obj(lv_obj_t *lv_obj)
void status_set_error(const char *message="unspecified")
LvglComponent(std::vector< display::Display *> displays, float buffer_frac, bool full_refresh, int draw_rounding, bool resume_on_input)
void add_on_pause_callback(std::function< void(bool)> &&callback)
std::string str_sprintf(const char *fmt,...)
static void esphome_lvgl_init()
Initialize the LVGL library and register custom events.
void add_page(LvPageType *page)
void set_obj(lv_obj_t *lv_obj) override
static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
PauseTrigger(LvglComponent *parent, TemplatableValue< bool > paused)
display::DisplayRotation rotation
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
IdleTrigger(LvglComponent *parent, TemplatableValue< uint32_t > timeout)
TemplatableValue< uint32_t > timeout_
void set_options(std::vector< std::string > options)
std::vector< LvPageType * > pages_
lv_disp_draw_buf_t draw_buf_
virtual void mark_failed()
Mark this component as failed.
void * lv_custom_mem_realloc(void *ptr, size_t size)
void show_next_page(lv_scr_load_anim_t anim, uint32_t time)
Implementation of SPI Controller mode.
CallbackManager< void(uint32_t)> idle_callbacks_
void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time)
void show_prev_page(lv_scr_load_anim_t anim, uint32_t time)
void lv_animimg_stop(lv_obj_t *obj)
std::vector< display::Display * > displays_