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