363 lines
10 KiB
C
363 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, 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
|