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
ssd1306_common.inl
1 /*
2  MIT License
3 
4  Copyright (c) 2016-2020, 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 */
24 
28 
29 #include "lcd_hal/io.h"
30 #include <stdio.h>
31 
32 #if 0
33 
34 uint8_t ssd1306_printFixed(uint8_t xpos, uint8_t y, const char *ch, EFontStyle style)
35 {
36  uint8_t i, j=0;
37  uint8_t text_index = 0;
38  uint8_t page_offset = 0;
39  uint8_t x = xpos;
40  y >>= 3;
41  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
42  for(;;)
43  {
44  uint8_t ldata;
45  if ( (x > ssd1306_lcd.width - s_fixedFont.h.width) || (ch[j] == '\0') )
46  {
47  x = xpos;
48  y++;
49  if (y >= (ssd1306_lcd.height >> 3))
50  {
51  break;
52  }
53  page_offset++;
54  if (page_offset == s_fixedFont.pages)
55  {
56  text_index = j;
57  page_offset = 0;
58  if (ch[j] == '\0')
59  {
60  break;
61  }
62  }
63  else
64  {
65  j = text_index;
66  }
67  ssd1306_intf.stop();
68  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
69  }
70  uint16_t unicode;
71  do
72  {
73  unicode = ssd1306_unicode16FromUtf8(ch[j]);
74  j++;
75  } while ( unicode == SSD1306_MORE_CHARS_REQUIRED );
76  SCharInfo char_info;
77  ssd1306_getCharBitmap(unicode, &char_info);
78  ldata = 0;
79  x += char_info.width + char_info.spacing;
80  if (char_info.height > page_offset * 8)
81  {
82  char_info.glyph += page_offset * char_info.width;
83  for( i = char_info.width; i>0; i--)
84  {
85  uint8_t data;
86  if ( style == STYLE_NORMAL )
87  {
88  data = pgm_read_byte(&char_info.glyph[0]);
89  }
90  else if ( style == STYLE_BOLD )
91  {
92  uint8_t temp = pgm_read_byte(&char_info.glyph[0]);
93  data = temp | ldata;
94  ldata = temp;
95  }
96  else
97  {
98  uint8_t temp = pgm_read_byte(&char_info.glyph[1]);
99  data = (temp & 0xF0) | ldata;
100  ldata = (temp & 0x0F);
101  }
102  ssd1306_lcd.send_pixels1(data^s_ssd1306_invertByte);
103  char_info.glyph++;
104  }
105  }
106  else
107  {
108  char_info.spacing += char_info.width;
109  }
110  for (i = 0; i < char_info.spacing; i++)
111  ssd1306_lcd.send_pixels1(s_ssd1306_invertByte);
112  }
113  ssd1306_intf.stop();
114  return j;
115 }
116 
117 uint8_t ssd1306_printFixed_oldStyle(uint8_t xpos, uint8_t y, const char *ch, EFontStyle style)
118 {
119  uint8_t i, j=0;
120  uint8_t text_index = 0;
121  uint8_t page_offset = 0;
122  uint8_t x = xpos;
123  y >>= 3;
124  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
125  for(;;)
126  {
127  uint8_t c;
128  uint8_t ldata;
129  uint16_t offset;
130  if( (x > ssd1306_lcd.width - s_fixedFont.h.width) || (ch[j] == '\0') )
131  {
132  x = xpos;
133  y++;
134  if (y >= (ssd1306_lcd.height >> 3))
135  {
136  break;
137  }
138  page_offset++;
139  if (page_offset == s_fixedFont.pages)
140  {
141  text_index = j;
142  page_offset = 0;
143  if (ch[j] == '\0')
144  {
145  break;
146  }
147  }
148  else
149  {
150  j = text_index;
151  }
152  ssd1306_intf.stop();
153  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
154  }
155  c = ch[j];
156  if ( c >= s_fixedFont.h.ascii_offset )
157  {
158  c -= s_fixedFont.h.ascii_offset;
159  }
160  ldata = 0;
161  offset = (c * s_fixedFont.pages + page_offset) * s_fixedFont.h.width;
162  for( i=s_fixedFont.h.width; i>0; i--)
163  {
164  uint8_t data;
165  if ( style == STYLE_NORMAL )
166  {
167  data = pgm_read_byte(&s_fixedFont.primary_table[offset]);
168  }
169  else if ( style == STYLE_BOLD )
170  {
171  uint8_t temp = pgm_read_byte(&s_fixedFont.primary_table[offset]);
172  data = temp | ldata;
173  ldata = temp;
174  }
175  else
176  {
177  uint8_t temp = pgm_read_byte(&s_fixedFont.primary_table[offset + 1]);
178  data = (temp & 0xF0) | ldata;
179  ldata = (temp & 0x0F);
180  }
181  ssd1306_lcd.send_pixels1(data^s_ssd1306_invertByte);
182  offset++;
183  }
184  x += s_fixedFont.h.width;
185  j++;
186  }
187  ssd1306_intf.stop();
188  return j;
189 }
190 
191 uint8_t ssd1306_printFixed2x(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style)
192 {
193  uint8_t i, j=0;
194  uint8_t text_index = 0;
195  uint8_t page_offset = 0;
196  uint8_t x = xpos;
197  y >>= 3;
198  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
199  for(;;)
200  {
201  uint8_t c;
202  uint8_t ldata;
203  uint16_t offset;
204  if( (x > ssd1306_lcd.width - (s_fixedFont.h.width << 1)) || (ch[j] == '\0') )
205  {
206  x = xpos;
207  y++;
208  if (y >= (ssd1306_lcd.height >> 3))
209  {
210  break;
211  }
212  page_offset++;
213  if (page_offset == (s_fixedFont.pages << 1))
214  {
215  text_index = j;
216  page_offset = 0;
217  if (ch[j] == '\0')
218  {
219  break;
220  }
221  }
222  else
223  {
224  j = text_index;
225  }
226  ssd1306_intf.stop();
227  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
228  }
229  c = ch[j];
230  if ( c >= 32 )
231  {
232  c -= 32;
233  }
234  ldata = 0;
235  offset = (c * s_fixedFont.pages + (page_offset >> 1)) * s_fixedFont.h.width;
236  for( i=s_fixedFont.h.width; i>0; i--)
237  {
238  uint8_t data;
239  if ( style == STYLE_NORMAL )
240  {
241  data = pgm_read_byte(&s_fixedFont.primary_table[offset]);
242  }
243  else if ( style == STYLE_BOLD )
244  {
245  uint8_t temp = pgm_read_byte(&s_fixedFont.primary_table[offset]);
246  data = temp | ldata;
247  ldata = temp;
248  }
249  else
250  {
251  uint8_t temp = pgm_read_byte(&s_fixedFont.primary_table[offset + 1]);
252  data = (temp & 0xF0) | ldata;
253  ldata = (temp & 0x0F);
254  }
255  if (page_offset & 1) data >>= 4;
256  data = ((data & 0x01) ? 0x03: 0x00) |
257  ((data & 0x02) ? 0x0C: 0x00) |
258  ((data & 0x04) ? 0x30: 0x00) |
259  ((data & 0x08) ? 0xC0: 0x00);
260  ssd1306_lcd.send_pixels1(data^s_ssd1306_invertByte);
261  ssd1306_lcd.send_pixels1(data^s_ssd1306_invertByte);
262  offset++;
263  }
264  x += (s_fixedFont.h.width << 1);
265  j++;
266  }
267  ssd1306_intf.stop();
268  return j;
269 }
270 
271 
272 uint8_t ssd1306_printFixedN(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style, uint8_t factor)
273 {
274  uint8_t i, j=0;
275  uint8_t text_index = 0;
276  uint8_t page_offset = 0;
277  uint8_t x = xpos;
278  y >>= 3;
279  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
280  for(;;)
281  {
282  uint8_t ldata;
283  if( (x > ssd1306_lcd.width - (s_fixedFont.h.width << factor)) || (ch[j] == '\0') )
284  {
285  x = xpos;
286  y++;
287  if (y >= (ssd1306_lcd.height >> 3))
288  {
289  break;
290  }
291  page_offset++;
292  if (page_offset == (s_fixedFont.pages << factor))
293  {
294  text_index = j;
295  page_offset = 0;
296  if (ch[j] == '\0')
297  {
298  break;
299  }
300  }
301  else
302  {
303  j = text_index;
304  }
305  ssd1306_intf.stop();
306  ssd1306_lcd.set_block(xpos, y, ssd1306_lcd.width - xpos);
307  }
308  uint16_t unicode;
309  do
310  {
311  unicode = ssd1306_unicode16FromUtf8(ch[j]);
312  j++;
313  } while ( unicode == SSD1306_MORE_CHARS_REQUIRED );
314  SCharInfo char_info;
315  ssd1306_getCharBitmap(unicode, &char_info);
316  ldata = 0;
317  x += ((char_info.width + char_info.spacing) << factor);
318  if (char_info.height > (page_offset >> factor) * 8)
319  {
320  char_info.glyph += (page_offset >> factor) * char_info.width;
321  for( i=char_info.width; i>0; i--)
322  {
323  uint8_t data;
324  if ( style == STYLE_NORMAL )
325  {
326  data = pgm_read_byte(char_info.glyph);
327  }
328  else if ( style == STYLE_BOLD )
329  {
330  uint8_t temp = pgm_read_byte(char_info.glyph);
331  data = temp | ldata;
332  ldata = temp;
333  }
334  else
335  {
336  uint8_t temp = pgm_read_byte(char_info.glyph+1);
337  data = (temp & 0xF0) | ldata;
338  ldata = (temp & 0x0F);
339  }
340  if ( factor > 0 )
341  {
342  uint8_t accum = 0;
343  uint8_t mask = ~((0xFF) << (1<<factor));
344  // N=0 -> right shift is always 0
345  // N=1 -> right shift goes through 0, 4
346  // N=2 -> right shift goes through 0, 2, 4, 6
347  // N=3 -> right shift goes through 0, 1, 2, 3, 4, 5, 6, 7
348  data >>= ((page_offset & ((1<<factor) - 1))<<(3-factor));
349  for (uint8_t idx = 0; idx < 1<<(3-factor); idx++)
350  {
351  accum |= (((data>>idx) & 0x01) ? (mask<<(idx<<factor)) : 0);
352  }
353  data = accum;
354  }
355  for (uint8_t z=(1<<factor); z>0; z--)
356  {
357  ssd1306_lcd.send_pixels1(data^s_ssd1306_invertByte);
358  }
359  char_info.glyph++;
360  }
361  }
362  else
363  {
364  char_info.spacing += char_info.width;
365  }
366  for (i = 0; i < (char_info.spacing << factor); i++)
367  ssd1306_lcd.send_pixels1(s_ssd1306_invertByte);
368  }
369  ssd1306_intf.stop();
370  return j;
371 }
372 
373 void ssd1306_putPixel_delayed(uint8_t x, uint8_t y, uint8_t complete)
374 {
375  static uint8_t lx = 0, ly = 0xFF;
376  static uint8_t pixels = 0;
377  if ((lx != x) || ((ly & 0xF8) != (y & 0xF8)) || (complete))
378  {
379  if (ly != 0xFF)
380  {
381  ssd1306_putPixels( lx, ly, pixels );
382  }
383  pixels = 0;
384  ly = 0xFF;
385  }
386  if ( !complete )
387  {
388  pixels |= (1 << (y & 0x07));
389  lx = x; ly = y;
390  }
391 }
392 
393 void ssd1306_drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
394 {
395  lcduint_t dx = x1 > x2 ? (x1 - x2): (x2 - x1);
396  lcduint_t dy = y1 > y2 ? (y1 - y2): (y2 - y1);
397  lcduint_t err = 0;
398  if (dy > dx)
399  {
400  if (y1 > y2)
401  {
402  ssd1306_swap_data(x1, x2, uint8_t);
403  ssd1306_swap_data(y1, y2, uint8_t);
404  }
405  for(; y1<=y2; y1++)
406  {
407  err += dx;
408  if (err >= dy)
409  {
410  err -= dy;
411  x1 < x2 ? x1++: x1--;
412  }
413  ssd1306_putPixel_delayed( x1, y1, 0 );
414  }
415  ssd1306_putPixel_delayed( 0, 0, 1 );
416  }
417  else
418  {
419  if (x1 > x2)
420  {
421  ssd1306_swap_data(x1, x2, uint8_t);
422  ssd1306_swap_data(y1, y2, uint8_t);
423  }
424  for(; x1<=x2; x1++)
425  {
426  err += dy;
427  if (err >= dx)
428  {
429  err -= dx;
430  if (y1 < y2) y1++; else y1--;
431  }
432  ssd1306_putPixel( x1, y1 );
433  }
434  }
435 }
436 
437 void ssd1306_drawBufferFast(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buf)
438 {
439  uint8_t j;
440  ssd1306_lcd.set_block(x, y >> 3, w);
441  for(j=(h >> 3); j>0; j--)
442  {
443  ssd1306_lcd.send_pixels_buffer1(buf,w);
444  buf+=w;
445  ssd1306_lcd.next_page();
446  }
447  ssd1306_intf.stop();
448 }
449 
450 void gfx_drawMonoBitmap(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buf)
451 {
452  lcduint_t origin_width = w;
453  uint8_t offset = y & 0x07;
454  uint8_t complexFlag = 0;
455  uint8_t mainFlag = 1;
456  uint8_t max_pages;
457  uint8_t pages;
458  lcduint_t i, j;
459  if (y + (lcdint_t)h <= 0) return;
460  if (y >= (lcdint_t)ssd1306_lcd.height) return;
461  if (x + (lcdint_t)w <= 0) return;
462  if (x >= (lcdint_t)ssd1306_lcd.width) return;
463  if (y < 0)
464  {
465  buf += ((lcduint_t)((-y) + 7) >> 3) * w;
466  h += y;
467  y = 0;
468  complexFlag = 1;
469  }
470  if (x < 0)
471  {
472  buf += -x;
473  w += x;
474  x = 0;
475  }
476  max_pages = (lcduint_t)(h + 15 - offset) >> 3;
477  if ((lcduint_t)((lcduint_t)y + h) > (lcduint_t)ssd1306_lcd.height)
478  {
479  h = (lcduint_t)(ssd1306_lcd.height - (lcduint_t)y);
480  }
481  if ((lcduint_t)((lcduint_t)x + w) > (lcduint_t)ssd1306_lcd.width)
482  {
483  w = (lcduint_t)(ssd1306_lcd.width - (lcduint_t)x);
484  }
485  pages = ((y + h - 1) >> 3) - (y >> 3) + 1;
486 
487  ssd1306_lcd.set_block(x, y >> 3, w);
488  for(j=0; j < pages; j++)
489  {
490  if ( j == (lcduint_t)(max_pages - 1) ) mainFlag = !offset;
491  for( i=w; i > 0; i--)
492  {
493  uint8_t data = 0;
494  if ( mainFlag ) data |= (pgm_read_byte(buf) << offset);
495  if ( complexFlag ) data |= (pgm_read_byte(buf - origin_width) >> (8 - offset));
496  buf++;
497  ssd1306_lcd.send_pixels1(s_ssd1306_invertByte^data);
498  }
499  buf += origin_width - w;
500  complexFlag = offset;
501  ssd1306_lcd.next_page();
502  }
503  ssd1306_intf.stop();
504 }
505 
506 
507 void ssd1306_setFont6x8(const uint8_t * progmemFont)
508 {
509  s_font6x8 = progmemFont + 4;
510 }
511 
512 #endif
513 
515 //
516 // COMMON GRAPHICS
517 //
519 
520 template <class O, class I> void NanoDisplayOps<O, I>::putPixel(const NanoPoint &p)
521 {
522  this->putPixel(p.x, p.y);
523 }
524 
525 template <class O, class I> void NanoDisplayOps<O, I>::drawRect(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2)
526 {
527  this->drawHLine(x1, y1, x2);
528  this->drawHLine(x1, y2, x2);
529  this->drawVLine(x1, y1, y2);
530  this->drawVLine(x2, y1, y2);
531 }
532 
533 template <class O, class I> void NanoDisplayOps<O, I>::drawRect(const NanoRect &rect)
534 {
535  this->drawRect(rect.p1.x, rect.p1.y, rect.p2.x, rect.p2.y);
536 }
537 
538 template <class O, class I> void NanoDisplayOps<O, I>::drawLine(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2)
539 {
540  lcduint_t dx = x1 > x2 ? (x1 - x2) : (x2 - x1);
541  lcduint_t dy = y1 > y2 ? (y1 - y2) : (y2 - y1);
542  lcduint_t err = 0;
543  if ( dy > dx )
544  {
545  // Steep line: the column (x) stays constant across runs of consecutive
546  // rows. Emit each same-column run as a single drawVLine so the bus is
547  // addressed once per run rather than once per pixel. On 1-bit page
548  // addressed controllers this also renders the run correctly - per-pixel
549  // putPixel would rewrite the shared page byte and clobber earlier pixels.
550  if ( y1 > y2 )
551  {
552  ssd1306_swap_data(x1, x2, lcdint_t);
553  ssd1306_swap_data(y1, y2, lcdint_t);
554  }
555  lcdint_t runStart = y1;
556  for ( ; y1 <= y2; y1++ )
557  {
558  err += dx;
559  if ( err >= dy )
560  {
561  err -= dy;
562  this->drawVLine(x1, runStart, y1);
563  x1 < x2 ? x1++ : x1--;
564  runStart = y1 + 1;
565  }
566  }
567  if ( runStart <= y2 )
568  {
569  this->drawVLine(x1, runStart, y2);
570  }
571  }
572  else
573  {
574  // Shallow line: the row (y) stays constant across runs of consecutive
575  // columns. Emit each same-row run as a single drawHLine.
576  if ( x1 > x2 )
577  {
578  ssd1306_swap_data(x1, x2, lcdint_t);
579  ssd1306_swap_data(y1, y2, lcdint_t);
580  }
581  lcdint_t runStart = x1;
582  for ( ; x1 <= x2; x1++ )
583  {
584  err += dy;
585  if ( err >= dx )
586  {
587  err -= dx;
588  this->drawHLine(runStart, y1, x1);
589  if ( y1 < y2 )
590  y1++;
591  else
592  y1--;
593  runStart = x1 + 1;
594  }
595  }
596  if ( runStart <= x2 )
597  {
598  this->drawHLine(runStart, y1, x2);
599  }
600  }
601 }
602 
603 template <class O, class I> void NanoDisplayOps<O, I>::drawLine(const NanoRect &rect)
604 {
605  this->drawLine(rect.p1.x, rect.p1.y, rect.p2.x, rect.p2.y);
606 }
607 
608 template <class O, class I> void NanoDisplayOps<O, I>::fillRect(const NanoRect &rect)
609 {
610  this->fillRect(rect.p1.x, rect.p1.y, rect.p2.x, rect.p2.y);
611 }
612 
613 template <class O, class I> void NanoDisplayOps<O, I>::clearRect(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2)
614 {
615  uint16_t old_color = this->getColor();
616  this->setColor(0x0000);
617  this->fillRect(x1, y1, x2, y2);
618  this->setColor(old_color);
619 }
620 
621 template <class O, class I> void NanoDisplayOps<O, I>::clearRect(const NanoRect &rect)
622 {
623  this->clearRect(rect.p1.x, rect.p1.y, rect.p2.x, rect.p2.y);
624 }
625 
626 template <class O, class I> void NanoDisplayOps<O, I>::drawCircle(lcdint_t xc, lcdint_t yc, lcdint_t r, uint8_t options)
627 {
628  lcdint_t d = 3 - 2 * r;
629  lcdint_t x = 0;
630  lcdint_t y = r;
631  if (options & (2 + 4)) putPixel(xc, yc + r);
632  if (options & (1 + 8)) putPixel(xc, yc - r);
633  if (options & (1 + 2)) putPixel(xc + r, yc);
634  if (options & (4 + 8)) putPixel(xc - r, yc);
635  while ( y >= x )
636  {
637  x++;
638  if ( d > 0 )
639  {
640  y--;
641  d += -4 * y + 4;
642  }
643  d += 4 * x + 6;
644  if (options & (2)) putPixel(xc + x, yc + y);
645  if (options & (4)) putPixel(xc - x, yc + y);
646  if (options & (1)) putPixel(xc + x, yc - y);
647  if (options & (8)) putPixel(xc - x, yc - y);
648  if (options & (2)) putPixel(xc + y, yc + x);
649  if (options & (4)) putPixel(xc - y, yc + x);
650  if (options & (1)) putPixel(xc + y, yc - x);
651  if (options & (8)) putPixel(xc - y, yc - x);
652  }
653 }
654 
655 template <class O, class I>
657 {
658  this->m_fontStyle = style;
659  this->m_cursorX = xpos;
660  this->m_cursorY = y;
661  for ( ;; )
662  {
663  char c = pgm_read_byte(ch);
664  if ( !c )
665  break;
666  this->write(c);
667  ch++;
668  }
669 }
670 
671 template <class O, class I> void NanoDisplayOps<O, I>::write(const char *str)
672 {
673  while ( *str )
674  {
675  this->write(*str);
676  str++;
677  }
678 }
679 
680 template <class O, class I> void NanoDisplayOps<O, I>::print(int number)
681 {
682  char intStr[12];
683  snprintf(intStr, sizeof(intStr), "%i", number);
684  this->write(intStr);
685 }
686 
687 template <class O, class I> void NanoDisplayOps<O, I>::print(float number)
688 {
689  char intStr[16];
690  snprintf(intStr, sizeof(intStr), "%f", number);
691  this->write(intStr);
692 }
693 
694 template <class O, class I> void NanoDisplayOps<O, I>::print(char c)
695 {
696  const char intStr[2] = {c, '\0'};
697  this->write(intStr);
698 }
699 
700 #ifndef lcd_gfx_min
701 #define lcd_gfx_min(x, y) ((x) < (y) ? (x) : (y))
702 #endif
703 
704 #ifndef lcd_gfx_max
705 #define lcd_gfx_max(x, y) ((x) > (y) ? (x) : (y))
706 #endif
707 
708 template <class O, class I> static uint8_t getMaxScreenItems(NanoDisplayOps<O, I> &display, const SAppMenu *menu)
709 {
710  return (menu->height - 16) / display.getFont().getHeader().height;
711 }
712 
713 template <class O, class I>
714 static uint8_t calculateScrollPosition(NanoDisplayOps<O, I> &display, const SAppMenu *menu, uint8_t selection)
715 {
716  if ( selection < menu->scrollPosition )
717  {
718  return selection;
719  }
720  else if ( selection - menu->scrollPosition > getMaxScreenItems<O, I>(display, menu) - 1 )
721  {
722  return selection - getMaxScreenItems<O, I>(display, menu) + 1;
723  }
724  return menu->scrollPosition;
725 }
726 
727 template <class O, class I> static void drawMenuItem(NanoDisplayOps<O, I> &display, SAppMenu *menu, uint8_t index)
728 {
729  if ( index == menu->selection )
730  {
731  display.invertColors();
732  }
733  lcdint_t item_top = 8 + menu->top + (index - menu->scrollPosition) * display.getFont().getHeader().height;
734  display.printFixed(menu->left + 8, item_top, menu->items[index], STYLE_NORMAL);
735  if ( index == menu->selection )
736  {
737  display.invertColors();
738  }
739 }
740 
741 template <class O, class I> static void drawMenuItemSmooth(NanoDisplayOps<O, I> &display, SAppMenu *menu, uint8_t index)
742 {
743  if ( index == menu->selection )
744  {
745  display.invertColors();
746  }
747  lcdint_t item_top = 8 + menu->top + (index - menu->scrollPosition) * display.getFont().getHeader().height;
748  display.setColor(0x0000);
749  display.fillRect(menu->left + 8 + display.getFont().getTextSize(menu->items[index]), item_top,
750  menu->width + menu->left - 9, item_top + display.getFont().getHeader().height - 1);
751  display.setColor(0xFFFF);
752  display.printFixed(menu->left + 8, item_top, menu->items[index], STYLE_NORMAL);
753  if ( index == menu->selection )
754  {
755  display.invertColors();
756  }
757 }
758 
759 template <class O, class I>
760 void NanoDisplayOps<O, I>::createMenu(SAppMenu *menu, const char **items, uint8_t count, const NanoRect &rect)
761 {
762  menu->items = items;
763  menu->count = count;
764  menu->selection = 0;
765  menu->oldSelection = 0;
766  menu->scrollPosition = 0;
767  menu->top = rect.p1.y;
768  menu->left = rect.p1.x;
769  menu->width = rect.p2.x ? rect.width() : (this->width() - menu->left);
770  menu->height = rect.p2.y ? rect.height() : (this->height() - menu->top);
771 }
772 
773 template <class O, class I> void NanoDisplayOps<O, I>::showMenu(SAppMenu *menu)
774 {
775  drawRect(4 + menu->left, 4 + menu->top, menu->width + menu->left - 5, menu->height + menu->top - 5);
776  menu->scrollPosition = calculateScrollPosition<O, I>(*this, menu, menu->selection);
777  for ( uint8_t i = menu->scrollPosition;
778  i < lcd_gfx_min(menu->count, (menu->scrollPosition + getMaxScreenItems<O, I>(*this, menu))); i++ )
779  {
780  drawMenuItem<O, I>(*this, menu, i);
781  }
782  menu->oldSelection = menu->selection;
783 }
784 
785 template <class O, class I> void NanoDisplayOps<O, I>::showMenuSmooth(SAppMenu *menu)
786 {
787  drawRect(4 + menu->left, 4 + menu->top, menu->width + menu->left - 5, menu->height + menu->top - 5);
788  menu->scrollPosition = calculateScrollPosition<O, I>(*this, menu, menu->selection);
789  for ( uint8_t i = menu->scrollPosition;
790  i < lcd_gfx_min(menu->count, (menu->scrollPosition + getMaxScreenItems<O, I>(*this, menu))); i++ )
791  {
792  drawMenuItemSmooth<O, I>(*this, menu, i);
793  }
794  menu->oldSelection = menu->selection;
795 }
796 
797 template <class O, class I> void NanoDisplayOps<O, I>::updateMenu(SAppMenu *menu)
798 {
799  if ( menu->selection != menu->oldSelection )
800  {
801  uint8_t scrollPosition = calculateScrollPosition<O, I>(*this, menu, menu->selection);
802  if ( scrollPosition != menu->scrollPosition )
803  {
804  this->clear();
805  showMenu(menu);
806  }
807  else
808  {
809  drawMenuItem<O, I>(*this, menu, menu->oldSelection);
810  drawMenuItem<O, I>(*this, menu, menu->selection);
811  menu->oldSelection = menu->selection;
812  }
813  }
814 }
815 
816 template <class O, class I> void NanoDisplayOps<O, I>::updateMenuSmooth(SAppMenu *menu)
817 {
818  if ( menu->selection != menu->oldSelection )
819  {
820  uint8_t scrollPosition = calculateScrollPosition<O, I>(*this, menu, menu->selection);
821  if ( scrollPosition != menu->scrollPosition )
822  {
823  showMenuSmooth(menu);
824  }
825  else
826  {
827  drawMenuItemSmooth<O, I>(*this, menu, menu->oldSelection);
828  drawMenuItemSmooth<O, I>(*this, menu, menu->selection);
829  menu->oldSelection = menu->selection;
830  }
831  }
832 }
833 
834 template <class O, class I> uint8_t NanoDisplayOps<O, I>::menuSelection(const SAppMenu *menu)
835 {
836  return menu->selection;
837 }
838 
839 template <class O, class I> void NanoDisplayOps<O, I>::menuDown(SAppMenu *menu)
840 {
841  if ( menu->selection < menu->count - 1 )
842  {
843  menu->selection++;
844  }
845  else
846  {
847  menu->selection = 0;
848  }
849 }
850 
851 template <class O, class I> void NanoDisplayOps<O, I>::menuUp(SAppMenu *menu)
852 {
853  if ( menu->selection > 0 )
854  {
855  menu->selection--;
856  }
857  else
858  {
859  menu->selection = menu->count - 1;
860  }
861 }
862 
863 template <class O, class I> void NanoDisplayOps<O, I>::drawCanvas(lcdint_t x, lcdint_t y, NanoCanvasOps<1> &canvas)
864 {
865  this->drawBuffer1Fast(x, y, canvas.width(), canvas.height(), canvas.getData());
866 }
867 
868 template <class O, class I> void NanoDisplayOps<O, I>::drawCanvas(lcdint_t x, lcdint_t y, NanoCanvasOps<4> &canvas)
869 {
870  this->drawBuffer4(x, y, canvas.width(), canvas.height(), canvas.getData());
871 }
872 
873 template <class O, class I> void NanoDisplayOps<O, I>::drawCanvas(lcdint_t x, lcdint_t y, NanoCanvasOps<8> &canvas)
874 {
875  this->drawBuffer8(x, y, canvas.width(), canvas.height(), canvas.getData());
876 }
877 
878 template <class O, class I> void NanoDisplayOps<O, I>::drawCanvas(lcdint_t x, lcdint_t y, NanoCanvasOps<16> &canvas)
879 {
880  this->drawBuffer16(x, y, canvas.width(), canvas.height(), canvas.getData());
881 }
882 
883 template <class O, class I> void NanoDisplayOps<O, I>::drawProgressBar(int8_t progress)
884 {
885  lcduint_t height = 8;
886  lcduint_t width = 8;
887  char str[5] = "100%";
888  if ( progress < 100 )
889  {
890  str[0] = ' ';
891  str[1] = progress / 10 + '0';
892  str[2] = progress % 10 + '0';
893  str[3] = '%';
894  }
895  if ( this->m_font != nullptr )
896  {
897  width = this->getFont().getTextSize(str, &height);
898  }
899  lcdint_t middle = this->height() / 2;
900  lcdint_t progress_pos = 8 + (int16_t)(this->width() - 16) * progress / 100;
901  uint16_t color = this->m_color;
902  this->m_color = 0x0000;
903  this->fillRect(progress_pos, middle, this->width() - 8, middle + height);
904  this->m_color = color;
905  this->drawRect(progress_pos, middle, this->width() - 8, middle + height);
906  this->fillRect(8, middle, progress_pos, middle + height);
907  if ( this->m_font != nullptr )
908  {
909  this->printFixed(this->width() / 2 - width / 2, middle - height, str);
910  }
911 }
912 
913 template <class O, class I>
914 void NanoDisplayOps<O, I>::drawWindow(lcdint_t x, lcdint_t y, lcduint_t width, lcduint_t height, const char *caption,
915  bool blank)
916 {
917  if ( width == 0 )
918  {
919  width = this->width() - 8;
920  x = 4;
921  }
922  if ( height == 0 )
923  {
924  height = this->height() - 4;
925  y = 0;
926  }
927  if ( blank )
928  {
929  uint16_t color = this->m_color;
930  this->m_color = 0x0000;
931  this->fillRect(x, y, x + width - 1, y + height - 1);
932  this->m_color = color;
933  }
934  if ( caption )
935  {
936  y += this->getFont().getHeader().height / 2;
937  height -= this->getFont().getHeader().height / 2;
938  }
939  this->drawRect(x, y, x + width - 1, y + height - 1);
940  if ( caption )
941  {
942  lcduint_t theight;
943  lcduint_t twidth = this->getFont().getTextSize(caption, &theight);
944  this->printFixed(x + (width - twidth) / 2, y - theight / 2, caption);
945  }
946 }
lcdint_t left
left offset
Definition: canvas_types.h:167
uint8_t height
char height in pixels
Definition: canvas_types.h:144
uint8_t lcduint_t
internal int type, used by the library.
Definition: canvas_types.h:79
Describes point.
Definition: point.h:39
void menuUp(SAppMenu *menu)
Moves selection pointer up by 1 item.
Structure describes single char information.
Definition: canvas_types.h:141
NanoRect structure describes rectangle area.
Definition: rect.h:42
lcdint_t height() const
returns height of NanoRect
Definition: rect.h:69
void drawRect(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2)
Draws rectangle.
NanoPoint p2
right-bottom point of the rectangle area
Definition: rect.h:48
void clearRect(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2)
Clears rectangle area (fills with black/zero color).
int8_t lcdint_t
internal int type, used by the library.
Definition: canvas_types.h:77
void write(const char *str)
Prints text at current cursor position.
void createMenu(SAppMenu *menu, const char **items, uint8_t count, const NanoRect &rect={})
Creates menu object with the provided list of menu items.
SSD1306 HAL IO communication functions.
lcduint_t width
width of menu
Definition: canvas_types.h:169
uint8_t * getData()
Return pointer to canvas pixels data.
Definition: canvas.h:428
lcdint_t y
y position in pixels
Definition: point.h:44
#define SSD1306_MORE_CHARS_REQUIRED
Flag means that more chars are required to decode utf-8.
Definition: canvas_types.h:43
lcduint_t width()
Returns canvas width in pixels.
Definition: canvas.h:434
NanoCanvasOps provides operations for drawing in memory buffer.
Definition: canvas.h:53
lcduint_t height
height of menu
Definition: canvas_types.h:171
void putPixel(const NanoPoint &p)
Draws pixel on specified position.
lcduint_t height()
Returns canvas height in pixels.
Definition: canvas.h:440
void drawCanvas(lcdint_t x, lcdint_t y, NanoCanvasOps< 1 > &canvas) __attribute__((noinline))
Draws 1-bit canvas on lcd display.
void drawCircle(lcdint_t xc, lcdint_t yc, lcdint_t r, uint8_t options=0x0F)
Draws circle.
void print(int number)
Prints number at current cursor position To specify cursor position using setTextCursor() method...
void drawLine(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2)
Draws line.
void updateMenuSmooth(SAppMenu *menu)
Updates menu items on the display.
void updateMenu(SAppMenu *menu)
Updates menu items on the display.
uint8_t selection
currently selected item. Internally updated.
Definition: canvas_types.h:159
void showMenuSmooth(SAppMenu *menu)
Shows menu items on the display.
uint8_t scrollPosition
position of menu scrolling. Internally updated
Definition: canvas_types.h:163
#define lcd_gfx_min(a, b)
Macros returning minimum of 2 numbers.
uint8_t count
count of menu items in the menu
Definition: canvas_types.h:157
uint8_t width
char width in pixels
Definition: canvas_types.h:143
void showMenu(SAppMenu *menu)
Shows menu items on the display.
Describes menu object.
Definition: canvas_types.h:152
lcdint_t top
top offset
Definition: canvas_types.h:165
void fillRect(const NanoRect &rect)
Fills rectangle area.
const uint8_t * glyph
char data, located in progmem.
Definition: canvas_types.h:146
lcdint_t width() const
returns width of NanoRect
Definition: rect.h:51
void drawProgressBar(int8_t progress)
Displays progress bar in the middle of the display.
uint8_t menuSelection(const SAppMenu *menu)
Returns currently selected menu item.
void printFixedPgm(lcdint_t xpos, lcdint_t y, const char *ch, EFontStyle style=STYLE_NORMAL) __attribute__((noinline))
Print text at specified position to canvas.
NanoCanvasOps provides operations for drawing in memory buffer.
Definition: display.h:978
uint8_t spacing
additional spaces after char in pixels
Definition: canvas_types.h:145
void drawWindow(lcdint_t x, lcdint_t y, lcduint_t width, lcduint_t height, const char *caption, bool blank)
Displays window at specified position and of specified size.
EFontStyle
Supported font styles.
Definition: canvas_types.h:88
NanoPoint p1
top-left point of the rectangle area
Definition: rect.h:45
lcdint_t x
x position in pixels
Definition: point.h:42
#define ssd1306_swap_data(a, b, type)
swaps content of a and b variables of type type
Definition: io.h:114
uint8_t oldSelection
selected item, when last redraw operation was performed. Internally updated.
Definition: canvas_types.h:161
void menuDown(SAppMenu *menu)
Moves selection pointer down by 1 item.
const char ** items
list of menu items of the menu
Definition: canvas_types.h:155