Jump to content

Draggable button


crteensy

Recommended Posts

Nothing earth-shattering, but I thought I might share my draggable button code. It might be interesting for others who want to implement their own custom widgets.

It's based on uGFX's button and can be dragged around while the mouse is pressed over the button. It uses styles just like the original button. I have not tested the fancier dawing functions (arc, ellipse, polygons).

There are two MouseMove functions in the .c file, the first (commented out) one doesn't add a dragging threshold. This is fine when used with a mouse. The second one (not commented out) adds a 5 pixel threshold before the button actually starts moving. This is a bit more convenient when used with flaky touch input.

draggableButton.h:

#include "gfx.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct GDraggableButtonObject {
GWidgetObject w;
#if GINPUT_NEED_TOGGLE
uint16_t toggle;
#endif
coord_t hotspot_x;
coord_t hotspot_y;
} GDraggableButtonObject;


GHandle gwinGDraggableButtonCreate(GDisplay *g, GDraggableButtonObject *gb, const GWidgetInit *pInit);
#define gwinDraggableButtonCreate(gb, pInit) gwinGDraggableButtonCreate(GDISP, gb, pInit)

void gwinDraggableButtonDraw_Normal(GWidgetObject *gw, void *param); // @< A standard button
#if GDISP_NEED_ARC || defined(__DOXYGEN__)
void gwinDraggableButtonDraw_Rounded(GWidgetObject *gw, void *param); // @< A rounded rectangle button
#endif
#if GDISP_NEED_ELLIPSE || defined(__DOXYGEN__)
void gwinDraggableButtonDraw_Ellipse(GWidgetObject *gw, void *param); // @< A circular button
#endif
#if GDISP_NEED_CONVEX_POLYGON || defined(__DOXYGEN__)
void gwinDraggableButtonDraw_ArrowUp(GWidgetObject *gw, void *param); // @< An up arrow button
void gwinDraggableButtonDraw_ArrowDown(GWidgetObject *gw, void *param); // @< A down arrow button
void gwinDraggableButtonDraw_ArrowLeft(GWidgetObject *gw, void *param); // @< A left arrow button
void gwinDraggableButtonDraw_ArrowRight(GWidgetObject *gw, void *param); // @< A right arrow button
#endif
#if GDISP_NEED_IMAGE || defined(__DOXYGEN__)
void gwinDraggableButtonDraw_Image(GWidgetObject *gw, void *param); // @< An image button - see the notes above on the param.
#endif

#ifdef __cplusplus
}
#endif

draggableButton.c:

#include "draggableButton.h"

#include

#include "src/gwin/gwin_class.h"

// Our pressed state
#define GBUTTON_FLG_PRESSED (GWIN_FIRST_CONTROL_FLAG<<0)
#define GBUTTON_FLG_MOVED (GWIN_FIRST_CONTROL_FLAG<<1)

#if GINPUT_NEED_MOUSE
// A mouse down has occurred over the button
static void MouseDown(GWidgetObject *gw, coord_t x, coord_t y) {
GDraggableButtonObject* p = (GDraggableButtonObject*)gw;
gw->g.flags |= GBUTTON_FLG_PRESSED;
p->hotspot_x = x;
p->hotspot_y = y;
_gwinUpdate((GHandle)gw);
}

// A mouse up has occurred (it is over the button, because the button can be dragged)
static void MouseUp(GWidgetObject *gw, coord_t x, coord_t y) {
(void) x; (void) y;
gw->g.flags &= ~GBUTTON_FLG_PRESSED;
_gwinUpdate((GHandle)gw);

if(!(gw->g.flags & GBUTTON_FLG_MOVED))
{
_gwinSendEvent(&gw->g, GEVENT_GWIN_BUTTON);
}
else // dropped
{
gw->g.flags &= ~GBUTTON_FLG_MOVED;
gwinRedrawDisplay(GDISP, FALSE);
}
}

// // A mouse move has occurred
// static void MouseMove(GWidgetObject *gw, coord_t x, coord_t y) {
// GDraggableButtonObject* p = (GDraggableButtonObject*)gw;
// coord_t winx = gwinGetScreenX((GWindowObject*)gw);
// coord_t winy = gwinGetScreenY((GWindowObject*)gw);
// gw->g.flags |= GBUTTON_FLG_MOVED;
// #if(GWIN_NEED_CONTAINERS)
// if (gw->g.parent)
// {
// GWindowObject* parent = gw->g.parent;
// winx -= parent->x;
// winy -= parent->y;
// }
// #endif
//
// gwinMove((GWindowObject *)gw, winx+x-p->hotspot_x, winy+y-p->hotspot_y);
// }

// A mouse move has occurred
static void MouseMove(GWidgetObject *gw, coord_t x, coord_t y) {
GDraggableButtonObject* p = (GDraggableButtonObject*)gw;
coord_t winx = gwinGetScreenX((GWindowObject*)gw);
coord_t winy = gwinGetScreenY((GWindowObject*)gw);
// not moving yet? calculate displacement
if (!(gw->g.flags & GBUTTON_FLG_MOVED))
{
coord_t dx = abs(x - p->hotspot_x);
coord_t dy = abs(y - p->hotspot_y);
// displacement > 5 pixels in any direction: start moving
if((dx > 5) || (dy > 5))
{
gw->g.flags |= GBUTTON_FLG_MOVED;
}
}

#if(GWIN_NEED_CONTAINERS)
if (gw->g.parent)
{
GWindowObject* parent = gw->g.parent;
winx -= parent->x;
winy -= parent->y;
}
#endif

if(gw->g.flags & GBUTTON_FLG_MOVED)
{
gwinMove((GWindowObject *)gw, winx+x-p->hotspot_x, winy+y-p->hotspot_y);
}
}
#endif

#if GINPUT_NEED_TOGGLE
// A toggle off has occurred
static void ToggleOff(GWidgetObject *gw, uint16_t role) {
(void) role;
gw->g.flags &= ~GBUTTON_FLG_PRESSED;
_gwinUpdate((GHandle)gw);
}

// A toggle on has occurred
static void ToggleOn(GWidgetObject *gw, uint16_t role) {
(void) role;
gw->g.flags |= GBUTTON_FLG_PRESSED;
_gwinUpdate((GHandle)gw);
// Trigger the event on button down (different than for mouse/touch)
_gwinSendEvent(&gw->g, GEVENT_GWIN_BUTTON);
}

static void ToggleAssign(GWidgetObject *gw, uint16_t role, uint16_t instance) {
(void) role;
((GButtonObject *)gw)->toggle = instance;
}

static uint16_t ToggleGet(GWidgetObject *gw, uint16_t role) {
(void) role;
return ((GButtonObject *)gw)->toggle;
}
#endif

// The button VMT table
static const gwidgetVMT draggableButtonVMT = {
{
"Draggable Button", // The classname
sizeof(GButtonObject), // The object size
_gwidgetDestroy, // The destroy routine
_gwidgetRedraw, // The redraw routine
0, // The after-clear routine
},
gwinDraggableButtonDraw_Normal, // The default drawing routine
#if GINPUT_NEED_MOUSE
{
MouseDown, // Process mouse down events
MouseUp, // Process mouse up events
MouseMove, // Process mouse move events
},
#endif
#if GINPUT_NEED_TOGGLE
{
1, // 1 toggle role
ToggleAssign, // Assign Toggles
ToggleGet, // Get Toggles
ToggleOff, // Process toggle off events
ToggleOn, // Process toggle on events
},
#endif
#if GINPUT_NEED_DIAL
{
0, // No dial roles
0, // Assign Dials (NOT USED)
0, // Get Dials (NOT USED)
0, // Process dial move events (NOT USED)
},
#endif
};

GHandle gwinGDraggableButtonCreate(GDisplay *g, GDraggableButtonObject *gw, const GWidgetInit *pInit) {
if (!(gw = (GDraggableButtonObject *)_gwidgetCreate(g, &gw->w, pInit, &draggableButtonVMT)))
return 0;

#if GINPUT_NEED_TOGGLE
gw->toggle = GWIDGET_NO_INSTANCE;
#endif
gwinSetVisible((GHandle)gw, pInit->g.show);
return (GHandle)gw;
}

/*----------------------------------------------------------
* Custom Draw Routines
*----------------------------------------------------------*/

static const GColorSet *getDrawColors(GWidgetObject *gw) {
if (!(gw->g.flags & GWIN_FLG_SYSENABLED)) return &gw->pstyle->disabled;
if ((gw->g.flags & GBUTTON_FLG_PRESSED)) return &gw->pstyle->pressed;
return &gw->pstyle->enabled;
}

#if GWIN_FLAT_STYLING
void gwinDraggableButtonDraw_Normal(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
(void) param;

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

gdispGFillStringBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width-1, gw->g.height-1, gw->text, gw->g.font, pcol->text, pcol->fill, justifyCenter);
gdispGDrawLine(gw->g.display, gw->g.x+gw->g.width-1, gw->g.y, gw->g.x+gw->g.width-1, gw->g.y+gw->g.height-1, pcol->edge);
gdispGDrawLine(gw->g.display, gw->g.x, gw->g.y+gw->g.height-1, gw->g.x+gw->g.width-2, gw->g.y+gw->g.height-1, pcol->edge);
}
#else
void gwinDraggableButtonDraw_Normal(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
fixed alpha;
fixed dalpha;
coord_t i;
color_t tcol, bcol;
(void) param;

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

/* Fill the box blended from variants of the fill color */
tcol = gdispBlendColor(White, pcol->fill, TOP_FADE);
bcol = gdispBlendColor(Black, pcol->fill, BOTTOM_FADE);
dalpha = FIXED(255)/gw->g.height;
for(alpha = 0, i = 0; i < gw->g.height; i++, alpha += dalpha)
gdispGDrawLine(gw->g.display, gw->g.x, gw->g.y+i, gw->g.x+gw->g.width-2, gw->g.y+i, gdispBlendColor(bcol, tcol, NONFIXED(alpha)));

gdispGDrawStringBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width-1, gw->g.height-1, gw->text, gw->g.font, pcol->text, justifyCenter);
gdispGDrawLine(gw->g.display, gw->g.x+gw->g.width-1, gw->g.y, gw->g.x+gw->g.width-1, gw->g.y+gw->g.height-1, pcol->edge);
gdispGDrawLine(gw->g.display, gw->g.x, gw->g.y+gw->g.height-1, gw->g.x+gw->g.width-2, gw->g.y+gw->g.height-1, pcol->edge);
}
#endif

#if GDISP_NEED_ARC
void gwinDraggableButtonDraw_Rounded(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
(void) param;

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
if (gw->g.width >= 2*RND_CNR_SIZE+10) {
gdispGFillRoundedBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, RND_CNR_SIZE-1, pcol->fill);
gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+RND_CNR_SIZE, gw->g.width-2, gw->g.height-(2*RND_CNR_SIZE), gw->text, gw->g.font, pcol->text, justifyCenter);
gdispGDrawRoundedBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, RND_CNR_SIZE, pcol->edge);
} else {
gdispGFillStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, pcol->fill, justifyCenter);
gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);
}
}
#endif

#if GDISP_NEED_ELLIPSE
void gwinDraggableButtonDraw_Ellipse(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
(void) param;

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
gdispGFillEllipse(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width/2-1, gw->g.height/2-1, pcol->fill);
gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);
gdispGDrawEllipse(gw->g.display, gw->g.x, gw->g.y, gw->g.width/2, gw->g.height/2, pcol->edge);
}
#endif

#if GDISP_NEED_CONVEX_POLYGON
void gwinDraggableButtonDraw_ArrowUp(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
(void) param;
point arw[7];

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

arw[0].x = gw->g.width/2; arw[0].y = 0;
arw[1].x = gw->g.width-1; arw[1].y = gw->g.height/ARROWHEAD_DIVIDER;
arw[2].x = (gw->g.width + gw->g.width/ARROWBODY_DIVIDER)/2; arw[2].y = gw->g.height/ARROWHEAD_DIVIDER;
arw[3].x = (gw->g.width + gw->g.width/ARROWBODY_DIVIDER)/2; arw[3].y = gw->g.height-1;
arw[4].x = (gw->g.width - gw->g.width/ARROWBODY_DIVIDER)/2; arw[4].y = gw->g.height-1;
arw[5].x = (gw->g.width - gw->g.width/ARROWBODY_DIVIDER)/2; arw[5].y = gw->g.height/ARROWHEAD_DIVIDER;
arw[6].x = 0; arw[6].y = gw->g.height/ARROWHEAD_DIVIDER;

gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
gdispGFillConvexPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->fill);
gdispGDrawPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->edge);
gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);
}

void gwinDraggableButtonDraw_ArrowDown(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
(void) param;
point arw[7];

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

arw[0].x = gw->g.width/2; arw[0].y = gw->g.height-1;
arw[1].x = gw->g.width-1; arw[1].y = gw->g.height-1-gw->g.height/ARROWHEAD_DIVIDER;
arw[2].x = (gw->g.width + gw->g.width/ARROWBODY_DIVIDER)/2; arw[2].y = gw->g.height-1-gw->g.height/ARROWHEAD_DIVIDER;
arw[3].x = (gw->g.width + gw->g.width/ARROWBODY_DIVIDER)/2; arw[3].y = 0;
arw[4].x = (gw->g.width - gw->g.width/ARROWBODY_DIVIDER)/2; arw[4].y = 0;
arw[5].x = (gw->g.width - gw->g.width/ARROWBODY_DIVIDER)/2; arw[5].y = gw->g.height-1-gw->g.height/ARROWHEAD_DIVIDER;
arw[6].x = 0; arw[6].y = gw->g.height-1-gw->g.height/ARROWHEAD_DIVIDER;

gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
gdispGFillConvexPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->fill);
gdispGDrawPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->edge);
gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);
}

void gwinDraggableButtonDraw_ArrowLeft(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
(void) param;
point arw[7];

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

arw[0].x = 0; arw[0].y = gw->g.height/2;
arw[1].x = gw->g.width/ARROWHEAD_DIVIDER; arw[1].y = 0;
arw[2].x = gw->g.width/ARROWHEAD_DIVIDER; arw[2].y = (gw->g.height - gw->g.height/ARROWBODY_DIVIDER)/2;
arw[3].x = gw->g.width-1; arw[3].y = (gw->g.height - gw->g.height/ARROWBODY_DIVIDER)/2;
arw[4].x = gw->g.width-1; arw[4].y = (gw->g.height + gw->g.height/ARROWBODY_DIVIDER)/2;
arw[5].x = gw->g.width/ARROWHEAD_DIVIDER; arw[5].y = (gw->g.height + gw->g.height/ARROWBODY_DIVIDER)/2;
arw[6].x = gw->g.width/ARROWHEAD_DIVIDER; arw[6].y = gw->g.height-1;

gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
gdispGFillConvexPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->fill);
gdispGDrawPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->edge);
gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);
}

void gwinDraggableButtonDraw_ArrowRight(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
(void) param;
point arw[7];

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

arw[0].x = gw->g.width-1; arw[0].y = gw->g.height/2;
arw[1].x = gw->g.width-1-gw->g.width/ARROWHEAD_DIVIDER; arw[1].y = 0;
arw[2].x = gw->g.width-1-gw->g.width/ARROWHEAD_DIVIDER; arw[2].y = (gw->g.height - gw->g.height/ARROWBODY_DIVIDER)/2;
arw[3].x = 0; arw[3].y = (gw->g.height - gw->g.height/ARROWBODY_DIVIDER)/2;
arw[4].x = 0; arw[4].y = (gw->g.height + gw->g.height/ARROWBODY_DIVIDER)/2;
arw[5].x = gw->g.width-1-gw->g.width/ARROWHEAD_DIVIDER; arw[5].y = (gw->g.height + gw->g.height/ARROWBODY_DIVIDER)/2;
arw[6].x = gw->g.width-1-gw->g.width/ARROWHEAD_DIVIDER; arw[6].y = gw->g.height-1;

gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
gdispGFillConvexPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->fill);
gdispGDrawPoly(gw->g.display, gw->g.x, gw->g.y, arw, 7, pcol->edge);
gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);
}
#endif

#if GDISP_NEED_IMAGE || defined(__DOXYGEN__)
void gwinDraggableButtonDraw_Image(GWidgetObject *gw, void *param) {
const GColorSet * pcol;
coord_t sy;

if (gw->g.vmt != (gwinVMT *)&draggableButtonVMT) return;
pcol = getDrawColors(gw);

if (!(gw->g.flags & GWIN_FLG_SYSENABLED)) {
sy = 2 * gw->g.height;
} else if ((gw->g.flags & GBUTTON_FLG_PRESSED)) {
sy = gw->g.height;
} else {
sy = 0;
}

gdispGImageDraw(gw->g.display, (gdispImage *)param, gw->g.x, gw->g.y, gw->g.width, gw->g.height, 0, sy);
gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);
}
#endif

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...