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