]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/BoxLayout.cxx
Off-by-one error in the OSG_VERSION_LESS_THAN macro
[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,
50                            int stretch,
51                            uint8_t alignment )
52   {
53     insertItem(-1, item, stretch, alignment);
54   }
55
56   //----------------------------------------------------------------------------
57   void BoxLayout::addStretch(int stretch)
58   {
59     insertStretch(-1, stretch);
60   }
61
62   //----------------------------------------------------------------------------
63   void BoxLayout::addSpacing(int size)
64   {
65     insertSpacing(-1, size);
66   }
67
68   //----------------------------------------------------------------------------
69   void BoxLayout::insertItem( int index,
70                               const LayoutItemRef& item,
71                               int stretch,
72                               uint8_t alignment )
73   {
74     ItemData item_data = {0};
75     item_data.layout_item = item;
76     item_data.stretch = std::max(0, stretch);
77
78     if( alignment != AlignFill )
79       item->setAlignment(alignment);
80
81     if( SGWeakReferenced::count(this) )
82       item->setParent(this);
83     else
84       SG_LOG( SG_GUI,
85               SG_WARN,
86               "Adding item to expired or non-refcounted layout" );
87
88     if( index < 0 )
89       _layout_items.push_back(item_data);
90     else
91       _layout_items.insert(_layout_items.begin() + index, item_data);
92
93     invalidate();
94   }
95
96   //----------------------------------------------------------------------------
97   void BoxLayout::insertStretch(int index, int stretch)
98   {
99     insertItem(index, LayoutItemRef(new SpacerItem()), stretch);
100   }
101
102   //----------------------------------------------------------------------------
103   void BoxLayout::insertSpacing(int index, int size)
104   {
105     SGVec2i size_hint = horiz()
106                           ? SGVec2i(size, 0)
107                           : SGVec2i(0, size),
108                 max_size  = size_hint;
109
110     insertItem(index, LayoutItemRef(new SpacerItem(size_hint, max_size)));
111   }
112
113   //----------------------------------------------------------------------------
114   size_t BoxLayout::count() const
115   {
116     return _layout_items.size();
117   }
118
119   //----------------------------------------------------------------------------
120   LayoutItemRef BoxLayout::itemAt(size_t index)
121   {
122     if( index >= _layout_items.size() )
123       return LayoutItemRef();
124
125     return _layout_items[index].layout_item;
126   }
127
128   //----------------------------------------------------------------------------
129   LayoutItemRef BoxLayout::takeAt(size_t index)
130   {
131     if( index >= _layout_items.size() )
132       return LayoutItemRef();
133
134     LayoutItems::iterator it = _layout_items.begin() + index;
135     LayoutItemRef item = it->layout_item;
136     item->onRemove();
137     item->setParent(LayoutItemWeakRef());
138     _layout_items.erase(it);
139
140     invalidate();
141
142     return item;
143   }
144
145   //----------------------------------------------------------------------------
146   void BoxLayout::clear()
147   {
148     for( LayoutItems::iterator it = _layout_items.begin();
149                                it != _layout_items.end();
150                              ++it )
151     {
152       it->layout_item->onRemove();
153       it->layout_item->setParent(LayoutItemWeakRef());
154     }
155     _layout_items.clear();
156     invalidate();
157   }
158
159   //----------------------------------------------------------------------------
160   void BoxLayout::setStretch(size_t index, int stretch)
161   {
162     if( index >= _layout_items.size() )
163       return;
164
165     _layout_items.at(index).stretch = std::max(0, stretch);
166     invalidate();
167   }
168
169   //----------------------------------------------------------------------------
170   bool BoxLayout::setStretchFactor(const LayoutItemRef& item, int stretch)
171   {
172     for( LayoutItems::iterator it = _layout_items.begin();
173                                it != _layout_items.end();
174                              ++it )
175     {
176       if( item == it->layout_item )
177       {
178         it->stretch = std::max(0, stretch);
179         invalidate();
180         return true;
181       }
182     }
183
184     return false;
185   }
186
187   //----------------------------------------------------------------------------
188   int BoxLayout::stretch(size_t index) const
189   {
190     if( index >= _layout_items.size() )
191       return 0;
192
193     return _layout_items.at(index).stretch;
194   }
195
196   //----------------------------------------------------------------------------
197   void BoxLayout::setSpacing(int spacing)
198   {
199     if( spacing == _padding )
200       return;
201
202     _padding = spacing;
203     invalidate();
204   }
205
206   //----------------------------------------------------------------------------
207   int BoxLayout::spacing() const
208   {
209     return _padding;
210   }
211
212   //----------------------------------------------------------------------------
213   void BoxLayout::setDirection(Direction dir)
214   {
215     _direction = dir;
216     _get_layout_coord = &SGVec2i::x;
217     _get_fixed_coord = &SGVec2i::y;
218
219     if( !horiz() )
220       std::swap(_get_layout_coord, _get_fixed_coord);
221
222     invalidate();
223   }
224
225   //----------------------------------------------------------------------------
226   BoxLayout::Direction BoxLayout::direction() const
227   {
228     return _direction;
229   }
230
231   //----------------------------------------------------------------------------
232   bool BoxLayout::hasHeightForWidth() const
233   {
234     if( _flags & SIZE_INFO_DIRTY )
235       updateSizeHints();
236
237     return _layout_data.has_hfw;
238   }
239
240   //----------------------------------------------------------------------------
241   void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
242   {
243     _canvas = canvas;
244
245     for(size_t i = 0; i < _layout_items.size(); ++i)
246       _layout_items[i].layout_item->setCanvas(canvas);
247   }
248
249   //----------------------------------------------------------------------------
250   bool BoxLayout::horiz() const
251   {
252     return (_direction == LeftToRight) || (_direction == RightToLeft);
253   }
254
255   //----------------------------------------------------------------------------
256   void BoxLayout::updateSizeHints() const
257   {
258     SGVec2i min_size(0, 0),
259             max_size(0, 0),
260             size_hint(0, 0);
261
262     _layout_data.reset();
263     _hfw_width = _hfw_height = _hfw_min_height = -1;
264
265     bool is_first = true;
266
267     for(size_t i = 0; i < _layout_items.size(); ++i)
268     {
269       ItemData& item_data = _layout_items[i];
270       LayoutItem const& item = *item_data.layout_item;
271
272       item_data.visible = item.isVisible();
273       if( !item_data.visible )
274         continue;
275
276       item_data.min_size  = (item.minimumSize().*_get_layout_coord)();
277       item_data.max_size  = (item.maximumSize().*_get_layout_coord)();
278       item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
279       item_data.has_hfw = item.hasHeightForWidth();
280
281       uint8_t alignment_mask = horiz()
282                              ? AlignHorizontal_Mask
283                              : AlignVertical_Mask;
284       item_data.has_align = (item.alignment() & alignment_mask) != 0;
285
286       if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
287       {
288         if( is_first )
289         {
290           item_data.padding_orig = 0;
291           is_first = false;
292         }
293         else
294         {
295           item_data.padding_orig = _padding;
296           _layout_data.padding += item_data.padding_orig;
297         }
298       }
299
300       // Add sizes of all children in layout direction
301       SGMisc<int>::addClipOverflowInplace(min_size.x(),  item_data.min_size);
302       SGMisc<int>::addClipOverflowInplace(max_size.x(),  item_data.max_size);
303       SGMisc<int>::addClipOverflowInplace(size_hint.x(), item_data.size_hint);
304
305       // Take maximum in fixed (non-layouted) direction
306       min_size.y()  = std::max( min_size.y(),
307                                 (item.minimumSize().*_get_fixed_coord)() );
308       max_size.y()  = std::max( max_size.y(),
309                                 (item.maximumSize().*_get_fixed_coord)() );
310       size_hint.y() = std::max( size_hint.y(),
311                                 (item.sizeHint().*_get_fixed_coord)() );
312
313       _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
314     }
315
316     SGMisc<int>::addClipOverflowInplace(min_size.x(),  _layout_data.padding);
317     SGMisc<int>::addClipOverflowInplace(max_size.x(),  _layout_data.padding);
318     SGMisc<int>::addClipOverflowInplace(size_hint.x(), _layout_data.padding);
319
320     _layout_data.min_size = min_size.x();
321     _layout_data.max_size = max_size.x();
322     _layout_data.size_hint = size_hint.x();
323
324     _min_size.x()  = (min_size.*_get_layout_coord)();
325     _max_size.x()  = (max_size.*_get_layout_coord)();
326     _size_hint.x() = (size_hint.*_get_layout_coord)();
327
328     _min_size.y()  = (min_size.*_get_fixed_coord)();
329     _max_size.y()  = (max_size.*_get_fixed_coord)();
330     _size_hint.y() = (size_hint.*_get_fixed_coord)();
331
332     _flags &= ~SIZE_INFO_DIRTY;
333   }
334
335   //----------------------------------------------------------------------------
336   void BoxLayout::updateWFHCache(int w) const
337   {
338     if( w == _hfw_width )
339       return;
340
341     _hfw_height = 0;
342     _hfw_min_height = 0;
343
344     if( horiz() )
345     {
346       _layout_data.size = w;
347       const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
348
349       for(size_t i = 0; i < _layout_items.size(); ++i)
350       {
351         ItemData const& data = _layout_items[i];
352         if( !data.visible )
353           continue;
354
355         _hfw_height = std::max(_hfw_height, data.hfw(data.size));
356         _hfw_min_height = std::max(_hfw_min_height, data.mhfw(data.size));
357       }
358     }
359     else
360     {
361       for(size_t i = 0; i < _layout_items.size(); ++i)
362       {
363         ItemData const& data = _layout_items[i];
364         if( !data.visible )
365           continue;
366
367         _hfw_height += data.hfw(w) + data.padding_orig;
368         _hfw_min_height += data.mhfw(w) + data.padding_orig;
369       }
370     }
371
372     _hfw_width = w;
373   }
374
375   //----------------------------------------------------------------------------
376   SGVec2i BoxLayout::sizeHintImpl() const
377   {
378     updateSizeHints();
379     return _size_hint;
380   }
381
382   //----------------------------------------------------------------------------
383   SGVec2i BoxLayout::minimumSizeImpl() const
384   {
385     updateSizeHints();
386     return _min_size;
387   }
388
389   //----------------------------------------------------------------------------
390   SGVec2i BoxLayout::maximumSizeImpl() const
391   {
392     updateSizeHints();
393     return _max_size;
394   }
395
396
397   //----------------------------------------------------------------------------
398   int BoxLayout::heightForWidthImpl(int w) const
399   {
400     if( !hasHeightForWidth() )
401       return -1;
402
403     updateWFHCache(w);
404     return _hfw_height;
405   }
406
407   //----------------------------------------------------------------------------
408   int BoxLayout::minimumHeightForWidthImpl(int w) const
409   {
410     if( !hasHeightForWidth() )
411       return -1;
412
413     updateWFHCache(w);
414     return _hfw_min_height;
415   }
416
417   //----------------------------------------------------------------------------
418   void BoxLayout::doLayout(const SGRecti& geom)
419   {
420     if( _flags & SIZE_INFO_DIRTY )
421       updateSizeHints();
422
423     // Store current size hints because vertical layouts containing
424     // height-for-width items the size hints are update for the actual width of
425     // the layout
426     int min_size_save = _layout_data.min_size,
427         size_hint_save = _layout_data.size_hint;
428
429     _layout_data.size = (geom.size().*_get_layout_coord)();
430
431     // update width dependent data for layouting of vertical layouts
432     if( _layout_data.has_hfw && !horiz() )
433     {
434       for(size_t i = 0; i < _layout_items.size(); ++i)
435       {
436         ItemData& data = _layout_items[i];
437         if( !data.visible )
438           continue;
439
440         if( data.has_hfw )
441         {
442           int w = SGMisc<int>::clip( geom.width(),
443                                      data.layout_item->minimumSize().x(),
444                                      data.layout_item->maximumSize().x() );
445
446           data.min_size = data.mhfw(w);
447           data.size_hint = data.hfw(w);
448
449           // Update size hints for layouting with difference to size hints
450           // calculated by using the size hints provided (without trading
451           // height for width)
452           _layout_data.min_size  += data.min_size
453                                   - data.layout_item->minimumSize().y();
454           _layout_data.size_hint += data.size_hint
455                                   - data.layout_item->sizeHint().y();
456         }
457       }
458     }
459
460     // now do the actual layouting
461     distribute(_layout_items, _layout_data);
462
463     // Restore size hints possibly changed by vertical layouting
464     _layout_data.min_size = min_size_save;
465     _layout_data.size_hint = size_hint_save;
466
467     // and finally set the layouted geometry for each item
468     SGVec2i size( 0,
469                  // Always assign all available space. Alignment handles final
470                  // size.
471                  (geom.size().*_get_fixed_coord)() );
472     SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
473                      (geom.pos().*_get_fixed_coord)() );
474
475     bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
476     if( reverse )
477       cur_pos.x() += (geom.size().*_get_layout_coord)();
478
479     for(size_t i = 0; i < _layout_items.size(); ++i)
480     {
481       ItemData const& data = _layout_items[i];
482       if( !data.visible )
483         continue;
484
485       cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
486       size.x() = data.size;
487
488       data.layout_item->setGeometry(SGRecti(
489         (cur_pos.*_get_layout_coord)(),
490         (cur_pos.*_get_fixed_coord)(),
491         (size.*_get_layout_coord)(),
492         (size.*_get_fixed_coord)()
493       ));
494
495       if( !reverse )
496         cur_pos.x() += data.size;
497     }
498   }
499
500   //----------------------------------------------------------------------------
501   void BoxLayout::visibilityChanged(bool visible)
502   {
503     for(size_t i = 0; i < _layout_items.size(); ++i)
504       callSetVisibleInternal(_layout_items[i].layout_item.get(), visible);
505   }
506
507   //----------------------------------------------------------------------------
508   HBoxLayout::HBoxLayout():
509     BoxLayout(LeftToRight)
510   {
511
512   }
513
514   //----------------------------------------------------------------------------
515   VBoxLayout::VBoxLayout():
516     BoxLayout(TopToBottom)
517   {
518
519   }
520
521 } // namespace canvas
522 } // namespace simgear