Jump to content

dragging a widget


crteensy

Recommended Posts

Is it possible to drag a widget around? Currently I just need a label, though I'm not yet sure if that will change in the future. I'd use this in a force directed graph demo.

The buttonVMT table in gwin_button.c seems to indicate that dragging is not possible because there's no function that handles mouse move events (I have no idea if I'm looking in the right place!)

Link to comment
Share on other sites

You are looking in the right place. Widgets are in the place they are put. The application can move them with gwinMove but that is something not well tested as the moving of widgets seldom happens in embedded platforms (or even desktop applications except in dynamic sizing forms). It should be possible however to copy the label code into your project and modify it to get the functionality you want.

You are indeed looking at the right spot in the code to be doing something like that.

Link to comment
Share on other sites

Incredibly sketchy and hacked together from button and label code, but it works, sort of.

The code below does more than just show a draggable label because I added the label code to an existing draft. It's kind of a main menu with two buttons, one of which opens a sub menu with a "back" button. These work fine.

The magic is inside the MouseDown, MouseUp and MouseMove functions. Once clicked a hotspot is stored and while the mouse is down the label is moved relative to this hotspot. There is one problem, though: If the label's parent container doesn't cover the whole screen (see cwidth and cheight in createWidgets()), the calculated coordinates for gwinMove are wrong. How can I move the label relative to its parent?

#include 
#include "gfx.h"
#include "src/gwin/gwin_class.h"
// macros to assist in data type conversions
#define gh2obj ((GLabelObject *)gh)
#define gw2obj ((GLabelObject *)gw)

// flags for the GLabelObject
#define GLABEL_FLG_WAUTO (GWIN_FIRST_CONTROL_FLAG << 0)
#define GLABEL_FLG_HAUTO (GWIN_FIRST_CONTROL_FLAG << 1)
#define GLABEL_FLG_BORDER (GWIN_FIRST_CONTROL_FLAG << 2)

static GHandle ghMainContainer;
static GHandle ghSubContainer;
static GHandle ghButton1;
static GHandle ghButton1Back;
static GHandle ghButton2;
static GHandle ghDragLabel;
static GWidgetStyle myStyle = {
Black, // background
{White, Orange, Orange, Black}, // enabled (text edge fill progress)
{White, Gray, Gray, Black}, // disabled
{White, Gray, Orange, Black} // pressed
};

typedef struct GDragLabelObject {
GWidgetObject w;
coord_t hotspot_x;
coord_t hotspot_y;
bool_t beingDragged;
} GDragLabelObject;

#if GINPUT_NEED_MOUSE
// A mouse down has occurred over the button
static void MouseDown(GWidgetObject *gw, coord_t x, coord_t y) {
(void) x; (void) y;
GDragLabelObject* p = (GDragLabelObject*)gw;
p->beingDragged = TRUE;
p->hotspot_x = x;
p->hotspot_y = y;
}

// A mouse up has occurred (it may or may not be over the button)
static void MouseUp(GWidgetObject *gw, coord_t x, coord_t y) {
(void) x; (void) y;
GDragLabelObject* p = (GDragLabelObject*)gw;
p->beingDragged = FALSE;
}
// A mouse move has occurred
static void MouseMove(GWidgetObject *gw, coord_t x, coord_t y) {
if (((GDragLabelObject*)gw)->beingDragged == TRUE)
{
GDragLabelObject* p = (GDragLabelObject*)gw;
gwinMove((GWindowObject *)gw, p->w.g.x+x-p->hotspot_x, p->w.g.y+y-p->hotspot_y);
}
}
#endif

static void gwinDragLabelDefaultDraw(GWidgetObject *gw, void *param);

static const gwidgetVMT dragLabelVMT = {
{
"DragLabel", // The classname
sizeof(GDragLabelObject), // The object size
_gwidgetDestroy, // The destroy routine
_gwidgetRedraw, // The redraw routine
0, // The after-clear routine
},
gwinDragLabelDefaultDraw, // The default drawing routine
#if GINPUT_NEED_MOUSE
{
MouseDown, // Process mouse down events
MouseUp, // Process mouse up events
MouseMove, // Process mouse move events (NOT USED)
},
#endif
#if GINPUT_NEED_TOGGLE
{
0, 0, 0, 0, 0, // No toggles
},
#endif
#if GINPUT_NEED_DIAL
{
0, 0, 0, 0, // No dials
},
#endif
};

// simple: single line with no wrapping
static coord_t getwidth(const char *text, font_t font, coord_t maxwidth) {
(void) maxwidth;

return gdispGetStringWidth(text, font)+2; // Allow one pixel of padding on each side
}

// simple: single line with no wrapping
static coord_t getheight(const char *text, font_t font, coord_t maxwidth) {
(void) text;
(void) maxwidth;

return gdispGetFontMetric(font, fontHeight);
}

static void gwinDragLabelDefaultDraw(GWidgetObject *gw, void *param) {
coord_t w, h;
color_t c;
(void) param;

// is it a valid handle?
if (gw->g.vmt != (gwinVMT *)&dragLabelVMT)
return;

w = (gw->g.flags & GLABEL_FLG_WAUTO) ? getwidth(gw->text, gw->g.font, gdispGGetWidth(gw->g.display) - gw->g.x) : gw->g.width;
h = (gw->g.flags & GLABEL_FLG_HAUTO) ? getheight(gw->text, gw->g.font, gdispGGetWidth(gw->g.display) - gw->g.x) : gw->g.height;
c = (gw->g.flags & GWIN_FLG_SYSENABLED) ? gw->pstyle->enabled.text : gw->pstyle->disabled.text;

if (gw->g.width != w || gw->g.height != h) {
gwinResize(&gw->g, w, h);

return;
}

#if GWIN_LABEL_ATTRIBUTE
if (gw2obj->attr) {
gdispGFillStringBox(gw->g.display, gw->g.x, gw->g.y, gw2obj->tab, h, gw2obj->attr, gw->g.font, c, gw->pstyle->background, justifyLeft);
gdispGFillStringBox(gw->g.display, gw->g.x + gw2obj->tab, gw->g.y, w-gw2obj->tab, h, gw->text, gw->g.font, c, gw->pstyle->background, justifyLeft);
} else
gdispGFillStringBox(gw->g.display, gw->g.x, gw->g.y, w, h, gw->text, gw->g.font, c, gw->pstyle->background, justifyLeft);
#else
gdispGFillStringBox(gw->g.display, gw->g.x, gw->g.y, w, h, gw->text, gw->g.font, c, gw->pstyle->background, justifyLeft);
#endif

// render the border (if any)
if (gw->g.flags & GLABEL_FLG_BORDER)
gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, w, h, (gw->g.flags & GWIN_FLG_SYSENABLED) ? gw->pstyle->enabled.edge : gw->pstyle->disabled.edge);
}

GHandle gwinGDragLabelCreate(GDisplay *g, GDragLabelObject *gw, GWidgetInit *pInit) {
uint16_t flags = 0;

// auto assign width
if (pInit->g.width <= 0) {

flags |= GLABEL_FLG_WAUTO;
pInit->g.width = getwidth(pInit->text, gwinGetDefaultFont(), gdispGGetWidth(g) - pInit->g.x);
}

// auto assign height
if (pInit->g.height <= 0) {
flags |= GLABEL_FLG_HAUTO;
pInit->g.height = getheight(pInit->text, gwinGetDefaultFont(), gdispGGetWidth(g) - pInit->g.x);
}

if (!(gw = (GDragLabelObject *)_gwidgetCreate(g, &gw->w, pInit, &dragLabelVMT)))
return 0;

gwinSetVisible((GHandle)gw, pInit->g.show);
return (GHandle)gw;
}
#define gwinDragLabelCreate(gb, pInit) gwinGDragLabelCreate(GDISP, gb, pInit)

static void createWidgets(void) {
uint8_t btnSize = 48;
uint8_t btnDist = btnSize/2;
coord_t height = gdispGetHeight();
coord_t width = gdispGetWidth();
coord_t cheight = height/2;
coord_t cwidth = width/2;
GWidgetInit wi;

// Apply some default values for GWIN
gwinWidgetClearInit(&wi);

// Apply the container parameters
wi.g.show = FALSE;
wi.g.width = cwidth;
wi.g.height = cheight;
wi.g.x = (width - cwidth)/2;
wi.g.y = (height - cheight)/2;
wi.text = "Main Container";
ghMainContainer = gwinContainerCreate(0, &wi, 0);//GWIN_CONTAINER_BORDER);
wi.g.show = TRUE;

wi.text = "Bake Container";
wi.g.show = FALSE;
ghSubContainer = gwinContainerCreate(0, &wi, 0);//GWIN_CONTAINER_BORDER);

// Apply the button parameters
wi.g.width = btnSize;
wi.g.height = btnSize;
wi.g.x = cwidth/2 - btnSize - btnDist/2;
wi.g.y = cheight/2-btnSize/2;
wi.text = "Button 1";
wi.g.parent = ghMainContainer;
wi.g.show = TRUE;
ghButton1 = gwinButtonCreate(0, &wi);

// Apply the button parameters
wi.g.x = cwidth/2 + btnDist/2;
wi.text = "Button 2";
wi.g.parent = ghMainContainer;
ghButton2 = gwinButtonCreate(0, &wi);

// Apply the button parameters
wi.g.x = 3*cwidth/4;
wi.g.y = 3*cheight/4;
wi.text = "drag me";
wi.g.parent = ghMainContainer;
ghDragLabel = gwinDragLabelCreate(0, &wi);

wi.g.x = btnDist;
wi.g.y = btnDist;
wi.text = "Back";
wi.g.parent = ghSubContainer;
ghButton1Back = gwinButtonCreate(0, &wi);


// Make the container become visible - therefore all its children
// become visible as well
gwinHide(ghSubContainer);
gwinShow(ghMainContainer);
}

static GListener gl;
int main(void) {

// Initialize the display
gfxInit();

// Set the widget defaults
gwinSetDefaultFont(gdispOpenFont("*"));
gwinSetDefaultStyle(&myStyle, FALSE);
// gwinSetDefaultStyle(&WhiteWidgetStyle, FALSE);
gdispClear(Black);

// Create the widget
createWidgets();

geventListenerInit(&gl);
gwinAttachListener(&gl);
GEvent* pe;

while(1) {
pe = geventEventWait(&gl, TIME_INFINITE);
switch(pe->type) {
case GEVENT_GWIN_BUTTON:
if (((GEventGWinButton*)pe)->gwin == ghButton1) {
gwinHide(ghMainContainer);
gwinShow(ghSubContainer);
}
else if (((GEventGWinButton*)pe)->gwin == ghButton1Back) {
gwinHide(ghSubContainer);
gwinShow(ghMainContainer);
}
break;

default:
break;
}
}

return 0;
}

Link to comment
Share on other sites

Also, when the label is dragged across one of the buttons, the button is "damaged". It should be redrawn at least after the label is placed at its destination (I solved that by now, when the label is placed after being moved it calls gwinRedrawDisplay()).

Link to comment
Share on other sites

My current workaround is this:

	// A mouse move has occurred
static void MouseMove(GWidgetObject *gw, coord_t x, coord_t y) {
if (((GDragLabelObject*)gw)->beingDragged == TRUE)
{
GDragLabelObject* p = (GDragLabelObject*)gw;
p->moved = TRUE;
// gwinMove((GWindowObject *)gw, p->w.g.x+x-p->hotspot_x, p->w.g.y+y-p->hotspot_y);
coord_t winx = gwinGetScreenX((GWindowObject*)gw);
coord_t winy = gwinGetScreenY((GWindowObject*)gw);
printf("winx = %d, winy = %d\n", winx, winy);
#if(GWIN_NEED_CONTAINERS)
if (gw->g.parent)
{
GWindowObject* parent = gw->g.parent;
winx -= parent->x;
winy -= parent->y;
printf(" relx = %d, rely = %d\n", winx, winy);
}
#endif

gwinMove((GWindowObject *)gw, winx+x-p->hotspot_x, winy+y-p->hotspot_y);
}
else
{
printf("empty move\n");
}
}

is there something like gwinGetRelX() and gwinGetRelY() functions or maybe even a more elegant way that I haven't found?

Link to comment
Share on other sites

Overlapping window redrawing is something that ugfx doesn't handle because the code to do so is very complex and it requires fairly large ram tables (not good for an embedded platform). In practice this is seldom a problem due to containers and rudimentary support for Z order. Your use case however is slightly different and it is why you see the artifacts when dragging over a button. You solution is probably the best one, just to redraw the screen afterwards.

With regard to parent relative coordinates, widgets always store their coordinates relative to the display. The reason for this is that it makes drawing in each widget much simpler. GwinMove, being a user api, takes coordinates relative to the parent. Your solution to correct the coordinates is the right approach however you also need to subtract the top and left border width. There are calls to get the border or at least the inner width and height of a container. Look at the code for gwinMove itself. Creating an api to get the relative position is probably a good idea. I will look at that when I get some time.

Nice work.

Link to comment
Share on other sites

It's not quite clear to me how I would displace a widget within its parent container. I have the following code, but the widget always ends up in one of the corners after this has been done a few times with dx = dy = 0:

int dx = 0;//round(c4*it->force_x);
int dy = 0;//round(c4*it->force_y);
coord_t x = gwinGetScreenX(lblHandle) + dx;
coord_t y = gwinGetScreenY(lblHandle) + dy;
x -= gwinGetScreenX(ghContainer);
y -= gwinGetScreenY(ghContainer);
uint8_t border = 2;
x -= border;
y -= border;
printf("d = %03d %03d, c = %03d %03d\n", dx, dy, x, y);
gwinMove(lblHandle, x, y);

with border < 2 it's in the lower right corner, with border = 2 it's in the upper left. I can't find a coordinate combo that does what I want. You already said that gwinMove() is not thoroughly tested - might that be the culprit? Working with the container's inner height and width might also work, but I expect to get off-by-one errors with that, too.

Link to comment
Share on other sites

Nice video.

I noticed some redraw delays in a few instances. If that is really on the display and not just a video artifact have a look at the gwin window manager redraw options for your gfxconf.h

These trade of stack depth required, visual flashing, and visual responsiveness. You might find a setting that works better for your application.

Link to comment
Share on other sites

I'm targeting a Cortex-M0 platform with an RA8875 controlled TFT (you know my other threads). I'll try to get it to run on that platform today. My impression regarding real hardware vs X11 drawing is that X11 looks more sluggish and less "crisp". When I draw a character on my TFT it is "just there", whereas it looks like being built on X11.

My force directed graph code is probably too CPU hungry and I'd probably have to use fixed point calculations instead of floating point. Using std::list is probably also a very bad idea...

I'll also have a look at the different window manager options, thanks for that hint!

Link to comment
Share on other sites

Running on real hardware with a touch panel now. The Cortex-M0 I wanted to use simply doesn't have enough flash space to hold the application, so I switched to a larger Cortex-M4 board. It's still using floating point calculations for the forces acting on my labels and turns out the be quite responsive and fast.

Crappy calibration turns out to be a problem when I try to drag items around, because not hitting an item results in another one being added, making the place quite crowded at times. Many force directed items are quite entertaining to look at, especially if their initial position is off screen. They are pushed into a place far far away that somewhen "rolls over" to zero, making the label appear on screen again and then trying to make its way to the button it belongs to.

GWIN_REDRAW_IMMEDIATE (the only option I tried so far) results in far less drawing artifacts. I just don't know what exactly it does and what the tradeoff is. The problem is that at some (seemingly random) point my app crashes, which might be related to my memory management.

I'll try to make a video later today!

Link to comment
Share on other sites

When my application crashes/hangs, it seems to be stuck in gfxYield(). Without the call to gfxYield() I don't get any touch events, that's why I added it in the first place. I'm not sure why it hangs there, but it's after I touched the touch panel.

Link to comment
Share on other sites

Perhaps stack on the main thread.

Redraw immediate redraws the moment it is asked for rather than delaying it to the timer thread.

That has the effect of more immediate redraws but it is less efficient (more overdraw) and requires more stack space on the main thread.

Link to comment
Share on other sites

GfxYield or gfxSleepMilliseconds is required to give other threads a chance to run - in this case the gtimer thread which is responsible for redraw (as discussed above) and for polling input devices such as your touch panel.

The reason the yield is required to make other threads run is that you are running bare metal and the threading implemented for the bare metal GOS is co-operative rather than preemptive.

Link to comment
Share on other sites

The purpose of gfxYield() is now quite clear to me in theory, it just surprised me that the touch panel driver wouldn't get polled without it while mouse input (when using X11) "just worked".

Nonetheless, the problem remains. At the moment I can add items withoug problems, and drag them around. However, when I have removed one by clicking on it, the system hangs when I try to add the next item. This is not 100% reproducible, sometimes adding a new item works fine after having removed one previously. FWIW, here's my code if anyone wants to have a look:

ContainerButton is basically a container, but it sends a button event when its background is clicked. One of these is my main container that I add "Items" to.

containerButton.h:

#ifndef CONTAINER_BUTTON_H
#define CONTAINER_BUTTON_H

#include "gfx.h"

//typedef GWidgetObject GContainerButtonObject;

#ifdef __cplusplus
extern "C" {
#endif

typedef struct GContainerButtonObject {
GWidgetObject w;
coord_t click_x;
coord_t click_y;
} GContainerButtonObject;

GHandle gwinGContainerButtonCreate(GDisplay *g, GContainerButtonObject *gc, const GWidgetInit *pInit, uint32_t flags);
#define gwinContainerButtonCreate(gc, pInit, flags) gwinGContainerButtonCreate(GDISP, gc, pInit, flags)

void gwinContainerButtonDraw_Std(GWidgetObject *gw, void *param);
void gwinContainerButtonDraw_Transparent(GWidgetObject *gw, void *param);
void gwinContainerButtonDraw_Image(GWidgetObject *gw, void *param);

#ifdef __cplusplus
}
#endif


#endif // CONTAINER_BUTTON_H

containerButton.c:

#include "containerButton.h"

#if GFX_USE_GWIN && GWIN_NEED_CONTAINER

#include "src/gwin/gwin_class.h"

#define GBUTTON_FLG_PRESSED (GWIN_FIRST_CONTROL_FLAG<<0)

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

// A mouse up has occurred (it may or may not be over the button)
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 !GWIN_BUTTON_LAZY_RELEASE
// If the mouse up was not over the button then cancel the event
if (x < 0 || y < 0 || x >= gw->g.width || y >= gw->g.height)
return;
#endif
((GContainerButtonObject*)gw)->click_x = x;
((GContainerButtonObject*)gw)->click_y = y;

_gwinSendEvent(&gw->g, GEVENT_GWIN_BUTTON);
}
#endif // GINPUT_NEED_MOUSE

#if GWIN_CONTAINER_BORDER != GWIN_FIRST_CONTROL_FLAG
#error "GWIN Container: - Flag definitions don't match"
#endif

#define BORDER_WIDTH 2

static coord_t BorderSize(GHandle gh) { return (gh->flags & GWIN_CONTAINER_BORDER) ? BORDER_WIDTH : 0; }

// The container VMT table
static const gcontainerVMT containerButtonVMT = {
{
{
"Container", // The classname
sizeof(GContainerObject), // The object size
_gcontainerDestroy, // The destroy routine
_gcontainerRedraw, // The redraw routine
0, // The after-clear routine
},
gwinContainerButtonDraw_Std, // The default drawing routine
#if GINPUT_NEED_MOUSE
{
MouseUp,
MouseDown,
0, // not used
},
#endif
#if GINPUT_NEED_TOGGLE
{
0, 0, 0, 0, 0, // No toggles
},
#endif
#if GINPUT_NEED_DIAL
{
0, 0, 0, 0, // No dials
},
#endif
},
BorderSize, // The size of the left border (mandatory)
BorderSize, // The size of the top border (mandatory)
BorderSize, // The size of the right border (mandatory)
BorderSize, // The size of the bottom border (mandatory)
0, // A child has been added (optional)
0, // A child has been deleted (optional)
};

GHandle gwinGContainerButtonCreate(GDisplay *g, GContainerButtonObject *gc, const GWidgetInit *pInit, uint32_t flags)
{
if (!(gc = (GContainerButtonObject *)_gcontainerCreate(g, &gc->w, pInit, &containerButtonVMT)))
return 0;
GWidgetObject* gw = (GWidgetObject*)gc;
gw->g.flags |= (flags & GWIN_CONTAINER_BORDER);

gwinSetVisible((GHandle)gw, pInit->g.show);
return (GHandle)gc;
}

void gwinContainerButtonDraw_Transparent(GWidgetObject *gw, void *param) {
(void)param;
if (gw->g.vmt != (gwinVMT *)&containerButtonVMT)
return;

if ((gw->g.flags & GWIN_CONTAINER_BORDER))
gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, (gw->g.flags & GWIN_FLG_SYSENABLED) ? gw->pstyle->enabled.edge : gw->pstyle->disabled.edge);

// Don't touch the client area
}

void gwinContainerButtonDraw_Std(GWidgetObject *gw, void *param) {
(void)param;
if (gw->g.vmt != (gwinVMT *)&containerButtonVMT)
return;

gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
gwinContainerDraw_Transparent(gw, param);
}

#if GDISP_NEED_IMAGE
void gwinContainerButtonDraw_Image(GWidgetObject *gw, void *param) {
#define gi ((gdispImage *)param)
coord_t x, y, iw, ih, mx, my;

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

// Draw the frame
gwinContainerDraw_Transparent(gw, param);

// Draw the client area by tiling the image
mx = gw->g.x+gw->g.width;
my = gw->g.y+gw->g.height;
y = gw->g.y;
if ((gw->g.flags & GWIN_CONTAINER_BORDER)) {
mx--;
my--;
y++;
}
for(ih=gi->height; y < my; y += ih) {
if (ih > my - y)
ih = my - y;
x = gw->g.x;
if ((gw->g.flags & GWIN_CONTAINER_BORDER))
x++;
for(iw=gi->width; x < mx; x += iw) {
if (iw > mx - x)
iw = mx - x;
gdispGImageDraw(gw->g.display, gi, x, y, ih, iw, 0, 0);
}
}

#undef gi
}
#endif

#endif

DraggableButton is a button, but it can also be dragged around with the mouse.

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.h:

#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);
}
#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

and my main.cpp, including the "Item" class that takes care of adding buttons, labels, and moving them around:

#include 
#include

#include
#include

#include
#include "gfx.h"
#include "src/gwin/gwin_class.h"
#include "ugfx_user/containerButton/containerButton.h"
#include "ugfx_user/draggableButton/draggableButton.h"

#define DEBUG(x) Serial.printf x ;Serial.flush()
//#define DEBUG(x)

static GHandle ghContainer;

static GWidgetStyle myStyle = {
White, // background
{Black, SkyBlue, SkyBlue, Black}, // enabled (text edge fill progress)
{White, Gray, Gray, Black}, // disabled
{White, Orange, Orange, Black} // pressed
};

static void createWidgets(void) {
coord_t height = gdispGetHeight();
coord_t width = gdispGetWidth();
GWidgetInit wi;

// Apply some default values for GWIN
gwinWidgetClearInit(&wi);

// Apply the container parameters
wi.g.show = FALSE;
wi.g.width = width;
wi.g.height = height;
wi.g.x = 0;
wi.g.y = 0;
wi.text = "Container";
ghContainer = gwinContainerButtonCreate(0, &wi, 0);//GWIN_CONTAINER_BORDER);
wi.g.show = TRUE;

gwinShow(ghContainer);
}

class Item
{
public:
static void add(coord_t x, coord_t y)
{
// create a new button
DEBUG(("Item::create (%d,%d)\n", x, y));
Item* p = new Item;
if(p == NULL)
{
DEBUG(("Could not allocate space for a new item\n"));
while(1);
}
DEBUG(("Initializing item @%p\n", p));
p->init(x,y);
DEBUG(("Prepending item @%p to list\n", p));
p->next = pItems;
pItems = p;
DEBUG(("add: done\n"));
}
static void remove(GHandle gh)
{
// find item with btn handle gh
DEBUG(("remove(%p)\n", gh));
Item* p = pItems;
while((p != NULL) && (p->_btnHandle != gh))
{
p = p->next;
}
if(p == NULL)
{
// not found
DEBUG(("remove: item not found\n"));
return;
}
// now we're sure that an object with this button handle exists and it is at p
DEBUG(("remove p @%p\n", p));
DEBUG(("p->_btnHandle = %p\n", p->_btnHandle));
// p points to the item that is to be removed
if(pItems == p)
{
DEBUG(("Removing root item, next = %p\n", p->next));
pItems = p->next;
}
else
{
DEBUG(("Removing non-root item, next = %p\n", p->next));
Item* prev = pItems;
while((prev != NULL) && (prev->next != p))
{
prev = prev->next;
}
if(prev->next == p)
{
// found
DEBUG(("Found previous item @%p\n",prev));
DEBUG((" prev->next = %p\n",prev->next));
prev->next = p->next;
}
else
{
DEBUG(("your list is borked\n"));
}
}
p->deinit();
DEBUG(("remove: item deinitialized\n"));
delete p;
}
Item()
{
DEBUG(("Item being constructed @%p\n", this));
_btnHandle = NULL;
_lblHandle = NULL;
next = NULL;
}
~Item()
{
DEBUG(("Item @%p being deleted\n", this));
}
static bool update()
{
coord_t left = 0;
coord_t right = gdispGetWidth()-1;
coord_t top = 0;
coord_t bottom = gdispGetHeight()-1;
// calculate forces on labels
Item* it = pItems;
while(it != NULL)
{
// reset
double fx = 0;
double fy = 0;
coord_t labelWidth = gwinGetWidth(it->_lblHandle);
coord_t labelHeight = gwinGetHeight(it->_lblHandle);
double label_x = gwinGetScreenX(it->_lblHandle) + labelWidth/2.;
double label_y = gwinGetScreenY(it->_lblHandle) + labelHeight/2.;
double btn_x = gwinGetScreenX(it->_btnHandle) + gwinGetWidth(it->_btnHandle)/2.;
double btn_y = gwinGetScreenY(it->_btnHandle) + gwinGetHeight(it->_btnHandle)/2.;
// add forces for screen boundary
fx -= c3/pow(right-label_x, 2);
fx += c3/pow(label_x - left, 2);
fy -= c3/pow(bottom-label_y, 2);
fy += c3/pow(label_y - top, 2);
// add force for this item's button
double dx = btn_x - label_x;
double dy = btn_y - label_y;
double dist_sq = pow(dx,2) + pow(dy,2);
double dist = sqrt(dist_sq);
double f = c1*log(dist/c2);
fx += f*dx/dist;
fy += f*dy/dist;
// add forces for each other item
Item* other = pItems;
while(other != NULL)
{
if(other == it)
{
other = other->next;
continue;
}
coord_t other_x = gwinGetScreenX(other->_lblHandle) + gwinGetWidth(other->_lblHandle)/2;
coord_t other_y = gwinGetScreenY(other->_lblHandle) + gwinGetHeight(other->_lblHandle)/2;
dx = other_x - label_x;
dy = other_y - label_y;
dist_sq = pow(dx,2) + pow(dy,2);
dist = sqrt(dist_sq);
f = c3/dist_sq;
fx -= dx/dist*f;
fy -= dy/dist*f;
other_x = gwinGetScreenX(other->_btnHandle) + gwinGetWidth(other->_btnHandle)/2;
other_y = gwinGetScreenY(other->_btnHandle) + gwinGetHeight(other->_btnHandle)/2;
dx = other_x - label_x;
dy = other_y - label_y;
dist_sq = pow(dx,2) + pow(dy,2);
dist = sqrt(dist_sq);
f = c3/dist_sq;
fx -= dx/dist*f;
fy -= dy/dist*f;
other = other->next;
}
it->force_x = fx;
it->force_y = fy;

it = it->next;
}

// move labels
it = pItems;
bool somethingMoved = false;
while(it != NULL)
{
int dx = round(c4*it->force_x);
int dy = round(c4*it->force_y);
bool thisMoved = ((dx != 0) || (dy != 0));
somethingMoved |= thisMoved;
coord_t x = gwinGetScreenX(it->_lblHandle) + dx;
coord_t y = gwinGetScreenY(it->_lblHandle) + dy;
it->_lblHandle->x = x;
it->_lblHandle->y = y;
if(thisMoved)
{
gwinRedraw(it->_lblHandle);
}
it = it->next;
}

return somethingMoved;
}
void init(coord_t x, coord_t y)
{
DEBUG(("Item::init (%d,%d)\n", x, y));
GWidgetInit wi;
gwinWidgetClearInit(&wi);
wi.g.x = x - btnSize/2;
wi.g.y = y - btnSize/2;
wi.g.width = btnSize;
wi.g.height = btnSize;
wi.g.parent = ghContainer;
wi.text = " ";
wi.g.show = TRUE;
_btnHandle = gwinDraggableButtonCreate(0, &wi);
if(_btnHandle == NULL)
{
DEBUG(("invalid button handle!\n"));
while(1);
}
wi.g.x = x + btnSize/2 + 5;
wi.g.y = y + btnSize/2 + 5;
wi.g.width = 0;
wi.g.height = 0;
wi.text = " ";
_lblHandle = gwinLabelCreate(0, &wi);
if(_lblHandle == NULL)
{
DEBUG(("invalid label handle!\n"));
while(1);
}
char str[4];
snprintf(str, 4, "%u", (unsigned int)nextNumber);
gwinSetText(_lblHandle, str, TRUE);
nextNumber++;
DEBUG(("Item::init done\n", x, y));
}
void deinit()
{
gwinDestroy(_btnHandle);
gwinDestroy(_lblHandle);
next = NULL;
}
private:
static Item* lastItem()
{
Item* p = pItems;
while((p != NULL ) && (p->next != NULL))
{
p = p->next;
}
return p;
}
static Item* previousItem(const Item* i)
{
// fint item such that item->next = i
Item* p = pItems;
while(p != NULL)
{
if(p->next == i)
{
DEBUG(("previousItem found: p->next = %p, i = %p\n", p->next, i));
break;
}
p = p->next;
}
return p;
}

static Item* pItems;
static uint8_t nextNumber;
static const uint8_t btnSize = 32;
static constexpr double c1 = 2;
static constexpr double c2 = 1.5*btnSize;
static constexpr double c3 = 2560;
static constexpr double c4 = 1;

Item* next;
GHandle _btnHandle;
GHandle _lblHandle;
double force_x;
double force_y;
};

Item* Item::pItems = NULL;
uint8_t Item::nextNumber = 0;

void setup()
{
// while(!Serial.available()); // wait for serial input before starting the app
DEBUG(("Start"));
SPI.begin();
}

static GListener gl;
void loop(void) {

gfxInit();

// Set the widget defaults
gwinSetDefaultFont(gdispOpenFont("UI2 Narrow"));
gwinSetDefaultStyle(&myStyle, FALSE);

gdispClear(White);
createWidgets();

geventListenerInit(&gl);
gwinAttachListener(&gl);
GEvent* pe;

while(1) {
// alive-check over serial
if (Serial.available())
{
Serial.read();
Serial.printf("alive!\n");
}

pe = geventEventWait(&gl, 0);
if (pe != NULL)
{
switch(pe->type) {
case GEVENT_GWIN_BUTTON:
if(((GEventGWinButton*)pe)->gwin == ghContainer) {
GContainerButtonObject* p = (GContainerButtonObject*)((GEventGWinButton*)pe)->gwin;
coord_t x = p->click_x;
coord_t y = p->click_y;
DEBUG(("add @ %d %d\n", x, y));
Item::add(x,y);
}
else
{
// if one of the buttons was clicked, remove it
Item::remove(((GEventGWinButton*)pe)->gwin);
gwinRedraw(ghContainer);
}
break;

default:
DEBUG(("unhandled event type\n"));
break;
}
DEBUG(("Event handler done\n"));
}
static bool keepUpdating = false;
static systemticks_t last = gfxSystemTicks();
systemticks_t now = gfxSystemTicks();
if (((now - last) >= 100) || keepUpdating)
{
last = now;
if (Item::update())
{
keepUpdating = true;
}
else
{
keepUpdating = false;
}
}
gfxYield();
}
}

Still no video of the real hardware, sorry!

Link to comment
Share on other sites

I'm making frequent use of gwinDestroy(). Is it possible that under certain circumstances ugfx tries to access or redraw a widget that has been destroyed previously? Can widget desctruction have any other side effects?

my application does not crash/hang when I replace gwinDestroy() with gwinHide() (that's an obvious memory leak, but I wanted to test the behavior with that).

Link to comment
Share on other sites

Just a note on the gfxYield, if you replace the 0 in the geventEventWait call with a 1 I suspect the gfxYield will not be needed.

Yes indeed, thanks for that! I kinda hoped that this would also make my other problem disappear, but it's still there. Thanks for having a look even if it's next week.

Link to comment
Share on other sites

Does the same behaviour occur in X with the same redraw flags?

If not then it is possible the arduino memory allocator is stack sensitive (i have seen this before although it shouldn't happen in any well written host routine). Unfortunately the only way to test it is to replace the memory allocation/free routines in the gos module with the alternative routines from raw32 gos. Those routines require you to give it a one off big block of memory at init time and it will manage that block thereafter.

Link to comment
Share on other sites

Yup, that's it. I gave ugfx 4096 bytes and it now happily destroys and recreates widgets. I could now take this problem to the teensyduino people, but I think that's quite a rabbit hole to explore - I wouldn't even know how to start explaining this in such a way that someone else can even recreate the problem.

Edited by Guest
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...