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
menu.h
Go to the documentation of this file.
1 /*
2  MIT License
3 
4  Copyright (c) 2020,2022, 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_MENU_H_
29 #define _LCDGFX_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 
38 #define lcd_gfx_min(x, y) ((x) < (y) ? (x) : (y))
39 #endif
40 
41 #ifndef lcd_gfx_max
42 
43 #define lcd_gfx_max(x, y) ((x) > (y) ? (x) : (y))
44 #endif
45 
55 {
56 public:
66  LcdGfxMenu(const char **items, uint8_t count, const NanoRect &rect = {});
67 
82  template <typename D> void show(D &d);
83 
88  template <typename D> void update(D &d) { show(d); }
89 
96  void invalidate() { m_initialized = false; }
97 
103  void down();
104 
110  void up();
111 
116  uint8_t selection();
117 
118  void setSelection(uint8_t s);
119 
125  void setRect(const NanoRect &rect = {});
126 
130  uint8_t size();
131 
135  template <typename D> void updateSize(D &d)
136  {
137  if ( !menu.width )
138  {
139  menu.width = d.width() - menu.left;
140  }
141  if ( !menu.height )
142  {
143  menu.height = d.height() - menu.top;
144  }
145  }
146 
156  template <typename D> uint8_t itemAtPoint(D &d, lcdint_t x, lcdint_t y)
157  {
158  updateSize(d);
159  if ( x < menu.left || x >= (lcdint_t)(menu.left + menu.width) ) return 0xFF;
160  if ( y < menu.top || y >= (lcdint_t)(menu.top + menu.height) ) return 0xFF;
161  lcdint_t itemsTop = 8 + menu.top;
162  lcduint_t itemH = d.getFont().getHeader().height;
163  if ( itemH == 0 ) return 0xFF;
164  uint8_t maxItems = getMaxScreenItems(d);
165  lcdint_t itemsBot = itemsTop + maxItems * (lcdint_t)itemH;
166  if ( y < itemsTop || y >= itemsBot ) return 0xFF;
167  uint8_t row = (uint8_t)((y - itemsTop) / itemH);
168  uint8_t index = menu.scrollPosition + row;
169  if ( index >= menu.count ) return 0xFF;
170  return index;
171  }
172 
186  template <typename D> bool onTouch(D &d, lcdint_t x, lcdint_t y)
187  {
188  updateSize(d);
189  if ( x < menu.left || x >= (lcdint_t)(menu.left + menu.width) ) return false;
190  if ( y < menu.top || y >= (lcdint_t)(menu.top + menu.height) ) return false;
191  lcdint_t borderTop = 4 + menu.top;
192  lcdint_t borderBot = menu.height + menu.top - 5;
193  lcdint_t itemsTop = 8 + menu.top;
194  lcduint_t itemH = d.getFont().getHeader().height;
195  if ( itemH == 0 ) return false;
196  uint8_t maxItems = getMaxScreenItems(d);
197  lcdint_t itemsBot = itemsTop + maxItems * (lcdint_t)itemH;
198  if ( y >= borderTop && y < itemsTop )
199  {
200  if ( menu.scrollPosition > 0 )
201  {
202  up();
203  return true;
204  }
205  return false;
206  }
207  if ( y >= itemsBot && y < borderBot )
208  {
209  if ( menu.scrollPosition + maxItems < menu.count )
210  {
211  down();
212  return true;
213  }
214  return false;
215  }
216  if ( y >= itemsTop && y < itemsBot )
217  {
218  uint8_t row = (uint8_t)((y - itemsTop) / itemH);
219  uint8_t index = menu.scrollPosition + row;
220  if ( index < menu.count )
221  {
222  setSelection(index);
223  return true;
224  }
225  }
226  return false;
227  }
228 
229 private:
230  SAppMenu menu;
231  uint8_t m_oldScrollPosition = 0;
232  bool m_initialized = false;
233 
234  template <typename D> uint8_t getMaxScreenItems(D &d)
235  {
236  return (menu.height - 16) / d.getFont().getHeader().height;
237  }
238 
239  template <typename D> uint8_t calculateScrollPosition(D &d, uint8_t selection)
240  {
241  if ( selection < menu.scrollPosition )
242  {
243  return selection;
244  }
245  else if ( selection - menu.scrollPosition > getMaxScreenItems(d) - 1 )
246  {
247  return selection - getMaxScreenItems(d) + 1;
248  }
249  return menu.scrollPosition;
250  }
251 
252  template <typename D> void drawMenuItem(D &d, uint8_t index)
253  {
254  if ( index == menu.selection )
255  {
256  d.invertColors();
257  }
258  lcdint_t item_top = 8 + menu.top + (index - menu.scrollPosition) * d.getFont().getHeader().height;
259  uint16_t color = d.getColor();
260  d.setColor(0x0000);
261  // Clear only the trailing portion (from the end of the new label
262  // to just inside the right border / scrollbar). printFixed paints
263  // the cell backgrounds under the new text itself, so this single
264  // trailing fillRect is enough — even after scrolling, when a
265  // shorter label replaces a longer one at the same screen row.
266  d.fillRect(menu.left + 8 + d.getFont().getTextSize(menu.items[index]), item_top,
267  menu.width + menu.left - 9, item_top + d.getFont().getHeader().height - 1);
268  d.setColor(color);
269  d.printFixed(menu.left + 8, item_top, menu.items[index], STYLE_NORMAL);
270  if ( index == menu.selection )
271  {
272  d.invertColors();
273  }
274  }
275 
276  template <typename D> void drawScrollIndicators(D &d, uint8_t maxItems)
277  {
278  // Scrollbar on the right edge conveys the visible-window position;
279  // it lives entirely in the item-area pages (page-aligned) so a
280  // partial fillRect for the thumb is safe even on SSD1306-style
281  // 1bpp drivers that stream whole 8-pixel page bytes.
282  uint16_t color = d.getColor();
283  lcdint_t itemsTop = 8 + menu.top;
284  lcdint_t itemsBot = itemsTop + maxItems * d.getFont().getHeader().height;
285  lcdint_t sbX = menu.width + menu.left - 8;
286  lcdint_t sbH = itemsBot - itemsTop - 1;
287  if ( sbH > 4 )
288  {
289  lcdint_t thumbH = lcd_gfx_max((lcdint_t)2, (lcdint_t)(sbH * maxItems / menu.count));
290  lcdint_t maxScroll = menu.count - maxItems;
291  lcdint_t thumbY = itemsTop;
292  if ( maxScroll > 0 )
293  {
294  thumbY = itemsTop + (lcdint_t)((long)(sbH - thumbH) * menu.scrollPosition / maxScroll);
295  }
296  d.setColor(0x0000);
297  d.fillRect(sbX, itemsTop, sbX + 1, itemsTop + sbH);
298  d.setColor(color);
299  d.fillRect(sbX, thumbY, sbX + 1, thumbY + thumbH);
300  }
301  d.setColor(color);
302  }
303 };
304 
305 template <typename D> void LcdGfxMenu::show(D &d)
306 {
307  updateSize(d);
308  uint8_t maxItems = getMaxScreenItems(d);
309  uint8_t newScroll = this->calculateScrollPosition(d, menu.selection);
310 
311  // Incremental fast path: nothing about the visible window changed,
312  // only the selection moved within it. Repaint just the two affected
313  // item rows. They live on page-aligned y offsets so redrawing them
314  // does not disturb the border or neighbouring items even on
315  // SSD1306-style 1bpp drivers that stream whole 8-pixel page bytes.
316  if ( m_initialized && newScroll == m_oldScrollPosition )
317  {
318  if ( menu.selection != menu.oldSelection )
319  {
320  menu.scrollPosition = newScroll;
321  uint8_t prev = menu.oldSelection;
322  this->drawMenuItem(d, prev);
323  this->drawMenuItem(d, menu.selection);
324  menu.oldSelection = menu.selection;
325  }
326  return;
327  }
328 
329  // Full redraw: first call, or visible window scrolled. Each
330  // drawMenuItem clears its own row, so no full-interior fillRect is
331  // needed here — the border stays painted without flicker.
332  menu.scrollPosition = newScroll;
333  d.drawRect(4 + menu.left, 4 + menu.top, menu.width + menu.left - 5, menu.height + menu.top - 5);
334  for ( uint8_t i = menu.scrollPosition; i < lcd_gfx_min(menu.count, (menu.scrollPosition + maxItems)); i++ )
335  {
336  this->drawMenuItem(d, i);
337  }
338  menu.oldSelection = menu.selection;
339  m_oldScrollPosition = menu.scrollPosition;
340  m_initialized = true;
341  if ( menu.count > maxItems )
342  {
343  this->drawScrollIndicators(d, maxItems);
344  }
345 }
346 
351 #endif
uint8_t itemAtPoint(D &d, lcdint_t x, lcdint_t y)
Returns the menu item index at the given screen coordinate, or 0xFF if the point lies outside any ite...
Definition: menu.h:156
void update(D &d)
Equivalent to show().
Definition: menu.h:88
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
#define lcd_gfx_min(x, y)
Custom min function.
Definition: menu.h:38
Point class.
int8_t lcdint_t
internal int type, used by the library.
Definition: canvas_types.h:77
LcdGfxMenu(const char **items, uint8_t count, const NanoRect &rect={})
Creates menu object with the provided list of menu items.
lcduint_t width
width of menu
Definition: canvas_types.h:169
void up()
Moves selection pointer up by 1 item.
Rectangle class.
lcduint_t height
height of menu
Definition: canvas_types.h:171
void updateSize(D &d)
Updates size of the object, if it was not set previously.
Definition: menu.h:135
void show(D &d)
Shows menu items on the display.
Definition: menu.h:305
void invalidate()
Forces the next show() to perform a full redraw (border + all visible items + scroll indicators)...
Definition: menu.h:96
uint8_t size()
Returns total count of menu items in menu.
Basic structures of nano gfx library.
uint8_t selection()
Returns currently selected menu item.
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
#define lcd_gfx_max(x, y)
Custom max function.
Definition: menu.h:43
uint8_t count
count of menu items in the menu
Definition: canvas_types.h:157
void down()
Moves selection pointer down by 1 item.
Class implements menu objects for lcdgfx library.
Definition: menu.h:54
void setRect(const NanoRect &rect={})
Sets rect area for the menu.
Describes menu object.
Definition: canvas_types.h:152
lcdint_t top
top offset
Definition: canvas_types.h:165
Font class.
bool onTouch(D &d, lcdint_t x, lcdint_t y)
Handles a touch event at the given screen coordinates.
Definition: menu.h:186
uint8_t oldSelection
selected item, when last redraw operation was performed. Internally updated.
Definition: canvas_types.h:161
const char ** items
list of menu items of the menu
Definition: canvas_types.h:155