root/trunk/luigi/arranger.d

Revision 37, 25.0 kB (checked in by baxissimo, 2 years ago)

Improvements to dxut mostly (adding scrollbar)

Line 
1 //---------------------------------------------------------------------
2 /**
3    In Luigi, Arrangers are responsible for deciding how widgets are
4    arranged in a container.  The Arranger interface actually does not
5    depend on the GUI portion of the library it all. It only requires
6    items to support the Arrangeable interface.  The Arrangeable
7    interface just provides information about an item's preferred,
8    and minimum sizes.
9
10    Note in many toolkits "arranging" is referred to as "layout".
11    Layout is a good word, but the problem is that its hard to come up
12    with good names for the things that can be laid-out and the thing
13    that does the laying-out.  You end up with words like "Layoutable",
14    "Layouter", "Layoutee", "Layouting", or "LayoutManager", which are
15    all either dubious as English words, or just too long.  Adding to
16    the mess is the fact that "layout" is both a verb and a noun, so
17    just "layout" alone is ambiguious.  Is layout() a method that does
18    the layout or one that returns an arrangement of items, or perhaps
19    one that returns the object responsible for laying things out?
20
21    In contrast, starting from the verb "arrange" we can use the very
22    reasonable English words "arrange", "arranging", "arranged",
23    "arrangement", "arranger", and "arrangeable".
24
25 */ 
26 //---------------------------------------------------------------------
27 /*
28  Copyright:
29
30   luigi/arranger.d -- arrangers for 'luigi' user interface library.
31
32   Copyright (C) 2006 William V. Baxter III
33
34   This software is provided 'as-is', without any express or implied
35   warranty.  In no event will the authors be held liable for any
36   damages arising from the use of this software.
37
38   Permission is granted to anyone to use this software for any
39   purpose, including commercial applications, and to alter it and
40   redistribute it freely, subject to the following restrictions:
41
42   1. The origin of this software must not be misrepresented; you must
43      not claim that you wrote the original software. If you use this
44      software in a product, an acknowledgment in the product
45      documentation would be appreciated but is not required.
46
47   2. Altered source versions must be plainly marked as such, and must
48      not be misrepresented as being the original software.
49   3. This notice may not be removed or altered from any source distribution.
50
51   William Baxter wbaxter@gmail.com
52 */
53 module luigi.arranger;
54
55 import luigi.base;
56 import math = std.math;
57
58 import std.stdio : writefln;
59
60 //--------------------------------------------------------------------------
61 /** An abstract interface for an Object that can be arranged */
62 interface Arrangeable
63 {
64     Size minimum_size(Size bounds);
65     Size preferred_size(Size bounds);
66
67     /** Return stretch factor in x and y directions.
68      *  If component is fixed size, then should be 0.
69      *  If nonzero then all nonzero elements share the
70      *  available space in proportion with their stretch
71      *  factors.  So a stretch=2 gets twice as much
72      *  space as a stretch=1.
73      */
74     int stretch_x();
75     int stretch_y();
76
77     void set_rect(Rect s);
78     void set_position(Point p);
79     void set_size(Size sz);
80
81     /** Arrange any sub-objects */
82     void arrange();
83 }
84
85 enum Alignment
86 {
87     Center = 0x44, //CenterX|CenterY
88
89     Left    = 0x01,
90     Right   = 0x02,
91     CenterX = 0x04,
92     FillX   = 0x08,
93     Top     = 0x10,
94     Bottom  = 0x20,
95     CenterY = 0x40,
96     FillY   = 0x80,
97
98     Fill = FillX|FillY,
99
100     XMASK = Left|Right|CenterX|FillX,
101     YMASK = Top|Bottom|CenterY|FillY,
102 }
103
104 struct Gaps
105 {
106     float x=0,y=0;
107     bool standard=true;
108
109     static Gaps opCall(float xgap, float ygap) {
110         Gaps g;
111         g.standard = false;
112         g.x = xgap;
113         g.y = ygap;
114         return g;
115     }
116
117     static Gaps opCall(float gap) {
118         Gaps g;
119         g.standard = false;
120         g.x = g.y = gap;
121         return g;
122     }
123
124     static Gaps opCall() {
125         Gaps g;
126         g.standard = true;
127         g.x = g.y = 0;
128         return g;
129     }
130 }
131
132 typedef Gaps Pads;
133
134
135 //--------------------------------------------------------------------------
136 /** An abstract interface for an Object that arranges things */
137 interface Arranger
138 {
139     void add(Arrangeable item, ...);
140     void remove(Arrangeable item, ...);
141     void clear();
142     void set_rect(Rect p);
143
144     Size minimum_size(Size bounds);
145     Size preferred_size(Size bounds);
146
147     void arrange();
148
149     /// Return the natural order (tab order) of the item.
150     /// The item should be contained by the given arranger.
151     int orderof(Arrangeable item);
152
153     // ArrangeableIterator child_iterator();
154
155     bool auto_add();
156     bool auto_add(bool);
157 }
158
159 //--------------------------------------------------------------------------
160 /** An abstract base class that implements some common parts of the Arranger
161  *  interface, and leaves others for subclasses to specify.
162  */
163 class BaseArranger : Arranger
164 {
165     this()
166     {
167         rect = Rect(0,0,0,0);
168     }
169
170     // Implementation of arranger interface
171     abstract override void add(Arrangeable item,...);
172     abstract override void remove(Arrangeable item,...);
173     abstract override void clear();
174     abstract override void arrange();
175
176     abstract override Size minimum_size(Size bounds);
177     abstract override Size preferred_size(Size bounds);
178
179     abstract override int orderof(Arrangeable a);
180
181     bool auto_add() { return m_auto_add; }
182     bool auto_add(bool v) { return m_auto_add=v; }
183
184     override void set_rect(Rect p)
185     {
186         rect = p;
187     }
188
189     void set_size(Size s)
190     {
191         rect.w = s.w;
192         rect.h = s.h;
193     }
194
195
196 private:
197     Rect rect;
198     bool m_auto_add = true;
199 }
200
201 //----------------------------------------------------------------------------
202 class FlowArranger : BaseArranger
203 {
204     this()
205     {
206         this(Alignment.Left|Alignment.Top, Gaps());
207     }
208        
209     this(Alignment align_)
210     {   
211         this(align_, Gaps());
212     }
213
214     this(Alignment align_, Gaps gaps_)
215     {
216         this.rect = Rect(0,0,0,0);
217         this.alignment = align_;
218         if (gaps_.standard) {
219             gaps_.x = DEFAULT_GAP;
220             gaps_.y = DEFAULT_GAP;
221         }
222         m_gaps = gaps_;
223     }
224
225     override int orderof(Arrangeable item) {
226         assert(item !is null);
227         int idx = m_items.find_item(item);
228         assert(idx != NOT_FOUND);
229         return idx;
230     }
231
232     override void add(Arrangeable item, ...)
233     in {
234         assert(_arguments.length == 0, "FlowArranger.add does not take arguments");
235     }
236     body{
237         m_items ~= item;
238         return item;
239     }
240
241     override void remove(Arrangeable item, ...)
242     in {
243         assert(_arguments.length == 0, "FlowArranger.remove does not take arguments");
244     }
245     body{
246         m_items.drop_item(item);
247     }
248
249     override void clear()
250     {
251         m_items.length = 0;
252     }
253
254     Size minimum_size(Size bounds)
255     {
256         return Size(_max_item_width(), _max_item_height());
257     }
258
259     private float _max_item_width()
260     {
261         float maxWidth = 0;
262         foreach(Arrangeable item; m_items)
263         {
264             maxWidth = math.fmax(maxWidth, item.preferred_size(rect.size).width);
265         }
266         return maxWidth;
267     }
268
269     private float _max_item_height()
270     {
271         float maxHeight = 0;
272         foreach(Arrangeable item ; m_items)
273         {
274             maxHeight = math.fmax(maxHeight, item.preferred_size(rect.size).height);
275         }
276         return maxHeight;
277     }
278
279     override  Size preferred_size(Size bounds)
280     {
281         float htotal=0, height = 0;
282         float wtotal = 0, width = 0;
283         int rows = 0;
284        
285         foreach(Arrangeable c; m_items)
286         {
287             Size s = c.preferred_size(bounds);
288             width += s.width + xgap;
289             height = max(height, s.height);
290             if (width>bounds.width) {
291                 wtotal = max(wtotal, width-xgap);
292                 width = 0;
293                 htotal += height + ygap;
294                 rows++;
295             }
296         }
297         htotal += height;
298         wtotal = max(wtotal,width-xgap);
299         return Size(wtotal, htotal);
300     }
301
302     override  void arrange()
303     {
304         FlowRow[] rows;
305         float currentWidth = xgap;
306         FlowRow currentRow = new FlowRow(xgap);
307         foreach(Arrangeable c ; m_items)
308         {
309             Size s = c.preferred_size(rect.size);
310             currentWidth += s.width + xgap;
311             if(currentWidth > rect.width)
312             {
313                 currentWidth = s.width - xgap;
314                 rows ~= currentRow;
315                 currentRow = new FlowRow(xgap);
316             }
317             currentRow.add(c);
318         }
319         rows ~= currentRow;
320         float totalHeight = 0;
321         foreach(FlowRow r ; rows)
322         {
323             totalHeight += r.size.height;
324         }
325         totalHeight += ygap*(rows.length - 1);
326
327         alias Alignment A;
328
329         float y;
330         switch(alignment & A.YMASK) {
331         case A.Top:
332             y = rect.y;
333             break;
334         case A.Bottom:
335             y = rect.y2 - totalHeight;
336             break;
337         case A.CenterY:
338         default:
339             y = rect.y + (rect.height - totalHeight)/2;
340             break;
341
342         }
343
344         switch(alignment & A.XMASK)
345         {
346         case A.Left:
347             foreach(FlowRow r; rows)
348             {
349                 r.position = Point(0, y-rect.y);
350                 r.arrange();
351                 y += r.size.height + ygap;
352             }
353             break;
354         case A.Right:
355             foreach(FlowRow r; rows)
356             {
357                 float x = rect.x2 - r.size.width;
358                 r.position = Point(x-rect.x, y-rect.y);
359                 r.arrange();
360                 y += r.size.height + ygap;
361             }
362             break;
363
364         case A.CenterX:
365         default:
366             foreach(FlowRow r; rows)
367             {
368                 float x = rect.x + (rect.width - r.size.width)/2;
369                 r.position = Point(x-rect.x, y-rect.y);
370                 r.arrange();
371                 y += r.size.height + ygap;
372             }
373             break;
374         }
375         foreach(Arrangeable item ; m_items)
376         {
377             item.arrange();
378         }
379     }
380
381
382     float xgap() { return m_gaps.x; }
383     float xgap(float v) { m_gaps.x = v; return m_gaps.x; }
384     float ygap() { return m_gaps.y; }
385     float ygap(float v) { m_gaps.y = v; return m_gaps.y; }
386
387     Alignment alignment() { return m_alignment; }
388     Alignment alignment(Alignment v) { m_alignment = v; return m_alignment; }
389
390     const float DEFAULT_GAP = 5;
391
392     private:
393     Arrangeable[] m_items;
394     Alignment m_alignment;
395     Gaps m_gaps;
396 }
397
398 //----------------------------------------------------------------------------
399 /**
400  * FlowRow is a private implementation detail of the FlowArranger class
401  */
402 private class FlowRow
403 {
404     this(float xgap)
405     {
406         this.xgap = xgap;
407     }
408
409     void add(Arrangeable item)
410     {
411         m_items ~= item;
412         _recalc();
413         return item;
414     }
415
416     void remove(Arrangeable item)
417     {
418         m_items.drop_item(item);
419         _recalc();
420     }
421
422     void arrange()
423     {
424         _recalc();
425         float x0 = rect.x;
426         float y0 = rect.y;
427         float x = x0;
428         float y = y0 + (rect.height / 2);
429         foreach(Arrangeable item ; m_items)
430         {
431             Size s = item.preferred_size(rect.size);
432             item.set_rect(Rect(x, y-(s.height/2), s.width, s.height));
433             x += s.width + xgap;
434         }
435     }
436
437     private void _recalc()
438     {
439         float height = _max_item_height();
440         float width = 0;
441         foreach(Arrangeable item ; m_items)
442         {
443             Size s = item.preferred_size(rect.size);
444             width += s.width;
445         }
446         width += xgap * (m_items.length - 1);
447         m_rect.width = width;
448         m_rect.height = height;
449     }
450
451     private float _max_item_height()
452     {
453         float maxHeight = 0;
454         foreach(Arrangeable item ; m_items)
455         {
456             maxHeight = math.fmax(maxHeight, item.preferred_size(rect.size).height);
457         }
458         return maxHeight;
459     }
460
461     float xgap() { return m_xgap; }
462     float xgap(float v) { m_xgap = v; _recalc(); return m_xgap; }
463
464     //int ygap() { return m_ygap; }
465     //int ygap() { m_ygap = v; _recalc(); return m_ygap; }
466
467     Point position() { return Point(m_rect.x, m_rect.y); }
468     Point position(Point p) {
469         m_rect.x = p.x;
470         m_rect.y = p.y;
471         _recalc();
472         return p;
473     }
474     Rect rect() { return m_rect; }
475     Rect rect(Rect r) {
476         m_rect = r;
477         _recalc();
478         return m_rect;
479     }
480
481     Size size() { return Size(m_rect.width, m_rect.height); }
482
483 private:
484     Arrangeable[] m_items;
485     Rect m_rect;
486     float m_xgap;
487 }
488
489
490
491 //----------------------------------------------------------------------------
492 /**
493  * The GridArranger arranges items into a grid with a fixed number of
494  * columns or rows.
495  */
496 class GridArranger : BaseArranger
497 {
498     this()
499     {
500         this(1, 0, Gaps());
501     }
502
503     this(int Columns, int Rows = 0)
504     {
505         this(Columns, Rows, Gaps());
506     }
507
508     this(int Columns, int Rows, Gaps gaps_)
509     {
510         super();
511         this.columns = Columns;
512         this.rows = Rows;
513         if (gaps_.standard) {
514             gaps_.x = 0;
515             gaps_.y = 0;
516         }
517         this.m_gaps = gaps_;
518     }
519
520     override int orderof(Arrangeable item) {
521         assert(item !is null);
522         int idx = m_items.find_item(item);
523         assert(idx != NOT_FOUND);
524         return idx;
525     }
526
527     override Size preferred_size(Size bounds) {
528         float[] widths, heights;
529         return _calc_size(
530             widths,heights,
531             (Arrangeable item){ return item.preferred_size(bounds); } );
532     }
533
534     override Size minimum_size(Size bounds) {
535         float[] widths, heights;
536         return _calc_size(
537             widths, heights,
538             (Arrangeable item){ return item.minimum_size(bounds); } );
539     }
540
541     override void arrange()
542     {
543         if (m_items.length == 0) return;
544
545         float[] widths, heights;
546         GridCells grid = _update_cell_widths(
547             widths,heights,
548             (Arrangeable i){ return i.preferred_size(rect.size); });
549
550
551         float x0 = rect.x;
552         float y0 = rect.y;
553         float x,y;
554         int c,r;
555
556         for(c = 0, x = x0; c < grid.cols; x += widths[c] + xgap, c++)
557         {
558             for(r = 0, y = y0; r < grid.rows; y += heights[r] + ygap, r++)
559             {
560                 int i = r * grid.cols + c;
561                 if(i < m_items.length)
562                 {
563                     Arrangeable item = m_items[i];
564                     item.set_rect(Rect(x-x0, y-y0, widths[c], heights[r]));
565                     item.arrange();
566                 }
567             }
568         }
569     }
570
571     override  void add(Arrangeable item, ...)
572     in{
573         assert(_arguments.length == 0, "GridArranger.add does not take arguments");
574     }
575     body{
576         m_items ~= item;
577         return item;
578     }
579
580     override  void remove(Arrangeable item, ...)
581     in {
582         assert(_arguments.length == 0, "GridArranger.remove does not take arguments");
583     }
584     body{
585         m_items.drop_item(item);
586     }
587
588     override  void clear()
589     {
590         m_items.length = 0;
591     }
592
593     float xgap() { return m_gaps.x; }
594     float xgap(float v) { m_gaps.x = v; return m_gaps.x; }
595     float ygap() { return m_gaps.y; }
596     float ygap(float v) { m_gaps.y = v; return m_gaps.y; }
597
598
599     int rows() { return m_rows; }
600     int rows(int v) { return m_rows = v; }
601
602     int columns() { return m_cols; }
603     int columns(int v) { return m_cols = v; }
604
605     private struct GridCells {
606         static GridCells opCall(int c, int r)
607         { GridCells g; g.rows = r; g.cols = c; return g; }
608         int cols=0;
609         int rows=0;
610     }
611
612     private GridCells _calc_grid()
613     {
614         int newRows = rows;
615         int newCols = columns;
616         int nItems = m_items.length;
617         if(newRows > 0)
618         {
619             newCols = (nItems + newRows - 1)/newRows;
620         }
621         else
622         {
623             newRows = (nItems + newCols - 1)/newCols;
624         }
625         return GridCells(newCols, newRows);
626     }
627
628     private GridCells _update_cell_widths(
629         inout float[] widths, inout float[] heights, Size delegate(Arrangeable i)size_getter
630         )
631     {
632         GridCells grid = _calc_grid();
633         widths.length = grid.cols;
634         heights.length = grid.rows;
635
636         widths[] = 0;
637         heights[] = 0;
638
639         foreach(c, inout w; widths)
640         {
641             foreach(r, inout h; heights)
642             {
643                 int i = r * grid.cols + c;
644                 if(i < m_items.length)
645                 {
646                     Size s = size_getter(m_items[i]);
647                     if (s.w > w) w = s.w;
648                     if (s.h > h) h = s.h;
649                 }
650             }
651         }
652         return