]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/BoxLayout.cxx
canvas::Layout: clear parent/canvas after calling onRemove.
[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   int BoxLayout::heightForWidth(int w) const
234   {
235     if( !hasHeightForWidth() )
236       return -1;
237
238     updateWFHCache(w);
239     return _hfw_height;
240   }
241
242   //----------------------------------------------------------------------------
243   int BoxLayout::minimumHeightForWidth(int w) const
244   {
245     if( !hasHeightForWidth() )
246       return -1;
247
248     updateWFHCache(w);
249     return _hfw_min_height;
250   }
251
252   //----------------------------------------------------------------------------
253   void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
254   {
255     _canvas = canvas;
256
257     for(size_t i = 0; i < _layout_items.size(); ++i)
258       _layout_items[i].layout_item->setCanvas(canvas);
259   }
260
261   //----------------------------------------------------------------------------
262   bool BoxLayout::horiz() const
263   {
264     return (_direction == LeftToRight) || (_direction == RightToLeft);
265   }
266
267   //----------------------------------------------------------------------------
268   void BoxLayout::updateSizeHints() const
269   {
270     SGVec2i min_size(0, 0),
271             max_size(0, 0),
272             size_hint(0, 0);
273
274     _layout_data.reset();
275     _hfw_width = _hfw_height = _hfw_min_height = -1;
276
277     bool is_first = true;
278
279     for(size_t i = 0; i < _layout_items.size(); ++i)
280     {
281       ItemData& item_data = _layout_items[i];
282       LayoutItem const& item = *item_data.layout_item;
283
284       item_data.visible = item.isVisible();
285       if( !item_data.visible )
286         continue;
287
288       item_data.min_size  = (item.minimumSize().*_get_layout_coord)();
289       item_data.max_size  = (item.maximumSize().*_get_layout_coord)();
290       item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
291       item_data.has_hfw = item.hasHeightForWidth();
292
293       if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
294       {
295         if( is_first )
296         {
297           item_data.padding_orig = 0;
298           is_first = false;
299         }
300         else
301         {
302           item_data.padding_orig = _padding;
303           _layout_data.padding += item_data.padding_orig;
304         }
305       }
306
307       // Add sizes of all children in layout direction
308       safeAdd(min_size.x(),  item_data.min_size);
309       safeAdd(max_size.x(),  item_data.max_size);
310       safeAdd(size_hint.x(), item_data.size_hint);
311
312       // Take maximum in fixed (non-layouted) direction
313       min_size.y()  = std::max( min_size.y(),
314                                 (item.minimumSize().*_get_fixed_coord)() );
315       max_size.y()  = std::max( max_size.y(),
316                                 (item.maximumSize().*_get_fixed_coord)() );
317       size_hint.y() = std::max( size_hint.y(),
318                                 (item.sizeHint().*_get_fixed_coord)() );
319
320       _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
321     }
322
323     safeAdd(min_size.x(),  _layout_data.padding);
324     safeAdd(max_size.x(),  _layout_data.padding);
325     safeAdd(size_hint.x(), _layout_data.padding);
326
327     _layout_data.min_size = min_size.x();
328     _layout_data.max_size = max_size.x();
329     _layout_data.size_hint = size_hint.x();
330
331     _min_size.x()  = (min_size.*_get_layout_coord)();
332     _max_size.x()  = (max_size.*_get_layout_coord)();
333     _size_hint.x() = (size_hint.*_get_layout_coord)();
334
335     _min_size.y()  = (min_size.*_get_fixed_coord)();
336     _max_size.y()  = (max_size.*_get_fixed_coord)();
337     _size_hint.y() = (size_hint.*_get_fixed_coord)();
338
339     _flags &= ~SIZE_INFO_DIRTY;
340   }
341
342   //----------------------------------------------------------------------------
343   void BoxLayout::updateWFHCache(int w) const
344   {
345     if( w == _hfw_width )
346       return;
347
348     _hfw_height = 0;
349     _hfw_min_height = 0;
350
351     if( horiz() )
352     {
353       _layout_data.size = w;
354       const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
355
356       for(size_t i = 0; i < _layout_items.size(); ++i)
357       {
358         ItemData const& data = _layout_items[i];
359         if( !data.visible )
360           continue;
361
362         _hfw_height = std::max(_hfw_height, data.hfw(data.size));
363         _hfw_min_height = std::max(_hfw_min_height, data.mhfw(data.size));
364       }
365     }
366     else
367     {
368       for(size_t i = 0; i < _layout_items.size(); ++i)
369       {
370         ItemData const& data = _layout_items[i];
371         if( !data.visible )
372           continue;
373
374         _hfw_height += data.hfw(w) + data.padding_orig;
375         _hfw_min_height += data.mhfw(w) + data.padding_orig;
376       }
377     }
378
379     _hfw_width = w;
380   }
381
382   //----------------------------------------------------------------------------
383   SGVec2i BoxLayout::sizeHintImpl() const
384   {
385     updateSizeHints();
386     return _size_hint;
387   }
388
389   //----------------------------------------------------------------------------
390   SGVec2i BoxLayout::minimumSizeImpl() const
391   {
392     updateSizeHints();
393     return _min_size;
394   }
395
396   //----------------------------------------------------------------------------
397   SGVec2i BoxLayout::maximumSizeImpl() const
398   {
399     updateSizeHints();
400     return _max_size;
401   }
402
403   //----------------------------------------------------------------------------
404   void BoxLayout::doLayout(const SGRecti& geom)
405   {
406     if( _flags & SIZE_INFO_DIRTY )
407       updateSizeHints();
408
409     // Store current size hints because vertical layouts containing
410     // height-for-width items the size hints are update for the actual width of
411     // the layout
412     int min_size_save = _layout_data.min_size,
413         size_hint_save = _layout_data.size_hint;
414
415     _layout_data.size = (geom.size().*_get_layout_coord)();
416
417     // update width dependent data for layouting of vertical layouts
418     if( _layout_data.has_hfw && !horiz() )
419     {
420       for(size_t i = 0; i < _layout_items.size(); ++i)
421       {
422         ItemData& data = _layout_items[i];
423         if( !data.visible )
424           continue;
425
426         if( data.has_hfw )
427         {
428           int w = SGMisc<int>::clip( geom.width(),
429                                      data.layout_item->minimumSize().x(),
430                                      data.layout_item->maximumSize().x() );
431
432           data.min_size = data.mhfw(w);
433           data.size_hint = data.hfw(w);
434
435           // Update size hints for layouting with difference to size hints
436           // calculated by using the size hints provided (without trading
437           // height for width)
438           _layout_data.min_size  += data.min_size
439                                   - data.layout_item->minimumSize().y();
440           _layout_data.size_hint += data.size_hint
441                                   - data.layout_item->sizeHint().y();
442         }
443       }
444     }
445
446     // now do the actual layouting
447     distribute(_layout_items, _layout_data);
448
449     // Restore size hints possibly changed by vertical layouting
450     _layout_data.min_size = min_size_save;
451     _layout_data.size_hint = size_hint_save;
452
453     // and finally set the layouted geometry for each item
454     int fixed_size = (geom.size().*_get_fixed_coord)();
455     SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
456                      (geom.pos().*_get_fixed_coord)() );
457
458     bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
459     if( reverse )
460       cur_pos.x() += (geom.size().*_get_layout_coord)();
461
462     for(size_t i = 0; i < _layout_items.size(); ++i)
463     {
464       ItemData const& data = _layout_items[i];
465       if( !data.visible )
466         continue;
467
468       cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
469
470       SGVec2i size(
471         data.size,
472         std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(),
473                   fixed_size )
474       );
475
476       // Center in fixed direction (TODO allow specifying alignment)
477       int offset_fixed = (fixed_size - size.y()) / 2;
478       cur_pos.y() += offset_fixed;
479
480       data.layout_item->setGeometry(SGRecti(
481         (cur_pos.*_get_layout_coord)(),
482         (cur_pos.*_get_fixed_coord)(),
483         (size.*_get_layout_coord)(),
484         (size.*_get_fixed_coord)()
485       ));
486
487       if( !reverse )
488         cur_pos.x() += data.size;
489       cur_pos.y() -= offset_fixed;
490     }
491   }
492
493   //----------------------------------------------------------------------------
494   void BoxLayout::visibilityChanged(bool visible)
495   {
496     for(size_t i = 0; i < _layout_items.size(); ++i)
497       callSetVisibleInternal(_layout_items[i].layout_item.get(), visible);
498   }
499
500   //----------------------------------------------------------------------------
501   HBoxLayout::HBoxLayout():
502     BoxLayout(LeftToRight)
503   {
504
505   }
506
507   //----------------------------------------------------------------------------
508   VBoxLayout::VBoxLayout():
509     BoxLayout(TopToBottom)
510   {
511
512   }
513
514 } // namespace canvas
515 } // namespace simgear