LCDGFX LCD display driver  1.2.0
Lightweight graphics library for SSD1306, SSD1325, SSD1327, SSD1331, SSD1351, SH1106, SH1107, IL9163, ST7735, ST7789, ILI9341, PCD8544 displays over I2C/SPI
checkbox_menu.h
Go to the documentation of this file.
1 /*
2  MIT License
3 
4  Copyright (c) 2020,2022,2025 Alexey Dynda
5 
6  Permission is hereby granted, free of charge, to any person obtaining a copy
7  of this software and associated documentation files (the "Software"), to deal
8  in the Software without restriction, including without limitation the rights
9  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  copies of the Software, and to permit persons to whom the Software is
11  furnished to do so, subject to the following conditions:
12 
13  The above copyright notice and this permission notice shall be included in all
14  copies or substantial portions of the Software.
15 
16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  SOFTWARE.
23 */
28 #ifndef _LCDGFX_CHECKBOX_MENU_H_
29 #define _LCDGFX_CHECKBOX_MENU_H_
30 
31 #include "nano_gfx_types.h"
32 #include "canvas/point.h"
33 #include "canvas/rect.h"
34 #include "canvas/font.h"
35 
36 #ifndef lcd_gfx_min
37 #define lcd_gfx_min(x, y) ((x) < (y) ? (x) : (y))
38 #endif
39 
40 #ifndef lcd_gfx_max
41 #define lcd_gfx_max(x, y) ((x) > (y) ? (x) : (y))
42 #endif
43 
70 {
71 public:
79  LcdGfxCheckboxMenu(const char **items, uint8_t count, const NanoRect &rect = {});
80 
94  template <typename D> void show(D &d);
95 
99  template <typename D> void update(D &d) { show(d); }
100 
105  void invalidate() { m_initialized = false; }
106 
110  void down();
111 
115  void up();
116 
120  void toggle();
121 
127  bool isChecked(uint8_t index);
128 
134  void setChecked(uint8_t index, bool checked);
135 
141  uint16_t checkedMask();
142 
146  uint8_t selection();
147 
152  void setSelection(uint8_t s);
153 
158  void setRect(const NanoRect &rect = {});
159 
163  uint8_t size();
164 
168  template <typename D> void updateSize(D &d)
169  {
170  if ( !menu.width )
171  {
172  menu.width = d.width() - menu.left;
173  }
174  if ( !menu.height )
175  {
176  menu.height = d.height() - menu.top;
177  }
178  }
179 
180 private:
181  SAppMenu menu;
182  uint16_t m_checked;
183  uint16_t m_oldChecked = 0;
184  uint8_t m_oldScrollPosition = 0;
185  bool m_initialized = false;
186 
187  template <typename D> uint8_t getMaxScreenItems(D &d)
188  {
189  return (menu.height - 16) / d.getFont().getHeader().height;
190  }
191 
192  template <typename D> uint8_t calculateScrollPosition(D &d, uint8_t selection)
193  {
194  if ( selection < menu.scrollPosition )
195  {
196  return selection;
197  }
198  else if ( selection - menu.scrollPosition > getMaxScreenItems(d) - 1 )
199  {
200  return selection - getMaxScreenItems(d) + 1;
201  }
202  return menu.scrollPosition;
203  }
204 
205  template <typename D> void drawMenuItem(D &d, uint8_t index)
206  {
207  lcduint_t fh = d.getFont().getHeader().height;
208  lcdint_t item_top = 8 + menu.top + (index - menu.scrollPosition) * fh;
209 
210  // Checkbox box size scales with font
211  lcduint_t boxSize = (fh >= 12) ? 7 : 5;
212  lcdint_t boxX = menu.left + 6;
213  lcdint_t boxY = item_top + (fh - boxSize) / 2;
214  lcdint_t textX = boxX + boxSize + 3;
215 
216  // Clear the small checkbox area (so a previous filled box gets
217  // wiped on toggle / scroll) and the trailing area beyond the
218  // label. printFixed repaints the cells under the new text itself.
219  uint16_t fg = d.getColor();
220  d.setColor(0x0000);
221  d.fillRect(menu.left + 5, item_top, textX - 1, item_top + fh - 1);
222 
223  // Checkbox box: drawn with the current (non-inverted) colors so
224  // it stays visible on the highlighted row.
225  d.setColor(fg);
226  if ( m_checked & (1u << index) )
227  {
228  d.fillRect(boxX, boxY, boxX + boxSize - 1, boxY + boxSize - 1);
229  }
230  else
231  {
232  d.drawRect(boxX, boxY, boxX + boxSize - 1, boxY + boxSize - 1);
233  }
234 
235  // Text strip: invert only here so the highlight bar covers just
236  // the label, not the checkbox. printFixed + trailing fillRect
237  // handles leftover characters from a longer previous label.
238  if ( index == menu.selection )
239  {
240  d.invertColors();
241  }
242  uint16_t textColor = d.getColor();
243  d.setColor(0x0000);
244  d.fillRect(textX + d.getFont().getTextSize(menu.items[index]), item_top,
245  menu.width + menu.left - 9, item_top + fh - 1);
246  d.setColor(textColor);
247  d.printFixed(textX, item_top, menu.items[index], STYLE_NORMAL);
248  if ( index == menu.selection )
249  {
250  d.invertColors();
251  }
252  }
253 
254  template <typename D> void drawScrollIndicators(D &d, uint8_t maxItems)
255  {
256  // Scrollbar on the right edge is the sole overflow indicator. It
257  // lives entirely in page-aligned item rows so partial fillRect for
258  // the thumb is safe even on SSD1306-style 1bpp drivers.
259  uint16_t color = d.getColor();
260  lcdint_t itemsTop = 8 + menu.top;
261  lcdint_t itemsBot = itemsTop + maxItems * d.getFont().getHeader().height;
262  lcdint_t sbX = menu.width + menu.left - 8;
263  lcdint_t sbH = itemsBot - itemsTop - 1;
264  if ( sbH > 4 )
265  {
266  lcdint_t thumbH = lcd_gfx_max((lcdint_t)2, (lcdint_t)(sbH * maxItems / menu.count));
267  lcdint_t maxScroll = menu.count - maxItems;
268  lcdint_t thumbY = itemsTop;
269  if ( maxScroll > 0 )
270  {
271  thumbY = itemsTop + (lcdint_t)((long)(sbH - thumbH) * menu.scrollPosition / maxScroll);
272  }
273  d.setColor(0x0000);
274  d.fillRect(sbX, itemsTop, sbX + 1, itemsTop + sbH);
275  d.setColor(color);
276  d.fillRect(sbX, thumbY, sbX + 1, thumbY + thumbH);
277  }
278  d.setColor(color);
279  }
280 };
281 
282 template <typename D> void LcdGfxCheckboxMenu::show(D &d)
283 {
284  updateSize(d);
285  uint8_t maxItems = getMaxScreenItems(d);
286  uint8_t newScroll = this->calculateScrollPosition(d, menu.selection);
287 
288  // Incremental fast path: only the selection moved within the visible
289  // window and/or some checkbox bits flipped — repaint just the affected
290  // page-aligned rows.
291  if ( m_initialized && newScroll == m_oldScrollPosition )
292  {
293  menu.scrollPosition = newScroll;
294  uint16_t changedCheck = m_checked ^ m_oldChecked;
295  if ( menu.selection == menu.oldSelection && changedCheck == 0 )
296  {
297  return;
298  }
299  if ( menu.oldSelection != menu.selection )
300  {
301  this->drawMenuItem(d, menu.oldSelection);
302  this->drawMenuItem(d, menu.selection);
303  }
304  else if ( changedCheck & (1u << menu.selection) )
305  {
306  // Selection didn't move, but its checkbox was toggled —
307  // redraw it so the user sees the change immediately.
308  this->drawMenuItem(d, menu.selection);
309  }
310  // Redraw any other on-screen item whose checked-state flipped.
311  for ( uint8_t i = menu.scrollPosition; i < lcd_gfx_min(menu.count, (menu.scrollPosition + maxItems)); i++ )
312  {
313  if ( (changedCheck & (1u << i)) && i != menu.selection && i != menu.oldSelection )
314  {
315  this->drawMenuItem(d, i);
316  }
317  }
318  menu.oldSelection = menu.selection;
319  m_oldChecked = m_checked;
320  return;
321  }
322 
323  // Full redraw: first call, or visible window scrolled. Each
324  // drawMenuItem clears its own row, so no full-interior fillRect is
325  // needed here — the border stays painted without flicker.
326  menu.scrollPosition = newScroll;
327  d.drawRect(4 + menu.left, 4 + menu.top, menu.width + menu.left - 5, menu.height + menu.top - 5);
328  for ( uint8_t i = menu.scrollPosition; i < lcd_gfx_min(menu.count, (menu.scrollPosition + maxItems)); i++ )
329  {
330  this->drawMenuItem(d, i);
331  }
332  menu.oldSelection = menu.selection;
333  m_oldScrollPosition = menu.scrollPosition;
334  m_oldChecked = m_checked;
335  m_initialized = true;
336  if ( menu.count > maxItems )
337  {
338  this->drawScrollIndicators(d, maxItems);
339  }
340 }
341 
346 #endif
lcdint_t left
left offset
Definition: canvas_types.h:167
uint8_t lcduint_t
internal int type, used by the library.
Definition: canvas_types.h:79
NanoRect structure describes rectangle area.
Definition: rect.h:42
uint16_t checkedMask()
Returns the full checkbox state as a bitmask.
uint8_t size()
Returns total count of menu items.
Point class.
int8_t lcdint_t
internal int type, used by the library.
Definition: canvas_types.h:77
void show(D &d)
Shows the checkbox menu on the display.
void up()
Moves selection up by one item.
void updateSize(D &d)
Updates size of the object if not set previously.
lcduint_t width
width of menu
Definition: canvas_types.h:169
#define lcd_gfx_max(a, b)
Macros returning maximum of 2 numbers.
Rectangle class.
lcduint_t height
height of menu
Definition: canvas_types.h:171
void setChecked(uint8_t index, bool checked)
Sets the checkbox state of a specific item.
Basic structures of nano gfx library.
uint8_t selection
currently selected item. Internally updated.
Definition: canvas_types.h:159
uint8_t scrollPosition
position of menu scrolling. Internally updated
Definition: canvas_types.h:163
void setSelection(uint8_t s)
Sets the current selection index.
#define lcd_gfx_min(a, b)
Macros returning minimum of 2 numbers.
void toggle()
Toggles the checkbox state of the currently selected item.
uint8_t selection()
Returns currently selected menu item index.
void down()
Moves selection down by one item.
uint8_t count
count of menu items in the menu
Definition: canvas_types.h:157
Checkbox menu widget for lcdgfx.
Definition: checkbox_menu.h:69
Describes menu object.
Definition: canvas_types.h:152
lcdint_t top
top offset
Definition: canvas_types.h:165
void setRect(const NanoRect &rect={})
Sets rect area for the menu.
bool isChecked(uint8_t index)
Returns true if the item at the given index is checked.
void update(D &d)
Equivalent to show().
Definition: checkbox_menu.h:99
LcdGfxCheckboxMenu(const char **items, uint8_t count, const NanoRect &rect={})
Creates checkbox menu object.
Font class.
uint8_t oldSelection
selected item, when last redraw operation was performed. Internally updated.
Definition: canvas_types.h:161
void invalidate()
Forces the next show() to perform a full redraw (border + all visible items + scroll indicators)...
const char ** items
list of menu items of the menu
Definition: canvas_types.h:155