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