ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
graphical_display_menu.cpp
Go to the documentation of this file.
2#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5#include <cstdlib>
7
9
10ESPHOME_LOG_TAG(TAG, "graphical_display_menu");
11
13 if (this->display_ != nullptr) {
14 display::display_writer_t writer = [this](display::Display &it) { this->draw_menu(); };
15 this->display_page_ = make_unique<display::DisplayPage>(writer);
16 }
17
18 if (!this->menu_item_value_.has_value()) {
19 this->menu_item_value_ = [](const MenuItemValueArguments *it) {
20 std::string label = " ";
21 if (it->is_item_selected && it->is_menu_editing) {
22 label.append(">");
23 label.append(it->item->get_value_text());
24 label.append("<");
25 } else {
26 label.append("(");
27 label.append(it->item->get_value_text());
28 label.append(")");
29 }
30 return label;
31 };
32 }
33
35}
36
38 ESP_LOGCONFIG(
39 TAG,
40 "Graphical Display Menu\n"
41 " Has Display: %s\n"
42 " Popup Mode: %s\n"
43 " Advanced Drawing Mode: %s\n"
44 " Has Font: %s\n"
45 " Mode: %s\n"
46 " Active: %s\n"
47 " Menu items:",
48 YESNO(this->display_ != nullptr), YESNO(this->display_ != nullptr), YESNO(this->display_ == nullptr),
49 YESNO(this->font_ != nullptr),
50 this->mode_ == display_menu_base::MENU_MODE_ROTARY ? LOG_STR_LITERAL("Rotary") : LOG_STR_LITERAL("Joystick"),
51 YESNO(this->active_));
52 for (size_t i = 0; i < this->displayed_item_->items_size(); i++) {
53 auto *item = this->displayed_item_->get_item(i);
54 ESP_LOGCONFIG(TAG, " %i: %s (Type: %s, Immediate Edit: %s)", i, item->get_text().c_str(),
55 LOG_STR_ARG(display_menu_base::menu_item_type_to_string(item->get_type())),
56 YESNO(item->get_immediate_edit()));
57 }
58}
59
60void GraphicalDisplayMenu::set_foreground_color(Color foreground_color) { this->foreground_color_ = foreground_color; }
61void GraphicalDisplayMenu::set_background_color(Color background_color) { this->background_color_ = background_color; }
62
64 if (this->display_ != nullptr) {
66 this->display_->show_page(this->display_page_.get());
67 this->display_->clear();
68 } else {
69 this->update();
70 }
71}
72
74 if (this->previous_display_page_ != nullptr) {
76 this->display_->clear();
77 this->update();
78 this->previous_display_page_ = nullptr;
79 } else {
80 this->update();
81 }
82}
83
85 this->update();
86
87 // If we're in advanced drawing mode we won't have a display and will instead require the update callback to do
88 // our drawing
89 if (this->display_ != nullptr) {
90 draw_menu();
91 }
92}
93
95 if (this->display_ == nullptr) {
96 ESP_LOGE(TAG, "draw_menu() called without a display_. This is only available when using the menu in pop up mode");
97 return;
98 }
99 display::Rect bounds(0, 0, this->display_->get_width(), this->display_->get_height());
100 this->draw_menu_internal_(this->display_, &bounds);
101}
102
104 this->draw_menu_internal_(display, bounds);
105}
106
108 int16_t total_height = 0;
109 int16_t max_width = 0;
110 int y_padding = 2;
111 bool scroll_menu_items = false;
112 std::vector<display::Rect> menu_dimensions;
113 int number_items_fit_to_screen = 0;
114 const int max_item_index = this->displayed_item_->items_size() - 1;
115
116 for (size_t i = 0; max_item_index >= 0 && i <= static_cast<size_t>(max_item_index); i++) {
117 const auto *item = this->displayed_item_->get_item(i);
118 const bool selected = i == this->cursor_index_;
119 const display::Rect item_dimensions = this->measure_item_(display, item, bounds, selected);
120
121 menu_dimensions.push_back(item_dimensions);
122 total_height += item_dimensions.h + (i == 0 ? 0 : y_padding);
123 max_width = std::max(max_width, item_dimensions.w);
124
125 if (total_height <= bounds->h) {
126 number_items_fit_to_screen++;
127 } else {
128 // Scroll the display if the selected item or the item immediately after it overflows
129 if ((selected) || (i == this->cursor_index_ + 1)) {
130 scroll_menu_items = true;
131 }
132 }
133 }
134
135 // Determine what items to draw
136 int first_item_index = 0;
137 int last_item_index = max_item_index;
138
139 if (number_items_fit_to_screen <= 1) {
140 // If only one item can fit to the bounds draw the current cursor item
141 last_item_index = std::min(last_item_index, this->cursor_index_ + 1);
142 first_item_index = this->cursor_index_;
143 } else {
144 if (scroll_menu_items) {
145 // Attempt to draw the item after the current item (+1 for equality check in the draw loop)
146 last_item_index = std::min(last_item_index, this->cursor_index_ + 1);
147
148 // Go back through the measurements to determine how many prior items we can fit
149 int height_left_to_use = bounds->h;
150 for (int i = last_item_index; i >= 0; i--) {
151 const display::Rect item_dimensions = menu_dimensions[i];
152 height_left_to_use -= (item_dimensions.h + y_padding);
153
154 if (height_left_to_use <= 0) {
155 // Ran out of space - this is our first item to draw
156 first_item_index = i;
157 break;
158 }
159 }
160 const int items_to_draw = last_item_index - first_item_index;
161 // Dont't draw last item partially if it is the selected item
162 if ((this->cursor_index_ == last_item_index) && (number_items_fit_to_screen <= items_to_draw) &&
163 (first_item_index < max_item_index)) {
164 first_item_index++;
165 }
166 }
167 }
168
169 // Render the items into the view port
170 display->start_clipping(*bounds);
171
172 display->filled_rectangle(bounds->x, bounds->y, max_width, total_height, this->background_color_);
173 auto y_offset = bounds->y;
174 for (size_t i = static_cast<size_t>(first_item_index);
175 last_item_index >= 0 && i <= static_cast<size_t>(last_item_index); i++) {
176 const auto *item = this->displayed_item_->get_item(i);
177 const bool selected = i == this->cursor_index_;
178 display::Rect dimensions = menu_dimensions[i];
179
180 dimensions.y = y_offset;
181 dimensions.x = bounds->x;
182 this->draw_item_(display, item, &dimensions, selected);
183
184 y_offset += dimensions.h + y_padding;
185 }
186
187 display->end_clipping();
188}
189
191 const display::Rect *bounds, const bool selected) {
192 display::Rect dimensions(0, 0, 0, 0);
193
194 if (selected) {
195 // TODO: Support selection glyph
196 dimensions.w += 0;
197 dimensions.h += 0;
198 }
199
200 std::string label = item->get_text();
201 if (item->has_value()) {
202 // Append to label
203 MenuItemValueArguments args(item, selected, this->editing_);
204 label.append(this->menu_item_value_.value(&args));
205 }
206
207 int x1;
208 int y1;
209 int width;
210 int height;
211 display->get_text_bounds(0, 0, label.c_str(), this->font_, display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);
212
213 dimensions.w = std::min((int16_t) width, bounds->w);
214 dimensions.h = std::min((int16_t) height, bounds->h);
215
216 return dimensions;
217}
218
220 const display::Rect *bounds, const bool selected) {
221 const auto background_color = selected ? this->foreground_color_ : this->background_color_;
222 const auto foreground_color = selected ? this->background_color_ : this->foreground_color_;
223
224 // int background_width = std::max(bounds->width, available_width);
225 int background_width = bounds->w;
226
227 display->filled_rectangle(bounds->x, bounds->y, background_width, bounds->h, background_color);
228
229 std::string label = item->get_text();
230 if (item->has_value()) {
231 MenuItemValueArguments args(item, selected, this->editing_);
232 label.append(this->menu_item_value_.value(&args));
233 }
234
235 display->print(bounds->x, bounds->y, this->font_, foreground_color, display::TextAlign::TOP_LEFT, label.c_str(),
236 background_color);
237}
238
239void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, const uint8_t row, const bool selected) {
240 ESP_LOGE(TAG, "draw_item(MenuItem *item, uint8_t row, bool selected) called. The graphical_display_menu specific "
241 "draw_item should be called.");
242}
243
245
246} // namespace esphome::graphical_display_menu
uint8_t h
Definition bl0906.h:2
virtual void setup()
Where the component's initialization should happen.
Definition component.cpp:84
void show_page(DisplayPage *page)
Definition display.cpp:745
virtual void clear()
Clear the entire screen by filling it with OFF pixels.
Definition display.cpp:17
void end_clipping()
Reset the invalidation region.
Definition display.cpp:802
void start_clipping(Rect rect)
Set the clipping rectangle for further drawing.
Definition display.cpp:794
const DisplayPage * get_active_page() const
Definition display.h:737
virtual int get_width()
Get the calculated width of the display in pixels with rotation applied.
Definition display.h:336
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text, Color background=COLOR_OFF)
Print text with the anchor point at [x,y] with font.
Definition display.cpp:572
void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width, int *height)
Get the text bounds of the given string.
Definition display.cpp:640
void filled_rectangle(int x1, int y1, int width, int height, Color color=COLOR_ON)
Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,...
Definition display.cpp:173
int16_t x
X coordinate of corner.
Definition rect.h:11
int16_t h
Height of region.
Definition rect.h:14
int16_t y
Y coordinate of corner.
Definition rect.h:12
int16_t w
Width of region.
Definition rect.h:13
virtual bool has_value() const
Definition menu_item.h:52
std::unique_ptr< display::DisplayPage > display_page_
display::Rect measure_item_(display::Display *display, const display_menu_base::MenuItem *item, const display::Rect *bounds, bool selected)
TemplatableValue< std::string, const MenuItemValueArguments * > menu_item_value_
LazyCallbackManager< void(const GraphicalDisplayMenu *)> on_redraw_callbacks_
void draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) override
void draw_item_(display::Display *display, const display_menu_base::MenuItem *item, const display::Rect *bounds, bool selected)
void draw_menu_internal_(display::Display *display, const display::Rect *bounds)
const LogString * menu_item_type_to_string(MenuItemType type)
Returns a string representation of a menu item type suitable for logging.
Definition menu_item.cpp:7
ESPHOME_LOG_TAG(TAG, "graphical_display_menu")