From f7ddad3689543e75f0ec109dec90a0639201fd14 Mon Sep 17 00:00:00 2001 From: Joel Bodenmann Date: Sat, 12 Nov 2016 20:16:55 +0100 Subject: [PATCH] First implementation of the spinbox widget --- gfxconf.example.h | 1 + src/gwin/gwin.mk | 3 +- src/gwin/gwin_mk.c | 1 + src/gwin/gwin_options.h | 7 + src/gwin/gwin_spinbox.c | 360 ++++++++++++++++++++++++++++++++++++++++ src/gwin/gwin_spinbox.h | 180 ++++++++++++++++++++ src/gwin/gwin_widget.h | 4 + 7 files changed, 555 insertions(+), 1 deletion(-) create mode 100644 src/gwin/gwin_spinbox.c create mode 100644 src/gwin/gwin_spinbox.h diff --git a/gfxconf.example.h b/gfxconf.example.h index 8b3a53b5..cd1dac66 100644 --- a/gfxconf.example.h +++ b/gfxconf.example.h @@ -215,6 +215,7 @@ // #define GWIN_KEYBOARD_DEFAULT_LAYOUT VirtualKeyboard_English1 // #define GWIN_NEED_KEYBOARD_ENGLISH1 TRUE // #define GWIN_NEED_TEXTEDIT FALSE +// #define GWIN_NEED_SPINBOX FALSE // #define GWIN_FLAT_STYLING FALSE // #define GWIN_WIDGET_TAGS FALSE diff --git a/src/gwin/gwin.mk b/src/gwin/gwin.mk index d735c3e1..16e2e56d 100644 --- a/src/gwin/gwin.mk +++ b/src/gwin/gwin.mk @@ -22,6 +22,7 @@ GFXSRC += $(GFXLIB)/src/gwin/gwin.c \ $(GFXLIB)/src/gwin/gwin_gl3d.c \ $(GFXLIB)/src/gwin/gwin_keyboard.c \ $(GFXLIB)/src/gwin/gwin_keyboard_layout.c \ - $(GFXLIB)/src/gwin/gwin_textedit.c + $(GFXLIB)/src/gwin/gwin_textedit.c \ + $(GFXLIB)/src/gwin/gwin_spinbox.c GFXINC += $(GFXLIB)/3rdparty/tinygl-0.4-ugfx/include diff --git a/src/gwin/gwin_mk.c b/src/gwin/gwin_mk.c index 2af501b5..0ec1fbf4 100644 --- a/src/gwin/gwin_mk.c +++ b/src/gwin/gwin_mk.c @@ -25,3 +25,4 @@ #include "gwin_keyboard.c" #include "gwin_keyboard_layout.c" #include "gwin_textedit.c" +#include "gwin_spinbox.c" diff --git a/src/gwin/gwin_options.h b/src/gwin/gwin_options.h index 6bf59d2a..b8aec479 100644 --- a/src/gwin/gwin_options.h +++ b/src/gwin/gwin_options.h @@ -163,6 +163,13 @@ #ifndef GWIN_NEED_TEXTEDIT #define GWIN_NEED_TEXTEDIT FALSE #endif + /** + * @brief Should the spinbox widget be included. + * @details Defaults to FALSE + */ + #ifndef GWIN_NEED_SPINBOX + #define GWIN_NEED_SPINBOX FALSE + #endif /** * @} * diff --git a/src/gwin/gwin_spinbox.c b/src/gwin/gwin_spinbox.c new file mode 100644 index 00000000..ff2e7f8b --- /dev/null +++ b/src/gwin/gwin_spinbox.c @@ -0,0 +1,360 @@ +/* + * This file is subject to the terms of the GFX License. If a copy of + * the license was not distributed with this file, you can obtain one at: + * + * http://ugfx.org/license.html + */ + +/** + * @file src/gwin/gwin_spinbox.c + * @brief GWIN sub-system spinbox code + */ + +// This widget uses partial redraw by using the widget flags to indicate which +// portions of the widget needs to be redrawn. + +#include "gfx.h" +#include "gwin_spinbox.h" +#include "stdlib.h" +#include "string.h" + +#if GFX_USE_GWIN && GWIN_NEED_SPINBOX + +#include "src/gwin/gwin_class.h" + +// Configuration options +#define TEXTPADDING 2 +#define BOXPADDING 8 +#define MAXCHARS 20 + +// Macros to assist in data type conversions +#define gw2obj ((GSpinboxObject *)gw) +#define gw2ButtonSize (gw->g.height) + +// Function to convert number to string, add decimal mark and sign +// Parameter *s initially points to end of buffer +char* fmtNum(int value, char* s, short placesOrTxtlen, const char* mark) +{ + uint8_t places = 0; + bool_t sign; + bool_t markAdded = FALSE; + + sign = (value < 0) ? 1 : 0; // Sign flag + + *--s = 0; // Add null-terminator to end of string + + for (; value; value /= 10) { + *--s = '0' + abs(value) % 10; + if (++places == placesOrTxtlen) { + *--s = *mark; // Add decimal mark + markAdded = TRUE; + } + } + + if (placesOrTxtlen && !markAdded) { // If a decimalplaces and no mark added then add leading zeroes + do { + *--s = '0'; + } while (++places != placesOrTxtlen); + *--s = *mark; // Add decimal mark + } + + if (*s == *mark) + *--s = '0'; // If first char is a decimalmark then add a leading zero + if (sign) + *--s = '-'; + + return s; +} + +static void sendSpinboxEvent(GSpinboxObject* gsw) +{ + #define ple ((GEventGWinSpinbox*)pe) + + GSourceListener* psl; + GEvent* pe; + + // Trigger a GWIN spinbox event + psl = 0; + + while ((psl = geventGetSourceListener(GWIDGET_SOURCE, psl))) { + if (!(pe = geventGetEventBuffer(psl))) + continue; + + // Fill in the event + ple->type = GEVENT_GWIN_SPINBOX; + ple->gwin = (GHandle) gsw; + ple->value = gsw->initial; +#if GWIN_WIDGET_TAGS + ple->tag = gw->tag; +#endif + + geventSendEvent(psl); + } + + #undef ple +} + +static void mouseUp(GWidgetObject* gw, coord_t x, coord_t y) +{ + #define gsw ((GSpinboxObject*)gw) + + // Store the current value so we can figure out whether the value + // changed as we don't want to send events if the value remains + // the same. + int oldValue = gsw->initial; + + // Only handle releases on the up/down buttons + if (y <= gw2ButtonSize) { + // The 'down' area was released + if (x >= gw->g.width - gw2ButtonSize) { + gsw->initial -= gsw->increment; + if (gsw->initial < gsw->minval) + gsw->initial = gsw->minval; + } + + else if (x <= gw->g.x + gw2ButtonSize) { + // The 'up' area was released + gsw->initial += gsw->increment; + if (gsw->initial > gsw->maxval) + gsw->initial = gsw->maxval; + } + + // Otherwise, don't do anything + else { + return; + } + + // Issue a redraw. Use partial redrawing. + gw->g.flags |= GSPINBOX_NUM_REDRAW; + _gwinUpdate((GHandle) gw); + + // Spinbox events are sent upon mouse release + if (gsw->initial != oldValue) + sendSpinboxEvent(gsw); + } + + #undef gsw +} + +static const gwidgetVMT SpinboxVMT = { + { + "Spinbox", // The class name + sizeof(GSpinboxObject), // The object size + _gwidgetDestroy, // The destroy routine + _gwidgetRedraw, // The redraw routine + 0, // The after-clear routine + }, + gwinSpinboxDefaultDraw, // Default drawing routine +#if GINPUT_NEED_MOUSE + { 0, // Process mouse down events + mouseUp, // Process mouse up events + 0, // Process mouse move events + }, +#endif +#if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD + { + 0 // Process keyboard key down events + }, +#endif +#if GINPUT_NEED_TOGGLE + { + 0, // No toggle role + 0, // Assign Toggles + 0, // Get Toggles + 0, // Process toggle off event + 0, // Process toggle on event + }, +#endif +#if GINPUT_NEED_DIAL + { + 0, // Dial roles + 0, // Assign Dials + 0, // Get Dials + 0, // Procees dial move events + }, +#endif +}; + +GHandle gwinGSpinboxTxtCreate(GDisplay* g, GSpinboxObject* wt, GWidgetInit* pInit, const char** textArray, uint8_t numEntries, short fieldWidth) +{ + // Create the widget + if (!(wt = (GSpinboxObject*)_gwidgetCreate(g, &wt->w, pInit, &SpinboxVMT))) { + return 0; + } + + // Initialize + wt->initial = 0; // Set to first item in string array + wt->minval = 0; // Set minval to first text item index + wt->maxval = numEntries-1; // Set maxval to last text item index + wt->increment = 1; + wt->txtArray = textArray; + wt->fieldwidth = fieldWidth; + wt->w.g.flags |= GSPINBOX_TXT; // Set flag for text spinbox + gwinSetVisible(&wt->w.g, pInit->g.show); + + return (GHandle)wt; +} + +GHandle gwinGSpinboxNumCreate(GDisplay* g, GSpinboxObject* wt, GWidgetInit* pInit, + int init, int min, int max, int step, const char* mark, + short places, const char* units, short fieldWidth) { + + // Create the widget + if (!(wt = (GSpinboxObject*) _gwidgetCreate(g, &wt->w, pInit, &SpinboxVMT))) { + return 0; + } + + // Initialize + wt->initial = init; + wt->minval = min; + wt->maxval = max; + wt->increment = step; + wt->decimalmark = mark; + wt->placesOrTxtlen = places; + wt->strData = units; + wt->fieldwidth = fieldWidth; + wt->w.g.flags |= GSPINBOX_NUM; // Set flag for numeric spinbox + gwinSetVisible(&wt->w.g, pInit->g.show); + + return (GHandle)wt; +} + +void gwinSpinboxDefaultDraw(GWidgetObject* gw, void* param) +{ + #define gsw ((GSpinboxObject*)gw) + + (void)param; + + char p[MAXCHARS]; // spinbox text max char + char* ptr; + const GColorSet* ps; + coord_t num=0; + coord_t arrowSize, border; + + // is it a valid handle? + if (gw->g.vmt != (gwinVMT*)&SpinboxVMT) { + return; + } + + // Retrieve colours + if ((gw->g.flags & GWIN_FLG_SYSENABLED)) { + ps = &gw->pstyle->enabled; + } else { + ps = &gw->pstyle->disabled; + } + + if (gw->g.flags & GSPINBOX_NUM) { + // Add padding zeroes for required decimalplaces + if (gsw->placesOrTxtlen >= num) { + while (num <= gsw->placesOrTxtlen) + num++; // If num <= places, (not including sign), add 1 to num for each additional place inc leading 0 + num++; // Add 1 for digit before decimal mark + } + + // format text string for display including sign and decimal mark if required + ptr = &p[MAXCHARS - 1]; // End of buffer + ptr = fmtNum(gsw->initial, ptr, gsw->placesOrTxtlen, gsw->decimalmark); + } + else if (gw->g.flags & GSPINBOX_TXT) { + ptr = (char*)gsw->txtArray[gsw->initial]; + } + + // Only numeric/text field requires updating if GSPINBOX_NUM_REDRAW flag set + if ((gw->g.flags & GSPINBOX_NUM_REDRAW)) { + + // Fill the stringbox with value - justifyRight + gdispGFillStringBox(gw->g.display, gw->g.x + gw2ButtonSize + 1 + TEXTPADDING, + gw->g.y + 1, gsw->fieldwidth, gw->g.height - 2, ptr, + gw->g.font, ps->text, gw->pstyle->background, justifyRight); + gw->g.flags &= ~GSPINBOX_NUM_REDRAW; // Reset flag + + } else { + + border = gw2ButtonSize / 8; // border is one eighth of gw2ButtonWidth + arrowSize = border*6; // arrowsize is 0.75 * gw2ButtonWidth + + #if GDISP_NEED_CONVEX_POLYGON + point upArrow[3] = { + { 0, arrowSize }, + { arrowSize + 1, arrowSize }, + { arrowSize / 2 + 1, 0 } + }; + + point downArrow[3] = { + { 0, -arrowSize }, + { arrowSize, -arrowSize }, + { arrowSize / 2, 0 } + }; + #endif + + // Draw the border + gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, + gw->g.height, ps->edge); + + // Fill left button box background area + gdispGFillArea(gw->g.display, gw->g.x + 1, + gw->g.y + 1, gw2ButtonSize-1, gw->g.height - 2, + gdispBlendColor(ps->fill, gw->pstyle->background, 128)); + + // Fill right button box background area + gdispGFillArea(gw->g.display, gw->g.x + gw->g.width - gw2ButtonSize, + gw->g.y + 1, gw2ButtonSize-1, gw->g.height - 2, + gdispBlendColor(ps->fill, gw->pstyle->background, 128)); + + // Draw left vertical line + gdispGDrawLine(gw->g.display, gw->g.x + gw2ButtonSize, + gw->g.y + 1, gw->g.x + gw2ButtonSize, + gw->g.y + gw->g.height - 2, ps->edge); + + // Draw right vertical line + gdispGDrawLine(gw->g.display, gw->g.x + gw->g.width - gw2ButtonSize, + gw->g.y + 1, gw->g.x + gw->g.width - gw2ButtonSize, + gw->g.y + gw->g.height - 2, ps->edge); + + #if GDISP_NEED_CONVEX_POLYGON + // Up Arrow + gdispGFillConvexPoly(gw->g.display,gw->g.x + border, gw->g.y + border, upArrow, 3, ps->edge); + // Down Arrow + gdispGFillConvexPoly(gw->g.display,gw->g.x + gw->g.width - arrowSize - border,gw->g.y + gw->g.height - border,downArrow,3,ps->edge); + #else + #warning "GWIN: Spinbox will display arrow symbols when GDISP_NEED_CONVEX_POLYGON is turned on" + // Plus symbol horizontal line + gdispGDrawLine(gw->g.display, gw->g.x + border, + gw->g.y + gw->g.height / 2, gw->g.x + gw2ButtonSize - border, + gw->g.y + gw->g.height / 2, ps->edge); + + // Plus symbol vertical line + gdispGDrawLine(gw->g.display, gw->g.x + gw2ButtonSize/2, + gw->g.y + border, gw->g.x + gw2ButtonSize/2, + gw->g.y + gw->g.height - border-1, ps->edge); + + // Minus symbol horizontal line + gdispGDrawLine(gw->g.display, gw->g.x + gw->g.width - 1 - border, + gw->g.y + gw->g.height - gw->g.height / 2 -1, gw->g.x + gw->g.width - gw2ButtonSize + border, + gw->g.y + gw->g.height - gw->g.height / 2 -1, ps->edge); + #endif + + if ((gw->g.flags & GSPINBOX_NUM)) { + // Fill the stringbox with units - justifyRight + gdispGFillStringBox(gw->g.display, gw->g.x + gw2ButtonSize + gsw->fieldwidth - TEXTPADDING, + gw->g.y + 1, gw->g.width - gsw->fieldwidth - gw2ButtonSize * 2, + gw->g.height - 2, gsw->strData, gw->g.font, ps->text, + gw->pstyle->background, justifyRight); + } + + // Fill the stringbox with value - justifyRight + gdispGFillStringBox(gw->g.display, gw->g.x + gw2ButtonSize + 1 + TEXTPADDING, + gw->g.y + 1, gsw->fieldwidth, gw->g.height - 2, ptr, gw->g.font, ps->text, gw->pstyle->background, justifyRight); + } + + #undef gsw +} + +// Limit the scope of the macros. Required for single-file compilation. +#undef TEXTPADDING +#undef BOXPADDING +#undef MAXCHARS +#undef gw2obj +#undef gw2ButtonSize + +#endif // GFX_USE_GWIN && GWIN_NEED_SPINBOX diff --git a/src/gwin/gwin_spinbox.h b/src/gwin/gwin_spinbox.h new file mode 100644 index 00000000..0b30be46 --- /dev/null +++ b/src/gwin/gwin_spinbox.h @@ -0,0 +1,180 @@ +/* + * This file is subject to the terms of the GFX License. If a copy of + * the license was not distributed with this file, you can obtain one at: + * + * http://ugfx.org/license.html + */ + +/** + * @file src/gwin/gwin_spinbox.h + * @brief GWIN Spinbox widget header file + * + * @defgroup Spinbox Spinbox + * @ingroup Widgets + * + * @brief Widget that spins through a text array or numeric values and returns an integer for the selection. + * + * @note The numeric spinbox is created using the following syntax: + * ghSpinBox1 = gwinSpinboxNumCreate(0, &wi, 10, 0, 90, 3, ",", 1, "px"); + * where parms = initial, min, max, step, mark, places, units, (see typedef struct GSpinboxObject) + * + * The text spinbox is created using the following syntax: + * ghSpinBox2 = gwinSpinboxTxtCreate(0, &wi, DaysOfWeek); + * where DaysOfWeek is defined thus: + * static const char *DaysOfWeek[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", 0}; + * + * GDISP_NEED_CONVEX_POLYGON will display up/down arrows if set, '+' & '-' symbols if not + * + * @pre GFX_USE_GDISP must be set to TRUE in your gfxconf.h + * @pre GFX_USE_GWIN must be set to TRUE in your gfxconf.h + * @pre GDISP_NEED_TEXT must be set to TRUE in your gfxconf.h + * @pre GWIN_NEED_SPINBOX must be set to TRUE in your gfxconf.h + * @pre The fonts you want to use must be enabled in your gfxconf.h + * + * @{ + */ + +#ifndef _GWIN_SPINBOX_H +#define _GWIN_SPINBOX_H + +// This file is included within "src/gwin/gwin_widget.h" + +/** + * @brief The event type for a spinbox event + */ +#define GEVENT_GWIN_SPINBOX (GEVENT_GWIN_CTRL_FIRST+10) + +/** + * @brief The internal spinbox flags + * @note Used only for writing a custom draw routine. + * @{ + */ +#define GSPINBOX_NUM_REDRAW 0x01 // Set flag for minimal redraw of numeric value or text, (no widget or units redrawn) +#define GSPINBOX_TXT 0x02 // Flag for text spinbox code flow +#define GSPINBOX_NUM 0x04 // Flag for numeric spinbox code flow +#define GSPINBOX_UPDOWN 0x08 // Increment/decrement flag + +/** + * @brief A spinbox event + */ +typedef struct GEventGWinSpinbox { + GEventType type; // The type of this event (GEVENT_GWIN_SPINBOX) + GHandle gwin; // The spinbox + #if GWIN_WIDGET_TAGS + WidgetTag tag; // Tag is an short - allows user to assign a numeric ID to identify widget without knowing its type. + #endif + int value; // The spinbox value +} GEventGWinSpinbox; + + +// A Spinbox widget +typedef struct GSpinboxObject { + GWidgetObject w; // Base Class + + int initial; // Initial value and current value + int minval; // Minimum numeric value + int maxval; // Maximum numeric value + int increment; // Numeric step value + const char* decimalmark; // Decimal mark character (eg. ".", "," etc.) + short placesOrTxtlen; // Number of digits after decimal point or the length of the text string + const char* strData; // Inits for numeric spinbox + const char** txtArray; // Pointer to array of pointers for spinbox text data + short fieldwidth; // Text field width in Text Spinbox,Value field width in Numeric Spinbox +} GSpinboxObject; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a text Spinbox widget + * @details A Spinbox widget is a rectangular box which can contain text or numeric data and allows the user to select + * different numeric/text values with the use of mouse/touchpanel, (up/down arrows, keyboard & dial controls not supported). + * + * @param[in] g The GDisplay on which the Spinbox should be displayed + * @param[in] wt The Spinbox structure to initialise. If this is NULL, the structure is dynamically allocated. + * @param[in] pInit The initialisation parameters to use. + * @param[in] textArray Array of strings through which the spinbox will spin through. + * @param[in] numEntries The number of strings in the @p textArray. + * @param[in] numEntries Text field pixel width in Text Spinbox,Value field width in Numeric Spinbox + * + * @return NULL if there is no resultant drawing area, otherwise the widget handle. + * + * @note If the initial text set is larger than maxSize then the text is truncated at maxSize characters. + * @api + */ +GHandle gwinGSpinboxTxtCreate(GDisplay* g, GSpinboxObject* wt, GWidgetInit* pInit, const char** textArray, uint8_t numEntries,short fieldWidth); +#define gwinSpinboxTxtCreate(wt, pInit, textArray, numEntries, fieldWidth) gwinGSpinboxTxtCreate(GDISP, wt, pInit, textArray, numEntries, fieldWidth) + + +/** + * @brief Create a numeric Spinbox widget + * @details A Spinbox widget is a rectangular box which can contain text or numeric data and allows the user to select + * different numeric/text values with the use of mouse/touchpanel, (up/down arrows, keyboard & dial controls not supported). + * + * @param[in] g The GDisplay on which the Spinbox should be displayed + * @param[in] wt The Spinbox structure to initialise. If this is NULL, the structure is dynamically allocated. + * @param[in] pInit The initialisation parameters to use. + * @param[in] init The initial spinbox value. + * @param[in] min The minimum spinbox value. + * @param[in] max The maximum spinbox value. + * @param[in] step The increment value. + * @param[in] mark The mark character separator, (decimal point, comma, colon etc). + * @param[in] places The number of places after the mark, (0 will prevent any mark displayed). + * @param[in] units The units string, (blank for no units). + * + * @return NULL if there is no resultant drawing area, otherwise the widget handle. + * + * @note If the initial text set is larger than maxSize then the text is truncated at maxSize characters. + * @api + */ +GHandle gwinGSpinboxNumCreate(GDisplay* g, GSpinboxObject* wt, GWidgetInit* pInit, int init, int min, int max, int step, const char* mark, short places, const char* units, short fieldWidth); +#define gwinSpinboxNumCreate(wt, pInit, init, min, max, step, mark, places, units, fieldWidth) gwinGSpinboxNumCreate(GDISP, wt, pInit, init, min, max, step, mark, places, units, fieldWidth) + +/** + * @defgroup Renderings_Spinbox Renderings + * + * @brief Built-in rendering functions for the Spinbox widget. + * + * @details These function may be passed to @p gwinSetCustomDraw() to get different Spinbox drawing styles. + * + * @note In your custom Spinbox drawing function you may optionally call these + * standard functions and then draw your extra details on top. + * @note These custom drawing routines don't have to worry about setting clipping as the framework + * sets clipping to the object window prior to calling these routines. + * + * @{ + */ + +/** + * @brief The default rendering function for the Spinbox widget. + * + * @param[in] gw The widget object (must be a Spinbox). + * @param[in] param A parameter passed in from the user. Ignored by this function. + * + * @api + */ + +void gwinSpinboxDefaultDraw(GWidgetObject* gw, void* param); +/** + * @brief Set the spinbox minimum and maximum values. + * + * @param[in] gh The window handle (must be a spinbox window) + * @param[in] min The minimum value + * @param[in] max The maximum value + * @param[in] step The value to increment + * @note Sets the minimum, maximum and step values, returns if min > max. + * + * @api + */ +void gwinSpinboxSetParams (GHandle gh, int initial, int min, int max, int step, const char* decimalmark, short placesOrTxtlen, const char* units); + + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif // _GWIN_Spinbox_H +/** @} */ diff --git a/src/gwin/gwin_widget.h b/src/gwin/gwin_widget.h index ece98a06..5bd0bdd8 100644 --- a/src/gwin/gwin_widget.h +++ b/src/gwin/gwin_widget.h @@ -447,5 +447,9 @@ bool_t gwinAttachListener(GListener *pl); #include "gwin_textedit.h" #endif +#if GWIN_NEED_SPINBOX || defined(__DOXYGEN__) + #include "gwin_spinbox.h" +#endif + #endif /* _GWIDGET_H */ /** @} */