ugfx/src/gwin/gwin_spinbox.c

361 lines
10 KiB
C

/*
* 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"
#if GFX_USE_GWIN && GWIN_NEED_SPINBOX
#include "gwin_class.h"
#include "stdlib.h"
#include "string.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)
// Macros to assist in calculations
#define FIELD_WIDTH(gw) (gw->g.width - 2*gw->g.height)
// Function to convert number to string, add decimal mark and sign
// Parameter s initially points to end of buffer
// ToDo: Improve this function. Need some serious clean-up & proper error detection/prevention/handling.
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)
{
// 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->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) {
// 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->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, gw->g.width - 2 * gw2ButtonSize - gdispGetStringWidth(gsw->strData, gw->g.font) - 6, 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 + gw->g.width - gw2ButtonSize - gdispGetStringWidth(gsw->strData, gw->g.font) - TEXTPADDING,
gw->g.y + 1, gdispGetStringWidth(gsw->strData, gw->g.font),
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, gw->g.width - 2 * gw2ButtonSize - gdispGetStringWidth(gsw->strData, gw->g.font) - 6, 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