#pragma once #include "esphome/core/defines.h" #ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" #endif // USE_BINARY_SENSOR #ifdef USE_IMAGE #include "esphome/components/image/image.h" #endif // USE_LVGL_IMAGE #ifdef USE_LVGL_ROTARY_ENCODER #include "esphome/components/rotary_encoder/rotary_encoder.h" #endif // USE_LVGL_ROTARY_ENCODER // required for clang-tidy #ifndef LV_CONF_H #define LV_CONF_SKIP 1 // NOLINT #endif // LV_CONF_H #include "esphome/components/display/display.h" #include "esphome/components/display/display_color_utils.h" #include "esphome/core/component.h" #include #include #include #include #include #ifdef USE_ESP32_VARIANT_ESP32P4 #include "driver/ppa.h" #endif #ifdef USE_FONT #include "esphome/components/font/font.h" #endif // USE_LVGL_FONT #ifdef USE_TOUCHSCREEN #include "esphome/components/touchscreen/touchscreen.h" #endif // USE_LVGL_TOUCHSCREEN #if defined(USE_LVGL_BUTTONMATRIX) || defined(USE_LVGL_KEYBOARD) #include "esphome/components/key_provider/key_provider.h" #endif // USE_LVGL_BUTTONMATRIX namespace esphome::lvgl { #if LV_COLOR_DEPTH == 16 using lv_color_data = uint16_t; #endif #if LV_COLOR_DEPTH == 32 using lv_color_data = uint32_t; #endif extern lv_event_code_t lv_api_event; // NOLINT extern lv_event_code_t lv_update_event; // NOLINT extern std::string lv_event_code_name_for(lv_event_t *event); lv_obj_t *lv_container_create(lv_obj_t *parent); #ifdef USE_LVGL_SCALE void lv_scale_draw_event_cb(lv_event_t *e, int16_t range_start, int16_t range_end, lv_color_t color_start, lv_color_t color_end, int width, bool local); #endif #if LV_COLOR_DEPTH == 16 static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565; #elif LV_COLOR_DEPTH == 32 static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_888; #else // LV_COLOR_DEPTH static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_332; #endif // LV_COLOR_DEPTH #if defined(USE_FONT) && defined(USE_LVGL_FONT) inline void lv_obj_set_style_text_font(lv_obj_t *obj, const font::Font *font, lv_style_selector_t part) { lv_obj_set_style_text_font(obj, font->get_lv_font(), part); } inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) { lv_style_set_text_font(style, font->get_lv_font()); } #endif #if defined(USE_LVGL_IMAGE) && defined(USE_IMAGE) // Shortcut / overload, so that the source of an image can easily be updated // from within a lambda. inline void lv_image_set_src(lv_obj_t *obj, image::Image *image) { lv_image_set_src(obj, image->get_lv_image_dsc()); } inline void lv_obj_set_style_bitmap_mask_src(lv_obj_t *obj, image::Image *image, lv_style_selector_t selector) { lv_obj_set_style_bitmap_mask_src(obj, image->get_lv_image_dsc(), selector); } inline void lv_obj_set_style_bg_image_src(lv_obj_t *obj, image::Image *image, lv_style_selector_t selector) { lv_obj_set_style_bg_image_src(obj, image->get_lv_image_dsc(), selector); } #endif // USE_LVGL_IMAGE #ifdef USE_LVGL_ANIMIMG inline void lv_animimg_set_src(lv_obj_t *img, std::vector images) { auto *dsc = static_cast *>(lv_obj_get_user_data(img)); if (dsc == nullptr) { // object will be lazily allocated but never freed. dsc = new std::vector(images.size()); // NOLINT lv_obj_set_user_data(img, dsc); } dsc->clear(); for (auto &image : images) { dsc->push_back(image->get_lv_image_dsc()); } lv_animimg_set_src(img, (const void **) dsc->data(), dsc->size()); } #endif // USE_LVGL_ANIMIMG #ifdef USE_LVGL_METER int16_t lv_get_needle_angle_for_value(lv_obj_t *obj, int value); #endif // Parent class for things that wrap an LVGL object class LvCompound { public: virtual ~LvCompound() = default; virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; } lv_obj_t *obj{}; }; class LvglComponent; class LvPageType : public Parented { public: LvPageType(bool skip) : skip(skip) {} void setup(size_t index) { this->index = index; this->obj = lv_obj_create(nullptr); } bool is_showing() const; lv_obj_t *obj{}; size_t index{}; bool skip; }; using event_callback_t = void(lv_event_t *); class LvLambdaComponent : public Component { public: LvLambdaComponent(void (*callback)()) : callback_(callback) {} void setup() override { this->callback_(); } // execute after the LvglComponent is setup float get_setup_priority() const override { return setup_priority::PROCESSOR - 5; } protected: void (*callback_)(); }; template class ObjUpdateAction : public Action { public: explicit ObjUpdateAction(std::function &&lamb) : lamb_(std::move(lamb)) {} void play(const Ts &...x) override { this->lamb_(x...); } protected: std::function lamb_; }; #ifdef USE_LVGL_ANIMIMG void lv_animimg_stop(lv_obj_t *obj); #endif // USE_LVGL_ANIMIMG enum RotationType : uint8_t { ROTATION_UNUSED, ROTATION_SOFTWARE, ROTATION_HARDWARE, }; class LvglComponent : public PollingComponent { constexpr static const char *const TAG = "lvgl"; public: LvglComponent(std::vector displays, float buffer_frac, bool full_refresh, int draw_rounding, bool resume_on_input, bool update_when_display_idle, RotationType rotation_type); static void static_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p); float get_setup_priority() const override { return setup_priority::PROCESSOR; } void setup() override; void update() override; void loop() override; template void add_on_idle_callback(F &&callback) { this->idle_callbacks_.add(std::forward(callback)); } static void render_end_cb(lv_event_t *event); static void render_start_cb(lv_event_t *event); void dump_config() override; lv_display_t *get_disp() { return this->disp_; } lv_obj_t *get_screen_active() { return lv_display_get_screen_active(this->disp_); } // Pause or 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. void set_paused(bool paused, bool show_snow); // Returns true if the display is explicitly paused, or a blocking display update is in progress. bool is_paused() const; // If the display is paused and we have resume_on_input_ set to true, resume the display. void maybe_wakeup() { if (this->paused_ && this->resume_on_input_) { this->set_paused(false, false); } } /** * Initialize the LVGL library and register custom events. */ static void esphome_lvgl_init(); static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event); static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2); static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2, lv_event_code_t event3); void add_page(LvPageType *page); void show_page(size_t index, lv_screen_load_anim_t anim, uint32_t time); void show_next_page(lv_screen_load_anim_t anim, uint32_t time); void show_prev_page(lv_screen_load_anim_t anim, uint32_t time); void set_page_wrap(bool wrap) { this->page_wrap_ = wrap; } void set_big_endian(bool big_endian) { this->big_endian_ = big_endian; } size_t get_current_page() const; void set_focus_mark(lv_group_t *group) { this->focus_marks_[group] = lv_group_get_focused(group); } void restore_focus_mark(lv_group_t *group) { auto *mark = this->focus_marks_[group]; if (mark != nullptr) { lv_group_focus_obj(mark); } } // rounding factor to align bounds of update area when drawing size_t draw_rounding{2}; void set_pause_trigger(Trigger<> *trigger) { this->pause_callback_ = trigger; } void set_resume_trigger(Trigger<> *trigger) { this->resume_callback_ = trigger; } void set_draw_start_trigger(Trigger<> *trigger) { this->draw_start_callback_ = trigger; } void set_draw_end_trigger(Trigger<> *trigger) { this->draw_end_callback_ = trigger; } void set_rotation(display::DisplayRotation rotation); display::DisplayRotation get_rotation() const { return this->rotation_; } void rotate_coordinates(int32_t &x, int32_t &y) const; uint16_t get_width() const { return lv_display_get_horizontal_resolution(this->disp_); } uint16_t get_height() const { return lv_display_get_vertical_resolution(this->disp_); } protected: void set_resolution_() const; void draw_end_(); // Not checking for non-null callback since the // LVGL callback that calls it is not set in that case void draw_start_() const { this->draw_start_callback_->trigger(); } void write_random_(); void draw_buffer_(const lv_area_t *area, lv_color_data *ptr); #ifdef USE_ESP32_VARIANT_ESP32P4 bool ppa_rotate_(const lv_color_data *src, lv_color_data *dst, uint16_t width, uint16_t height, uint32_t height_rounded); #endif void flush_cb_(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p); std::vector displays_{}; size_t buffer_frac_{1}; bool full_refresh_{}; bool resume_on_input_{}; bool update_when_display_idle_{}; uint8_t *draw_buf_{}; lv_display_t *disp_{}; uint16_t width_{}; uint16_t height_{}; bool paused_{}; std::vector pages_{}; size_t current_page_{0}; bool show_snow_{}; bool page_wrap_{true}; bool big_endian_{}; std::map focus_marks_{}; CallbackManager idle_callbacks_{}; Trigger<> *pause_callback_{}; Trigger<> *resume_callback_{}; Trigger<> *draw_start_callback_{}; Trigger<> *draw_end_callback_{}; void *rotate_buf_{}; display::DisplayRotation rotation_{display::DISPLAY_ROTATION_0_DEGREES}; RotationType rotation_type_; #ifdef USE_ESP32_VARIANT_ESP32P4 ppa_client_handle_t ppa_client_{}; #endif }; class IdleTrigger : public Trigger<> { public: explicit IdleTrigger(LvglComponent *parent, TemplatableFn timeout); protected: TemplatableFn timeout_; bool is_idle_{}; }; template class LvglAction : public Action, public Parented { public: explicit LvglAction(std::function &&lamb) : action_(std::move(lamb)) {} void play(const Ts &...x) override { this->action_(this->parent_); } protected: std::function action_{}; }; template class LvglCondition : public Condition, public Parented { public: LvglCondition(std::function &&condition_lambda) : condition_lambda_(std::move(condition_lambda)) {} bool check(const Ts &...x) override { return this->condition_lambda_(this->parent_); } protected: std::function condition_lambda_{}; }; #ifdef USE_LVGL_TOUCHSCREEN class LVTouchListener : public touchscreen::TouchListener, public Parented { public: LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent); void update(const touchscreen::TouchPoints_t &tpoints) override; void release() override { touch_pressed_ = false; this->parent_->maybe_wakeup(); } lv_indev_t *get_drv() { return this->drv_; } protected: lv_indev_t *drv_{}; touchscreen::TouchPoint touch_point_{}; bool touch_pressed_{}; }; #endif // USE_LVGL_TOUCHSCREEN #ifdef USE_LVGL_METER class IndicatorLine : public LvCompound { public: IndicatorLine() = default; void set_obj(lv_obj_t *lv_obj) override; void set_value(int value); private: void update_length_(); int16_t angle_{}; lv_point_precise_t points_[2]{}; }; #endif #ifdef USE_LVGL_KEY_LISTENER class LVEncoderListener : public Parented { public: LVEncoderListener(lv_indev_type_t type, uint16_t long_press_time, uint16_t long_press_repeat_time); #ifdef USE_BINARY_SENSOR void add_button(binary_sensor::BinarySensor *button, lv_key_t key) { button->add_on_state_callback([this, key](bool state) { this->event(key, state); }); } #endif #ifdef USE_LVGL_ROTARY_ENCODER void set_sensor(rotary_encoder::RotaryEncoderSensor *sensor) { sensor->register_listener([this](int32_t count) { this->set_count(count); }); } #endif // USE_LVGL_ROTARY_ENCODER void event(int key, bool pressed) { if (!this->parent_->is_paused()) { this->pressed_ = pressed; this->key_ = key; } else if (!pressed) { // maybe wakeup on release if paused this->parent_->maybe_wakeup(); } } void set_count(int32_t count) { if (!this->parent_->is_paused()) { this->count_ = count; } else { this->parent_->maybe_wakeup(); } } lv_indev_t *get_drv() { return this->drv_; } protected: lv_indev_t *drv_{}; bool pressed_{}; int32_t count_{}; int32_t last_count_{}; int key_{}; }; #endif // USE_LVGL_KEY_LISTENER #ifdef USE_LVGL_LINE class LvLineType : public LvCompound { public: void set_points(FixedVector points) { this->points_ = std::move(points); lv_line_set_points(this->obj, this->points_.begin(), this->points_.size()); } protected: FixedVector points_{}; }; #endif #if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER) class LvSelectable : public LvCompound { public: virtual size_t get_selected_index() = 0; virtual void set_selected_index(size_t index, lv_anim_enable_t anim) = 0; void set_selected_text(const std::string &text, lv_anim_enable_t anim); std::string get_selected_text(); const std::vector &get_options() { return this->options_; } void set_options(std::vector options); protected: virtual void set_option_string(const char *options) = 0; std::vector options_{}; }; #ifdef USE_LVGL_DROPDOWN class LvDropdownType : public LvSelectable { public: size_t get_selected_index() override { return lv_dropdown_get_selected(this->obj); } void set_selected_index(size_t index, lv_anim_enable_t anim) override { lv_dropdown_set_selected(this->obj, index); } protected: void set_option_string(const char *options) override { lv_dropdown_set_options(this->obj, options); } }; #endif // USE_LVGL_DROPDOWN #ifdef USE_LVGL_ROLLER class LvRollerType : public LvSelectable { public: size_t get_selected_index() override { return lv_roller_get_selected(this->obj); } void set_selected_index(size_t index, lv_anim_enable_t anim) override { lv_roller_set_selected(this->obj, index, anim); } void set_mode(lv_roller_mode_t mode) { this->mode_ = mode; } protected: void set_option_string(const char *options) override { lv_roller_set_options(this->obj, options, this->mode_); } lv_roller_mode_t mode_{LV_ROLLER_MODE_NORMAL}; }; #endif #endif // defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER) #ifdef USE_LVGL_BUTTONMATRIX class LvButtonMatrixType : public key_provider::KeyProvider, public LvCompound { public: void set_obj(lv_obj_t *lv_obj) override; uint16_t get_selected() { return lv_buttonmatrix_get_selected_button(this->obj); } void set_key(size_t idx, uint8_t key) { this->key_map_[idx] = key; } protected: std::map key_map_{}; }; #endif // USE_LVGL_BUTTONMATRIX #ifdef USE_LVGL_KEYBOARD class LvKeyboardType : public key_provider::KeyProvider, public LvCompound { public: void set_obj(lv_obj_t *lv_obj) override; }; #endif // USE_LVGL_KEYBOARD } // namespace esphome::lvgl