1 //
2 // "$Id$"
3 //
4 // Tile widget for the Fast Light Tool Kit (FLTK).
5 //
6 // Copyright 1998-2016 by Bill Spitzak and others.
7 //
8 // This library is free software. Distribution and use rights are outlined in
9 // the file "COPYING" which should have been included with this file. If this
10 // file is missing or damaged, see the license at:
11 //
12 // http://www.fltk.org/COPYING.php
13 //
14 // Please report all bugs and problems on the following page:
15 //
16 // http://www.fltk.org/str.php
17 //
18
19 /**
20 \class Fl_Tile
21
22 The Fl_Tile class lets you resize its children by dragging
23 the border between them.
24
25 \image html Fl_Tile.png
26 \image latex Fl_Tile.png "Fl_Tile" width=5cm
27
28 For the tiling to work correctly, the children of an Fl_Tile must
29 cover the entire area of the widget, but not overlap.
30 This means that all children must touch each other at their edges,
31 and no gaps can be left inside the Fl_Tile.
32
33 Fl_Tile does not normally draw any graphics of its own.
34 The "borders" which can be seen in the snapshot above are actually
35 part of the children. Their boxtypes have been set to FL_DOWN_BOX
36 creating the impression of "ridges" where the boxes touch. What
37 you see are actually two adjacent FL_DOWN_BOX's drawn next to each
38 other. All neighboring widgets share the same edge - the widget's
39 thick borders make it appear as though the widgets aren't actually
40 touching, but they are. If the edges of adjacent widgets do not
41 touch, then it will be impossible to drag the corresponding edges.
42
43 Fl_Tile allows objects to be resized to zero dimensions.
44 To prevent this you can use the resizable() to limit where
45 corners can be dragged to. For more information see note below.
46
47 Even though objects can be resized to zero sizes, they must initially
48 have non-zero sizes so the Fl_Tile can figure out their layout.
49 If desired, call position() after creating the children but before
50 displaying the window to set the borders where you want.
51
52 <b>Note on resizable(Fl_Widget &w):</b>
53 The "resizable" child widget (which should be invisible) limits where
54 the borders can be dragged to. All dragging will be limited inside the
55 resizable widget's borders. If you don't set it, it will be possible
56 to drag the borders right to the edges of the Fl_Tile widget, and thus
57 resize objects on the edges to zero width or height. When the entire
58 Fl_Tile widget is resized, the resizable() widget will keep its border
59 distance to all borders the same (this is normal resize behavior), so
60 that you can effectively set a border width that will never change.
61 To ensure correct event delivery to all child widgets the resizable()
62 widget must be the first child of the Fl_Tile widget group. Otherwise
63 some events (e.g. FL_MOVE and FL_ENTER) might be consumed by the resizable()
64 widget so that they are lost for widgets covered (overlapped) by the
65 resizable() widget.
66
67 \note
68 You can still resize widgets \b inside the resizable() to zero width and/or
69 height, i.e. box \b 2b above to zero width and box \b 3a to zero height.
70
71 \see void Fl_Group::resizable(Fl_Widget &w)
72
73 Example for resizable with 20 pixel border distance:
74 \code
75 int dx = 20, dy = dx;
76 Fl_Tile tile(50,50,300,300);
77 // create resizable() box first
78 Fl_Box r(tile.x()+dx,tile.y()+dy,tile.w()-2*dx,tile.h()-2*dy);
79 tile.resizable(r);
80 // ... create widgets inside tile (see test/tile.cxx) ...
81 tile.end();
82 \endcode
83
84 See also the complete example program in test/tile.cxx.
85 */
86
87 #include <FL/Fl.H>
88 #include <FL/Fl_Tile.H>
89 #include <FL/Fl_Window.H>
90 #include <stdlib.h>
91
92 /**
93 Drags the intersection at (\p oldx,\p oldy) to (\p newx,\p newy).
94 This redraws all the necessary children.
95
96 Pass zero as \p oldx or \p oldy to disable drag in that direction.
97 */
position(int oldx,int oldy,int newx,int newy)98 void Fl_Tile::position(int oldx, int oldy, int newx, int newy) {
99 Fl_Widget*const* a = array();
100 int *p = sizes();
101 p += 8; // skip group & resizable's saved size
102 for (int i=children(); i--; p += 4) {
103 Fl_Widget* o = *a++;
104 if (o == resizable()) continue;
105 int X = o->x();
106 int R = X+o->w();
107 if (oldx) {
108 int t = p[0];
109 if (t == oldx || (t>oldx && X<newx) || (t<oldx && X>newx) ) X = newx;
110 t = p[1];
111 if (t == oldx || (t>oldx && R<newx) || (t<oldx && R>newx) ) R = newx;
112 }
113 int Y = o->y();
114 int B = Y+o->h();
115 if (oldy) {
116 int t = p[2];
117 if (t == oldy || (t>oldy && Y<newy) || (t<oldy && Y>newy) ) Y = newy;
118 t = p[3];
119 if (t == oldy || (t>oldy && B<newy) || (t<oldy && B>newy) ) B = newy;
120 }
121 o->damage_resize(X,Y,R-X,B-Y);
122 }
123 }
124
125 /**
126 Resizes the Fl_Tile widget and its children.
127
128 Fl_Tile implements its own resize() method. It does not use
129 Fl_Group::resize() to resize itself and its children.
130
131 Enlarging works by just moving the lower-right corner and resizing
132 the bottom and right border widgets accordingly.
133
134 Shrinking the Fl_Tile works in the opposite way by shrinking
135 the bottom and right border widgets, unless they are reduced to zero
136 width or height, resp. or to their minimal sizes defined by the
137 resizable() widget. In this case other widgets will be shrunk as well.
138
139 See the Fl_Tile class documentation about how the resizable() works.
140 */
141
resize(int X,int Y,int W,int H)142 void Fl_Tile::resize(int X,int Y,int W,int H) {
143
144 // remember how much to move the child widgets:
145 int dx = X-x();
146 int dy = Y-y();
147 int dw = W-w();
148 int dh = H-h();
149 int *p = sizes();
150 // resize this (skip the Fl_Group resize):
151 Fl_Widget::resize(X,Y,W,H);
152
153 // find bottom-right corner of resizable:
154 int OR = p[5]; // old right border
155 int NR = X+W-(p[1]-OR); // new right border
156 int OB = p[7]; // old bottom border
157 int NB = Y+H-(p[3]-OB); // new bottom border
158
159 // move everything to be on correct side of new resizable:
160 Fl_Widget*const* a = array();
161 p += 8;
162 for (int i=children(); i--;) {
163 Fl_Widget* o = *a++;
164 int xx = o->x()+dx;
165 int R = xx+o->w();
166 if (*p++ >= OR) xx += dw; else if (xx > NR) xx = NR;
167 if (*p++ >= OR) R += dw; else if (R > NR) R = NR;
168 int yy = o->y()+dy;
169 int B = yy+o->h();
170 if (*p++ >= OB) yy += dh; else if (yy > NB) yy = NB;
171 if (*p++ >= OB) B += dh; else if (B > NB) B = NB;
172 o->resize(xx,yy,R-xx,B-yy);
173 // do *not* call o->redraw() here! If you do, and the tile is inside a
174 // scroll, it'll set the damage areas wrong for all children!
175 }
176 }
177
set_cursor(Fl_Tile * t,Fl_Cursor c)178 static void set_cursor(Fl_Tile*t, Fl_Cursor c) {
179 static Fl_Cursor cursor;
180 if (cursor == c || !t->window()) return;
181 cursor = c;
182 #ifdef __sgi
183 t->window()->cursor(c,FL_RED,FL_WHITE);
184 #else
185 t->window()->cursor(c);
186 #endif
187 }
188
189 static Fl_Cursor cursors[4] = {
190 FL_CURSOR_DEFAULT,
191 FL_CURSOR_WE,
192 FL_CURSOR_NS,
193 FL_CURSOR_MOVE};
194
handle(int event)195 int Fl_Tile::handle(int event) {
196 static int sdrag;
197 static int sdx, sdy;
198 static int sx, sy;
199 #define DRAGH 1
200 #define DRAGV 2
201 #define GRABAREA 4
202
203 int mx = Fl::event_x();
204 int my = Fl::event_y();
205
206 switch (event) {
207
208 case FL_MOVE:
209 case FL_ENTER:
210 case FL_PUSH:
211 // don't potentially change the mouse cursor if inactive:
212 if (!active()) break; // will cascade inherited handle()
213 {
214 int mindx = 100;
215 int mindy = 100;
216 int oldx = 0;
217 int oldy = 0;
218 Fl_Widget*const* a = array();
219 int *q = sizes();
220 int *p = q+8;
221 for (int i=children(); i--; p += 4) {
222 Fl_Widget* o = *a++;
223 if (o == resizable()) continue;
224 if (p[1]<q[1] && o->y()<=my+GRABAREA && o->y()+o->h()>=my-GRABAREA) {
225 int t = mx - (o->x()+o->w());
226 if (abs(t) < mindx) {
227 sdx = t;
228 mindx = abs(t);
229 oldx = p[1];
230 }
231 }
232 if (p[3]<q[3] && o->x()<=mx+GRABAREA && o->x()+o->w()>=mx-GRABAREA) {
233 int t = my - (o->y()+o->h());
234 if (abs(t) < mindy) {
235 sdy = t;
236 mindy = abs(t);
237 oldy = p[3];
238 }
239 }
240 }
241 sdrag = 0; sx = sy = 0;
242 if (mindx <= GRABAREA) {sdrag = DRAGH; sx = oldx;}
243 if (mindy <= GRABAREA) {sdrag |= DRAGV; sy = oldy;}
244 set_cursor(this, cursors[sdrag]);
245 if (sdrag) return 1;
246 return Fl_Group::handle(event);
247 }
248
249 case FL_LEAVE:
250 set_cursor(this, FL_CURSOR_DEFAULT);
251 break;
252
253 case FL_DRAG:
254 // This is necessary if CONSOLIDATE_MOTION in Fl_x.cxx is turned off:
255 // if (damage()) return 1; // don't fall behind
256 case FL_RELEASE: {
257 if (!sdrag) return 0; // should not happen
258 Fl_Widget* r = resizable(); if (!r) r = this;
259 int newx;
260 if (sdrag&DRAGH) {
261 newx = Fl::event_x()-sdx;
262 if (newx < r->x()) newx = r->x();
263 else if (newx > r->x()+r->w()) newx = r->x()+r->w();
264 } else
265 newx = sx;
266 int newy;
267 if (sdrag&DRAGV) {
268 newy = Fl::event_y()-sdy;
269 if (newy < r->y()) newy = r->y();
270 else if (newy > r->y()+r->h()) newy = r->y()+r->h();
271 } else
272 newy = sy;
273 position(sx,sy,newx,newy);
274 if (event == FL_DRAG) set_changed();
275 do_callback();
276 return 1;}
277
278 }
279
280 return Fl_Group::handle(event);
281 }
282
283 /**
284 Creates a new Fl_Tile widget using the given position, size,
285 and label string. The default boxtype is FL_NO_BOX.
286
287 The destructor <I>also deletes all the children</I>. This allows a
288 whole tree to be deleted at once, without having to keep a pointer to
289 all the children in the user code. A kludge has been done so the
290 Fl_Tile and all of its children can be automatic (local)
291 variables, but you must declare the Fl_Tile <I>first</I>, so
292 that it is destroyed last.
293
294 \see class Fl_Group
295 */
296
Fl_Tile(int X,int Y,int W,int H,const char * L)297 Fl_Tile::Fl_Tile(int X,int Y,int W,int H,const char*L)
298 : Fl_Group(X,Y,W,H,L)
299 {
300 }
301
302
303 //
304 // End of "$Id$".
305 //
306