1 // Align items horizontally or vertically in a box
3 // Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
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.
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.
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
19 #include "BoxLayout.hxx"
20 #include "SpacerItem.hxx"
21 #include <simgear/canvas/Canvas.hxx>
28 //----------------------------------------------------------------------------
29 BoxLayout::BoxLayout(Direction dir):
35 //----------------------------------------------------------------------------
36 void BoxLayout::addItem(const LayoutItemRef& item)
38 return addItem(item, 0);
41 //----------------------------------------------------------------------------
42 void BoxLayout::addItem(const LayoutItemRef& item, int stretch)
44 insertItem(-1, item, stretch);
47 //----------------------------------------------------------------------------
48 void BoxLayout::addStretch(int stretch)
50 insertStretch(-1, stretch);
53 //----------------------------------------------------------------------------
54 void BoxLayout::addSpacing(int size)
56 insertSpacing(-1, size);
59 //----------------------------------------------------------------------------
60 void BoxLayout::insertItem(int index, const LayoutItemRef& item, int stretch)
62 ItemData item_data = {0};
63 item_data.layout_item = item;
64 item_data.stretch = std::max(0, stretch);
66 item->setCanvas(_canvas);
67 item->setParent(this);
70 _layout_items.push_back(item_data);
72 _layout_items.insert(_layout_items.begin() + index, item_data);
77 //----------------------------------------------------------------------------
78 void BoxLayout::insertStretch(int index, int stretch)
80 insertItem(index, LayoutItemRef(new SpacerItem()), stretch);
83 //----------------------------------------------------------------------------
84 void BoxLayout::insertSpacing(int index, int size)
86 SGVec2i size_hint = horiz()
91 insertItem(index, LayoutItemRef(new SpacerItem(size_hint, max_size)));
94 //----------------------------------------------------------------------------
95 size_t BoxLayout::count() const
97 return _layout_items.size();
100 //----------------------------------------------------------------------------
101 LayoutItemRef BoxLayout::itemAt(size_t index)
103 if( index >= _layout_items.size() )
104 return LayoutItemRef();
106 return _layout_items[index].layout_item;
109 //----------------------------------------------------------------------------
110 LayoutItemRef BoxLayout::takeAt(size_t index)
112 if( index >= _layout_items.size() )
113 return LayoutItemRef();
115 LayoutItems::iterator it = _layout_items.begin() + index;
116 LayoutItemRef item = it->layout_item;
117 _layout_items.erase(it);
124 //----------------------------------------------------------------------------
125 void BoxLayout::clear()
127 _layout_items.clear();
131 //----------------------------------------------------------------------------
132 void BoxLayout::setStretch(size_t index, int stretch)
134 if( index >= _layout_items.size() )
137 _layout_items.at(index).stretch = std::max(0, stretch);
141 //----------------------------------------------------------------------------
142 int BoxLayout::stretch(size_t index) const
144 if( index >= _layout_items.size() )
147 return _layout_items.at(index).stretch;
150 //----------------------------------------------------------------------------
151 void BoxLayout::setSpacing(int spacing)
153 if( spacing == _padding )
160 //----------------------------------------------------------------------------
161 int BoxLayout::spacing() const
166 //----------------------------------------------------------------------------
167 void BoxLayout::setDirection(Direction dir)
170 _get_layout_coord = &SGVec2i::x;
171 _get_fixed_coord = &SGVec2i::y;
174 std::swap(_get_layout_coord, _get_fixed_coord);
179 //----------------------------------------------------------------------------
180 BoxLayout::Direction BoxLayout::direction() const
185 //----------------------------------------------------------------------------
186 bool BoxLayout::hasHeightForWidth() const
188 if( _flags & SIZE_INFO_DIRTY )
191 return _layout_data.has_hfw;
194 //----------------------------------------------------------------------------
195 int BoxLayout::heightForWidth(int w) const
197 if( !hasHeightForWidth() )
204 //----------------------------------------------------------------------------
205 int BoxLayout::minimumHeightForWidth(int w) const
207 if( !hasHeightForWidth() )
211 return _hfw_min_height;
214 //----------------------------------------------------------------------------
215 void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
219 for(size_t i = 0; i < _layout_items.size(); ++i)
220 _layout_items[i].layout_item->setCanvas(canvas);
223 //----------------------------------------------------------------------------
224 bool BoxLayout::horiz() const
226 return (_direction == LeftToRight) || (_direction == RightToLeft);
229 //----------------------------------------------------------------------------
230 void BoxLayout::updateSizeHints() const
232 SGVec2i min_size(0, 0),
236 _layout_data.reset();
237 _hfw_width = _hfw_height = _hfw_min_height = -1;
239 bool is_first = true;
241 for(size_t i = 0; i < _layout_items.size(); ++i)
243 // TODO check visible
245 ItemData& item_data = _layout_items[i];
246 LayoutItem const& item = *item_data.layout_item;
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();
253 if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
257 item_data.padding_orig = 0;
262 item_data.padding_orig = _padding;
263 _layout_data.padding += item_data.padding_orig;
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);
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)() );
280 _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
283 safeAdd(min_size.x(), _layout_data.padding);
284 safeAdd(max_size.x(), _layout_data.padding);
285 safeAdd(size_hint.x(), _layout_data.padding);
287 _layout_data.min_size = min_size.x();
288 _layout_data.max_size = max_size.x();
289 _layout_data.size_hint = size_hint.x();
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)();
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)();
299 _flags &= ~SIZE_INFO_DIRTY;
302 //----------------------------------------------------------------------------
303 void BoxLayout::updateWFHCache(int w) const
305 if( w == _hfw_width )
313 _layout_data.size = w;
314 const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
316 for(size_t i = 0; i < _layout_items.size(); ++i)
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));
325 for(size_t i = 0; i < _layout_items.size(); ++i)
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;
336 //----------------------------------------------------------------------------
337 SGVec2i BoxLayout::sizeHintImpl() const
343 //----------------------------------------------------------------------------
344 SGVec2i BoxLayout::minimumSizeImpl() const
350 //----------------------------------------------------------------------------
351 SGVec2i BoxLayout::maximumSizeImpl() const
357 //----------------------------------------------------------------------------
358 void BoxLayout::doLayout(const SGRecti& geom)
360 if( _flags & SIZE_INFO_DIRTY )
363 // Store current size hints because vertical layouts containing
364 // height-for-width items the size hints are update for the actual width of
366 int min_size_save = _layout_data.min_size,
367 size_hint_save = _layout_data.size_hint;
369 _layout_data.size = (geom.size().*_get_layout_coord)();
371 // update width dependent data for layouting of vertical layouts
372 if( _layout_data.has_hfw && !horiz() )
374 for(size_t i = 0; i < _layout_items.size(); ++i)
376 ItemData& data = _layout_items[i];
379 int w = SGMisc<int>::clip( geom.width(),
380 data.layout_item->minimumSize().x(),
381 data.layout_item->maximumSize().x() );
383 int d = data.layout_item->minimumHeightForWidth(w) - data.min_size;
385 _layout_data.min_size += d;
387 d = data.layout_item->heightForWidth(w) - data.size_hint;
389 _layout_data.size_hint += d;
394 // now do the actual layouting
395 distribute(_layout_items, _layout_data);
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;
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)() );
406 bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
408 cur_pos.x() += (geom.size().*_get_layout_coord)();
410 for(size_t i = 0; i < _layout_items.size(); ++i)
412 ItemData const& data = _layout_items[i];
413 cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
417 std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(),
421 // Center in fixed direction (TODO allow specifying alignment)
422 int offset_fixed = (fixed_size - size.y()) / 2;
423 cur_pos.y() += offset_fixed;
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)()
433 cur_pos.x() += data.size;
434 cur_pos.y() -= offset_fixed;
438 //----------------------------------------------------------------------------
439 HBoxLayout::HBoxLayout():
440 BoxLayout(LeftToRight)
445 //----------------------------------------------------------------------------
446 VBoxLayout::VBoxLayout():
447 BoxLayout(TopToBottom)
452 } // namespace canvas
453 } // namespace simgear