]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/BoxLayout.cxx
canvas::Layout: support for contents margins.
[simgear.git] / simgear / canvas / layout / BoxLayout.cxx
1 // Align items horizontally or vertically in a box
2 //
3 // Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
18
19 #include "BoxLayout.hxx"
20 #include "SpacerItem.hxx"
21 #include <simgear/canvas/Canvas.hxx>
22
23 namespace simgear
24 {
25 namespace canvas
26 {
27
28   //----------------------------------------------------------------------------
29   BoxLayout::BoxLayout(Direction dir):
30     _padding(5)
31   {
32     setDirection(dir);
33   }
34
35   //----------------------------------------------------------------------------
36   BoxLayout::~BoxLayout()
37   {
38     _parent.reset(); // No need to invalidate parent again...
39     clear();
40   }
41
42   //----------------------------------------------------------------------------
43   void BoxLayout::addItem(const LayoutItemRef& item)
44   {
45     return addItem(item, 0);
46   }
47
48   //----------------------------------------------------------------------------
49   void BoxLayout::addItem(const LayoutItemRef& item, int stretch)
50   {
51     insertItem(-1, item, stretch);
52   }
53
54   //----------------------------------------------------------------------------
55   void BoxLayout::addStretch(int stretch)
56   {
57     insertStretch(-1, stretch);
58   }
59
60   //----------------------------------------------------------------------------
61   void BoxLayout::addSpacing(int size)
62   {
63     insertSpacing(-1, size);
64   }
65
66   //----------------------------------------------------------------------------
67   void BoxLayout::insertItem(int index, const LayoutItemRef& item, int stretch)
68   {
69     ItemData item_data = {0};
70     item_data.layout_item = item;
71     item_data.stretch = std::max(0, stretch);
72
73     if( SGWeakReferenced::count(this) )
74       item->setParent(this);
75     else
76       SG_LOG( SG_GUI,
77               SG_WARN,
78               "Adding item to expired or non-refcounted layout" );
79
80     if( index < 0 )
81       _layout_items.push_back(item_data);
82     else
83       _layout_items.insert(_layout_items.begin() + index, item_data);
84
85     invalidate();
86   }
87
88   //----------------------------------------------------------------------------
89   void BoxLayout::insertStretch(int index, int stretch)
90   {
91     insertItem(index, LayoutItemRef(new SpacerItem()), stretch);
92   }
93
94   //----------------------------------------------------------------------------
95   void BoxLayout::insertSpacing(int index, int size)
96   {
97     SGVec2i size_hint = horiz()
98                           ? SGVec2i(size, 0)
99                           : SGVec2i(0, size),
100                 max_size  = size_hint;
101
102     insertItem(index, LayoutItemRef(new SpacerItem(size_hint, max_size)));
103   }
104
105   //----------------------------------------------------------------------------
106   size_t BoxLayout::count() const
107   {
108     return _layout_items.size();
109   }
110
111   //----------------------------------------------------------------------------
112   LayoutItemRef BoxLayout::itemAt(size_t index)
113   {
114     if( index >= _layout_items.size() )
115       return LayoutItemRef();
116
117     return _layout_items[index].layout_item;
118   }
119
120   //----------------------------------------------------------------------------
121   LayoutItemRef BoxLayout::takeAt(size_t index)
122   {
123     if( index >= _layout_items.size() )
124       return LayoutItemRef();
125
126     LayoutItems::iterator it = _layout_items.begin() + index;
127     LayoutItemRef item = it->layout_item;
128     item->onRemove();
129     item->setParent(LayoutItemWeakRef());
130     _layout_items.erase(it);
131
132     invalidate();
133
134     return item;
135   }
136
137   //----------------------------------------------------------------------------
138   void BoxLayout::clear()
139   {
140     for( LayoutItems::iterator it = _layout_items.begin();
141                                it != _layout_items.end();
142                              ++it )
143     {
144       it->layout_item->onRemove();
145       it->layout_item->setParent(LayoutItemWeakRef());
146     }
147     _layout_items.clear();
148     invalidate();
149   }
150
151   //----------------------------------------------------------------------------
152   void BoxLayout::setStretch(size_t index, int stretch)
153   {
154     if( index >= _layout_items.size() )
155       return;
156
157     _layout_items.at(index).stretch = std::max(0, stretch);
158     invalidate();
159   }
160
161   //----------------------------------------------------------------------------
162   bool BoxLayout::setStretchFactor(const LayoutItemRef& item, int stretch)
163   {
164     for( LayoutItems::iterator it = _layout_items.begin();
165                                it != _layout_items.end();
166                              ++it )
167     {
168       if( item == it->layout_item )
169       {
170         it->stretch = std::max(0, stretch);
171         invalidate();
172         return true;
173       }
174     }
175
176     return false;
177   }
178
179   //----------------------------------------------------------------------------
180   int BoxLayout::stretch(size_t index) const
181   {
182     if( index >= _layout_items.size() )
183       return 0;
184
185     return _layout_items.at(index).stretch;
186   }
187
188   //----------------------------------------------------------------------------
189   void BoxLayout::setSpacing(int spacing)
190   {
191     if( spacing == _padding )
192       return;
193
194     _padding = spacing;
195     invalidate();
196   }
197
198   //----------------------------------------------------------------------------
199   int BoxLayout::spacing() const
200   {
201     return _padding;
202   }
203
204   //----------------------------------------------------------------------------
205   void BoxLayout::setDirection(Direction dir)
206   {
207     _direction = dir;
208     _get_layout_coord = &SGVec2i::x;
209     _get_fixed_coord = &SGVec2i::y;
210
211     if( !horiz() )
212       std::swap(_get_layout_coord, _get_fixed_coord);
213
214     invalidate();
215   }
216
217   //----------------------------------------------------------------------------
218   BoxLayout::Direction BoxLayout::direction() const
219   {
220     return _direction;
221   }
222
223   //----------------------------------------------------------------------------
224   bool BoxLayout::hasHeightForWidth() const
225   {
226     if( _flags & SIZE_INFO_DIRTY )
227       updateSizeHints();
228
229     return _layout_data.has_hfw;
230   }
231
232   //----------------------------------------------------------------------------
233   void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
234   {
235     _canvas = canvas;
236
237     for(size_t i = 0; i < _layout_items.size(); ++i)
238       _layout_items[i].layout_item->setCanvas(canvas);
239   }
240
241   //----------------------------------------------------------------------------
242   bool BoxLayout::horiz() const
243   {
244     return (_direction == LeftToRight) || (_direction == RightToLeft);
245   }
246
247   //----------------------------------------------------------------------------
248   void BoxLayout::updateSizeHints() const
249   {
250     SGVec2i min_size(0, 0),
251             max_size(0, 0),
252             size_hint(0, 0);
253
254     _layout_data.reset();
255     _hfw_width = _hfw_height = _hfw_min_height = -1;
256
257     bool is_first = true;
258
259     for(size_t i = 0; i < _layout_items.size(); ++i)
260     {
261       ItemData& item_data = _layout_items[i];
262       LayoutItem const& item = *item_data.layout_item;
263
264       item_data.visible = item.isVisible();
265       if( !item_data.visible )
266         continue;
267
268       item_data.min_size  = (item.minimumSize().*_get_layout_coord)();
269       item_data.max_size  = (item.maximumSize().*_get_layout_coord)();
270       item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
271       item_data.has_hfw = item.hasHeightForWidth();
272
273       if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
274       {
275         if( is_first )
276         {
277           item_data.padding_orig = 0;
278           is_first = false;
279         }
280         else
281         {
282           item_data.padding_orig = _padding;
283           _layout_data.padding += item_data.padding_orig;
284         }
285       }
286
287       // Add sizes of all children in layout direction
288       SGMisc<int>::addClipOverflowInplace(min_size.x(),  item_data.min_size);
289       SGMisc<int>::addClipOverflowInplace(max_size.x(),  item_data.max_size);
290       SGMisc<int>::addClipOverflowInplace(size_hint.x(), item_data.size_hint);
291
292       // Take maximum in fixed (non-layouted) direction
293       min_size.y()  = std::max( min_size.y(),
294                                 (item.minimumSize().*_get_fixed_coord)() );
295       max_size.y()  = std::max( max_size.y(),
296                                 (item.maximumSize().*_get_fixed_coord)() );
297       size_hint.y() = std::max( size_hint.y(),
298                                 (item.sizeHint().*_get_fixed_coord)() );
299
300       _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
301     }
302
303     SGMisc<int>::addClipOverflowInplace(min_size.x(),  _layout_data.padding);
304     SGMisc<int>::addClipOverflowInplace(max_size.x(),  _layout_data.padding);
305     SGMisc<int>::addClipOverflowInplace(size_hint.x(), _layout_data.padding);
306
307     _layout_data.min_size = min_size.x();
308     _layout_data.max_size = max_size.x();
309     _layout_data.size_hint = size_hint.x();
310
311     _min_size.x()  = (min_size.*_get_layout_coord)();
312     _max_size.x()  = (max_size.*_get_layout_coord)();
313     _size_hint.x() = (size_hint.*_get_layout_coord)();
314
315     _min_size.y()  = (min_size.*_get_fixed_coord)();
316     _max_size.y()  = (max_size.*_get_fixed_coord)();
317     _size_hint.y() = (size_hint.*_get_fixed_coord)();
318
319     _flags &= ~SIZE_INFO_DIRTY;
320   }
321
322   //----------------------------------------------------------------------------
323   void BoxLayout::updateWFHCache(int w) const
324   {
325     if( w == _hfw_width )
326       return;
327
328     _hfw_height = 0;
329     _hfw_min_height = 0;
330
331     if( horiz() )
332     {
333       _layout_data.size = w;
334       const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
335
336       for(size_t i = 0; i < _layout_items.size(); ++i)
337       {
338         ItemData const& data = _layout_items[i];
339         if( !data.visible )
340           continue;
341
342         _hfw_height = std::max(_hfw_height, data.hfw(data.size));
343         _hfw_min_height = std::max(_hfw_min_height, data.mhfw(data.size));
344       }
345     }
346     else
347     {
348       for(size_t i = 0; i < _layout_items.size(); ++i)
349       {
350         ItemData const& data = _layout_items[i];
351         if( !data.visible )
352           continue;
353
354         _hfw_height += data.hfw(w) + data.padding_orig;
355         _hfw_min_height += data.mhfw(w) + data.padding_orig;
356       }
357     }
358
359     _hfw_width = w;
360   }
361
362   //----------------------------------------------------------------------------
363   SGVec2i BoxLayout::sizeHintImpl() const
364   {
365     updateSizeHints();
366     return _size_hint;
367   }
368
369   //----------------------------------------------------------------------------
370   SGVec2i BoxLayout::minimumSizeImpl() const
371   {
372     updateSizeHints();
373     return _min_size;
374   }
375
376   //----------------------------------------------------------------------------
377   SGVec2i BoxLayout::maximumSizeImpl() const
378   {
379     updateSizeHints();
380     return _max_size;
381   }
382
383
384   //----------------------------------------------------------------------------
385   int BoxLayout::heightForWidthImpl(int w) const
386   {
387     if( !hasHeightForWidth() )
388       return -1;
389
390     updateWFHCache(w);
391     return _hfw_height;
392   }
393
394   //----------------------------------------------------------------------------
395   int BoxLayout::minimumHeightForWidthImpl(int w) const
396   {
397     if( !hasHeightForWidth() )
398       return -1;
399
400     updateWFHCache(w);
401     return _hfw_min_height;
402   }
403
404   //----------------------------------------------------------------------------
405   void BoxLayout::doLayout(const SGRecti& geom)
406   {
407     if( _flags & SIZE_INFO_DIRTY )
408       updateSizeHints();
409
410     // Store current size hints because vertical layouts containing
411     // height-for-width items the size hints are update for the actual width of
412     // the layout
413     int min_size_save = _layout_data.min_size,
414         size_hint_save = _layout_data.size_hint;
415
416     _layout_data.size = (geom.size().*_get_layout_coord)();
417
418     // update width dependent data for layouting of vertical layouts
419     if( _layout_data.has_hfw && !horiz() )
420     {
421       for(size_t i = 0; i < _layout_items.size(); ++i)
422       {
423         ItemData& data = _layout_items[i];
424         if( !data.visible )
425           continue;
426
427         if( data.has_hfw )
428         {
429           int w = SGMisc<int>::clip( geom.width(),
430                                      data.layout_item->minimumSize().x(),
431                                      data.layout_item->maximumSize().x() );
432
433           data.min_size = data.mhfw(w);
434           data.size_hint = data.hfw(w);
435
436           // Update size hints for layouting with difference to size hints
437           // calculated by using the size hints provided (without trading
438           // height for width)
439           _layout_data.min_size  += data.min_size
440                                   - data.layout_item->minimumSize().y();
441           _layout_data.size_hint += data.size_hint
442                                   - data.layout_item->sizeHint().y();
443         }
444       }
445     }
446
447     // now do the actual layouting
448     distribute(_layout_items, _layout_data);
449
450     // Restore size hints possibly changed by vertical layouting
451     _layout_data.min_size = min_size_save;
452     _layout_data.size_hint = size_hint_save;
453
454     // and finally set the layouted geometry for each item
455     int fixed_size = (geom.size().*_get_fixed_coord)();
456     SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
457                      (geom.pos().*_get_fixed_coord)() );
458
459     bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
460     if( reverse )
461       cur_pos.x() += (geom.size().*_get_layout_coord)();
462
463     for(size_t i = 0; i < _layout_items.size(); ++i)
464     {
465       ItemData const& data = _layout_items[i];
466       if( !data.visible )
467         continue;
468
469       cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
470
471       SGVec2i size(
472         data.size,
473         std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(),
474                   fixed_size )
475       );
476
477       // Center in fixed direction (TODO allow specifying alignment)
478       int offset_fixed = (fixed_size - size.y()) / 2;
479       cur_pos.y() += offset_fixed;
480
481       data.layout_item->setGeometry(SGRecti(
482         (cur_pos.*_get_layout_coord)(),
483         (cur_pos.*_get_fixed_coord)(),
484         (size.*_get_layout_coord)(),
485         (size.*_get_fixed_coord)()
486       ));
487
488       if( !reverse )
489         cur_pos.x() += data.size;
490       cur_pos.y() -= offset_fixed;
491     }
492   }
493
494   //----------------------------------------------------------------------------
495   void BoxLayout::visibilityChanged(bool visible)
496   {
497     for(size_t i = 0; i < _layout_items.size(); ++i)
498       callSetVisibleInternal(_layout_items[i].layout_item.get(), visible);
499   }
500
501   //----------------------------------------------------------------------------
502   HBoxLayout::HBoxLayout():
503     BoxLayout(LeftToRight)
504   {
505
506   }
507
508   //----------------------------------------------------------------------------
509   VBoxLayout::VBoxLayout():
510     BoxLayout(TopToBottom)
511   {
512
513   }
514
515 } // namespace canvas
516 } // namespace simgear