muwerk mupplet Display Library
muwerk applets; mupplets: functional units that support specific hardware or reusable applications
Loading...
Searching...
No Matches
mup_display.h
1// mup_display.h - mupplet display base class
2
3#pragma once
4
5#ifdef USTD_FEATURE_PROGRAMPLAYER
6#include "ustd_array.h"
7#include "timeout.h"
8#endif
9
10#include "muwerk.h"
11#include "scheduler.h"
12
13namespace ustd {
14
15#define MUPDISP_FEATURE_MONO B00000001
16#define MUPDISP_FEATURE_COLOR B00000010
17#define MUPDISP_FEATURE_FONTS B00000100
18#define MUPDISP_FEATURE_PROGRAMPLAYER B00001000
19
25 public:
26 static const char *formatTokens[];
27 static const char *colorTokens[];
28
29#ifdef USTD_FEATURE_PROGRAMPLAYER
30 static const char *modeTokens[];
31
33 enum Mode {
34 Left,
35 Center,
36 Right,
37 SlideIn,
38 };
39#endif
40
41 protected:
42 // font helper
43 typedef struct {
44 uint8_t baseLine;
45 uint8_t xAdvance;
46 uint8_t yAdvance;
47 uint8_t dummy;
48 } FontSize;
49
50#ifdef USTD_FEATURE_PROGRAMPLAYER
51 // program item
52 typedef struct {
53 String name;
54 Mode mode;
55 ustd::timeout duration;
56 int16_t repeat;
57 uint8_t speed;
58 uint8_t font;
59 uint16_t color;
60 uint16_t bg;
61 String content;
62 } ProgramItem;
63
64 // program item state
65 enum ProgramState { None, FadeIn, Wait, FadeOut, Finished };
66#endif
67
68 // muwerk task management
69 Scheduler *pSched;
70 int tID;
71
72 // device configuration
73 String name;
74
75 // runtime
76 uint8_t features;
77 uint8_t current_font;
78 uint16_t current_bg;
79 uint16_t current_fg;
80
81#ifdef USTD_FEATURE_PROGRAMPLAYER
82 // runtime - program control
83 ustd::array<ProgramItem> program;
84 ProgramItem default_item;
85 int16_t program_counter;
86 ProgramState program_state;
87 int16_t program_pos;
88 int16_t program_width;
89 uint8_t program_height;
90 unsigned long anonymous_counter;
91 // runtime - effect control
92 uint8_t delayCtr; // effect delay counter
93 uint16_t charPos; // index of char to slide
94 uint16_t lastPos; // target position of sliding char
95 uint16_t slidePos; // position of sliding char
96 uint8_t charX; // width of current char
97 uint8_t charY; // height of current char
98#endif
99
100 public:
101 MuppletDisplay(String name, uint8_t features) : name(name), features(features) {
102 current_font = 0;
103 current_bg = 0;
104 current_fg = features && MUPDISP_FEATURE_COLOR ? 0xffff : 1;
105#ifdef USTD_FEATURE_PROGRAMPLAYER
106 this->features |= MUPDISP_FEATURE_PROGRAMPLAYER;
107#else
108 this->features &= (~MUPDISP_FEATURE_PROGRAMPLAYER);
109#endif
110 }
111
112#ifdef USTD_FEATURE_PROGRAMPLAYER
117 void setPlayer(int16_t posY = -1, uint8_t height = 0) {
118 int16_t w, h;
119 getDimensions(w, h);
120 if (posY < 0 || height == 0) {
121 // player will be disabled - clear player area before disabling
122 displayClear(0, program_pos, w, program_height);
123 }
124 if (height > h) {
125 height = h;
126 }
127 if (posY > (h - height)) {
128 posY = h - height;
129 }
130 program_pos = posY;
131 program_width = w;
132 program_height = height;
133 if (height > 0) {
134 // player will be enabled - clear player area
135 displayClear(0, program_pos, w, program_height);
136 }
137 }
138
148 void setDefaults(Mode mode, unsigned long duration, int16_t repeat, uint8_t speed, uint8_t font,
149 uint16_t color = 0xffff, uint16_t bg = 0x0000) {
150 default_item.mode = mode;
151 default_item.duration = duration;
152 default_item.repeat = repeat;
153 default_item.speed = speed > 16 ? 16 : speed;
154 default_item.font = font < getTextFontCount() ? font : 0;
155 default_item.color = features & MUPDISP_FEATURE_COLOR ? color : color ? 1 : 0;
156 default_item.bg = features & MUPDISP_FEATURE_COLOR ? bg : bg ? 1 : 0;
157 }
158
161 void clearItems() {
162 program.erase();
163 program_counter = 0;
164 program_state = None;
165 displayClear(0, program_pos, program_width, program_height);
166 }
167#endif
168
169 protected:
170#ifdef USTD_FEATURE_PROGRAMPLAYER
171 void programInit() {
172 // initialize default values
173 default_item.mode = Left;
174 default_item.duration = 2000;
175 default_item.repeat = 1;
176 default_item.speed = 16;
177 default_item.font = 0;
178 default_item.color = features && MUPDISP_FEATURE_COLOR ? 0xffff : 1;
179 default_item.bg = 0;
180
181 // initialize state machine
182 program_pos = -1;
183 program_width = 0;
184 program_height = 0;
185 program_counter = 0;
186 program_state = None;
187 anonymous_counter = 0;
188 }
189
190 void programLoop() {
191 if (program.length() == 0 || program_height == 0 || program_pos < 0) {
192 return;
193 }
194 // save state
195 int16_t x, y, w, h;
196 bool cur_wrap = getTextWrap();
197 uint8_t cur_font = current_font;
198 uint16_t cur_fg = current_fg;
199 uint16_t cur_bg = current_bg;
200 getCursor(x, y);
201 getDimensions(w, h);
202
203 if (program_state == None) {
204 startProgramItem(program[program_counter], x, y, w, h);
205 }
206 if (program_state == FadeIn) {
207 fadeInProgramItem(program[program_counter], x, y, w, h);
208 }
209 if (program_state == Wait) {
210 waitProgramItem(program[program_counter], x, y, w, h);
211 }
212 if (program_state == FadeOut) {
213 fadeOutProgramItem(program[program_counter], x, y, w, h);
214 }
215 if (program_state == Finished) {
216 endProgramItem(program[program_counter], x, y, w, h);
217 }
218 if (program.length() == 0) {
219 displayClear(0, program_pos, program_width, program_height);
220 }
221
222 // restore state
223 setTextColor(cur_fg, cur_bg);
224 setTextFont(cur_font, 0);
225 setTextWrap(cur_wrap);
226 setCursor(x, y);
227 }
228#endif
229
230 virtual bool commandParser(String command, String args, String topic) {
231 if (command.startsWith("cmnd/")) {
232 return commandCmdParser(command.substring(5), args);
233 } else if (command.startsWith("cursor/")) {
234 return cursorParser(command.substring(7), args, topic + "/cursor");
235 } else if (command.startsWith("wrap/")) {
236 return wrapParser(command.substring(5), args, topic + "/wrap");
237 } else if (command.startsWith("color/")) {
238 return colorParser(command.substring(6), args, topic + "/color", true);
239 } else if (command.startsWith("background/")) {
240 return colorParser(command.substring(11), args, topic + "/background", false);
241#ifdef USTD_FEATURE_PROGRAMPLAYER
242 } else if (command == "count/get") {
243 return publishItemsCount();
244 } else if (command.startsWith("default/")) {
245 return commandDefaultParser(command.substring(8), args, topic + "/default");
246 } else if (command.startsWith("items/")) {
247 return commandItemsParser(command.substring(6), args, topic + "/items");
248 } else if (command.startsWith("content/")) {
249 return commandContentParser(command.substring(8), args, topic + "/content");
250#endif
251 }
252 return false;
253 }
254
255 virtual bool commandCmdParser(String command, String args) {
256 int16_t x, y, w, h, d, width, height;
257 FontSize fs;
258 array<String> params;
259 if (command == "clear") {
260 if (args.length()) {
261 split(args, ';', params);
262 }
263 getDimensions(width, height);
264 fs = getTextFontSize();
265 switch (params.length()) {
266 case 0:
267 // clear the whole screen
268 displayClear(0, 0, width, height);
269 setCursor(0, 0 + fs.baseLine);
270 return true;
271 case 1:
272 // clear the specified line
273 y = parseLong(params[0], 0);
274 displayClear(0, y * fs.yAdvance, width, fs.yAdvance);
275 setCursor(0, y + fs.baseLine);
276 return true;
277 case 2:
278 // clear the rect from specified coordinates
279 x = parseLong(params[0], 0);
280 y = parseLong(params[1], 0);
281 displayClear(x, y, width, height);
282 setCursor(x, y + fs.baseLine);
283 return true;
284 case 4:
285 // clear the rect with specified coordinates and size
286 x = parseLong(params[0], 0);
287 y = parseLong(params[1], 0);
288 w = parseLong(params[2], 0);
289 h = parseLong(params[3], 0);
290 displayClear(x, y, w, h);
291 setCursor(x, y + fs.baseLine);
292 return true;
293 }
294 } else if (command == "print") {
295 displayPrint(args);
296 return true;
297 } else if (command == "println") {
298 displayPrint(args, true);
299 return true;
300 } else if (command == "printat") {
301 getDimensions(width, height);
302 x = parseRangedLong(shift(args, ';', "0"), 0, width - 1, 0, width - 1);
303 y = parseRangedLong(shift(args, ';', "0"), 0, height - 1, 0, height - 1);
304 setCursor(x, y);
305 displayPrint(args);
306 return true;
307 } else if (command == "format") {
308 getDimensions(width, height);
309 x = parseLong(shift(args, ';', ""), 0);
310 y = parseLong(shift(args, ';', ""), 0);
311 h = parseToken(shift(args, ';', "left"), formatTokens);
312 if (h == 3) {
313 // float mode
314 String size = shift(args, ';');
315 w = parseLong(shift(size, '.', ""), width);
316 d = parseRangedLong(size, 0, w, 0, w);
317 if (args.length() == 0) {
318 // empty value - blank out area
319 fs = getTextFontSize();
320 displayClear(x, y, w, fs.yAdvance);
321 } else if (!isNumber(args.c_str())) {
322 // not a Number
323 displayError(x, y, w, 2);
324 } else {
325 args = String(atof(args.c_str()), d);
326 if (!displayFormat(x, y, w, 2, args, current_font, current_fg, current_bg)) {
327 // overflow
328 displayError(x, y, w, 2);
329 }
330 return true;
331 }
332 return true;
333 }
334 w = parseLong(shift(args, ';', ""), width);
335 displayFormat(x, y, w, h, args, current_font, current_fg, current_bg);
336 return true;
337 }
338 return false;
339 }
340
341 bool cursorParser(String command, String args, String topic) {
342 int16_t x, y, width, height;
343 if (command == "get") {
344 getCursor(x, y);
345 pSched->publish(topic, String(x) + ";" + String(y));
346 return true;
347 } else if (command == "set") {
348 getDimensions(width, height);
349 setCursor(parseRangedLong(shift(args, ';'), 0, width - 1, 0, width - 1),
350 parseRangedLong(shift(args, ';'), 0, height - 1, 0, height - 1));
351 getCursor(x, y);
352 pSched->publish(topic, String(x) + ";" + String(y));
353 return true;
354 } else if (command == "x/get") {
355 getCursor(x, y);
356 pSched->publish(topic + "/x", String(x));
357 return true;
358 } else if (command == "x/set") {
359 getCursor(x, y);
360 getDimensions(width, height);
361 setCursor(parseRangedLong(shift(args, ';'), 0, width - 1, 0, width - 1), y);
362 getCursor(x, y);
363 pSched->publish(topic + "/x", String(x));
364 return true;
365 } else if (command == "y/get") {
366 getCursor(x, y);
367 pSched->publish(topic + "/y", String(y));
368 } else if (command == "y/set") {
369 getCursor(x, y);
370 getDimensions(width, height);
371 setCursor(x, parseRangedLong(shift(args, ';'), 0, height - 1, 0, height - 1));
372 getCursor(x, y);
373 pSched->publish(topic + "/y", String(y));
374 return true;
375 }
376 return false;
377 }
378
379 bool wrapParser(String command, String args, String topic) {
380 if (command == "get") {
381 pSched->publish(topic, getTextWrap() ? "on" : "off");
382 return true;
383 } else if (command == "set") {
384 int8_t wrap = parseBoolean(args);
385 if (wrap >= 0) {
386 setTextWrap(wrap == 1);
387 pSched->publish(topic, getTextWrap() ? "on" : "off");
388 return true;
389 }
390 }
391 return false;
392 }
393
394 bool colorParser(String command, String args, String topic, bool fg) {
395 if (features & MUPDISP_FEATURE_MONO) {
396 if (command == "get") {
397 pSched->publish(topic, (fg ? current_fg : current_bg) ? "0x1" : "0x0");
398 return true;
399 } else if (command == "set") {
400 if (fg) {
401 current_fg = parseColor(args, current_fg) ? 1 : 0;
402 setTextColor(current_fg, current_bg);
403 pSched->publish(topic, current_fg ? "0x1" : "0x0");
404 } else {
405 current_bg = parseColor(args, current_bg) ? 1 : 0;
406 setTextColor(current_fg, current_bg);
407 pSched->publish(topic, current_bg ? "0x1" : "0x0");
408 }
409 }
410 } else if (features & MUPDISP_FEATURE_COLOR) {
411 if (command == "get") {
412 pSched->publish(topic, "0x" + String(fg ? current_fg : current_bg, HEX));
413 return true;
414 } else if (command == "set") {
415 if (fg) {
416 current_fg = parseColor(args, current_fg);
417 setTextColor(current_fg, current_bg);
418 pSched->publish(topic, "0x" + String(current_fg, HEX));
419 } else {
420 current_bg = parseColor(args, current_bg);
421 setTextColor(current_fg, current_bg);
422 pSched->publish(topic, "0x" + String(current_bg, HEX));
423 }
424 }
425 }
426 return false;
427 }
428
429 uint16_t parseColor(String &args, uint16_t defaultVal) {
430 args.trim();
431 switch (parseToken(args, colorTokens)) {
432 case 0: // black
433 return features & MUPDISP_FEATURE_COLOR ? 0x0000 : 0;
434 case 1: // white
435 return features & MUPDISP_FEATURE_COLOR ? 0x1111 : 1;
436 case 2: // red
437 return features & MUPDISP_FEATURE_COLOR ? 0xF800 : 1;
438 case 3: // green
439 return features & MUPDISP_FEATURE_COLOR ? 0x07E0 : 1;
440 case 4: // blue
441 return features & MUPDISP_FEATURE_COLOR ? 0x001F : 1;
442 case 5: // cyan
443 return features & MUPDISP_FEATURE_COLOR ? 0x07FF : 1;
444 case 6: // magenta
445 return features & MUPDISP_FEATURE_COLOR ? 0xF81F : 1;
446 case 7: // yellow
447 return features & MUPDISP_FEATURE_COLOR ? 0xFFE0 : 1;
448 case 8: // orange
449 return features & MUPDISP_FEATURE_COLOR ? 0xFC00 : 1;
450 }
451 if (args.length() == 0) {
452 return defaultVal;
453 }
454 uint16_t color = 0;
455 if (args.startsWith("0x")) {
456 if (args.length() > 6) {
457 return defaultVal;
458 }
459 for (const char *pPtr = args.c_str() + 2; *pPtr; pPtr++) {
460 color <<= 4;
461 if (*pPtr >= '0' && *pPtr <= '9') {
462 color |= (*pPtr - '0');
463 } else if (*pPtr >= 'A' && *pPtr <= 'F') {
464 color |= (*pPtr - 55);
465 } else if (*pPtr >= 'a' && *pPtr <= 'f') {
466 color |= (*pPtr - 87);
467 } else {
468 return defaultVal;
469 }
470 }
471 return color;
472 }
473 return defaultVal;
474 }
475
476#ifdef USTD_FEATURE_PROGRAMPLAYER
477 bool commandDefaultParser(String command, String args, String topic) {
478 if (command == "get") {
479 return publishDefaults(topic);
480 } else if (command == "set") {
481 if (parseDefaults(args)) {
482 return publishDefaults(topic);
483 }
484 } else if (command == "mode/get") {
485 return publishDefaultMode(topic + "/mode");
486 } else if (command == "mode/set") {
487 if (parseMode(args, default_item)) {
488 return publishDefaultMode(topic + "/mode");
489 }
490 } else if (command == "repeat/get") {
491 return publishDefaultRepeat(topic + "/repeat");
492 } else if (command == "repeat/set") {
493 if (parseRepeat(args, default_item)) {
494 return publishDefaultRepeat(topic + "/repeat");
495 }
496 } else if (command == "duration/get") {
497 return publishDefaultDuration(topic + "/duration");
498 } else if (command == "duration/set") {
499 if (parseDuration(args, default_item)) {
500 return publishDefaultDuration(topic + "/duration");
501 }
502 } else if (command == "speed/get") {
503 return publishDefaultSpeed(topic + "/speed");
504 } else if (command == "speed/set") {
505 if (parseSpeed(args, default_item)) {
506 return publishDefaultSpeed(topic + "/speed");
507 }
508 } else if (command == "font/get") {
509 return publishDefaultFont(topic + "/font");
510 } else if (command == "font/set") {
511 if (parseFont(args, default_item)) {
512 return publishDefaultFont(topic + "/font");
513 }
514 } else if (command == "color/get") {
515 return publishDefaultColor(topic + "/color");
516 } else if (command == "color/set") {
517 if (parseColor(args, default_item)) {
518 return publishDefaultColor(topic + "/Color");
519 }
520 } else if (command == "background/get") {
521 return publishDefaultBackground(topic + "/background");
522 } else if (command == "background/set") {
523 if (parseBackground(args, default_item)) {
524 return publishDefaultBackground(topic + "/background");
525 }
526 }
527 return false;
528 }
529
530 bool commandItemsParser(String command, String args, String topic) {
531 String name, operation;
532 if (command == "clear") {
533 clearItems();
534 return publishItemsCount();
535 } else if (command == "get") {
536 return publishItems(topic);
537 } else if (command == "add") {
538 addItem("unnamed_" + String(++anonymous_counter), args);
539 return publishItemsCount();
540 } else if (parseItemCommand(command, name, operation)) {
541 int16_t index = findItemByName(name.c_str());
542 if (operation == "set") {
543 if (index < 0) {
544 index = addItem(name, args);
545 } else {
546 index = replaceItem(index, args);
547 }
548 if (index >= 0) {
549 return publishItem(topic, index);
550 }
551 } else if (operation == "get") {
552 return publishItem(topic, index);
553 } else if (operation == "jump") {
554 if (jumpItem(index)) {
555 return publishItem(topic, index);
556 }
557 } else if (operation == "clear") {
558 if (clearItem(index)) {
559 return publishItemsCount();
560 }
561 }
562 }
563 return false;
564 }
565
566 bool commandContentParser(String command, String args, String topic) {
567 String name, operation;
568 if (command == "clear") {
569 clearItems();
570 return publishItemsCount();
571 } else if (command == "get") {
572 return publishContents(topic);
573 } else if (command == "add") {
574 addContent("unnamed_" + String(++anonymous_counter), args);
575 return publishItemsCount();
576 } else if (parseItemCommand(command, name, operation)) {
577 int16_t index = findItemByName(name.c_str());
578 if (operation == "set") {
579 if (index < 0) {
580 index = addContent(name, args);
581 } else {
582 index = replaceContent(index, args);
583 }
584 if (index >= 0) {
585 return publishContent(topic, index);
586 }
587 } else if (operation == "get") {
588 return publishContent(topic, index);
589 } else if (operation == "jump") {
590 if (jumpItem(index)) {
591 return publishContent(topic, index);
592 }
593 } else if (operation == "clear") {
594 if (clearItem(index)) {
595 return publishItemsCount();
596 }
597 }
598 }
599 return false;
600 }
601
602 bool parseItemCommand(String command, String &name, String &operation) {
603 const char *pPtr = command.c_str();
604 const char *pOperation = strrchr(pPtr, '/');
605 if (!pOperation) {
606 return false;
607 }
608 size_t len = pOperation - pPtr;
609 operation = pOperation + 1;
610 name = pPtr;
611 name.remove(len);
612 return name.length() && operation.length();
613 }
614
615 bool publishItemsCount() {
616 pSched->publish(name + "/display/count", String(program.length()));
617 return true;
618 }
619
620 bool parseDefaults(String args) {
621 bool changed = parseMode(shift(args, ';'), default_item);
622 changed = changed || parseRepeat(shift(args, ';'), default_item);
623 changed = changed || parseDuration(shift(args, ';'), default_item);
624 changed = changed || parseSpeed(shift(args, ';'), default_item);
625 changed = changed || parseFont(shift(args, ';'), default_item);
626 changed = changed || parseColor(shift(args, ';'), default_item);
627 changed = changed || parseBackground(shift(args, ';'), default_item);
628 return changed;
629 }
630
631 bool publishDefaults(String topic) {
632 pSched->publish(topic, getItemString(default_item));
633 return true;
634 }
635
636 bool parseMode(String args, ProgramItem &item) {
637 int16_t iMode = parseToken(args, modeTokens);
638 if (iMode >= 0 && iMode != item.mode) {
639 item.mode = (Mode)iMode;
640 return true;
641 }
642 return false;
643 }
644
645 bool publishDefaultMode(String topic) {
646 pSched->publish(topic + "/mode", modeTokens[default_item.mode]);
647 return true;
648 }
649
650 bool parseRepeat(String args, ProgramItem &item) {
651 if (args.length()) {
652 int16_t value = parseRangedLong(args, 0, 32767, 0, 32767);
653 if (value != item.repeat) {
654 item.repeat = value;
655 return true;
656 }
657 }
658 return false;
659 }
660
661 bool publishDefaultRepeat(String topic) {
662 pSched->publish(topic + "/repeat", String(default_item.repeat));
663 return true;
664 }
665
666 bool parseDuration(String args, ProgramItem &item) {
667 if (args.length()) {
668 long value = atol(args.c_str());
669 if (value >= 0 && (unsigned long)value != item.duration) {
670 item.duration = value;
671 return true;
672 }
673 }
674 return false;
675 }
676
677 bool publishDefaultDuration(String topic) {
678 pSched->publish(topic + "/duration", String(default_item.duration));
679 return true;
680 }
681
682 bool parseSpeed(String args, ProgramItem &item) {
683 if (args.length()) {
684 long value = parseRangedLong(args, 1, 16, 0, 16);
685 if (value && value != item.speed) {
686 item.speed = value;
687 return true;
688 }
689 }
690 return false;
691 }
692
693 bool publishDefaultSpeed(String topic) {
694 pSched->publish(topic + "/speed", String(default_item.speed));
695 return true;
696 }
697
698 bool parseFont(String args, ProgramItem &item) {
699 if (args.length()) {
700 int value = atoi(args.c_str());
701 if (value >= 0 && value < (int)getTextFontCount() && value != item.font) {
702 item.font = value;
703 return true;
704 }
705 }
706 return false;
707 }
708
709 bool publishDefaultFont(String topic) {
710 pSched->publish(topic + "/font", String(default_item.font));
711 return true;
712 }
713
714 bool parseColor(String args, ProgramItem &item) {
715 if (args.length()) {
716 item.color = parseColor(args, item.color);
717 }
718 return false;
719 }
720
721 bool publishDefaultColor(String topic) {
722 pSched->publish(topic + "/color", "0x" + String(default_item.color, HEX));
723 return true;
724 }
725
726 bool parseBackground(String args, ProgramItem &item) {
727 if (args.length()) {
728 item.bg = parseColor(args, item.bg);
729 }
730 return false;
731 }
732
733 bool publishDefaultBackground(String topic) {
734 pSched->publish(topic + "/background", "0x" + String(default_item.bg, HEX));
735 return true;
736 }
737
738 int16_t addItem(String name, String args) {
739 ProgramItem item = default_item;
740 item.name = name;
741 parseMode(shift(args, ';'), item);
742 parseRepeat(shift(args, ';'), item);
743 parseDuration(shift(args, ';'), item);
744 parseSpeed(shift(args, ';'), item);
745 parseFont(shift(args, ';'), item);
746 parseColor(shift(args, ';'), item);
747 parseBackground(shift(args, ';'), item);
748 item.content = args;
749 return program.add(item);
750 }
751
752 int16_t replaceItem(int16_t i, String args) {
753 if (i < 0 || i > (int16_t)program.length() - 1) {
754 return -1;
755 }
756 ProgramItem item = program[i];
757 parseMode(shift(args, ';'), item);
758 parseRepeat(shift(args, ';'), item);
759 parseDuration(shift(args, ';'), item);
760 parseSpeed(shift(args, ';'), item);
761 parseFont(shift(args, ';'), item);
762 parseColor(shift(args, ';'), item);
763 parseBackground(shift(args, ';'), item);
764 item.content = args;
765 program[i] = item;
766 if (program_counter == i) {
767 changedProgramItem(program[i]);
768 }
769 return i;
770 }
771
772 bool jumpItem(int16_t i) {
773 if (i < 0 || i > (int16_t)program.length() - 1) {
774 return false;
775 }
776 program_counter = i;
777 program_state = None;
778 return true;
779 }
780
781 bool clearItem(int16_t i) {
782 if (i < 0 || i > (int16_t)program.length() - 1) {
783 return false;
784 }
785 program.erase(i);
786 if (program_counter > i) {
787 // adjust program counter
788 program_counter--;
789 } else if (program_counter == i) {
790 // the deleted item was the current item. program counter is OK, but we need to
791 // reset the sequence in order to start the next item immediately
792 program_state = None;
793 if (program_counter >= (int16_t)program.length()) {
794 program_counter = 0;
795 }
796 }
797 if (program.length() == 0) {
798 displayClear(0, program_pos, program_width, program_height);
799 }
800 return true;
801 }
802
803 bool publishItem(String topic, int16_t i) {
804 if (i < 0 || i > (int16_t)program.length() - 1) {
805 return true;
806 }
807 pSched->publish(topic + "/" + program[i].name, getItemString(program[i]));
808 return true;
809 }
810
811 bool publishItems(String topic) {
812 for (unsigned int i = 0; i < program.length(); i++) {
813 pSched->publish(topic + "/" + program[i].name, getItemString(program[i]));
814 }
815 return true;
816 }
817
818 int16_t addContent(String name, String args) {
819 ProgramItem item = default_item;
820 item.name = name;
821 item.content = args;
822 program.add(item);
823 return program.length();
824 }
825
826 int16_t replaceContent(int16_t i, String args) {
827 if (i < 0 || i > (int16_t)program.length() - 1) {
828 return -1;
829 }
830 program[i].content = args;
831 if (program_counter == i) {
832 changedProgramItem(program[i]);
833 }
834 return i;
835 }
836
837 bool publishContent(String topic, int16_t i) {
838 if (i < 0 || i > (int16_t)program.length() - 1) {
839 return true;
840 }
841 pSched->publish(topic + "/" + program[i].name, program[i].content);
842 return true;
843 }
844
845 bool publishContents(String topic) {
846 for (unsigned int i = 0; i < program.length(); i++) {
847 pSched->publish(topic + "/" + program[i].name, program[i].content);
848 }
849 return true;
850 }
851
852 int16_t findItemByName(const char *name) {
853 for (int16_t i = 0; i < (int16_t)program.length(); i++) {
854 if (program[i].name == name) {
855 return i;
856 }
857 }
858 return -1;
859 }
860
861 String getItemString(ProgramItem &item) {
862 String itemString = modeTokens[item.mode];
863 itemString.concat(";");
864 itemString.concat(item.repeat);
865 itemString.concat(";");
866 itemString.concat(item.duration);
867 itemString.concat(";");
868 itemString.concat(item.speed);
869 itemString.concat(";");
870 itemString.concat(item.font);
871 itemString.concat(";");
872 itemString.concat("0x" + String(item.color, HEX));
873 itemString.concat(";");
874 itemString.concat("0x" + String(item.bg, HEX));
875 if (item.content.length()) {
876 itemString.concat(";");
877 itemString.concat(item.content);
878 }
879 return itemString;
880 }
881#endif
882
883 // can be removed with release of mupplet-core 0.4.1
884 bool isNumber(const char *value, bool integer = false) {
885 if (!value) {
886 return false;
887 }
888 if (*value == '-') {
889 value++;
890 }
891 bool decimalpoint = false;
892 while (*value) {
893 if (*value < '0' || *value > '9') {
894 if (integer || decimalpoint || *value != '.') {
895 return false;
896 } else {
897 decimalpoint = true;
898 }
899 }
900 value++;
901 }
902 return true;
903 }
904
905 void displayError(int16_t x, int16_t y, int16_t w, int16_t align) {
906 if (w >= 5) {
907 displayFormat(x, y, w, align, "Error", current_font, current_fg, current_bg);
908 } else if (w >= 3) {
909 displayFormat(x, y, w, align, "Err", current_font, current_fg, current_bg);
910 } else {
911 displayFormat(x, y, w, align, "E", current_font, current_fg, current_bg);
912 }
913 }
914
915 // abstract methods
916 virtual void getDimensions(int16_t &width, int16_t &height) = 0;
917 virtual bool getTextWrap() = 0;
918 virtual void setTextWrap(bool wrap) = 0;
919 virtual FontSize getTextFontSize() = 0;
920 virtual uint8_t getTextFontCount() = 0;
921 virtual void setTextFont(uint8_t font, int16_t baseLineAdjustment) = 0;
922 virtual void setTextColor(uint16_t fg, uint16_t bg) = 0;
923 virtual void getCursor(int16_t &x, int16_t &y) = 0;
924 virtual void setCursor(int16_t x, int16_t y) = 0;
925 virtual void displayClear(int16_t x, int16_t y, int16_t w, int16_t h) = 0;
926 virtual void displayClear(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t bg) = 0;
927 virtual void displayPrint(String content, bool ln = false) = 0;
928 virtual bool displayFormat(int16_t x, int16_t y, int16_t w, int16_t align, String content,
929 uint8_t font, uint16_t color, uint16_t bg) = 0;
930
931#ifdef USTD_FEATURE_PROGRAMPLAYER
932 virtual void changedProgramItem(ProgramItem &item) {
933 switch (item.mode) {
934 case Left:
935 displayFormat(0, program_pos, program_width, 0, item.content, item.font, item.color,
936 item.bg);
937 break;
938 case Center:
939 displayFormat(0, program_pos, program_width, 1, item.content, item.font, item.color,
940 item.bg);
941 break;
942 case Right:
943 displayFormat(0, program_pos, program_width, 2, item.content, item.font, item.color,
944 item.bg);
945 break;
946 case SlideIn:
947 if (program_state == FadeIn && charPos < item.content.length() - 1) {
948 int16_t x, y;
949 displayFormat(0, program_pos, program_width, 0, item.content.substring(0, charPos),
950 item.font, item.color, item.bg);
951 getCursor(x, y);
952 lastPos = x;
953 initNextCharDimensions(item);
954 } else {
955 displayFormat(0, program_pos, program_width, 0, item.content, item.font, item.color,
956 item.bg);
957 program_state = Wait;
958 }
959 break;
960 default:
961 break;
962 }
963 }
964
965 virtual void startProgramItem(ProgramItem &item, int16_t x, int16_t y, int16_t w, int16_t h) {
966 item.duration.reset();
967 switch (item.mode) {
968 case Left:
969 displayFormat(0, program_pos, program_width, 0, item.content, item.font, item.color,
970 item.bg);
971 program_state = Wait;
972 break;
973 case Center:
974 displayFormat(0, program_pos, program_width, 1, item.content, item.font, item.color,
975 item.bg);
976 program_state = Wait;
977 break;
978 case Right:
979 displayFormat(0, program_pos, program_width, 2, item.content, item.font, item.color,
980 item.bg);
981 program_state = Wait;
982 break;
983 case SlideIn:
984 charPos = 0;
985 lastPos = 0;
986 delayCtr = 17 - item.speed;
987 slidePos = w;
988 if (initNextCharDimensions(item)) {
989 displayClear(0, program_pos, program_width, program_height);
990 program_state = FadeIn;
991 } else {
992 displayFormat(0, program_pos, program_width, 0, item.content, item.font, item.color,
993 item.bg);
994 program_state = Wait;
995 }
996 break;
997 default:
998 program_state = Finished;
999 break;
1000 }
1001 }
1002
1003 virtual void fadeInProgramItem(ProgramItem &item, int16_t x, int16_t y, int16_t w, int16_t h) {
1004 if (item.mode == SlideIn) {
1005 if (--delayCtr) {
1006 return;
1007 }
1008 delayCtr = 17 - item.speed;
1009 slidePos--;
1010 displayFormat(slidePos, program_pos, program_width - slidePos, 0,
1011 item.content.substring(charPos, charPos + 1), item.font, item.color,
1012 item.bg);
1013
1014 // prepare for next iterations:
1015 if (slidePos <= lastPos) {
1016 // char has arrived
1017 lastPos += charX;
1018 slidePos = w;
1019 if (lastPos >= slidePos) {
1020 // display full
1021 fadeInEnd(item, x, y, w, h);
1022 return;
1023 }
1024 ++charPos;
1025 if (!initNextCharDimensions(item)) {
1026 // end of string
1027 fadeInEnd(item, x, y, w, h);
1028 return;
1029 }
1030 }
1031 } else {
1032 fadeInEnd(item, x, y, w, h);
1033 return;
1034 }
1035 }
1036
1037 virtual void fadeInEnd(ProgramItem &item, int16_t x, int16_t y, int16_t w, int16_t h) {
1038 item.duration.reset();
1039 program_state = Wait;
1040 }
1041
1042 virtual void waitProgramItem(ProgramItem &item, int16_t x, int16_t y, int16_t w, int16_t h) {
1043 if (item.duration.test()) {
1044 program_state = FadeOut;
1045 }
1046 }
1047
1048 virtual void fadeOutProgramItem(ProgramItem &item, int16_t x, int16_t y, int16_t w, int16_t h) {
1049 // switch (item.mode) {
1050 // case Left:
1051 // case Center:
1052 // case Right:
1053 // case SlideIn:
1054 // default:
1055 // }
1056 fadeOutEnd(item, x, y, w, h);
1057 }
1058
1059 virtual void fadeOutEnd(ProgramItem &item, int16_t x, int16_t y, int16_t w, int16_t h) {
1060 program_state = Finished;
1061 }
1062
1063 virtual void endProgramItem(ProgramItem &item, int16_t x, int16_t y, int16_t w, int16_t h) {
1064 program_state = None;
1065 if (item.repeat) {
1066 if (--item.repeat) {
1067 // item still active -> skip to next
1068 program_counter++;
1069 } else {
1070 // remove current item from program
1071 program.erase(program_counter);
1072 }
1073 } else {
1074 program_counter++;
1075 }
1076 if (program_counter >= (int16_t)program.length()) {
1077 program_counter = 0;
1078 }
1079 }
1080
1081 virtual bool initNextCharDimensions(ProgramItem &item) = 0;
1082
1083#endif
1084};
1085
1086const char *MuppletDisplay::formatTokens[] = {"left", "center", "right", "number", nullptr};
1087const char *MuppletDisplay::colorTokens[] = {"black", "white", "red", "green", "blue",
1088 "cyan", "magenta", "yellow", "orange", nullptr};
1089
1090#ifdef USTD_FEATURE_PROGRAMPLAYER
1091const char *MuppletDisplay::modeTokens[] = {"left", "center", "right", "slidein", nullptr};
1092#endif
1093} // namespace ustd
The base class for all display mupplets.
Definition: mup_display.h:24
The muwerk namespace.
Definition: display_digits_max72xx.h:10