2017-08-05 19:44:49 +03:00
|
|
|
//
|
|
|
|
// 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:
|
|
|
|
//
|
2020-07-01 19:03:10 +03:00
|
|
|
// https://www.fltk.org/COPYING.php
|
2017-08-05 19:44:49 +03:00
|
|
|
//
|
2020-07-01 19:03:10 +03:00
|
|
|
// Please see the following page on how to report bugs and issues:
|
2017-08-05 19:44:49 +03:00
|
|
|
//
|
2020-07-01 19:03:10 +03:00
|
|
|
// https://www.fltk.org/bugs.php
|
2017-08-05 19:44:49 +03:00
|
|
|
//
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
*/
|
2022-12-30 21:14:36 +03:00
|
|
|
int handle(int e) FL_OVERRIDE {
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
switch (e) {
|
|
|
|
|
|
|
|
case FL_PUSH: {
|
2020-07-01 19:03:10 +03:00
|
|
|
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;
|
2017-08-05 19:44:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case FL_DRAG: {
|
2020-07-01 19:03:10 +03:00
|
|
|
if (!drag_widget)
|
|
|
|
break;
|
2017-08-05 19:44:49 +03:00
|
|
|
|
2020-07-01 19:03:10 +03:00
|
|
|
int nX = Fl::event_x() - xoff; // new x coordinate
|
|
|
|
int nY = Fl::event_y() - yoff; // new y coordinate
|
2017-08-05 19:44:49 +03:00
|
|
|
|
2020-07-01 19:03:10 +03:00
|
|
|
int bbx = Fl::box_dx(box()); // left and right border width
|
|
|
|
int bby = Fl::box_dy(box()); // top and bottom border width
|
2017-08-05 19:44:49 +03:00
|
|
|
|
2020-07-01 19:03:10 +03:00
|
|
|
// keep the widget inside its parent's borders
|
2017-08-05 19:44:49 +03:00
|
|
|
|
2020-07-01 19:03:10 +03:00
|
|
|
if (nX < x() + bbx) {
|
|
|
|
nX = x() + bbx;
|
|
|
|
} else if (nX + drag_widget->w() > x() + w() - bbx) {
|
|
|
|
nX = x() + w() - drag_widget->w() - bbx;
|
|
|
|
}
|
2017-08-05 19:44:49 +03:00
|
|
|
|
2020-07-01 19:03:10 +03:00
|
|
|
if (nY < y() + bby) {
|
|
|
|
nY = y() + bby;
|
|
|
|
} else if (nY + drag_widget->h() > y() + h() - bby) {
|
|
|
|
nY = y() + h() - drag_widget->h() - bby;
|
|
|
|
}
|
2017-08-05 19:44:49 +03:00
|
|
|
|
2020-07-01 19:03:10 +03:00
|
|
|
drag_widget->position(nX, nY); // set the new position
|
|
|
|
redraw();
|
|
|
|
return 1;
|
2017-08-05 19:44:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case FL_RELEASE: {
|
2020-07-01 19:03:10 +03:00
|
|
|
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;
|
2017-08-05 19:44:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2020-07-01 19:03:10 +03:00
|
|
|
break;
|
2017-08-05 19:44:49 +03:00
|
|
|
} // 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);
|
|
|
|
}
|
|
|
|
|
2017-08-12 17:53:39 +03:00
|
|
|
// tooltips:
|
|
|
|
|
2020-07-01 19:03:10 +03:00
|
|
|
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).";
|
2017-08-12 17:53:39 +03:00
|
|
|
const char *tt_button = "You can drag this button with the right mouse button "
|
2020-07-01 19:03:10 +03:00
|
|
|
"and you can click it with the left mouse button.";
|
2017-08-12 17:53:39 +03:00
|
|
|
|
|
|
|
// main program:
|
|
|
|
|
2017-08-05 19:44:49 +03:00
|
|
|
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);
|
2017-08-12 17:53:39 +03:00
|
|
|
area.tooltip(tt_drag);
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
// 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();
|
2017-08-12 17:53:39 +03:00
|
|
|
dobj.tooltip(tt_drag);
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
// 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();
|
2017-08-12 17:53:39 +03:00
|
|
|
dobj2.tooltip(tt_group);
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
// draggable group inside draggable area
|
|
|
|
DraggableGroup dobj3(245, 5, 150, 150, "DraggableGroup");
|
|
|
|
dobj3.align(FL_ALIGN_INSIDE);
|
2017-08-12 17:53:39 +03:00
|
|
|
dobj3.tooltip(tt_drag);
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
// 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();
|
2017-08-12 17:53:39 +03:00
|
|
|
dobj4.tooltip(tt_drag);
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
dobj3.end();
|
|
|
|
|
|
|
|
Fl_Button button1(200, 350, 180, 40, "Fl_Button inside DraggableGroup");
|
|
|
|
button1.align(FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
|
2017-08-12 17:53:39 +03:00
|
|
|
button1.tooltip(tt_button);
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
area.end();
|
|
|
|
|
|
|
|
Fl_Button button2(200, 410, 180, 40, "Fl_Button outside DraggableGroup");
|
|
|
|
button2.align(FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
|
2017-08-12 17:53:39 +03:00
|
|
|
button2.tooltip("This is a normal button and can't be dragged.");
|
2017-08-05 19:44:49 +03:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|