]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/BoxLayout.cxx
canvas::BoxLayout: add custom additional whitespace (spacer/stretch)
[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   void BoxLayout::setStretch(size_t index, int stretch)
96   {
97     if( index >= _layout_items.size() )
98       return;
99
100     _layout_items.at(index).stretch = std::max(0, stretch);
101     invalidate();
102   }
103
104   //----------------------------------------------------------------------------
105   int BoxLayout::stretch(size_t index) const
106   {
107     if( index >= _layout_items.size() )
108       return 0;
109
110     return _layout_items.at(index).stretch;
111   }
112
113   //----------------------------------------------------------------------------
114   void BoxLayout::setSpacing(int spacing)
115   {
116     if( spacing == _padding )
117       return;
118
119     _padding = spacing;
120     invalidate();
121   }
122
123   //----------------------------------------------------------------------------
124   int BoxLayout::spacing() const
125   {
126     return _padding;
127   }
128
129   //----------------------------------------------------------------------------
130   void BoxLayout::setDirection(Direction dir)
131   {
132     _direction = dir;
133     _get_layout_coord = &SGVec2i::x;
134     _get_fixed_coord = &SGVec2i::y;
135
136     if( !horiz() )
137       std::swap(_get_layout_coord, _get_fixed_coord);
138
139     invalidate();
140   }
141
142   //----------------------------------------------------------------------------
143   BoxLayout::Direction BoxLayout::direction() const
144   {
145     return _direction;
146   }
147
148   //----------------------------------------------------------------------------
149   void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
150   {
151     _canvas = canvas;
152
153     for(size_t i = 0; i < _layout_items.size(); ++i)
154       _layout_items[i].layout_item->setCanvas(canvas);
155   }
156
157   //----------------------------------------------------------------------------
158   bool BoxLayout::horiz() const
159   {
160     return (_direction == LeftToRight) || (_direction == RightToLeft);
161   }
162
163   //----------------------------------------------------------------------------
164   void BoxLayout::updateSizeHints() const
165   {
166     SGVec2i min_size(0, 0),
167             max_size(0, 0),
168             size_hint(0, 0);
169
170     _layout_data.reset();
171     bool is_first = true;
172
173     for(size_t i = 0; i < _layout_items.size(); ++i)
174     {
175       // TODO check visible
176
177       ItemData& item_data = _layout_items[i];
178       LayoutItem const& item = *item_data.layout_item;
179
180       item_data.min_size  = (item.minimumSize().*_get_layout_coord)();
181       item_data.max_size  = (item.maximumSize().*_get_layout_coord)();
182       item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
183
184       if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
185       {
186         if( is_first )
187         {
188           item_data.padding_orig = 0;
189           is_first = false;
190         }
191         else
192         {
193           item_data.padding_orig = _padding;
194           _layout_data.padding += item_data.padding_orig;
195         }
196       }
197
198       // Add sizes of all children in layout direction
199       safeAdd(min_size.x(),  item_data.min_size);
200       safeAdd(max_size.x(),  item_data.max_size);
201       safeAdd(size_hint.x(), item_data.size_hint);
202
203       // Take maximum in fixed (non-layouted) direction
204       min_size.y()  = std::max( min_size.y(),
205                                 (item.minimumSize().*_get_fixed_coord)() );
206       max_size.y()  = std::max( max_size.y(),
207                                 (item.maximumSize().*_get_fixed_coord)() );
208       size_hint.y() = std::max( size_hint.y(),
209                                 (item.sizeHint().*_get_fixed_coord)() );
210     }
211
212     safeAdd(min_size.x(),  _layout_data.padding);
213     safeAdd(max_size.x(),  _layout_data.padding);
214     safeAdd(size_hint.x(), _layout_data.padding);
215
216     _layout_data.min_size = min_size.x();
217     _layout_data.max_size = max_size.x();
218     _layout_data.size_hint = size_hint.x();
219
220     _min_size.x()  = (min_size.*_get_layout_coord)();
221     _max_size.x()  = (max_size.*_get_layout_coord)();
222     _size_hint.x() = (size_hint.*_get_layout_coord)();
223
224     _min_size.y()  = (min_size.*_get_fixed_coord)();
225     _max_size.y()  = (max_size.*_get_fixed_coord)();
226     _size_hint.y() = (size_hint.*_get_fixed_coord)();
227
228     _flags &= ~SIZE_INFO_DIRTY;
229   }
230
231   //----------------------------------------------------------------------------
232   SGVec2i BoxLayout::sizeHintImpl() const
233   {
234     updateSizeHints();
235     return _size_hint;
236   }
237
238   //----------------------------------------------------------------------------
239   SGVec2i BoxLayout::minimumSizeImpl() const
240   {
241     updateSizeHints();
242     return _min_size;
243   }
244
245   //----------------------------------------------------------------------------
246   SGVec2i BoxLayout::maximumSizeImpl() const
247   {
248     updateSizeHints();
249     return _max_size;
250   }
251
252   //----------------------------------------------------------------------------
253   void BoxLayout::doLayout(const SGRecti& geom)
254   {
255     if( _flags & SIZE_INFO_DIRTY )
256       updateSizeHints();
257
258     _layout_data.size = (geom.size().*_get_layout_coord)();
259     distribute(_layout_items, _layout_data);
260
261     int fixed_size = (geom.size().*_get_fixed_coord)();
262     SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
263                      (geom.pos().*_get_fixed_coord)() );
264
265     bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
266     if( reverse )
267       cur_pos.x() += (geom.size().*_get_layout_coord)();
268
269     // TODO handle reverse layouting (rtl/btt)
270     for(size_t i = 0; i < _layout_items.size(); ++i)
271     {
272       ItemData const& data = _layout_items[i];
273       cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
274
275       SGVec2i size(
276         data.size,
277         std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(),
278                   fixed_size )
279       );
280
281       // Center in fixed direction (TODO allow specifying alignment)
282       int offset_fixed = (fixed_size - size.y()) / 2;
283       cur_pos.y() += offset_fixed;
284
285       data.layout_item->setGeometry(SGRecti(
286         (cur_pos.*_get_layout_coord)(),
287         (cur_pos.*_get_fixed_coord)(),
288         (size.*_get_layout_coord)(),
289         (size.*_get_fixed_coord)()
290       ));
291
292       if( !reverse )
293         cur_pos.x() += data.size;
294       cur_pos.y() -= offset_fixed;
295     }
296   }
297
298   //----------------------------------------------------------------------------
299   HBoxLayout::HBoxLayout():
300     BoxLayout(LeftToRight)
301   {
302
303   }
304
305   //----------------------------------------------------------------------------
306   VBoxLayout::VBoxLayout():
307     BoxLayout(TopToBottom)
308   {
309
310   }
311
312 } // namespace canvas
313 } // namespace simgear