1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
|
//
// "$Id$"
//
// Demonstrate deriving a class with draggable children.
//
// Copyright 2017 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
// file is missing or damaged, see the license at:
//
// http://www.fltk.org/COPYING.php
//
// Please report all bugs and problems on the following page:
//
// http://www.fltk.org/str.php
//
// K Dmitrij <kdiman@...> wrote on Wed, 26 Jul 2017 19:54:12 +0000 in
// fltk.general, thread "[fltk.general] Draggable Group for examples FLTK":
//
// "Here is class for drag widgets and/or their children inside its parent
// widget. You can include it into examples of FLTK (and to adapt/modify
// if needs)".
//
// Thanks to Dmitrij for this contribution.
// Use `fltk-config --compile draggable-group.cxx' to build this example.
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <stdio.h>
/** \class DraggableGroup
\brief Class with draggable children, derived from Fl_Group.
Use this class if you want the user to be able to drag the children
of a group inside the borders of the group. DraggableGroup widgets
can be nested, but only direct children of the DraggableGroup widget
can be dragged.
Design decisions:
- Widgets can be dragged with the right mouse button.
This was chosen to avoid conflicts with other mouse button
events such as clicking a button.
- The dragged widget is raised to the top within its group for better
visual feedback while dragging by making it the last child of the group.
- The order of widgets is restored when the mouse button is released.
This can result in hiding the widget behind another widget, but this
was chosen to keep the order given by the programmer. If this
is not desired it can be changed easily.
*/
class DraggableGroup : public Fl_Group {
protected:
int xoff, yoff; // start offsets while dragging
int drag_index; // index of dragged child
Fl_Widget *drag_widget; // dragged child widget
public:
DraggableGroup(int X, int Y, int W, int H, const char *L = 0)
: Fl_Group(X, Y, W, H, L)
, xoff(0)
, yoff(0)
, drag_index(0)
, drag_widget(0) {
box(FL_UP_BOX);
}
/** Raise the dragged widget to the top (last child) of its group.
This ensures that the widget is always visible while it is dragged.
The original index is saved in 'drag_index' and restored when the
mouse button is released.
\internal
Since we allow only direct children of the DraggableGroup widget
to be dragged the argument 'q' is always a child of 'this'.
Note: add(q) first removes the widget from its parent group ('this')
and then adds it again as its last child.
This is the same as insert(*q, children()).
*/
void top_level(Fl_Widget *q) {
drag_index = find(q); // save the widget's current index
add(q); // raise to top (make it the last child)
}
/** Handle FL_PUSH, FL_DRAG, and FL_RELEASE events.
All other events are handled in Fl_Group::handle().
Dragged widgets are limited inside the borders of their parent group.
*/
virtual int handle(int e) {
switch (e) {
case FL_PUSH: {
if (Fl::event_button() == FL_RIGHT_MOUSE) {
if (Fl::belowmouse()->parent() == this) {
drag_widget = Fl::belowmouse();
// save pointer offsets relative to drag_widget's x/y position
xoff = Fl::event_x() - drag_widget->x();
yoff = Fl::event_y() - drag_widget->y();
top_level(drag_widget); // raise to top for visible feedback
redraw();
return 1;
}
}
break;
}
case FL_DRAG: {
if (!drag_widget)
break;
int nX = Fl::event_x() - xoff; // new x coordinate
int nY = Fl::event_y() - yoff; // new y coordinate
int bbx = Fl::box_dx(box()); // left and right border width
int bby = Fl::box_dy(box()); // top and bottom border width
// keep the widget inside its parent's borders
if (nX < x() + bbx) {
nX = x() + bbx;
} else if (nX + drag_widget->w() > x() + w() - bbx) {
nX = x() + w() - drag_widget->w() - bbx;
}
if (nY < y() + bby) {
nY = y() + bby;
} else if (nY + drag_widget->h() > y() + h() - bby) {
nY = y() + h() - drag_widget->h() - bby;
}
drag_widget->position(nX, nY); // set the new position
redraw();
return 1;
}
case FL_RELEASE: {
if (drag_widget && Fl::event_button() == FL_RIGHT_MOUSE) {
// Optional: restore the original widget order in the group.
// Remove the next statement (or comment it out) if not desired.
insert(*drag_widget, drag_index);
init_sizes(); // save widget positions for later resizing
drag_widget = 0;
redraw();
if (parent())
parent()->redraw();
return 1;
}
break;
}
default:
break;
} // switch(e)
return Fl_Group::handle(e);
}
};
// clear status message box after timeout
void clear_status(void *v) {
Fl_Box *status = (Fl_Box *)v;
status->label("");
status->redraw();
}
// button callback: display status message for two seconds
void button_cb(Fl_Widget *w, void *v) {
static char buf[128];
sprintf(buf, "button_cb: '%s'.\n", w->label());
Fl_Box *status = (Fl_Box *)v;
status->label(buf);
status->redraw();
Fl::remove_timeout(clear_status); // remove any pending timeout
Fl::add_timeout(2.0, clear_status, v);
}
// tooltips:
const char *tt_drag = "Drag this DraggableGroup and/or its child groups and squares. "
"Use the right mouse button (MB3) to drag objects.";
const char *tt_group = "You can drag this Fl_Group, but not its children (squares).";
const char *tt_button = "You can drag this button with the right mouse button "
"and you can click it with the left mouse button.";
// main program:
int main() {
Fl_Double_Window win(500, 500, "Drag children within their parent group");
DraggableGroup area(0, 0, 500, 400, "Use the right mouse button (MB3)\nto drag objects");
area.align(FL_ALIGN_INSIDE);
area.tooltip(tt_drag);
// draggable group inside draggable area
DraggableGroup dobj(5, 5, 140, 140, "DraggableGroup");
dobj.align(FL_ALIGN_INSIDE);
Fl_Box b1(25, 25, 20, 20);
b1.color(FL_RED);
b1.box(FL_FLAT_BOX);
Fl_Box b2(105, 105, 20, 20);
b2.color(FL_GREEN);
b2.box(FL_FLAT_BOX);
dobj.end();
dobj.tooltip(tt_drag);
// regular group inside draggable area
Fl_Group dobj2(5, 280, 110, 110, "Fl_Group");
dobj2.box(FL_DOWN_BOX);
dobj2.align(FL_ALIGN_INSIDE);
Fl_Box b3(15, 290, 20, 20);
b3.color(FL_BLUE);
b3.box(FL_FLAT_BOX);
Fl_Box b4(85, 360, 20, 20);
b4.color(FL_YELLOW);
b4.box(FL_FLAT_BOX);
dobj2.end();
dobj2.tooltip(tt_group);
// draggable group inside draggable area
DraggableGroup dobj3(245, 5, 150, 150, "DraggableGroup");
dobj3.align(FL_ALIGN_INSIDE);
dobj3.tooltip(tt_drag);
// nested draggable group
DraggableGroup dobj4(250, 10, 50, 50);
Fl_Box b5(255, 15, 15, 15);
b5.color(FL_BLACK);
b5.box(FL_FLAT_BOX);
Fl_Box b6(275, 30, 15, 15);
b6.color(FL_WHITE);
b6.box(FL_FLAT_BOX);
dobj4.end();
dobj4.tooltip(tt_drag);
dobj3.end();
Fl_Button button1(200, 350, 180, 40, "Fl_Button inside DraggableGroup");
button1.align(FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
button1.tooltip(tt_button);
area.end();
Fl_Button button2(200, 410, 180, 40, "Fl_Button outside DraggableGroup");
button2.align(FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
button2.tooltip("This is a normal button and can't be dragged.");
Fl_Box *status = new Fl_Box(0, 460, win.w(), 40, "Messages ...");
status->box(FL_DOWN_BOX);
button1.callback(button_cb, status);
button2.callback(button_cb, status);
win.end();
win.resizable(win);
win.show();
return Fl::run();
}
//
// End of "$Id$".
//
|