From 2d463366c18f9681cd51863b49c43a925e6d893c Mon Sep 17 00:00:00 2001 From: t-moe Date: Sun, 17 May 2015 14:23:12 +0200 Subject: [PATCH] Improved comments in implementation of button, checkbox, numupdown, tft, touch and screen modules/submodules. --- common/gui/button.c | 263 ++++++++++++++++++----------------------- common/gui/button.h | 23 +--- common/gui/checkbox.c | 89 +++++++++----- common/gui/checkbox.h | 3 +- common/gui/numupdown.c | 105 +++++++++------- common/gui/screen.c | 67 ++++++----- common/gui/screen.h | 1 + common/tft/tft.c | 59 +++++---- common/touch/touch.c | 227 +++++++++++++++++++---------------- 9 files changed, 441 insertions(+), 396 deletions(-) diff --git a/common/gui/button.c b/common/gui/button.c index e450b58..8abffcb 100644 --- a/common/gui/button.c +++ b/common/gui/button.c @@ -3,187 +3,152 @@ #include "button.h" #include -#define BRIGHTNESS_VAL 3 //How much the Brightness is in/decreased for button shadows (3 -> Add 1/3 off Full Value) +/* The Idea is as follows: + * When the user add's a button we create a touch area for that region and wait for PEN_DOWN events. + * Once the user puts the pen down in this area we'll redraw the button with different shadows (feedback) + * and we'll now wait on PEN_UP or PEN_LEAVE events. + * If the user takes the pen away while in the area (PEN_UP), we call the provided user callback + * Otherwise (PEN_LEAVE) we only restore the initial shadows + */ +/* Possible improvements: + * Move the button by 1 pixel while he is pressed, to create a "full 3d" experience + * Add events for the case when the button is pressed for a long time, without release + */ + +//Method to calculate the shadow colors used to create the "3d" effect +void calculate_shadows(uint16_t bgcolor, uint16_t* light_shadow, uint16_t* dark_shadow) { + #define BRIGHTNESS_VAL 3 //How much the Brightness is in/decreased for button shadows (3 -> Add/Subtract 1/3 off Full Value) + + uint16_t c_light,c_dark; //c_light and c_dark will be filled with a lighter and a darker color as the background color (for the shadows) + uint8_t r,g,b; + + //separate the channels of the 16-bit rgb565 color + r=(bgcolor&0xF800)>>11; + g=(bgcolor&0x07E0)>>5; + b=(bgcolor&0x001F)>>0; + + //For the light shadow color: + if((r + 0x1F/BRIGHTNESS_VAL) > 0x1F) //Adding one third would exceed the maximum of the red channel + c_light=0xF800; //Use full red + else //adding one third to the red channel is fine + c_light=(r+0x1F/BRIGHTNESS_VAL)<<11; //Use same red as in the background, but add one third + if((g + 0x3F/BRIGHTNESS_VAL) > 0x3F) //same for the green channel + c_light|=0x07E0; + else + c_light|=(g+0x3F/BRIGHTNESS_VAL)<<5; + if((b + 0x1F/BRIGHTNESS_VAL) > 0x1F) //and the blue channel + c_light|=0x0018; + else + c_light|=(b+0x1F/BRIGHTNESS_VAL)<<0; + + //For the dark shadow color + if(r > (0x1F/BRIGHTNESS_VAL)) //Subtracting one third would NOT exceed the minimum of the red channel + c_dark=(r-0x1F/BRIGHTNESS_VAL)<<11; //Use same red as in the background, but subtract one third + else //Subtracting one third would give us a number below zero + c_dark=0x0000; //use no red channel + if(g > (0x3F/BRIGHTNESS_VAL)) //Same for the green channel + c_dark|=(g-0x3F/BRIGHTNESS_VAL)<<5; + if(b > (0x1F/BRIGHTNESS_VAL)) //and the blue channel + c_dark|=(b-0x1F/BRIGHTNESS_VAL)<<0; + + //Assign the calculated shadows to out parameters + if(light_shadow!=NULL) *light_shadow = c_light; + if(dark_shadow!=NULL) *dark_shadow = c_dark; + +} + +//Callback which is called when the user touches the touch-area we created for the button void buttons_cb(void* touchArea, TOUCH_ACTION triggeredAction) -//Method shared between normal Buttons and Bitmap Buttons-> Look at comment in headerfile for explanation. { TOUCH_AREA_STRUCT * area = (TOUCH_AREA_STRUCT*)touchArea; BUTTON_STRUCT* button = (BUTTON_STRUCT*)touchArea; - unsigned int c1,c2; - unsigned char r,g,b; - r=(button->bgcolor&0xF800)>>11; - g=(button->bgcolor&0x07E0)>>5; - b=(button->bgcolor&0x001F)>>0; - if((r + 0x1F/BRIGHTNESS_VAL) >0x1F) - c1=0xF800; - else - c1=(r+0x1F/BRIGHTNESS_VAL)<<11; - if((g + 0x3F/BRIGHTNESS_VAL) >0x3F) - c1|=0x07E0; - else - c1|=(g+0x3F/BRIGHTNESS_VAL)<<5; - if((b + 0x1F/BRIGHTNESS_VAL) >0x1F) - c1|=0x0018; - else - c1|=(b+0x1F/BRIGHTNESS_VAL)<<0; - if(r > (0x1F/BRIGHTNESS_VAL)) - c2=(r-0x1F/BRIGHTNESS_VAL)<<11; - else - c2=0x0000; - if(g > (0x3F/BRIGHTNESS_VAL)) - c2|=(g-0x3F/BRIGHTNESS_VAL)<<5; - if(b > (0x1F/BRIGHTNESS_VAL)) - c2|=(b-0x1F/BRIGHTNESS_VAL)<<0; + + uint16_t c_light,c_dark; //c_light and c_dark will be filled with a lighter and a darker color as the background color (for the shadows) + calculate_shadows(button->bgcolor,&c_light,&c_dark); + switch(triggeredAction) { - case PEN_DOWN: - area->hookedActions=PEN_UP|PEN_LEAVE; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c2); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c2);//West - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c1); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c1); //Ost + case PEN_DOWN: //If the user touches the area for the "first time" + area->hookedActions=PEN_UP|PEN_LEAVE; //for the future we only want PEN_UP and PEN_LEAVE events + + //Draw shadows + tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_dark); //North + tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_dark);//West + tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_light); //South + tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_light); //East break; - case PEN_UP: - case PEN_LEAVE: - area->hookedActions=PEN_DOWN; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c1); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c1);//West - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c2); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c2); //Ost - if(triggeredAction==PEN_UP && button->callback!=NULL) - button->callback(button); + case PEN_UP: //If the user took the pen away, while in the area (=button pressed!) + case PEN_LEAVE: //or the user "slided out" of the area + area->hookedActions=PEN_DOWN; //for the future we only want PEN_DOWN events + + //Draw inverse shadows + tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_light); //North + tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_light);//West + tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_dark); //South + tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_dark); //East + + if(triggeredAction==PEN_UP && button->callback!=NULL) //If the button got "pressed" instead of left, and the user provided a callback + button->callback(button); //execute the user callback break; default:break; } } -bool gui_button_add(BUTTON_STRUCT* button)//Registers a button (fill Struct first). Return false if no more Space in the Pointertable (-->Change NUM_AREAS). +bool gui_button_add(BUTTON_STRUCT* button) { - if(touch_have_empty(1)) + if(touch_have_empty(1)) //Check if the touch module can handle one additional area { + //Calculate width and height of the button text unsigned int strwidth=tft_font_width(button->font)*strlen(button->text); unsigned char strheight=tft_font_height(button->font); - button->base.hookedActions=PEN_DOWN; - button->base.callback = buttons_cb; - if(button->base.x2==AUTO) + + button->base.hookedActions=PEN_DOWN; //At first we are interested in PEN_DOWN events + button->base.callback = buttons_cb; //Use our own callback for the touch area events + + if(button->base.x2==AUTO) { //The user wants us to calculate the button width automatically + //Use string width + half of a character width as button width button->base.x2= button->base.x1 -1 + strwidth+(tft_font_width(button->font)/2); - else if((button->base.x2-button->base.x1+1)<(strwidth+2)) - return false; + } else if((button->base.x2-button->base.x1+1)<(strwidth+2)) { //the provided width is too small to fit the entire text + return false; //report error + } - if(button->base.y2==AUTO) + if(button->base.y2==AUTO) { //The user wants us to calculate the button height automatically + //Use one and a half character heights as button height button->base.y2=button->base.y1 -1 +strheight+(strheight/2); - else if((button->base.y2-button->base.y1+1)<(strheight+2)) + } else if((button->base.y2-button->base.y1+1)<(strheight+2)) { //the provided height is too small to fit the text return false; - gui_button_redraw(button); - return touch_register_area(&button->base); + } + gui_button_redraw(button); //call the redraw method, which will take care of drawing the entire button + return touch_register_area(&button->base); //Register the touch area and receive events for this button, from now on } - return false; + + return false; //no more touch areas left } void gui_button_redraw(BUTTON_STRUCT* button) { - unsigned int strwidth=tft_font_width(button->font)*strlen(button->text); - unsigned char strheight=tft_font_height(button->font); - unsigned char r,g,b; - unsigned int c; - r=(button->bgcolor&0xF800)>>11; - g=(button->bgcolor&0x07E0)>>5; - b=(button->bgcolor&0x001F)>>0; + //Calculate text dimensions and shadow colors + unsigned int strwidth=tft_font_width(button->font)*strlen(button->text); + unsigned char strheight=tft_font_height(button->font); + uint16_t c_light,c_dark; + calculate_shadows(button->bgcolor,&c_light,&c_dark); + + //Draw the background and the 4 lines (shadow colors) tft_fill_rectangle(button->base.x1+1,button->base.y1+1,button->base.x2-1,button->base.y2-1,button->bgcolor); - if((r + 0x1F/BRIGHTNESS_VAL) >0x1F) - c=0xF800; - else - c=(r+0x1F/BRIGHTNESS_VAL)<<11; - if((g + 0x3F/BRIGHTNESS_VAL) >0x3F) - c|=0x07E0; - else - c|=(g+0x3F/BRIGHTNESS_VAL)<<5; - if((b + 0x1F/BRIGHTNESS_VAL) >0x1F) - c|=0x0018; - else - c|=(b+0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c);//West - if(r > (0x1F/BRIGHTNESS_VAL)) - c=(r-0x1F/BRIGHTNESS_VAL)<<11; - else - c=0x0000; - if(g > (0x3F/BRIGHTNESS_VAL)) - c|=(g-0x3F/BRIGHTNESS_VAL)<<5; - if(b > (0x1F/BRIGHTNESS_VAL)) - c|=(b-0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c); //Ost + tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_light); //North + tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_light);//West + tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_dark); //South + tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_dark); //East + + //Draw the text tft_print_line(button->base.x1+(button->base.x2-button->base.x1+1-strwidth)/2,button->base.y1+(button->base.y2-button->base.y1+1-strheight)/2,button->txtcolor,button->bgcolor,button->font,button->text); } + void gui_button_remove(BUTTON_STRUCT* button) { + //We only need to unregister the touch area, as we have not allocated anything else touch_unregister_area((TOUCH_AREA_STRUCT*)button); } - -/* -bool guiAddBitmapButton (BITMAPBUTTON_STRUCT* button) -{ - if(touchHaveEmpty(1)) - { - button->base.hookedActions=PEN_DOWN; - button->base.callback = buttons_cb; - if(button->base.x2==AUTO) - button->base.x2= button->base.x1 -1 + button->imgwidth + button->imgwidth/4; - else if((button->base.x2-button->base.x1+1)<(button->imgwidth+2)) - return false; - - if(button->base.y2==AUTO) - button->base.y2=button->base.y1 -1 +button->imgheight + button->imgheight/4; - else if((button->base.y2-button->base.y1+1)<(button->imgheight+2)) - return false; - guiRedrawBitmapButton(button); - return touchRegisterArea(&button->base); - } - return false; -} - -void guiRedrawBitmapButton(BITMAPBUTTON_STRUCT* button) -{ - - unsigned char r,g,b; - unsigned int c; - r=(button->bgcolor&0xF800)>>11; - g=(button->bgcolor&0x07E0)>>5; - b=(button->bgcolor&0x001F)>>0; - tftFillRectangle(button->base.x1+1,button->base.y1+1,button->base.x2-1,button->base.y2-1,button->bgcolor); - if((r + 0x1F/BRIGHTNESS_VAL) >0x1F) - c=0xF800; - else - c=(r+0x1F/BRIGHTNESS_VAL)<<11; - if((g + 0x3F/BRIGHTNESS_VAL) >0x3F) - c|=0x07E0; - else - c|=(g+0x3F/BRIGHTNESS_VAL)<<5; - if((b + 0x1F/BRIGHTNESS_VAL) >0x1F) - c|=0x0018; - else - c|=(b+0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c); //Nord - tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c);//West - if(r > (0x1F/BRIGHTNESS_VAL)) - c=(r-0x1F/BRIGHTNESS_VAL)<<11; - else - c=0x0000; - if(g > (0x3F/BRIGHTNESS_VAL)) - c|=(g-0x3F/BRIGHTNESS_VAL)<<5; - if(b > (0x1F/BRIGHTNESS_VAL)) - c|=(b-0x1F/BRIGHTNESS_VAL)<<0; - tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c); //Süd - tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c); //Ost - tftDrawBitmapUnscaledStreamedRaw(button->base.x1+(button->base.x2-button->base.x1+1-button->imgwidth)/2,button->base.y1+(button->base.y2-button->base.y1+1-button->imgheight)/2,button->imgwidth,button->imgheight,button->filename); -} -void guiRemoveBitmapButton(BITMAPBUTTON_STRUCT* button) -{ - touchUnregisterArea((TOUCH_AREA_STRUCT*)button); -} - -*/ diff --git a/common/gui/button.h b/common/gui/button.h index 85087eb..14aff59 100644 --- a/common/gui/button.h +++ b/common/gui/button.h @@ -11,7 +11,8 @@ /** * @defgroup button Button - * The Button Gui-Element + * The Button Gui-Element is a clickable, rectangular box with a label inside. + * When it is pressed and released you will be notified via the provided callback. */ /*@}*/ @@ -64,26 +65,6 @@ void gui_button_remove(BUTTON_STRUCT* button); */ void gui_button_redraw(BUTTON_STRUCT* button); -/* -bool guiAddBitmapButton(BITMAPBUTTON_STRUCT* button); -void guiRemoveBitmapButton(BITMAPBUTTON_STRUCT* button); -void guiRedrawBitmapButton(BITMAPBUTTON_STRUCT* button); -*/ - - -/* -typedef struct { - TOUCH_AREA_STRUCT base; - unsigned int bgcolor; - BUTTON_CALLBACK callback; //Callback - unsigned char imgwidth; - unsigned char imgheight; - char* filename; -} BITMAPBUTTON_STRUCT; -*/ -//Notice that the first 3 Members are Equal, so it's possible to cast it to a BUTTON_STRUCT even if it's a BITMAPBUTTON_STRUCT (when changeing only the first 3 Members). - - /*@}*/ diff --git a/common/gui/checkbox.c b/common/gui/checkbox.c index 96325a6..3f0cb8a 100644 --- a/common/gui/checkbox.c +++ b/common/gui/checkbox.c @@ -3,29 +3,44 @@ #include "checkbox.h" #include -#define BRIGHTNESS_VAL 2 //How much the Brightness is in/decreased for checkbox shadows (3 -> Add 1/3 off Full Value) -#define ACTIVE_COLOR RGB(251,208,123) -#define BORDER_COLOR RGB(29,82,129) -#define BACKGROUND_COLOR WHITE +/* The idea is as follows: + * When the user creates a checkbox we create a touch area for that region and wait for PEN_DOWN events. + * Once the user puts the pen down in this area we'll redraw the checkbox with different shadows (feedback) + * and we'll now wait on PEN_UP or PEN_LEAVE events. + * If the user takes the pen away while in the area (PEN_UP), we toggle the checkbox and we call the provided user callback + * Otherwise (PEN_LEAVE) we only restore the initial shadows + */ + +#define ACTIVE_COLOR RGB(251,208,123) //shadow color (inside of border) +#define BORDER_COLOR RGB(29,82,129) //1px border color +#define BACKGROUND_COLOR WHITE //Background color + +//Callback which is called when the user touches the touch-area we created for the checkbox void checkboxes_cb(void* touchArea, TOUCH_ACTION triggeredAction) { TOUCH_AREA_STRUCT * area = (TOUCH_AREA_STRUCT*)touchArea; CHECKBOX_STRUCT* checkbox = (CHECKBOX_STRUCT*)touchArea; switch(triggeredAction) { - case PEN_DOWN: - area->hookedActions=PEN_UP|PEN_LEAVE; + case PEN_DOWN: //If the user touches the area for the "first time" + area->hookedActions=PEN_UP|PEN_LEAVE; //for the future we only want PEN_UP and PEN_LEAVE events + + //Draw active shadows tft_draw_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,ACTIVE_COLOR); tft_draw_rectangle(checkbox->base.x1+2,checkbox->base.y1+2,checkbox->base.x2-2,checkbox->base.y2-2,ACTIVE_COLOR); break; - case PEN_UP: - checkbox->checked=!checkbox->checked; - gui_checkbox_update(checkbox); - if(checkbox->callback!=NULL) - checkbox->callback(checkbox,checkbox->checked); - case PEN_LEAVE: - area->hookedActions=PEN_DOWN; + case PEN_UP: //If the user took the pen away, while in the area (=toggle checkbox!) + checkbox->checked=!checkbox->checked; //Toggle checkbox state + gui_checkbox_update(checkbox); //redraw/overdraw tickmark + if(checkbox->callback!=NULL) { //The user provided a callback + checkbox->callback(checkbox,checkbox->checked); //Call the provided callback with the new checked state + } + // no break statement here! + case PEN_LEAVE: //if the user "slided out" of the area + area->hookedActions=PEN_DOWN; //for the future we only want PEN_DOWN events + + //Draw inactive shadows tft_draw_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,BACKGROUND_COLOR); tft_draw_rectangle(checkbox->base.x1+2,checkbox->base.y1+2,checkbox->base.x2-2,checkbox->base.y2-2,BACKGROUND_COLOR); break; @@ -35,48 +50,66 @@ void checkboxes_cb(void* touchArea, TOUCH_ACTION triggeredAction) bool gui_checkbox_add(CHECKBOX_STRUCT* checkbox) { - if(touch_have_empty(1)) + if(touch_have_empty(1)) //Check if the touch module can handle one additional area { unsigned char size=0; - checkbox->base.hookedActions=PEN_DOWN; - checkbox->base.callback = checkboxes_cb; + checkbox->base.hookedActions=PEN_DOWN; //At first we are interested in PEN_DOWN events + checkbox->base.callback = checkboxes_cb; //Use our own callback for the touch area events + + //Check the size of the checkbox if(checkbox->base.x2>checkbox->base.x1) - size = checkbox->base.x2 - checkbox->base.x1; + size = checkbox->base.x2 - checkbox->base.x1; //use width a as size if(checkbox->base.y2>checkbox->base.y1) { - if((checkbox->base.y2 - checkbox->base.y1)>size) - size = checkbox->base.y2 - checkbox->base.y1; + if((checkbox->base.y2 - checkbox->base.y1)>size) //height is larger than size + size = checkbox->base.y2 - checkbox->base.y1; //use height as size } - if((size&0x01)) - size++; + if(size==0) { //no size found (maybe swap x2 and x1 or y2 and y1 ?) + return false; //signal error + } + if((size&0x01)) //the size is an odd number + size++; //make size an even number + + //Correct x2,y2 so that the checkbox is quadratic checkbox->base.x2 = checkbox->base.x1 + size; checkbox->base.y2 = checkbox->base.y1 + size; - gui_checkbox_redraw(checkbox); - return touch_register_area(&checkbox->base); + + gui_checkbox_redraw(checkbox);//Call redraw method, which will take care of the drawing of the entire checkbox + + return touch_register_area(&checkbox->base); //Register the touch area and receive events for this checkbox, from now on } - return false; + + return false; //no more touch areas left } void gui_checkbox_redraw(CHECKBOX_STRUCT* checkbox) { + //Draw background and border tft_fill_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,BACKGROUND_COLOR); tft_draw_rectangle(checkbox->base.x1,checkbox->base.y1,checkbox->base.x2,checkbox->base.y2,BORDER_COLOR); - if(checkbox->checked) - gui_checkbox_update(checkbox); + + if(checkbox->checked) { //checkbox is currently checked + gui_checkbox_update(checkbox); //Call update method which will draw the tickmark + } } void gui_checkbox_remove(CHECKBOX_STRUCT* checkbox) { + //We only need to unregister the touch area, as we have not allocated anything else touch_unregister_area((TOUCH_AREA_STRUCT*)checkbox); } void gui_checkbox_update(CHECKBOX_STRUCT* checkbox) { - unsigned int c = (checkbox->checked)? checkbox->fgcolor:BACKGROUND_COLOR; - unsigned int xcent = checkbox->base.x1+(checkbox->base.x2-checkbox->base.x1)*6/14; + unsigned int c = (checkbox->checked)? checkbox->fgcolor:BACKGROUND_COLOR; //color to use for the tickmark + + //helper points inside the checkbox + unsigned int xcent = checkbox->base.x1+(checkbox->base.x2-checkbox->base.x1)*6/14; unsigned int yleft = checkbox->base.y2 - (xcent- checkbox->base.x1) - 1 ; unsigned int yright = checkbox->base.y2 - (checkbox->base.x2 - xcent) - 1 ; unsigned int ybot = checkbox->base.y2 - 4; + + //Draw tickmark as a 3pixel wide line tft_draw_line(checkbox->base.x1+3,yleft-1,xcent,ybot -1,c); tft_draw_line(checkbox->base.x1+3,yleft,xcent,ybot ,c); tft_draw_line(checkbox->base.x1+3,yleft+1,xcent,ybot + 1,c); diff --git a/common/gui/checkbox.h b/common/gui/checkbox.h index 807fdf1..9165f6c 100644 --- a/common/gui/checkbox.h +++ b/common/gui/checkbox.h @@ -10,7 +10,8 @@ /** * @defgroup checkbox Checkbox - * The Checkbox Gui-Element + * The Checkbox Gui-Element is a clickable, rectangular box with an optional tickmark inside of it. + * When the checkbox is pressed and released it's tick state changes and you will be notified via the provided callback. */ /*@}*/ diff --git a/common/gui/numupdown.c b/common/gui/numupdown.c index 974bede..e17a48b 100644 --- a/common/gui/numupdown.c +++ b/common/gui/numupdown.c @@ -6,39 +6,51 @@ #include //for offsetof macro #include //for abs +/* The idea is as follows: + * When the user add's a numupdown we create two buttons, one with a plus and one with a minus sign in it + * When the user presses one of the buttons we check and increase the value and execute the provided user callback + */ -#define BASE_COLOR RGB(90,90,90) + +#define BASE_COLOR RGB(90,90,90) //Background color for the whole element + +//Callback which is called when the user presses the "plus" button void button_up_cb(void* button) { + //Get the pointer to the numupdown: subtract the offset of the buttonUp member in the struct from the button pointer NUMUPDOWN_STRUCT* element = button-offsetof(NUMUPDOWN_STRUCT,buttonUp); - if(element->valuemax) { - element->value++; - gui_numupdown_update(element); - if(element->callback!=NULL) { - element->callback(element,element->value); + + if(element->valuemax) { //old value lies below the maximum + element->value++; //let's increase the value + gui_numupdown_update(element); //and redraw everything + if(element->callback!=NULL) { //the user provided a callback + element->callback(element,element->value); //Call the user callback with the new value } } } +//Callback which is called when the user presses the "minus" button void button_down_cb(void* button) { + //Get the pointer to the numupdown: subtract the offset of the buttonDown member in the struct from the button pointer NUMUPDOWN_STRUCT* element = button-offsetof(NUMUPDOWN_STRUCT,buttonDown); - if(element->value>element->min) { - element->value--; - gui_numupdown_update(element); - if(element->callback!=NULL) { - element->callback(element,element->value); + + if(element->value>element->min) { //old value lies above the minimum + element->value--; //let's decrease the value + gui_numupdown_update(element); //and redraw everything + if(element->callback!=NULL) { //the user provided a callback + element->callback(element,element->value); //Call the user callback with the new value } } } - +//Method to calculate the number of characters needed to print the provided number in decimal notation (with optional sign) static uint8_t calc_text_width(int16_t val) { uint8_t width = 1 + (val<0); //1 if positive, 2 if negative (to let space for sign) - val=abs(val); - while(val>=10) { - val/=10; - width++; + val=abs(val); //Make the number positive + while(val>=10) { //while we have two or more digits + val/=10; //remove one digit + width++; //add one character } return width; } @@ -46,24 +58,24 @@ static uint8_t calc_text_width(int16_t val) { bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown) { - if(touch_have_empty(2)) //We require 2 TouchAreas (for Buttons) + if(touch_have_empty(2)) //Check if the touch module can handle two additional areas { - if(numupdown->min > numupdown->max) return false; - - if(numupdown->value > numupdown->max) { - numupdown->value = numupdown->max; + if(numupdown->min > numupdown->max) { //min is bigger than max? + return false; //invalid parameter } - if(numupdown->value < numupdown->min) { - numupdown->value = numupdown->min; - } else if(numupdown->value > numupdown->max) { - numupdown->value = numupdown->max; + + if(numupdown->value < numupdown->min) { //value is smaller than min? + numupdown->value = numupdown->min; //normalize value + } else if(numupdown->value > numupdown->max) { //value is bigger than max? + numupdown->value = numupdown->max; //normalize value } - uint8_t tw1 = calc_text_width(numupdown->max); - uint8_t tw2 = calc_text_width(numupdown->min); - if(tw2 > tw1) tw1 = tw2; - uint8_t width= tft_font_width(0)*(tw1+1); + uint8_t tw1 = calc_text_width(numupdown->max); //Calculate character width to render maximum value + uint8_t tw2 = calc_text_width(numupdown->min); //Calculate character width to render minimum value + if(tw2 > tw1) tw1 = tw2; //ensure tw1 contains the larger number of the two + uint8_t width= tft_font_width(0)*(tw1+1); //Calculate width of the number area + //Add "minus" button to the left side of the number area numupdown->buttonDown.base.x1=numupdown->x; numupdown->buttonDown.base.y1=numupdown->y; numupdown->buttonDown.base.x2=AUTO; @@ -74,6 +86,8 @@ bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown) numupdown->buttonDown.txtcolor=WHITE; numupdown->buttonDown.callback = button_down_cb; gui_button_add(&numupdown->buttonDown); + + //Add "plus" button to the right side of the number area numupdown->buttonUp.base.x1=numupdown->buttonDown.base.x2+width+2; numupdown->buttonUp.base.y1=numupdown->y; numupdown->buttonUp.base.x2=AUTO; @@ -85,34 +99,45 @@ bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown) numupdown->buttonUp.callback = button_up_cb; gui_button_add(&numupdown->buttonUp); + //Draw background and label of the number area tft_fill_rectangle(numupdown->buttonDown.base.x2+2,numupdown->y,numupdown->buttonDown.base.x2+width,numupdown->buttonUp.base.y2,BASE_COLOR); tft_print_formatted(numupdown->buttonDown.base.x2+2+tft_font_width(0)/2,numupdown->y+tft_font_height(0)/2,numupdown->fgcolor,BASE_COLOR,0,"%*d",tw1,numupdown->value); return true; } - return false; + + return false; //not enough touch areas left } void gui_numupdown_remove(NUMUPDOWN_STRUCT* numupdown) - { +{ + //remove the two buttons, we have no other allocated resources gui_button_remove(&numupdown->buttonUp); gui_button_remove(&numupdown->buttonDown); - } +} + + +void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown) +{ + //redraw the two buttons + gui_button_redraw(&numupdown->buttonUp); + gui_button_redraw(&numupdown->buttonDown); + + //call update method which will take care of the number-area rendering + gui_numupdown_update(numupdown); +} void gui_numupdown_update(NUMUPDOWN_STRUCT* numupdown) - { +{ + //Calculate the number area width again (see above) uint8_t tw1 = calc_text_width(numupdown->max); uint8_t tw2 = calc_text_width(numupdown->min); if(tw2 > tw1) tw1 = tw2; uint8_t width= tft_font_width(0)*(tw1+1); + //Draw background and label of the number area tft_fill_rectangle(numupdown->buttonDown.base.x2+2,numupdown->y,numupdown->buttonDown.base.x2+width,numupdown->buttonUp.base.y2,BASE_COLOR); tft_print_formatted(numupdown->buttonDown.base.x2+2+tft_font_width(0)/2,numupdown->y+tft_font_height(0)/2,numupdown->fgcolor,BASE_COLOR,0,"%*d",tw1,numupdown->value); - } +} + - void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown) - { - gui_button_redraw(&numupdown->buttonUp); - gui_button_redraw(&numupdown->buttonDown); - gui_numupdown_update(numupdown); - } diff --git a/common/gui/screen.c b/common/gui/screen.c index 23315f0..cc90cea 100644 --- a/common/gui/screen.c +++ b/common/gui/screen.c @@ -1,32 +1,40 @@ #include "screen.h" +/* The idea is as follows: + * We only call screen callbacks from the gui_screen_update() method, which is called from the applications main loop. + * Instructions to switch the screen will be delayed until the gui_screen_update() method is called again. + * This makes it safe to change the screen from an touch interrupt (e.g. button callback) + */ -static SCREEN_STRUCT* screen_list = NULL; -static SCREEN_STRUCT* screen_current = NULL; -static volatile SCREEN_STRUCT* screen_goto = NULL; +static SCREEN_STRUCT* screen_list = NULL; //Head of the linked list which stores the screen history. +static SCREEN_STRUCT* screen_current = NULL; //Pointer to the current screen (= tail of the list) +static volatile SCREEN_STRUCT* screen_goto = NULL; //Screen we should navigate to once we enter the gui_screen_update() method again SCREEN_STRUCT* gui_screen_get_current() { return screen_current; } + void gui_screen_update() { if(screen_goto!=NULL) { //we received the task to switch the screen SCREEN_STRUCT* go = (SCREEN_STRUCT*) screen_goto; //Backup volatile variable - screen_goto=NULL; - if(go->next!=NULL) { //we're going back - if(go->next!=screen_current) return; //list corrupted? - screen_current->on_leave(screen_current); - go->next=NULL; - } else { //we're going forward + screen_goto=NULL; //reset the "goto instruction", since we're processing it now + if(go->next!=NULL) { //The screen is not the last in the list, so we're going back + if(go->next!=screen_current) { //this condition should always be false + return; //list corrupted? + } + screen_current->on_leave(screen_current); //let the current screen free/unregister it's resources + go->next=NULL; //remove the current screen from the list + } else { //we're going forward (to a new screen) if(screen_current!=NULL) { //this is not the first screen - screen_current->on_leave(screen_current); - screen_current->next = go; - } else { //first screen ever seen - screen_list=go; - } + screen_current->on_leave(screen_current); //let the current screen free/unregister it's resources + screen_current->next = go; //append the new screen to the end of the list + } else { //first screen ever seen + screen_list=go; //set the new screen as list-head + } } - go->on_enter(go); - screen_current =go; + go->on_enter(go); //let the new screen allocate/register it's resources + screen_current = go; //the new screen is now the current screen. Transition done } if(screen_current!=NULL) { //A screen has been set @@ -37,16 +45,18 @@ void gui_screen_update() { bool gui_screen_navigate(SCREEN_STRUCT* screen) { - if(screen==NULL) return false; - screen->next = NULL; - screen_goto=screen; //send message to main loop, to switch the screen + if(screen==NULL) { //invalid argument passed + return false; + } + screen->next = NULL; //this will become the new tail of the list, so the next pointer must be NULL + screen_goto=screen; //"send message" to main loop, to switch the screen return true; } - - bool gui_screen_back() { - if(screen_list==NULL) return false; + if(screen_list==NULL) { //the list head is emtpy, nothing to go back to + return false; + } SCREEN_STRUCT* current = screen_list; SCREEN_STRUCT* last = NULL; //Find second last element in list @@ -54,15 +64,8 @@ bool gui_screen_back() { last = current; current = current->next; } - if(last==NULL) return false; //There's only a single screen. - if(current!=screen_current) return false; //List corrupted? - screen_goto=last; //send message to main loop, to switch the screen + if(last==NULL) return false; //There's only a single screen, there's no going back here + if(current!=screen_current) return false; //The last entry in the list is not the current screen. List corrupted? + screen_goto=last; //"send message" to main loop, to switch the screen return true; } - - - - - - - diff --git a/common/gui/screen.h b/common/gui/screen.h index e57f54d..fdef182 100644 --- a/common/gui/screen.h +++ b/common/gui/screen.h @@ -12,6 +12,7 @@ /** * @defgroup screen Screen * The Screen Submodule provides an api to navigate between different "screens" on the UI. + * Each screen must provide an enter, update and a leave method; which will be called from this module at the right time. * The implemented screens of the application are documented in the \ref screens module. */ /*@}*/ diff --git a/common/tft/tft.c b/common/tft/tft.c index 7558100..0b5679a 100644 --- a/common/tft/tft.c +++ b/common/tft/tft.c @@ -5,6 +5,16 @@ #include #include "filesystem.h" +/* The idea is as follows: + * Most of the tft_* functions can be forwarded to the lowlevel implementation. + * The exceptions are commented below. + * Make sure to have a look at the doxygen comments for the lowlevel functions and for the tft_* functions + */ + +/* Possible improvements: + * For formatted printing implement putchar, instead of writing into a buffer and drawing that buffer afterwards + */ + bool tft_init() { return ll_tft_init(); @@ -52,35 +62,41 @@ uint8_t tft_font_width(uint8_t fontnum) { return ll_tft_font_width(fontnum); } +//Print line can be done with multiple calls to draw_char void tft_print_line(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, const char* text) { - if(font>=ll_tft_num_fonts()) return; - for(int i=0; i=ll_tft_num_fonts()) return; //invalid font index + for(int i=0; i -#define NUM_AREAS 50 //Number of Structs Reserved in Memory for TouchAreas (e.g Buttons) -TOUCH_AREA_STRUCT* areas[NUM_AREAS] = {NULL}; +/* The idea is as follows: + * The user can add "touch-areas" which basically represent a rectangles on the screen. + * Once the user touches such a rectangle with the pen, we forward events to his provided callback. + * Touch events are provided to us from the low level implementation via touch_add_raw_event(). + * We then need to check which touch areas are effected by that event + */ -volatile POINT_STRUCT pos; -volatile TOUCH_STATE oldState=TOUCH_UP; +/* Possible improvements: + * Exchange pointer-list "areas" with a linked list. This would ensure that we can always accept new regions + * Implement calibration stuff, and calculate the real coordinates out of the data provided in touch_add_raw_event() + */ + +#define NUM_AREAS 50 //Number of Touch Areas we can manage +TOUCH_AREA_STRUCT* areas[NUM_AREAS] = {NULL}; //list with pointers to all managed touch area's + +volatile POINT_STRUCT pos; //the last touch point +volatile TOUCH_STATE oldState=TOUCH_UP; //the last touch state bool touch_init() { return ll_touch_init(); @@ -14,117 +26,124 @@ bool touch_init() { bool touch_add_raw_event(uint16_t touchX, uint16_t touchY, TOUCH_STATE state) { - bool penDown = (state==TOUCH_DOWN); - bool oldPenDown = (oldState==TOUCH_DOWN); - oldState=state; + //Update current and old position/state + bool penDown = (state==TOUCH_DOWN); + bool oldPenDown = (oldState==TOUCH_DOWN); + oldState=state; pos.x=touchX; pos.y=touchY; - if(penDown) - { - // tftDrawPixel(touchX,touchY,WHITE); - if(!oldPenDown) //First Touch - { - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) - { - areas[z]->flags=1; //PenInside=1 - if(areas[z]->hookedActions & PEN_DOWN) - areas[z]->callback(areas[z],PEN_DOWN); - } - } - } - else //Second, Third - { - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL ) - { - if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) - { - if(areas[z]->flags==0) //PenInside ==0 - { - areas[z]->flags=1; //PenInside=1 - if(areas[z]->hookedActions & PEN_ENTER) - areas[z]->callback(areas[z],PEN_ENTER); - } - } - else if(areas[z]->flags) //PenInside==1 - { - areas[z]->flags=0; //PenInside=0 - if(areas[z]->hookedActions & PEN_LEAVE) - areas[z]->callback(areas[z],PEN_LEAVE); - } - } - } - } - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL && areas[z]->hookedActions&PEN_MOVE) - { - if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) - { - areas[z]->callback(areas[z],PEN_MOVE); - } - } - } - } - else - { - if(oldPenDown) //Was the pen ever down (or was it a too short touch) - { - for(int z=0; z < NUM_AREAS; z++) // For every touch area - { - if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) - { - areas[z]->flags=0; //PenInside = 0; - if(areas[z]->hookedActions & PEN_UP) - areas[z]->callback(areas[z],PEN_UP); - } - } - } - touchX=0xFFFF; - touchY=0xFFFF; - } + if(penDown) //pen is down now + { + //tft_draw_pixel(touchX,touchY,WHITE); + if(!oldPenDown) //pen wasn't down before (positive edge) => First Touch + { + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + //Check if pos is inside area + if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) + { + areas[z]->flags=1; //Save PenInside=1 + if(areas[z]->hookedActions & PEN_DOWN) //The user wants to receive pen down events + areas[z]->callback(areas[z],PEN_DOWN); //Send event to user callback + } + } + } + else //Pen was down before => Second, Third event in row + { + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + if(areas[z]!=NULL ) + { + //Check if pos is inside area + if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) + { + if(areas[z]->flags==0) //Pen was not inside before (PenInside==0) + { + areas[z]->flags=1; //Pen is inside now (PenInside=1) + if(areas[z]->hookedActions & PEN_ENTER) //The user wants to receive pen enter events + areas[z]->callback(areas[z],PEN_ENTER); + } + } + else if(areas[z]->flags) //Pos not inside area, but it was before (PenInside==1) + { + areas[z]->flags=0; //Pen is no longer inside (PenInside=0) + if(areas[z]->hookedActions & PEN_LEAVE) //The user wants to receive pen leave events + areas[z]->callback(areas[z],PEN_LEAVE); + } + } + } + } + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + if(areas[z]!=NULL && (areas[z]->hookedActions&PEN_MOVE)) //User want's to receive pen move events + { + //Check if pos is inside area + if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2) + { + areas[z]->callback(areas[z],PEN_MOVE); + } + } + } + } + else //pen is not down now + { + if(oldPenDown) //but it was down before (negative edge) + { + for(int z=0; z < NUM_AREAS; z++) // For every touch area + { + //Check if pos is inside area + if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 ) + { + areas[z]->flags=0; //The pen is no longer inside (PenInside = 0); + if(areas[z]->hookedActions & PEN_UP) //user want's to receive pen up events + areas[z]->callback(areas[z],PEN_UP); + } + } + } + } return true; } bool touch_have_empty(unsigned char num) { - for(unsigned char i=0; iChange NUM_AREAS). -{ - - for(unsigned char i=0; iflags=0; - areas[i]=area; - return true; - } - } - return false; + //go through pointer array and check for free spaces + for(unsigned char i=0; iflags=0; //we start with empty flags (PenInside=0) + areas[i]=area; //save pointer into list + return true; + } + } + return false; //no free space found +} + +void touch_unregister_area(TOUCH_AREA_STRUCT* area) +{ + if(area==NULL) return; + + //go through pointer array and find the area to remove + for(unsigned char i=0; i