Krolyxon
Commit 1d55752

Branches

Tags

Refs →

orion-rf.piolibdepsesp32-s3-n16r8U8g2srcclibmui.c

960 lines · 30.7 KB · C

1/*
2
3 mui.c
4
5 Monochrome minimal user interface: Core library.
6
7 Universal 8bit Graphics Library (https://github.com/olikraus/u8g2/)
8
9 Copyright (c) 2021, olikraus@gmail.com
10 All rights reserved.
11
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions are met:
14
15 * Redistributions of source code must retain the above copyright notice, this
16 list of conditions and the following disclaimer.
17
18 * Redistributions in binary form must reproduce the above copyright notice,
19 this list of conditions and the following disclaimer in the documentation
20 and/or other materials provided with the distribution.
21
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23 CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
24 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
27 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
34 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
36
37
38 "mui.c" is a graphical user interface, developed as part of u8g2.
39 However "mui.c" is independent of u8g2 and can be used without u8g2 code.
40 The glue code between "mui.c" and u8g2 is located in "mui_u8g2.c"
41
42 c: cmd
43 i: ID0
44 j: ID1
45 xy: Position (x and y)
46 /text/: some text. The text can start with any delimiter (except 0 and |), but
47 also has to end with the same delimiter a: Single char argument u: Single char
48 argument with the user interface form number
49
50 "Uu" the interface --> no
51 ID
52
53 Manual ID:
54 "Fijxy" Generic field: Places field with id ii at x/y --> ID=ij
55 "Bijxy/text/" Generic field (Button) with Text --> ID=ij
56 "Tiixya/text/" Generic field with argument and text --> ID = ij
57 "Aiixya"
58
59 Fixed ID:
60 "Si" the style -->
61 ID=@i "Lxy/labeltext/" Places a text at the specified position, field with -
62 -> ID=.L, .l "Gxyu/menutext/" Go to the specified menu without placing the
63 user interface form on the stack --> ID=.G, .g
64
65
66 cijxy
67 cijxy/text/
68 cijxya/text/
69
70 cxy/text/
71 cxya/text/
72
73*/
74
75#include "mui.h"
76
77// #define mui_get_fds_char(s) ((uint8_t)(*s))
78
79// #include <stdio.h>
80// #define MUI_DEBUG(...) printf(__VA_ARGS__)
81#define MUI_DEBUG(...)
82
83uint8_t mui_get_fds_char(fds_t *s) {
84 // return (uint8_t)(*s);
85 return (uint8_t)mui_pgm_read(s);
86}
87
88/*
89 s must point to a valid command within FDS
90*/
91static size_t mui_fds_get_cmd_size_without_text(fds_t *s) MUI_NOINLINE;
92static size_t mui_fds_get_cmd_size_without_text(fds_t *s) {
93 uint8_t c = mui_get_fds_char(s);
94 c &= 0xdf; /* consider upper and lower case */
95 switch (c) {
96 case 'U':
97 return 2; // User Form: CMD (1 Byte), Form-Id (1 Byte)
98 case 'S':
99 return 2; // Style: CMD (1 Byte), Style Id (1 Byte)
100 case 'D':
101 return 3; // Data within Text: CMD (1 Byte), ID (2 Bytes), Text (does
102 // not count here)
103 case 'Z':
104 return 3; // Zero field without x, y, arg & text: CMD (1 Byte), ID (2
105 // Bytes)
106 case 'F':
107 return 5; // Field without arg & text: CMD (1 Byte), ID (2 Bytes), X, Y
108 case 'B':
109 return 5; // Field with text: CMD (1 Byte), ID (2 Bytes), X, Y, Text
110 // (does not count here)
111 case 'T':
112 return 6; // Field with arg & text: CMD (1 Byte), ID (2 Bytes), X, Y,
113 // Arg, Text (does not count here)
114 case 'A':
115 return 6; // Field with arg (no text): CMD (1 Byte), ID (2 Bytes), X, Y,
116 // Arg, Text
117 case 'L':
118 return 3; // Text Label: CMD (1 Byte), X, Y (same as 'B' but with fixed
119 // ID '.L', MUIF_LABEL, MUI_LABEL)
120 case 'G':
121 return 4; // Goto Btutton: CMD (1Byte), X, Y, Arg, Text (same as 'T'
122 // but with fixed ID '.G', MUIF_GOTO, MUI_GOTO)
123 case 0:
124 return 0;
125 }
126 return 1;
127}
128
129/*
130 s must point to the string delimiter start: first '/' for "B00ab/ok/"
131 - '/' actually is 0xff
132 - return the total size of the string, including the delimiter
133 - copies the content of the string ("ok") to the ui text buffer
134
135*/
136static size_t mui_fds_parse_text(mui_t *ui, fds_t *s) {
137 uint8_t i = 0;
138 ui->delimiter = mui_get_fds_char(s);
139 uint8_t c;
140 fds_t *t = s;
141
142 // printf("mui_fds_parse_text del=%d\n", delimiter);
143#ifdef MUI_CHECK_EOFDS
144 if (ui->delimiter == 0)
145 return 0;
146#endif
147 t++;
148 for (;;) {
149 c = mui_get_fds_char(t);
150 // printf("mui_fds_parse_text i=%d, c=%c\n", i, c);
151#ifdef MUI_CHECK_EOFDS
152 if (c == 0)
153 break;
154#endif
155 if (c == ui->delimiter) {
156 t++;
157 break;
158 }
159 if (i < MUI_MAX_TEXT_LEN) {
160 ui->text[i++] = c;
161 }
162 t++;
163 }
164 ui->text[i] = '\0';
165 return t - s;
166}
167
168/*
169 get the first token within a text argument.
170 The text argument may look like this:
171 "B00ab/banana|apple|peach|cherry/"
172 The outer delimiter "/" is not fixed and can be any char except "|" and "\0"
173 The inner delimiter "|" is fixed. It must be the pipe symbol.
174 This function will place "banana" into ui->text if the result is not 0
175
176 if ( mui_fds_first_token(ui) )
177 {
178 do
179 {
180 // handle token in ui->text
181 } while ( mui_fds_next_token(ui) )
182 }
183
184*/
185
186uint8_t mui_fds_first_token(mui_t *ui) {
187 ui->token = ui->fds;
188 ui->token += mui_fds_get_cmd_size_without_text(ui->fds);
189 ui->delimiter = mui_get_fds_char(ui->token);
190 ui->token++; // place ui->token on the first char of the token
191 return mui_fds_next_token(ui);
192}
193
194/*
195 The inner token delimiter "|" is fixed. It must be the pipe symbol.
196*/
197uint8_t mui_fds_next_token(mui_t *ui) {
198 uint8_t c;
199 uint8_t i = 0;
200 // printf("mui_fds_next_token: call, ui->token=%p\n", ui->token);
201 for (;;) {
202 c = mui_get_fds_char(ui->token);
203 // printf("mui_fds_next_token: i=%d c=%c\n", i, c);
204#ifdef MUI_CHECK_EOFDS
205 if (c == 0)
206 break;
207#endif
208 if (c == ui->delimiter)
209 break;
210 if (c == '|') {
211 ui->token++; // place ui->token on the first char of the next token
212 break;
213 }
214
215 if (i < MUI_MAX_TEXT_LEN) {
216 ui->text[i++] = c;
217 }
218
219 ui->token++;
220 }
221 ui->text[i] = '\0';
222 if (i == 0)
223 return 0; // no further token found
224 return 1; // token placed in ui->text
225}
226
227/*
228 find nth token ('|' delimiter), return 0 if n exceeds the number of tokens, 1
229 otherwise the result is stored in ui->text
230*/
231uint8_t mui_fds_get_nth_token(mui_t *ui, uint8_t n) {
232 // printf("mui_fds_get_nth_token: call, n=%d\n", n);
233 if (mui_fds_first_token(ui)) {
234 do {
235 if (n == 0) {
236 // printf("mui_fds_get_nth_token: found");
237 return 1;
238 }
239 n--;
240 } while (mui_fds_next_token(ui));
241 }
242 // printf("mui_fds_get_nth_token: NOT found\n");
243 return 0;
244}
245
246uint8_t mui_fds_get_token_cnt(mui_t *ui) {
247 uint8_t n = 0;
248 if (mui_fds_first_token(ui)) {
249 do {
250 n++;
251 } while (mui_fds_next_token(ui));
252 }
253 return n;
254}
255
256#define mui_fds_is_text(c) \
257 ((c) == 'U' || (c) == 'S' || (c) == 'F' || (c) == 'A' || (c) == 'Z' ? 0 : 1)
258
259/*
260 s must point to a valid command within FDS
261 return
262 The complete length of the command (including any text part)
263 sideeffect:
264 Any existing text part will be copied into ui->text
265 ui->text will be assigned to empty string if there is no text argument
266*/
267static size_t mui_fds_get_cmd_size(mui_t *ui, fds_t *s) MUI_NOINLINE;
268static size_t mui_fds_get_cmd_size(mui_t *ui, fds_t *s) {
269 size_t l = mui_fds_get_cmd_size_without_text(s);
270 uint8_t c = mui_get_fds_char(s);
271 ui->text[0] = '\0'; /* always reset the text buffer */
272 if (mui_fds_is_text(c)) {
273 l += mui_fds_parse_text(ui, s + l);
274 }
275 return l;
276}
277
278/*
279 mui_Init() will setup the menu system but will not activate or display
280 anything. Use mui_GotoForm() after this command, then use mui_Draw() to draw
281 the menu on a display.
282*/
283void mui_Init(mui_t *ui, void *graphics_data, fds_t *fds, muif_t *muif_tlist,
284 size_t muif_tcnt) {
285 memset(ui, 0, sizeof(mui_t));
286 ui->root_fds = fds;
287 // ui->current_form_fds = NULL; // not required, because there was a
288 // memset before
289 ui->muif_tlist = muif_tlist;
290 ui->muif_tcnt = muif_tcnt;
291 ui->graphics_data = graphics_data;
292 ui->last_form_stack_pos = -1;
293}
294
295static int mui_find_uif(mui_t *ui, uint8_t id0, uint8_t id1) {
296 size_t i;
297 for (i = 0; i < ui->muif_tcnt; i++) {
298 /*
299 if ( ui->muif_tlist[i].id0 == id0 )
300 if ( ui->muif_tlist[i].id1 == id1 )
301 return i;
302 */
303 if (muif_get_id0(ui->muif_tlist + i) == id0)
304 if (muif_get_id1(ui->muif_tlist + i) == id1)
305 return i;
306 }
307 return -1;
308}
309
310/*
311 assumes a valid position in ui->fds and calculates all the other variables
312 some fields are always calculated like the ui->cmd and ui->len field
313 other member vars are calculated only if the return value is 1
314 will return 1 if the field id was found.
315 will return 0 if the field id was not found in uif or if ui->fds points to
316 something else than a field
317*/
318static uint8_t mui_prepare_current_field(mui_t *ui) MUI_NOINLINE;
319static uint8_t mui_prepare_current_field(mui_t *ui) {
320 int muif_tidx;
321
322 ui->uif = NULL;
323 ui->dflags = 0;
324 ui->id0 = 0;
325 ui->id1 = 0;
326 ui->arg = 0;
327
328 /* calculate the length of the command and copy the text argument */
329 /* this will also clear the text in cases where there is no text argument */
330 ui->len = mui_fds_get_cmd_size(ui, ui->fds);
331 // printf("mui_prepare_current_field len=%d\n", ui->len);
332
333 /* get the command and check whether end of form is reached */
334 ui->cmd = mui_get_fds_char(ui->fds);
335 // printf("mui_prepare_current_field cmd='%c' len=%d\n", ui->cmd, ui->len);
336
337 /* Copy the cmd also to second id value. This is required for some commands,
338 * others will overwrite this below */
339 ui->id1 = ui->cmd;
340
341 /* now make the command uppercase so that both, upper and lower case are
342 * considered */
343 ui->cmd &= 0xdf; /* consider upper and lower case */
344
345 if (ui->cmd == 'U' || ui->cmd == 0)
346 return 0;
347
348 /* calculate the dynamic flags */
349 if (ui->fds == ui->cursor_focus_fds)
350 ui->dflags |= MUIF_DFLAG_IS_CURSOR_FOCUS;
351 if (ui->fds == ui->touch_focus_fds)
352 ui->dflags |= MUIF_DFLAG_IS_TOUCH_FOCUS;
353
354 /* get the id0 and id1 values */
355 if (ui->cmd == 'F' || ui->cmd == 'B' || ui->cmd == 'T' || ui->cmd == 'A') {
356 ui->id0 = mui_get_fds_char(ui->fds + 1);
357 ui->id1 = mui_get_fds_char(ui->fds + 2);
358 ui->x = mui_get_fds_char(ui->fds + 3);
359 ui->y = mui_get_fds_char(ui->fds + 4);
360 if (ui->cmd == 'A' || ui->cmd == 'T') {
361 ui->arg = mui_get_fds_char(ui->fds + 5);
362 }
363 } else if (ui->cmd == 'D' || ui->cmd == 'Z') {
364 ui->id0 = mui_get_fds_char(ui->fds + 1);
365 ui->id1 = mui_get_fds_char(ui->fds + 2);
366 } else if (ui->cmd == 'S') {
367 ui->id0 = 'S';
368 ui->id1 = mui_get_fds_char(ui->fds + 1);
369 } else {
370 ui->id0 = '.';
371 /* note that ui->id1 contains the original cmd value */
372 ui->x = mui_get_fds_char(ui->fds + 1);
373 ui->y = mui_get_fds_char(ui->fds + 2);
374 if (ui->cmd == 'G' ||
375 ui->cmd == 'M') /* this is also true for 'g' or 'm' */
376 {
377 ui->arg = mui_get_fds_char(ui->fds + 3);
378 }
379 }
380
381 // MUI_DEBUG("mui_prepare_current_field cmd='%c' len=%d arg=%d\n", ui->cmd,
382 // ui->len, ui->arg);
383
384 /* find the field */
385 muif_tidx = mui_find_uif(ui, ui->id0, ui->id1);
386 // printf("mui_prepare_current_field: muif_tidx=%d\n", muif_tidx);
387 if (muif_tidx >= 0) {
388 ui->uif = ui->muif_tlist + muif_tidx;
389 return 1;
390 }
391 return 0;
392}
393
394/*
395 assumes that ui->fds has been assigned correctly
396 and that ui->target_fds and ui->tmp_fds had been cleared if required
397
398 Usually do not call this function directly, instead use mui_loop_over_form
399
400*/
401
402static void mui_inner_loop_over_form(mui_t *ui,
403 uint8_t (*task)(mui_t *ui)) MUI_NOINLINE;
404static void mui_inner_loop_over_form(mui_t *ui, uint8_t (*task)(mui_t *ui)) {
405 uint8_t cmd;
406
407 // MUI_DEBUG("mui_inner_loop_over_form start %p\n", task);
408
409 ui->fds += mui_fds_get_cmd_size(
410 ui, ui->fds); // skip the first entry, it is U always
411 for (;;) {
412 // printf("fds=%p *fds='%c'\n", ui->fds, ui->fds[0]);
413 /* get the command and check whether end of form is reached */
414 cmd = mui_get_fds_char(ui->fds);
415 if (cmd == 'U' || cmd == 0)
416 break;
417 if (mui_prepare_current_field(ui)) /* side effect: calculate ui->len */
418 if (task(ui)) /* call the task, which was provided as argument to
419 this function */
420 {
421 // MUI_DEBUG("mui_inner_loop_over_form break by task\n");
422 break;
423 }
424 ui->fds += ui->len;
425 }
426
427 // MUI_DEBUG("mui_inner_loop_over_form end %p\n", task);
428}
429
430static void mui_loop_over_form(mui_t *ui,
431 uint8_t (*task)(mui_t *ui)) MUI_NOINLINE;
432static void mui_loop_over_form(mui_t *ui, uint8_t (*task)(mui_t *ui)) {
433 if (mui_IsFormActive(ui) == 0)
434 return;
435
436 ui->fds = ui->current_form_fds;
437 ui->target_fds = NULL;
438 ui->tmp_fds = NULL;
439
440 mui_inner_loop_over_form(ui, task);
441}
442
443/*
444 n is the form number
445*/
446static fds_t *mui_find_form(mui_t *ui, uint8_t n) {
447 fds_t *fds = ui->root_fds;
448 uint8_t cmd;
449
450 for (;;) {
451 cmd = mui_get_fds_char(fds);
452 if (cmd == 0)
453 break;
454 if (cmd == 'U') {
455 if (mui_get_fds_char(fds + 1) == n) {
456 return fds;
457 }
458 /* not found, just coninue */
459 }
460
461 fds += mui_fds_get_cmd_size(ui, fds);
462 }
463 return NULL;
464}
465
466/* === task procedures (arguments for mui_loop_over_form) === */
467/* ui->fds contains the current field */
468
469static uint8_t mui_task_draw(mui_t *ui) {
470 // printf("mui_task_draw fds=%p uif=%p text=%s\n", ui->fds, ui->uif,
471 // ui->text);
472 muif_get_cb(ui->uif)(ui, MUIF_MSG_DRAW);
473 return 0; /* continue with the loop */
474}
475
476static uint8_t mui_task_form_start(mui_t *ui) {
477 muif_get_cb(ui->uif)(ui, MUIF_MSG_FORM_START);
478 return 0; /* continue with the loop */
479}
480
481static uint8_t mui_task_form_end(mui_t *ui) {
482 muif_get_cb(ui->uif)(ui, MUIF_MSG_FORM_END);
483 return 0; /* continue with the loop */
484}
485
486static uint8_t mui_uif_is_cursor_selectable(mui_t *ui) MUI_NOINLINE;
487static uint8_t mui_uif_is_cursor_selectable(mui_t *ui) {
488 if (muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE) {
489 return 1;
490 }
491 return 0;
492}
493
494static uint8_t mui_task_find_prev_cursor_uif(mui_t *ui) {
495 // if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
496 if (mui_uif_is_cursor_selectable(ui)) {
497 if (ui->fds == ui->cursor_focus_fds) {
498 ui->target_fds = ui->tmp_fds;
499 return 1; /* stop looping */
500 }
501 ui->tmp_fds = ui->fds;
502 }
503 return 0; /* continue with the loop */
504}
505
506static uint8_t mui_task_find_first_cursor_uif(mui_t *ui) {
507 // if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
508 if (mui_uif_is_cursor_selectable(ui)) {
509 // if ( ui->target_fds == NULL )
510 // {
511 ui->target_fds = ui->fds;
512 return 1; /* stop looping */
513 // }
514 }
515 return 0; /* continue with the loop */
516}
517
518static uint8_t mui_task_find_last_cursor_uif(mui_t *ui) {
519 // if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
520 if (mui_uif_is_cursor_selectable(ui)) {
521 // ui->cursor_focus_position++;
522 ui->target_fds = ui->fds;
523 }
524 return 0; /* continue with the loop */
525}
526
527static uint8_t mui_task_find_next_cursor_uif(mui_t *ui) {
528 // if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
529 if (mui_uif_is_cursor_selectable(ui)) {
530 if (ui->tmp_fds != NULL) {
531 ui->target_fds = ui->fds;
532 ui->tmp_fds = NULL;
533 return 1; /* stop looping */
534 }
535 if (ui->fds == ui->cursor_focus_fds) {
536 ui->tmp_fds = ui->fds;
537 }
538 }
539 return 0; /* continue with the loop */
540}
541
542static uint8_t mui_task_get_current_cursor_focus_position(mui_t *ui) {
543 // if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
544 if (mui_uif_is_cursor_selectable(ui)) {
545 if (ui->fds == ui->cursor_focus_fds)
546 return 1; /* stop looping */
547 ui->tmp8++;
548 }
549 return 0; /* continue with the loop */
550}
551
552#ifdef OBSOLETE
553static uint8_t mui_task_read_nth_selectable_field(mui_t *ui) {
554 // if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
555 if (mui_uif_is_cursor_selectable(ui)) {
556 if (ui->tmp8 == 0)
557 return 1; /* stop looping */
558 ui->tmp8--;
559 }
560 return 0; /* continue with the loop */
561}
562#endif
563
564static uint8_t mui_task_find_execute_on_select_field(mui_t *ui) {
565 if (muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_EXECUTE_ON_SELECT) {
566 ui->target_fds = ui->fds;
567 return 1; /* stop looping */
568 }
569 return 0; /* continue with the loop */
570}
571
572/* === utility functions for the user API === */
573
574static uint8_t mui_send_cursor_msg(mui_t *ui, uint8_t msg) MUI_NOINLINE;
575static uint8_t mui_send_cursor_msg(mui_t *ui, uint8_t msg) {
576 if (ui->cursor_focus_fds) {
577 ui->fds = ui->cursor_focus_fds;
578 if (mui_prepare_current_field(ui))
579 return muif_get_cb(ui->uif)(ui, msg);
580 }
581 return 0; /* not called, msg not handled */
582}
583
584/* === user API === */
585
586/*
587 returns the field pos which has the current focus
588 If the first selectable field has the focus, then 0 will be returned
589 Unselectable fields (for example labels) are skipped by this count.
590 If no fields are selectable, then 0 is returned
591
592 The return value can be used as last argument for mui_EnterForm or
593 mui_GotoForm
594
595 WARNING: This function will destroy current fds and field information.
596*/
597uint8_t mui_GetCurrentCursorFocusPosition(mui_t *ui) {
598 // fds_t *fds = ui->fds;
599 ui->tmp8 = 0;
600 mui_loop_over_form(ui, mui_task_get_current_cursor_focus_position);
601 // ui->fds = fds;
602 return ui->tmp8;
603}
604
605void mui_Draw(mui_t *ui) { mui_loop_over_form(ui, mui_task_draw); }
606
607static void mui_next_field(mui_t *ui) {
608 mui_loop_over_form(ui, mui_task_find_next_cursor_uif);
609 // ui->cursor_focus_position++;
610 ui->cursor_focus_fds = ui->target_fds; // NULL is ok
611 if (ui->target_fds == NULL) {
612 mui_loop_over_form(ui, mui_task_find_first_cursor_uif);
613 ui->cursor_focus_fds = ui->target_fds; // NULL is ok
614 // ui->cursor_focus_position = 0;
615 }
616}
617
618/*
619 this function will overwrite the ui field related member variables
620 nth_token can be 0 if the fiel text is not a option list
621 the result is stored in ui->text
622
623 token delimiter is '|' (pipe symbol)
624
625 fds: The start of a field (MUI_DATA)
626 nth_token: The position of the token, which should be returned
627*/
628uint8_t mui_GetSelectableFieldTextOption(mui_t *ui, fds_t *fds,
629 uint8_t nth_token) {
630 if (fds != NULL) {
631 fds_t *fds_backup =
632 ui->fds; // backup the current fds, so that this function can be
633 // called inside a task loop
634 int len = ui->len; // backup length of the current command, 26 sep 2021:
635 // probably this is not required any more
636 uint8_t is_found;
637
638 ui->fds = fds;
639 // at this point ui->fds contains the field which contains the tokens
640 // now get the opion string out of the text field. nth_token can be 0 if
641 // this is no opion string
642 is_found = mui_fds_get_nth_token(
643 ui, nth_token); // return value is ignored here
644
645 ui->fds = fds_backup; // restore the previous fds position
646 ui->len = len;
647 // result is stored in ui->text
648 return is_found;
649 }
650 ui->text[0] = '\0';
651 return 0;
652}
653
654uint8_t mui_GetSelectableFieldOptionCnt(mui_t *ui, fds_t *fds) {
655 if (fds != NULL) {
656 fds_t *fds_backup =
657 ui->fds; // backup the current fds, so that this function can be
658 // called inside a task loop
659 int len = ui->len; // backup length of the current command 26 sep
660 // 2021: probably this is not required any more
661 uint8_t cnt = 0;
662
663 ui->fds = fds;
664 // at this point ui->fds contains the field which contains the tokens
665 // now get the opion string out of the text field. nth_token can be 0 if
666 // this is no opion string
667 cnt = mui_fds_get_token_cnt(ui);
668
669 ui->fds = fds_backup; // restore the previous fds position
670 ui->len = len;
671 // result is stored in ui->text
672 return cnt;
673 }
674 ui->text[0] = '\0';
675 return 0;
676}
677
678// static void mui_send_cursor_enter_msg(mui_t *ui) MUI_NOINLINE;
679static uint8_t mui_send_cursor_enter_msg(mui_t *ui) {
680 ui->is_mud = 0;
681 return mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_ENTER);
682}
683
684/*
685 if called from a field function, then the current field variables are
686 destroyed, so that call should be the last call in the field callback.
687 mui_EnterForm is similar to mui_GotoForm and differes only in the second
688 argument (which is the form id instead of the fds pointer)
689*/
690void mui_EnterForm(mui_t *ui, fds_t *fds, uint8_t initial_cursor_position) {
691 /* exit any previous form, will not do anything if there is no current form
692 */
693 mui_LeaveForm(ui);
694
695 /* clean focus fields */
696 ui->touch_focus_fds = NULL;
697 ui->cursor_focus_fds = NULL;
698
699 /* reset all the scoll values */
700 ui->form_scroll_top = 0;
701 ui->form_scroll_visible = 0;
702 ui->form_scroll_total = 0;
703
704 /* assign the form, which should be entered */
705 ui->current_form_fds = fds;
706
707 /* inform all fields that we start a new form */
708 MUI_DEBUG("mui_EnterForm: form_start, initial_cursor_position=%d\n",
709 initial_cursor_position);
710 mui_loop_over_form(ui, mui_task_form_start);
711
712 /* assign initional cursor focus */
713 MUI_DEBUG("mui_EnterForm: find_first_cursor_uif\n");
714 mui_loop_over_form(ui, mui_task_find_first_cursor_uif);
715 ui->cursor_focus_fds = ui->target_fds; // NULL is ok
716 MUI_DEBUG("mui_EnterForm: find_first_cursor_uif target_fds=%p\n",
717 ui->target_fds);
718
719 while (initial_cursor_position > 0) {
720 mui_NextField(
721 ui); // mui_next_field(ui) is not sufficient in case of scrolling
722 initial_cursor_position--;
723 }
724
725 while (mui_send_cursor_enter_msg(ui) == 255) {
726 mui_NextField(
727 ui); // mui_next_field(ui) is not sufficient in case of scrolling
728 }
729}
730
731/* input: current_form_fds */
732/*
733 if called from a field function, then the current field variables are
734 destroyed, so that call should be the last call in the field callback.
735*/
736void mui_LeaveForm(mui_t *ui) {
737 if (mui_IsFormActive(ui) == 0)
738 return;
739
740 mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
741 ui->cursor_focus_fds = NULL;
742
743 /* inform all fields that we leave the form */
744 MUI_DEBUG("mui_LeaveForm: form_end\n");
745 mui_loop_over_form(ui, mui_task_form_end);
746 ui->current_form_fds = NULL;
747}
748
749/* 0: error, form not found */
750/*
751 if called from a field function, then the current field variables are
752 destroyed, so that call should be the last call in the field callback.
753*/
754uint8_t mui_GotoForm(mui_t *ui, uint8_t form_id,
755 uint8_t initial_cursor_position) {
756 fds_t *fds = mui_find_form(ui, form_id);
757 if (fds == NULL)
758 return 0;
759 /* EnterForm will also leave any previous form */
760 mui_EnterForm(ui, fds, initial_cursor_position);
761 return 1;
762}
763
764/*
765 Save current form+cursor position. Used together with mui_RestoreForm
766*/
767void mui_SaveFormWithCursorPosition(mui_t *ui, uint8_t cursor_pos) {
768 if (mui_IsFormActive(ui) == 0)
769 return;
770
771 if (ui->last_form_stack_pos < MUI_MENU_LAST_FORM_STACK_SIZE - 1)
772 ui->last_form_stack_pos++;
773 else // discard the oldes entry
774 {
775 uint8_t i;
776 for (i = 0; i < ui->last_form_stack_pos; i++) {
777 ui->last_form_id[i] = ui->last_form_id[i + 1];
778 ui->last_form_cursor_focus_position[i] =
779 ui->last_form_cursor_focus_position[i + 1];
780 }
781 }
782
783 ui->last_form_fds =
784 ui->cursor_focus_fds; // 25 Aug 2024: I think this is not required, u8g2
785 // data function will store the value manually in
786 // last_form_fds
787 ui->last_form_id[ui->last_form_stack_pos] =
788 mui_get_fds_char(ui->current_form_fds + 1);
789 ui->last_form_cursor_focus_position[ui->last_form_stack_pos] = cursor_pos;
790}
791
792/*
793 Save current form+cursor position. Simplified version, which will not work
794 with pseudo scrolling forms
795*/
796void mui_SaveForm(mui_t *ui) {
797 mui_SaveFormWithCursorPosition(ui, mui_GetCurrentCursorFocusPosition(ui));
798}
799
800/*
801 Restore previous saved form and cursor position.
802 if called from a field function, then the current field variables are
803 destroyed, so that call should be the last call in the field callback. returns
804 0 if there is no form to restore
805*/
806uint8_t mui_RestoreForm(mui_t *ui) {
807 MUI_DEBUG("mui_RestoreForm last_form_stack_pos=%d\n",
808 ui->last_form_stack_pos);
809 if (ui->last_form_stack_pos >= 0) {
810 uint8_t form_id = ui->last_form_id[ui->last_form_stack_pos];
811 uint8_t focus =
812 ui->last_form_cursor_focus_position[ui->last_form_stack_pos];
813 ui->last_form_stack_pos--;
814 return mui_GotoForm(ui, form_id, focus);
815 // ui->last_form_fds = NULL;
816 }
817 return 0;
818}
819
820/*
821 Save a cursor position for mui_GotoFormAutoCursorPosition command
822*/
823void mui_SaveCursorPosition(mui_t *ui, uint8_t cursor_position) {
824 uint8_t form_id = mui_get_fds_char(ui->current_form_fds + 1);
825 uint8_t i;
826 MUI_DEBUG("mui_SaveCursorPosition form_id=%d cursor_position=%d\n", form_id,
827 cursor_position);
828
829 for (i = 0; i < MUI_MENU_CACHE_CNT; i++) {
830 if (form_id == ui->menu_form_id[i]) {
831 ui->menu_form_last_added = i;
832 break;
833 }
834 }
835 if (i >= MUI_MENU_CACHE_CNT) {
836 ui->menu_form_last_added++;
837 if (ui->menu_form_last_added >= MUI_MENU_CACHE_CNT)
838 ui->menu_form_last_added = 0;
839 }
840
841 /*
842 if ( form_id == ui->menu_form_id[0] )
843 ui->menu_form_last_added = 0;
844 else if ( form_id == ui->menu_form_id[1] )
845 ui->menu_form_last_added = 1;
846 else
847 ui->menu_form_last_added ^= 1;
848 */
849 ui->menu_form_id[ui->menu_form_last_added] = form_id;
850 ui->menu_form_cursor_focus_position[ui->menu_form_last_added] =
851 cursor_position;
852 MUI_DEBUG("mui_SaveCursorPosition ui->menu_form_last_added=%d \n",
853 ui->menu_form_last_added);
854}
855
856/*
857 Similar to mui_GotoForm, but will jump to previously stored cursor location
858 (mui_SaveCursorPosition) or 0 if the cursor position was not saved.
859*/
860uint8_t mui_GotoFormAutoCursorPosition(mui_t *ui, uint8_t form_id) {
861 uint8_t cursor_position = 0;
862 if (form_id == ui->menu_form_id[0])
863 cursor_position = ui->menu_form_cursor_focus_position[0];
864 if (form_id == ui->menu_form_id[1])
865 cursor_position = ui->menu_form_cursor_focus_position[1];
866 MUI_DEBUG("mui_GotoFormAutoCursorPosition form_id=%d cursor_position=%d\n",
867 form_id, cursor_position);
868 return mui_GotoForm(ui, form_id, cursor_position);
869}
870
871/*
872 return current form id or -1 if the menu system is inactive
873*/
874int mui_GetCurrentFormId(mui_t *ui) {
875 if (mui_IsFormActive(ui) == 0)
876 return -1;
877 return mui_get_fds_char(ui->current_form_fds + 1);
878}
879
880/*
881 updates "ui->cursor_focus_fds"
882*/
883/*
884 if called from a field function, then the current field variables are
885 destroyed, so that call should be the last call in the field callback.
886*/
887void mui_NextField(mui_t *ui) {
888 do {
889 if (mui_send_cursor_msg(ui, MUIF_MSG_EVENT_NEXT))
890 return;
891 mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
892 mui_next_field(ui);
893 } while (mui_send_cursor_enter_msg(ui) == 255);
894}
895
896/*
897 updates "ui->cursor_focus_fds"
898*/
899/*
900 if called from a field function, then the current field variables are
901 destroyed, so that call should be the last call in the field callback.
902*/
903void mui_PrevField(mui_t *ui) {
904 do {
905 if (mui_send_cursor_msg(ui, MUIF_MSG_EVENT_PREV))
906 return;
907 mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
908
909 mui_loop_over_form(ui, mui_task_find_prev_cursor_uif);
910 ui->cursor_focus_fds = ui->target_fds; // NULL is ok
911 if (ui->target_fds == NULL) {
912 // ui->cursor_focus_position = 0;
913 mui_loop_over_form(ui, mui_task_find_last_cursor_uif);
914 ui->cursor_focus_fds = ui->target_fds; // NULL is ok
915 }
916 } while (mui_send_cursor_enter_msg(ui) == 255);
917}
918
919void mui_SendSelect(mui_t *ui) {
920 mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_SELECT);
921}
922
923/*
924 Same as mui_SendSelect(), but will try to find a field, which is marked as
925 "execute on select" (MUIF_EXECUTE_ON_SELECT_BUTTON). If such a field exists,
926 then this field is executed, otherwise the current field will receive the
927 select message.
928
929 MUIF_EXECUTE_ON_SELECT_BUTTON is set by muif macro
930 MUIF_EXECUTE_ON_SELECT_BUTTON
931
932 used by MUIInputVersatileRotaryEncoder.ino example
933*/
934void mui_SendSelectWithExecuteOnSelectFieldSearch(mui_t *ui) {
935 mui_loop_over_form(
936 ui, mui_task_find_execute_on_select_field); /* Is there a exec on select
937 field? */
938 if (ui->target_fds !=
939 NULL) /* yes, found, ui->fds already points to the field */
940 {
941 fds_t *exec_on_select_field = ui->target_fds;
942 mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
943 ui->cursor_focus_fds = exec_on_select_field; /* more cursor on the "exec
944 on select" field */
945 mui_send_cursor_enter_msg(ui);
946 mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_SELECT);
947 } else {
948 /* no "exec on select" field found, just send the select message to the
949 * field */
950 mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_SELECT);
951 }
952}
953
954void mui_SendValueIncrement(mui_t *ui) {
955 mui_send_cursor_msg(ui, MUIF_MSG_VALUE_INCREMENT);
956}
957
958void mui_SendValueDecrement(mui_t *ui) {
959 mui_send_cursor_msg(ui, MUIF_MSG_VALUE_DECREMENT);
960}