]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/BoxLayout.cxx
canvas::BoxLayout: set stretch factor by item.
[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   bool BoxLayout::setStretchFactor(const LayoutItemRef& item, int stretch)
157   {
158     for( LayoutItems::iterator it = _layout_items.begin();
159                                it != _layout_items.end();
160                              ++it )
161     {
162       if( item == it->layout_item )
163       {
164         it->stretch = std::max(0, stretch);
165         invalidate();
166         return true;
167       }
168     }
169
170     return false;
171   }
172
173   //----------------------------------------------------------------------------
174   int BoxLayout::stretch(size_t index) const
175   {
176     if( index >= _layout_items.size() )
177       return 0;
178
179     return _layout_items.at(index).stretch;
180   }
181
182   //----------------------------------------------------------------------------
183   void BoxLayout::setSpacing(int spacing)
184   {
185     if( spacing == _padding )
186       return;
187
188     _padding = spacing;
189     invalidate();
190   }
191
192   //----------------------------------------------------------------------------
193   int BoxLayout::spacing() const
194   {
195     return _padding;
196   }
197
198   //----------------------------------------------------------------------------
199   void BoxLayout::setDirection(Direction dir)
200   {
201     _direction = dir;
202     _get_layout_coord = &SGVec2i::x;
203     _get_fixed_coord = &SGVec2i::y;
204
205     if( !horiz() )
206       std::swap(_get_layout_coord, _get_fixed_coord);
207
208     invalidate();
209   }
210
211   //----------------------------------------------------------------------------
212   BoxLayout::Direction BoxLayout::direction() const
213   {
214     return _direction;
215   }
216
217   //----------------------------------------------------------------------------
218   bool BoxLayout::hasHeightForWidth() const
219   {
220     if( _flags & SIZE_INFO_DIRTY )
221       updateSizeHints();
222
223     return _layout_data.has_hfw;
224   }
225
226   //----------------------------------------------------------------------------
227   int BoxLayout::heightForWidth(int w) const
228   {
229     if( !hasHeightForWidth() )
230       return -1;
231
232     updateWFHCache(w);
233     return _hfw_height;
234   }
235
236   //----------------------------------------------------------------------------
237   int BoxLayout::minimumHeightForWidth(int w) const
238   {
239     if( !hasHeightForWidth() )
240       return -1;
241
242     updateWFHCache(w);
243     return _hfw_min_height;
244   }
245
246   //----------------------------------------------------------------------------
247   void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
248   {
249     _canvas = canvas;
250
251     for(size_t i = 0; i < _layout_items.size(); ++i)
252       _layout_items[i].layout_item->setCanvas(canvas);
253   }
254
255   //----------------------------------------------------------------------------
256   bool BoxLayout::horiz() const
257   {
258     return (_direction == LeftToRight) || (_direction == RightToLeft);
259   }
260
261   //----------------------------------------------------------------------------
262   void BoxLayout::updateSizeHints() const
263   {
264     SGVec2i min_size(0, 0),
265             max_size(0, 0),
266             size_hint(0, 0);
267
268     _layout_data.reset();
269     _hfw_width = _hfw_height = _hfw_min_height = -1;
270
271     bool is_first = true;
272
273     for(size_t i = 0; i < _layout_items.size(); ++i)
274     {
275       // TODO check visible
276
277       ItemData& item_data = _layout_items[i];
278       LayoutItem const& item = *item_data.layout_item;
279
280       item_data.min_size  = (item.minimumSize().*_get_layout_coord)();
281       item_data.max_size  = (item.maximumSize().*_get_layout_coord)();
282       item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
283       item_data.has_hfw = item.hasHeightForWidth();
284
285       if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
286       {
287         if( is_first )
288         {
289           item_data.padding_orig = 0;
290           is_first = false;
291         }
292         else
293         {
294           item_data.padding_orig = _padding;
295           _layout_data.padding += item_data.padding_orig;
296         }
297       }
298
299       // Add sizes of all children in layout direction
300       safeAdd(min_size.x(),  item_data.min_size);
301       safeAdd(max_size.x(),  item_data.max_size);
302       safeAdd(size_hint.x(), item_data.size_hint);
303
304       // Take maximum in fixed (non-layouted) direction
305       min_size.y()  = std::max( min_size.y(),
306                                 (item.minimumSize().*_get_fixed_coord)() );
307       max_size.y()  = std::max( max_size.y(),
308                                 (item.maximumSize().*_get_fixed_coord)() );
309       size_hint.y() = std::max( size_hint.y(),
310                                 (item.sizeHint().*_get_fixed_coord)() );
311
312       _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
313     }
314
315     safeAdd(min_size.x(),  _layout_data.padding);
316     safeAdd(max_size.x(),  _layout_data.padding);
317     safeAdd(size_hint.x(), _layout_data.padding);
318
319     _layout_data.min_size = min_size.x();
320     _layout_data.max_size = max_size.x();
321     _layout_data.size_hint = size_hint.x();
322
323     _min_size.x()  = (min_size.*_get_layout_coord)();
324     _max_size.x()  = (max_size.*_get_layout_coord)();
325     _size_hint.x() = (size_hint.*_get_layout_coord)();
326
327     _min_size.y()  = (min_size.*_get_fixed_coord)();
328     _max_size.y()  = (max_size.*_get_fixed_coord)();
329     _size_hint.y() = (size_hint.*_get_fixed_coord)();
330
331     _flags &= ~SIZE_INFO_DIRTY;
332   }
333
334   //----------------------------------------------------------------------------
335   void BoxLayout::updateWFHCache(int w) const
336   {
337     if( w == _hfw_width )
338       return;
339
340     _hfw_height = 0;
341     _hfw_min_height = 0;
342
343     if( horiz() )
344     {
345       _layout_data.size = w;
346       const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
347
348       for(size_t i = 0; i < _layout_items.size(); ++i)
349       {
350         ItemData const& data = _layout_items[i];
351         _hfw_height = std::max(_hfw_height, data.hfw(data.size));
352         _hfw_min_height = std::max(_hfw_min_height, data.mhfw(data.size));
353       }
354     }
355     else
356     {
357       for(size_t i = 0; i < _layout_items.size(); ++i)
358       {
359         ItemData const& data = _layout_items[i];
360         _hfw_height += data.hfw(w) + data.padding_orig;
361         _hfw_min_height += data.mhfw(w) + data.padding_orig;
362       }
363     }
364
365     _hfw_width = w;
366   }
367
368   //----------------------------------------------------------------------------
369   SGVec2i BoxLayout::sizeHintImpl() const
370   {
371     updateSizeHints();
372     return _size_hint;
373   }
374
375   //----------------------------------------------------------------------------
376   SGVec2i BoxLayout::minimumSizeImpl() const
377   {
378     updateSizeHints();
379     return _min_size;
380   }
381
382   //----------------------------------------------------------------------------
383   SGVec2i BoxLayout::maximumSizeImpl() const
384   {
385     updateSizeHints();
386     return _max_size;
387   }
388
389   //----------------------------------------------------------------------------
390   void BoxLayout::doLayout(const SGRecti& geom)
391   {
392     if( _flags & SIZE_INFO_DIRTY )
393       updateSizeHints();
394
395     // Store current size hints because vertical layouts containing
396     // height-for-width items the size hints are update for the actual width of
397     // the layout
398     int min_size_save = _layout_data.min_size,
399         size_hint_save = _layout_data.size_hint;
400
401     _layout_data.size = (geom.size().*_get_layout_coord)();
402
403     // update width dependent data for layouting of vertical layouts
404     if( _layout_data.has_hfw && !horiz() )
405     {
406       for(size_t i = 0; i < _layout_items.size(); ++i)
407       {
408         ItemData& data = _layout_items[i];
409         if( data.has_hfw )
410         {
411           int w = SGMisc<int>::clip( geom.width(),
412                                      data.layout_item->minimumSize().x(),
413                                      data.layout_item->maximumSize().x() );
414
415           data.min_size = data.mhfw(w);
416           data.size_hint = data.hfw(w);
417
418           // Update size hints for layouting with difference to size hints
419           // calculated by using the size hints provided (without trading
420           // height for width)
421           _layout_data.min_size  += data.min_size
422                                   - data.layout_item->minimumSize().y();
423           _layout_data.size_hint += data.size_hint
424                                   - data.layout_item->sizeHint().y();
425         }
426       }
427     }
428
429     // now do the actual layouting
430     distribute(_layout_items, _layout_data);
431
432     // Restore size hints possibly changed by vertical layouting
433     _layout_data.min_size = min_size_save;
434     _layout_data.size_hint = size_hint_save;
435
436     // and finally set the layouted geometry for each item
437     int fixed_size = (geom.size().*_get_fixed_coord)();
438     SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
439                      (geom.pos().*_get_fixed_coord)() );
440
441     bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
442     if( reverse )
443       cur_pos.x() += (geom.size().*_get_layout_coord)();
444
445     for(size_t i = 0; i < _layout_items.size(); ++i)
446     {
447       ItemData const& data = _layout_items[i];
448       cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
449
450       SGVec2i size(
451         data.size,
452         std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(),
453                   fixed_size )
454       );
455
456       // Center in fixed direction (TODO allow specifying alignment)
457       int offset_fixed = (fixed_size - size.y()) / 2;
458       cur_pos.y() += offset_fixed;
459
460       data.layout_item->setGeometry(SGRecti(
461         (cur_pos.*_get_layout_coord)(),
462         (cur_pos.*_get_fixed_coord)(),
463         (size.*_get_layout_coord)(),
464         (size.*_get_fixed_coord)()
465       ));
466
467       if( !reverse )
468         cur_pos.x() += data.size;
469       cur_pos.y() -= offset_fixed;
470     }
471   }
472
473   //----------------------------------------------------------------------------
474   HBoxLayout::HBoxLayout():
475     BoxLayout(LeftToRight)
476   {
477
478   }
479
480   //----------------------------------------------------------------------------
481   VBoxLayout::VBoxLayout():
482     BoxLayout(TopToBottom)
483   {
484
485   }
486
487 } // namespace canvas
488 } // namespace simgear