942 lines
27 KiB
C
942 lines
27 KiB
C
/*
|
|
|
|
mui.c
|
|
|
|
Monochrome minimal user interface: Core library.
|
|
|
|
Universal 8bit Graphics Library (https://github.com/olikraus/u8g2/)
|
|
|
|
Copyright (c) 2021, olikraus@gmail.com
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this list
|
|
of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
list of conditions and the following disclaimer in the documentation and/or other
|
|
materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
"mui.c" is a graphical user interface, developed as part of u8g2.
|
|
However "mui.c" is independent of u8g2 and can be used without u8g2 code.
|
|
The glue code between "mui.c" and u8g2 is located in "mui_u8g2.c"
|
|
|
|
c: cmd
|
|
i: ID0
|
|
j: ID1
|
|
xy: Position (x and y)
|
|
/text/: some text. The text can start with any delimiter (except 0 and |), but also has to end with the same delimiter
|
|
a: Single char argument
|
|
u: Single char argument with the user interface form number
|
|
|
|
"Uu" the interface --> no ID
|
|
|
|
Manual ID:
|
|
"Fijxy" Generic field: Places field with id ii at x/y --> ID=ij
|
|
"Bijxy/text/" Generic field (Button) with Text --> ID=ij
|
|
"Tiixya/text/" Generic field with argument and text --> ID = ij
|
|
"Aiixya"
|
|
|
|
Fixed ID:
|
|
"Si" the style --> ID=@i
|
|
"Lxy/labeltext/" Places a text at the specified position, field with - -> ID=.L, .l
|
|
"Gxyu/menutext/" Go to the specified menu without placing the user interface form on the stack --> ID=.G, .g
|
|
|
|
|
|
cijxy
|
|
cijxy/text/
|
|
cijxya/text/
|
|
|
|
cxy/text/
|
|
cxya/text/
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "mui.h"
|
|
|
|
|
|
|
|
|
|
|
|
//#define mui_get_fds_char(s) ((uint8_t)(*s))
|
|
|
|
//#include <stdio.h>
|
|
//#define MUI_DEBUG(...) printf(__VA_ARGS__)
|
|
#define MUI_DEBUG(...)
|
|
|
|
uint8_t mui_get_fds_char(fds_t *s)
|
|
{
|
|
//return (uint8_t)(*s);
|
|
return (uint8_t)mui_pgm_read(s);
|
|
}
|
|
|
|
|
|
/*
|
|
s must point to a valid command within FDS
|
|
*/
|
|
static size_t mui_fds_get_cmd_size_without_text(fds_t *s) MUI_NOINLINE;
|
|
static size_t mui_fds_get_cmd_size_without_text(fds_t *s)
|
|
{
|
|
uint8_t c = mui_get_fds_char(s);
|
|
c &= 0xdf; /* consider upper and lower case */
|
|
switch(c)
|
|
{
|
|
case 'U': return 2; // User Form: CMD (1 Byte), Form-Id (1 Byte)
|
|
case 'S': return 2; // Style: CMD (1 Byte), Style Id (1 Byte)
|
|
case 'D': return 3; // Data within Text: CMD (1 Byte), ID (2 Bytes), Text (does not count here)
|
|
case 'Z': return 3; // Zero field without x, y, arg & text: CMD (1 Byte), ID (2 Bytes)
|
|
case 'F': return 5; // Field without arg & text: CMD (1 Byte), ID (2 Bytes), X, Y
|
|
case 'B': return 5; // Field with text: CMD (1 Byte), ID (2 Bytes), X, Y, Text (does not count here)
|
|
case 'T': return 6; // Field with arg & text: CMD (1 Byte), ID (2 Bytes), X, Y, Arg, Text (does not count here)
|
|
case 'A': return 6; // Field with arg (no text): CMD (1 Byte), ID (2 Bytes), X, Y, Arg, Text
|
|
case 'L': return 3; // Text Label: CMD (1 Byte), X, Y (same as 'B' but with fixed ID '.L', MUIF_LABEL, MUI_LABEL)
|
|
case 'G': return 4; // Goto Btutton: CMD (1Byte), X, Y, Arg, Text (same as 'T' but with fixed ID '.G', MUIF_GOTO, MUI_GOTO)
|
|
case 0: return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
s must point to the string delimiter start: first '/' for "B00ab/ok/"
|
|
- '/' actually is 0xff
|
|
- return the total size of the string, including the delimiter
|
|
- copies the content of the string ("ok") to the ui text buffer
|
|
|
|
*/
|
|
static size_t mui_fds_parse_text(mui_t *ui, fds_t *s)
|
|
{
|
|
uint8_t i = 0;
|
|
ui->delimiter = mui_get_fds_char(s);
|
|
uint8_t c;
|
|
fds_t *t = s;
|
|
|
|
//printf("mui_fds_parse_text del=%d\n", delimiter);
|
|
#ifdef MUI_CHECK_EOFDS
|
|
if ( ui->delimiter == 0 )
|
|
return 0;
|
|
#endif
|
|
t++;
|
|
for( ;; )
|
|
{
|
|
c = mui_get_fds_char(t);
|
|
//printf("mui_fds_parse_text i=%d, c=%c\n", i, c);
|
|
#ifdef MUI_CHECK_EOFDS
|
|
if ( c == 0 )
|
|
break;
|
|
#endif
|
|
if ( c == ui->delimiter )
|
|
{
|
|
t++;
|
|
break;
|
|
}
|
|
if ( i < MUI_MAX_TEXT_LEN )
|
|
{
|
|
ui->text[i++] = c;
|
|
}
|
|
t++;
|
|
}
|
|
ui->text[i] = '\0' ;
|
|
return t-s;
|
|
}
|
|
|
|
/*
|
|
get the first token within a text argument.
|
|
The text argument may look like this:
|
|
"B00ab/banana|apple|peach|cherry/"
|
|
The outer delimiter "/" is not fixed and can be any char except "|" and "\0"
|
|
The inner delimiter "|" is fixed. It must be the pipe symbol.
|
|
This function will place "banana" into ui->text if the result is not 0
|
|
|
|
if ( mui_fds_first_token(ui) )
|
|
{
|
|
do
|
|
{
|
|
// handle token in ui->text
|
|
} while ( mui_fds_next_token(ui) )
|
|
}
|
|
|
|
*/
|
|
|
|
uint8_t mui_fds_first_token(mui_t *ui)
|
|
{
|
|
ui->token = ui->fds;
|
|
ui->token += mui_fds_get_cmd_size_without_text(ui->fds);
|
|
ui->delimiter = mui_get_fds_char(ui->token);
|
|
ui->token++; // place ui->token on the first char of the token
|
|
return mui_fds_next_token(ui);
|
|
}
|
|
|
|
/*
|
|
The inner token delimiter "|" is fixed. It must be the pipe symbol.
|
|
*/
|
|
uint8_t mui_fds_next_token(mui_t *ui)
|
|
{
|
|
uint8_t c;
|
|
uint8_t i = 0;
|
|
// printf("mui_fds_next_token: call, ui->token=%p\n", ui->token);
|
|
for( ;; )
|
|
{
|
|
c = mui_get_fds_char(ui->token);
|
|
// printf("mui_fds_next_token: i=%d c=%c\n", i, c);
|
|
#ifdef MUI_CHECK_EOFDS
|
|
if ( c == 0 )
|
|
break;
|
|
#endif
|
|
if ( c == ui->delimiter )
|
|
break;
|
|
if ( c == '|' )
|
|
{
|
|
ui->token++; // place ui->token on the first char of the next token
|
|
break;
|
|
}
|
|
|
|
if ( i < MUI_MAX_TEXT_LEN )
|
|
{
|
|
ui->text[i++] = c;
|
|
}
|
|
|
|
ui->token++;
|
|
}
|
|
ui->text[i] = '\0' ;
|
|
if ( i == 0 )
|
|
return 0; // no further token found
|
|
return 1; // token placed in ui->text
|
|
}
|
|
|
|
/*
|
|
find nth token ('|' delimiter), return 0 if n exceeds the number of tokens, 1 otherwise
|
|
the result is stored in ui->text
|
|
*/
|
|
uint8_t mui_fds_get_nth_token(mui_t *ui, uint8_t n)
|
|
{
|
|
// printf("mui_fds_get_nth_token: call, n=%d\n", n);
|
|
if ( mui_fds_first_token(ui) )
|
|
{
|
|
do
|
|
{
|
|
if ( n == 0 )
|
|
{
|
|
// printf("mui_fds_get_nth_token: found");
|
|
return 1;
|
|
}
|
|
n--;
|
|
} while ( mui_fds_next_token(ui) );
|
|
}
|
|
//printf("mui_fds_get_nth_token: NOT found\n");
|
|
return 0;
|
|
}
|
|
|
|
uint8_t mui_fds_get_token_cnt(mui_t *ui)
|
|
{
|
|
uint8_t n = 0;
|
|
if ( mui_fds_first_token(ui) )
|
|
{
|
|
do
|
|
{
|
|
n++;
|
|
} while ( mui_fds_next_token(ui) );
|
|
}
|
|
return n;
|
|
}
|
|
|
|
|
|
#define mui_fds_is_text(c) ( (c) == 'U' || (c) == 'S' || (c) == 'F' || (c) == 'A' || (c) == 'Z' ? 0 : 1 )
|
|
|
|
/*
|
|
s must point to a valid command within FDS
|
|
return
|
|
The complete length of the command (including any text part)
|
|
sideeffect:
|
|
Any existing text part will be copied into ui->text
|
|
ui->text will be assigned to empty string if there is no text argument
|
|
*/
|
|
static size_t mui_fds_get_cmd_size(mui_t *ui, fds_t *s) MUI_NOINLINE;
|
|
static size_t mui_fds_get_cmd_size(mui_t *ui, fds_t *s)
|
|
{
|
|
size_t l = mui_fds_get_cmd_size_without_text(s);
|
|
uint8_t c = mui_get_fds_char(s);
|
|
ui->text[0] = '\0' ; /* always reset the text buffer */
|
|
if ( mui_fds_is_text(c) )
|
|
{
|
|
l += mui_fds_parse_text(ui, s+l);
|
|
}
|
|
return l;
|
|
}
|
|
|
|
|
|
/*
|
|
mui_Init() will setup the menu system but will not activate or display anything.
|
|
Use mui_GotoForm() after this command, then use mui_Draw() to draw the menu on a display.
|
|
*/
|
|
void mui_Init(mui_t *ui, void *graphics_data, fds_t *fds, muif_t *muif_tlist, size_t muif_tcnt)
|
|
{
|
|
memset(ui, 0, sizeof(mui_t));
|
|
ui->root_fds = fds;
|
|
//ui->current_form_fds = NULL; // not required, because there was a memset before
|
|
ui->muif_tlist = muif_tlist;
|
|
ui->muif_tcnt = muif_tcnt;
|
|
ui->graphics_data = graphics_data;
|
|
}
|
|
|
|
static int mui_find_uif(mui_t *ui, uint8_t id0, uint8_t id1)
|
|
{
|
|
size_t i;
|
|
for( i = 0; i < ui->muif_tcnt; i++ )
|
|
{
|
|
/*
|
|
if ( ui->muif_tlist[i].id0 == id0 )
|
|
if ( ui->muif_tlist[i].id1 == id1 )
|
|
return i;
|
|
*/
|
|
if ( muif_get_id0(ui->muif_tlist+i) == id0 )
|
|
if ( muif_get_id1(ui->muif_tlist+i) == id1 )
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*
|
|
assumes a valid position in ui->fds and calculates all the other variables
|
|
some fields are always calculated like the ui->cmd and ui->len field
|
|
other member vars are calculated only if the return value is 1
|
|
will return 1 if the field id was found.
|
|
will return 0 if the field id was not found in uif or if ui->fds points to something else than a field
|
|
*/
|
|
static uint8_t mui_prepare_current_field(mui_t *ui) MUI_NOINLINE;
|
|
static uint8_t mui_prepare_current_field(mui_t *ui)
|
|
{
|
|
int muif_tidx;
|
|
|
|
ui->uif = NULL;
|
|
ui->dflags = 0;
|
|
ui->id0 = 0;
|
|
ui->id1 = 0;
|
|
ui->arg = 0;
|
|
|
|
/* calculate the length of the command and copy the text argument */
|
|
/* this will also clear the text in cases where there is no text argument */
|
|
ui->len = mui_fds_get_cmd_size(ui, ui->fds);
|
|
//printf("mui_prepare_current_field len=%d\n", ui->len);
|
|
|
|
/* get the command and check whether end of form is reached */
|
|
ui->cmd = mui_get_fds_char(ui->fds);
|
|
//printf("mui_prepare_current_field cmd='%c' len=%d\n", ui->cmd, ui->len);
|
|
|
|
/* Copy the cmd also to second id value. This is required for some commands, others will overwrite this below */
|
|
ui->id1 = ui->cmd;
|
|
|
|
/* now make the command uppercase so that both, upper and lower case are considered */
|
|
ui->cmd &= 0xdf; /* consider upper and lower case */
|
|
|
|
if ( ui->cmd == 'U' || ui->cmd == 0 )
|
|
return 0;
|
|
|
|
/* calculate the dynamic flags */
|
|
if ( ui->fds == ui->cursor_focus_fds )
|
|
ui->dflags |= MUIF_DFLAG_IS_CURSOR_FOCUS;
|
|
if ( ui->fds == ui->touch_focus_fds )
|
|
ui->dflags |= MUIF_DFLAG_IS_TOUCH_FOCUS;
|
|
|
|
|
|
/* get the id0 and id1 values */
|
|
if ( ui->cmd == 'F' || ui->cmd == 'B' || ui->cmd == 'T' || ui->cmd == 'A' )
|
|
{
|
|
ui->id0 = mui_get_fds_char(ui->fds+1);
|
|
ui->id1 = mui_get_fds_char(ui->fds+2);
|
|
ui->x = mui_get_fds_char(ui->fds+3);
|
|
ui->y = mui_get_fds_char(ui->fds+4);
|
|
if ( ui->cmd == 'A' || ui->cmd == 'T' )
|
|
{
|
|
ui->arg = mui_get_fds_char(ui->fds+5);
|
|
}
|
|
}
|
|
else if ( ui->cmd == 'D' || ui->cmd == 'Z' )
|
|
{
|
|
ui->id0 = mui_get_fds_char(ui->fds+1);
|
|
ui->id1 = mui_get_fds_char(ui->fds+2);
|
|
}
|
|
else if ( ui->cmd == 'S' )
|
|
{
|
|
ui->id0 = 'S';
|
|
ui->id1 = mui_get_fds_char(ui->fds+1);
|
|
}
|
|
else
|
|
{
|
|
ui->id0 = '.';
|
|
/* note that ui->id1 contains the original cmd value */
|
|
ui->x = mui_get_fds_char(ui->fds+1);
|
|
ui->y = mui_get_fds_char(ui->fds+2);
|
|
if ( ui->cmd == 'G' || ui->cmd == 'M' ) /* this is also true for 'g' or 'm' */
|
|
{
|
|
ui->arg = mui_get_fds_char(ui->fds+3);
|
|
}
|
|
}
|
|
|
|
//MUI_DEBUG("mui_prepare_current_field cmd='%c' len=%d arg=%d\n", ui->cmd, ui->len, ui->arg);
|
|
|
|
|
|
/* find the field */
|
|
muif_tidx = mui_find_uif(ui, ui->id0, ui->id1);
|
|
//printf("mui_prepare_current_field: muif_tidx=%d\n", muif_tidx);
|
|
if ( muif_tidx >= 0 )
|
|
{
|
|
ui->uif = ui->muif_tlist + muif_tidx;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
assumes that ui->fds has been assigned correctly
|
|
and that ui->target_fds and ui->tmp_fds had been cleared if required
|
|
|
|
Usually do not call this function directly, instead use mui_loop_over_form
|
|
|
|
*/
|
|
|
|
static void mui_inner_loop_over_form(mui_t *ui, uint8_t (*task)(mui_t *ui)) MUI_NOINLINE;
|
|
static void mui_inner_loop_over_form(mui_t *ui, uint8_t (*task)(mui_t *ui))
|
|
{
|
|
uint8_t cmd;
|
|
|
|
//MUI_DEBUG("mui_inner_loop_over_form start %p\n", task);
|
|
|
|
ui->fds += mui_fds_get_cmd_size(ui, ui->fds); // skip the first entry, it is U always
|
|
for(;;)
|
|
{
|
|
//printf("fds=%p *fds='%c'\n", ui->fds, ui->fds[0]);
|
|
/* get the command and check whether end of form is reached */
|
|
cmd = mui_get_fds_char(ui->fds);
|
|
if ( cmd == 'U' || cmd == 0 )
|
|
break;
|
|
if ( mui_prepare_current_field(ui) ) /* side effect: calculate ui->len */
|
|
if ( task(ui) ) /* call the task, which was provided as argument to this function */
|
|
{
|
|
//MUI_DEBUG("mui_inner_loop_over_form break by task\n");
|
|
break;
|
|
}
|
|
ui->fds += ui->len;
|
|
}
|
|
|
|
//MUI_DEBUG("mui_inner_loop_over_form end %p\n", task);
|
|
}
|
|
|
|
static void mui_loop_over_form(mui_t *ui, uint8_t (*task)(mui_t *ui)) MUI_NOINLINE;
|
|
static void mui_loop_over_form(mui_t *ui, uint8_t (*task)(mui_t *ui))
|
|
{
|
|
if ( mui_IsFormActive(ui) == 0 )
|
|
return;
|
|
|
|
ui->fds = ui->current_form_fds;
|
|
ui->target_fds = NULL;
|
|
ui->tmp_fds = NULL;
|
|
|
|
mui_inner_loop_over_form(ui, task);
|
|
}
|
|
|
|
/*
|
|
n is the form number
|
|
*/
|
|
static fds_t *mui_find_form(mui_t *ui, uint8_t n)
|
|
{
|
|
fds_t *fds = ui->root_fds;
|
|
uint8_t cmd;
|
|
|
|
for( ;; )
|
|
{
|
|
cmd = mui_get_fds_char(fds);
|
|
if ( cmd == 0 )
|
|
break;
|
|
if ( cmd == 'U' )
|
|
{
|
|
if ( mui_get_fds_char(fds+1) == n )
|
|
{
|
|
return fds;
|
|
}
|
|
/* not found, just coninue */
|
|
}
|
|
|
|
fds += mui_fds_get_cmd_size(ui, fds);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* === task procedures (arguments for mui_loop_over_form) === */
|
|
/* ui->fds contains the current field */
|
|
|
|
static uint8_t mui_task_draw(mui_t *ui)
|
|
{
|
|
//printf("mui_task_draw fds=%p uif=%p text=%s\n", ui->fds, ui->uif, ui->text);
|
|
muif_get_cb(ui->uif)(ui, MUIF_MSG_DRAW);
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_form_start(mui_t *ui)
|
|
{
|
|
muif_get_cb(ui->uif)(ui, MUIF_MSG_FORM_START);
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_form_end(mui_t *ui)
|
|
{
|
|
muif_get_cb(ui->uif)(ui, MUIF_MSG_FORM_END);
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_uif_is_cursor_selectable(mui_t *ui) MUI_NOINLINE;
|
|
static uint8_t mui_uif_is_cursor_selectable(mui_t *ui)
|
|
{
|
|
if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t mui_task_find_prev_cursor_uif(mui_t *ui)
|
|
{
|
|
//if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
|
|
if ( mui_uif_is_cursor_selectable(ui) )
|
|
{
|
|
if ( ui->fds == ui->cursor_focus_fds )
|
|
{
|
|
ui->target_fds = ui->tmp_fds;
|
|
return 1; /* stop looping */
|
|
}
|
|
ui->tmp_fds = ui->fds;
|
|
}
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_find_first_cursor_uif(mui_t *ui)
|
|
{
|
|
//if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
|
|
if ( mui_uif_is_cursor_selectable(ui) )
|
|
{
|
|
// if ( ui->target_fds == NULL )
|
|
// {
|
|
ui->target_fds = ui->fds;
|
|
return 1; /* stop looping */
|
|
// }
|
|
}
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_find_last_cursor_uif(mui_t *ui)
|
|
{
|
|
//if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
|
|
if ( mui_uif_is_cursor_selectable(ui) )
|
|
{
|
|
//ui->cursor_focus_position++;
|
|
ui->target_fds = ui->fds;
|
|
}
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_find_next_cursor_uif(mui_t *ui)
|
|
{
|
|
//if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
|
|
if ( mui_uif_is_cursor_selectable(ui) )
|
|
{
|
|
if ( ui->tmp_fds != NULL )
|
|
{
|
|
ui->target_fds = ui->fds;
|
|
ui->tmp_fds = NULL;
|
|
return 1; /* stop looping */
|
|
}
|
|
if ( ui->fds == ui->cursor_focus_fds )
|
|
{
|
|
ui->tmp_fds = ui->fds;
|
|
}
|
|
}
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_get_current_cursor_focus_position(mui_t *ui)
|
|
{
|
|
//if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
|
|
if ( mui_uif_is_cursor_selectable(ui) )
|
|
{
|
|
if ( ui->fds == ui->cursor_focus_fds )
|
|
return 1; /* stop looping */
|
|
ui->tmp8++;
|
|
}
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_read_nth_selectable_field(mui_t *ui)
|
|
{
|
|
//if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_CURSOR_SELECTABLE )
|
|
if ( mui_uif_is_cursor_selectable(ui) )
|
|
{
|
|
if ( ui->tmp8 == 0 )
|
|
return 1; /* stop looping */
|
|
ui->tmp8--;
|
|
}
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
static uint8_t mui_task_find_execute_on_select_field(mui_t *ui)
|
|
{
|
|
if ( muif_get_cflags(ui->uif) & MUIF_CFLAG_IS_EXECUTE_ON_SELECT )
|
|
{
|
|
ui->target_fds = ui->fds;
|
|
return 1; /* stop looping */
|
|
}
|
|
return 0; /* continue with the loop */
|
|
}
|
|
|
|
|
|
/* === utility functions for the user API === */
|
|
|
|
static uint8_t mui_send_cursor_msg(mui_t *ui, uint8_t msg) MUI_NOINLINE;
|
|
static uint8_t mui_send_cursor_msg(mui_t *ui, uint8_t msg)
|
|
{
|
|
if ( ui->cursor_focus_fds )
|
|
{
|
|
ui->fds = ui->cursor_focus_fds;
|
|
if ( mui_prepare_current_field(ui) )
|
|
return muif_get_cb(ui->uif)(ui, msg);
|
|
}
|
|
return 0; /* not called, msg not handled */
|
|
}
|
|
|
|
/* === user API === */
|
|
|
|
/*
|
|
returns the field pos which has the current focus
|
|
If the first selectable field has the focus, then 0 will be returned
|
|
Unselectable fields (for example labels) are skipped by this count.
|
|
If no fields are selectable, then 0 is returned
|
|
|
|
The return value can be used as last argument for mui_EnterForm or mui_GotoForm
|
|
|
|
WARNING: This function will destroy current fds and field information.
|
|
*/
|
|
uint8_t mui_GetCurrentCursorFocusPosition(mui_t *ui)
|
|
{
|
|
//fds_t *fds = ui->fds;
|
|
ui->tmp8 = 0;
|
|
mui_loop_over_form(ui, mui_task_get_current_cursor_focus_position);
|
|
//ui->fds = fds;
|
|
return ui->tmp8;
|
|
}
|
|
|
|
|
|
void mui_Draw(mui_t *ui)
|
|
{
|
|
mui_loop_over_form(ui, mui_task_draw);
|
|
}
|
|
|
|
static void mui_next_field(mui_t *ui)
|
|
{
|
|
mui_loop_over_form(ui, mui_task_find_next_cursor_uif);
|
|
// ui->cursor_focus_position++;
|
|
ui->cursor_focus_fds = ui->target_fds; // NULL is ok
|
|
if ( ui->target_fds == NULL )
|
|
{
|
|
mui_loop_over_form(ui, mui_task_find_first_cursor_uif);
|
|
ui->cursor_focus_fds = ui->target_fds; // NULL is ok
|
|
// ui->cursor_focus_position = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
this function will overwrite the ui field related member variables
|
|
nth_token can be 0 if the fiel text is not a option list
|
|
the result is stored in ui->text
|
|
|
|
token delimiter is '|' (pipe symbol)
|
|
|
|
fds: The start of a field (MUI_DATA)
|
|
nth_token: The position of the token, which should be returned
|
|
*/
|
|
uint8_t mui_GetSelectableFieldTextOption(mui_t *ui, fds_t *fds, uint8_t nth_token)
|
|
{
|
|
fds_t *fds_backup = ui->fds; // backup the current fds, so that this function can be called inside a task loop
|
|
int len = ui->len; // backup length of the current command, 26 sep 2021: probably this is not required any more
|
|
uint8_t is_found;
|
|
|
|
ui->fds = fds;
|
|
// at this point ui->fds contains the field which contains the tokens
|
|
// now get the opion string out of the text field. nth_token can be 0 if this is no opion string
|
|
is_found = mui_fds_get_nth_token(ui, nth_token); // return value is ignored here
|
|
|
|
ui->fds = fds_backup; // restore the previous fds position
|
|
ui->len = len;
|
|
// result is stored in ui->text
|
|
return is_found;
|
|
}
|
|
|
|
uint8_t mui_GetSelectableFieldOptionCnt(mui_t *ui, fds_t *fds)
|
|
{
|
|
fds_t *fds_backup = ui->fds; // backup the current fds, so that this function can be called inside a task loop
|
|
int len = ui->len; // backup length of the current command 26 sep 2021: probably this is not required any more
|
|
uint8_t cnt = 0;
|
|
|
|
ui->fds = fds;
|
|
// at this point ui->fds contains the field which contains the tokens
|
|
// now get the opion string out of the text field. nth_token can be 0 if this is no opion string
|
|
cnt = mui_fds_get_token_cnt(ui);
|
|
|
|
ui->fds = fds_backup; // restore the previous fds position
|
|
ui->len = len;
|
|
// result is stored in ui->text
|
|
return cnt;
|
|
}
|
|
|
|
|
|
|
|
//static void mui_send_cursor_enter_msg(mui_t *ui) MUI_NOINLINE;
|
|
static uint8_t mui_send_cursor_enter_msg(mui_t *ui)
|
|
{
|
|
ui->is_mud = 0;
|
|
return mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_ENTER);
|
|
}
|
|
|
|
/*
|
|
if called from a field function, then the current field variables are destroyed, so that call should be the last call in the field callback.
|
|
mui_EnterForm is similar to mui_GotoForm and differes only in the second argument (which is the form id instead of the fds pointer)
|
|
*/
|
|
void mui_EnterForm(mui_t *ui, fds_t *fds, uint8_t initial_cursor_position)
|
|
{
|
|
/* exit any previous form, will not do anything if there is no current form */
|
|
mui_LeaveForm(ui);
|
|
|
|
/* clean focus fields */
|
|
ui->touch_focus_fds = NULL;
|
|
ui->cursor_focus_fds = NULL;
|
|
|
|
/* reset all the scoll values */
|
|
ui->form_scroll_top = 0;
|
|
ui->form_scroll_visible = 0;
|
|
ui->form_scroll_total = 0;
|
|
|
|
/* assign the form, which should be entered */
|
|
ui->current_form_fds = fds;
|
|
|
|
/* inform all fields that we start a new form */
|
|
MUI_DEBUG("mui_EnterForm: form_start, initial_cursor_position=%d\n", initial_cursor_position);
|
|
mui_loop_over_form(ui, mui_task_form_start);
|
|
|
|
/* assign initional cursor focus */
|
|
MUI_DEBUG("mui_EnterForm: find_first_cursor_uif\n");
|
|
mui_loop_over_form(ui, mui_task_find_first_cursor_uif);
|
|
ui->cursor_focus_fds = ui->target_fds; // NULL is ok
|
|
MUI_DEBUG("mui_EnterForm: find_first_cursor_uif target_fds=%p\n", ui->target_fds);
|
|
|
|
while( initial_cursor_position > 0 )
|
|
{
|
|
mui_NextField(ui); // mui_next_field(ui) is not sufficient in case of scrolling
|
|
initial_cursor_position--;
|
|
}
|
|
|
|
while( mui_send_cursor_enter_msg(ui) == 255 )
|
|
{
|
|
mui_NextField(ui); // mui_next_field(ui) is not sufficient in case of scrolling
|
|
}
|
|
}
|
|
|
|
/* input: current_form_fds */
|
|
/*
|
|
if called from a field function, then the current field variables are destroyed, so that call should be the last call in the field callback.
|
|
*/
|
|
void mui_LeaveForm(mui_t *ui)
|
|
{
|
|
if ( mui_IsFormActive(ui) == 0 )
|
|
return;
|
|
|
|
mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
|
|
ui->cursor_focus_fds = NULL;
|
|
|
|
/* inform all fields that we leave the form */
|
|
MUI_DEBUG("mui_LeaveForm: form_end\n");
|
|
mui_loop_over_form(ui, mui_task_form_end);
|
|
ui->current_form_fds = NULL;
|
|
}
|
|
|
|
/* 0: error, form not found */
|
|
/*
|
|
if called from a field function, then the current field variables are destroyed, so that call should be the last call in the field callback.
|
|
*/
|
|
uint8_t mui_GotoForm(mui_t *ui, uint8_t form_id, uint8_t initial_cursor_position)
|
|
{
|
|
fds_t *fds = mui_find_form(ui, form_id);
|
|
if ( fds == NULL )
|
|
return 0;
|
|
/* EnterForm will also leave any previous form */
|
|
mui_EnterForm(ui, fds, initial_cursor_position);
|
|
return 1;
|
|
}
|
|
|
|
void mui_SaveForm(mui_t *ui)
|
|
{
|
|
if ( mui_IsFormActive(ui) == 0 )
|
|
return;
|
|
|
|
ui->last_form_fds = ui->cursor_focus_fds;
|
|
ui->last_form_id = mui_get_fds_char(ui->current_form_fds+1);
|
|
ui->last_form_cursor_focus_position = mui_GetCurrentCursorFocusPosition(ui);
|
|
}
|
|
|
|
/*
|
|
if called from a field function, then the current field variables are destroyed, so that call should be the last call in the field callback.
|
|
*/
|
|
void mui_RestoreForm(mui_t *ui)
|
|
{
|
|
mui_GotoForm(ui, ui->last_form_id, ui->last_form_cursor_focus_position);
|
|
}
|
|
|
|
/*
|
|
Save a cursor position for mui_GotoFormAutoCursorPosition command
|
|
Two such positions is stored.
|
|
*/
|
|
void mui_SaveCursorPosition(mui_t *ui, uint8_t cursor_position)
|
|
{
|
|
uint8_t form_id = mui_get_fds_char(ui->current_form_fds+1);
|
|
MUI_DEBUG("mui_SaveCursorPosition form_id=%d cursor_position=%d\n", form_id, cursor_position);
|
|
|
|
if ( form_id == ui->menu_form_id[0] )
|
|
ui->menu_form_last_added = 0;
|
|
else if ( form_id == ui->menu_form_id[1] )
|
|
ui->menu_form_last_added = 1;
|
|
else
|
|
ui->menu_form_last_added ^= 1;
|
|
ui->menu_form_id[ui->menu_form_last_added] = form_id;
|
|
ui->menu_form_cursor_focus_position[ui->menu_form_last_added] = cursor_position;
|
|
MUI_DEBUG("mui_SaveCursorPosition ui->menu_form_last_added=%d \n", ui->menu_form_last_added);
|
|
}
|
|
|
|
/*
|
|
Similar to mui_GotoForm, but will jump to previously stored cursor location (mui_SaveCursorPosition) or 0 if the cursor position was not saved.
|
|
*/
|
|
uint8_t mui_GotoFormAutoCursorPosition(mui_t *ui, uint8_t form_id)
|
|
{
|
|
uint8_t cursor_position = 0;
|
|
if ( form_id == ui->menu_form_id[0] )
|
|
cursor_position = ui->menu_form_cursor_focus_position[0];
|
|
if ( form_id == ui->menu_form_id[1] )
|
|
cursor_position = ui->menu_form_cursor_focus_position[1];
|
|
MUI_DEBUG("mui_GotoFormAutoCursorPosition form_id=%d cursor_position=%d\n", form_id, cursor_position);
|
|
return mui_GotoForm(ui, form_id, cursor_position);
|
|
}
|
|
|
|
/*
|
|
return current form id or -1 if the menu system is inactive
|
|
*/
|
|
int mui_GetCurrentFormId(mui_t *ui)
|
|
{
|
|
if ( mui_IsFormActive(ui) == 0 )
|
|
return -1;
|
|
return mui_get_fds_char(ui->current_form_fds+1);
|
|
}
|
|
|
|
/*
|
|
updates "ui->cursor_focus_fds"
|
|
*/
|
|
/*
|
|
if called from a field function, then the current field variables are destroyed, so that call should be the last call in the field callback.
|
|
*/
|
|
void mui_NextField(mui_t *ui)
|
|
{
|
|
do
|
|
{
|
|
if ( mui_send_cursor_msg(ui, MUIF_MSG_EVENT_NEXT) )
|
|
return;
|
|
mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
|
|
mui_next_field(ui);
|
|
} while ( mui_send_cursor_enter_msg(ui) == 255 );
|
|
}
|
|
|
|
/*
|
|
updates "ui->cursor_focus_fds"
|
|
*/
|
|
/*
|
|
if called from a field function, then the current field variables are destroyed, so that call should be the last call in the field callback.
|
|
*/
|
|
void mui_PrevField(mui_t *ui)
|
|
{
|
|
do
|
|
{
|
|
if ( mui_send_cursor_msg(ui, MUIF_MSG_EVENT_PREV) )
|
|
return;
|
|
mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
|
|
|
|
mui_loop_over_form(ui, mui_task_find_prev_cursor_uif);
|
|
ui->cursor_focus_fds = ui->target_fds; // NULL is ok
|
|
if ( ui->target_fds == NULL )
|
|
{
|
|
//ui->cursor_focus_position = 0;
|
|
mui_loop_over_form(ui, mui_task_find_last_cursor_uif);
|
|
ui->cursor_focus_fds = ui->target_fds; // NULL is ok
|
|
}
|
|
} while( mui_send_cursor_enter_msg(ui) == 255 );
|
|
}
|
|
|
|
|
|
void mui_SendSelect(mui_t *ui)
|
|
{
|
|
mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_SELECT);
|
|
}
|
|
|
|
/*
|
|
Same as mui_SendSelect(), but will try to find a field, which is marked as "execute on select" (MUIF_EXECUTE_ON_SELECT_BUTTON).
|
|
If such a field exists, then this field is executed, otherwise the current field will receive the select message.
|
|
|
|
MUIF_EXECUTE_ON_SELECT_BUTTON is set by muif macro MUIF_EXECUTE_ON_SELECT_BUTTON
|
|
|
|
used by MUIInputVersatileRotaryEncoder.ino example
|
|
*/
|
|
void mui_SendSelectWithExecuteOnSelectFieldSearch(mui_t *ui)
|
|
{
|
|
mui_loop_over_form(ui, mui_task_find_execute_on_select_field); /* Is there a exec on select field? */
|
|
if ( ui->target_fds != NULL ) /* yes, found, ui->fds already points to the field */
|
|
{
|
|
fds_t *exec_on_select_field = ui->target_fds;
|
|
mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_LEAVE);
|
|
ui->cursor_focus_fds = exec_on_select_field; /* more cursor on the "exec on select" field */
|
|
mui_send_cursor_enter_msg(ui);
|
|
mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_SELECT);
|
|
}
|
|
else
|
|
{
|
|
/* no "exec on select" field found, just send the select message to the field */
|
|
mui_send_cursor_msg(ui, MUIF_MSG_CURSOR_SELECT);
|
|
}
|
|
}
|
|
|
|
|
|
void mui_SendValueIncrement(mui_t *ui)
|
|
{
|
|
mui_send_cursor_msg(ui, MUIF_MSG_VALUE_INCREMENT);
|
|
}
|
|
|
|
void mui_SendValueDecrement(mui_t *ui)
|
|
{
|
|
mui_send_cursor_msg(ui, MUIF_MSG_VALUE_DECREMENT);
|
|
}
|