fltk/examples/draggable-group.cxx

271 lines
8.0 KiB
C++

//
// 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:
//
// https://www.fltk.org/COPYING.php
//
// Please see the following page on how to report bugs and issues:
//
// https://www.fltk.org/bugs.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.
*/
int handle(int e) FL_OVERRIDE {
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();
}