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 BoxLayout::~BoxLayout()
38 _parent.reset(); // No need to invalidate parent again...
42 //----------------------------------------------------------------------------
43 void BoxLayout::addItem(const LayoutItemRef& item)
45 return addItem(item, 0);
48 //----------------------------------------------------------------------------
49 void BoxLayout::addItem(const LayoutItemRef& item, int stretch)
51 insertItem(-1, item, stretch);
54 //----------------------------------------------------------------------------
55 void BoxLayout::addStretch(int stretch)
57 insertStretch(-1, stretch);
60 //----------------------------------------------------------------------------
61 void BoxLayout::addSpacing(int size)
63 insertSpacing(-1, size);
66 //----------------------------------------------------------------------------
67 void BoxLayout::insertItem(int index, const LayoutItemRef& item, int stretch)
69 ItemData item_data = {0};
70 item_data.layout_item = item;
71 item_data.stretch = std::max(0, stretch);
73 item->setCanvas(_canvas);
74 item->setParent(this);
77 _layout_items.push_back(item_data);
79 _layout_items.insert(_layout_items.begin() + index, item_data);
84 //----------------------------------------------------------------------------
85 void BoxLayout::insertStretch(int index, int stretch)
87 insertItem(index, LayoutItemRef(new SpacerItem()), stretch);
90 //----------------------------------------------------------------------------
91 void BoxLayout::insertSpacing(int index, int size)
93 SGVec2i size_hint = horiz()
98 insertItem(index, LayoutItemRef(new SpacerItem(size_hint, max_size)));
101 //----------------------------------------------------------------------------
102 size_t BoxLayout::count() const
104 return _layout_items.size();
107 //----------------------------------------------------------------------------
108 LayoutItemRef BoxLayout::itemAt(size_t index)
110 if( index >= _layout_items.size() )
111 return LayoutItemRef();
113 return _layout_items[index].layout_item;
116 //----------------------------------------------------------------------------
117 LayoutItemRef BoxLayout::takeAt(size_t index)
119 if( index >= _layout_items.size() )
120 return LayoutItemRef();
122 LayoutItems::iterator it = _layout_items.begin() + index;
123 LayoutItemRef item = it->layout_item;
125 _layout_items.erase(it);
132 //----------------------------------------------------------------------------
133 void BoxLayout::clear()
135 for( LayoutItems::iterator it = _layout_items.begin();
136 it != _layout_items.end();
139 it->layout_item->onRemove();
141 _layout_items.clear();
145 //----------------------------------------------------------------------------
146 void BoxLayout::setStretch(size_t index, int stretch)
148 if( index >= _layout_items.size() )
151 _layout_items.at(index).stretch = std::max(0, stretch);
155 //----------------------------------------------------------------------------
156 bool BoxLayout::setStretchFactor(const LayoutItemRef& item, int stretch)
158 for( LayoutItems::iterator it = _layout_items.begin();
159 it != _layout_items.end();
162 if( item == it->layout_item )
164 it->stretch = std::max(0, stretch);
173 //----------------------------------------------------------------------------
174 int BoxLayout::stretch(size_t index) const
176 if( index >= _layout_items.size() )
179 return _layout_items.at(index).stretch;
182 //----------------------------------------------------------------------------
183 void BoxLayout::setSpacing(int spacing)
185 if( spacing == _padding )
192 //----------------------------------------------------------------------------
193 int BoxLayout::spacing() const
198 //----------------------------------------------------------------------------
199 void BoxLayout::setDirection(Direction dir)
202 _get_layout_coord = &SGVec2i::x;
203 _get_fixed_coord = &SGVec2i::y;
206 std::swap(_get_layout_coord, _get_fixed_coord);
211 //----------------------------------------------------------------------------
212 BoxLayout::Direction BoxLayout::direction() const
217 //----------------------------------------------------------------------------
218 bool BoxLayout::hasHeightForWidth() const
220 if( _flags & SIZE_INFO_DIRTY )
223 return _layout_data.has_hfw;
226 //----------------------------------------------------------------------------
227 int BoxLayout::heightForWidth(int w) const
229 if( !hasHeightForWidth() )
236 //----------------------------------------------------------------------------
237 int BoxLayout::minimumHeightForWidth(int w) const
239 if( !hasHeightForWidth() )
243 return _hfw_min_height;
246 //----------------------------------------------------------------------------
247 void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
251 for(size_t i = 0; i < _layout_items.size(); ++i)
252 _layout_items[i].layout_item->setCanvas(canvas);
255 //----------------------------------------------------------------------------
256 bool BoxLayout::horiz() const
258 return (_direction == LeftToRight) || (_direction == RightToLeft);
261 //----------------------------------------------------------------------------
262 void BoxLayout::updateSizeHints() const
264 SGVec2i min_size(0, 0),
268 _layout_data.reset();
269 _hfw_width = _hfw_height = _hfw_min_height = -1;
271 bool is_first = true;
273 for(size_t i = 0; i < _layout_items.size(); ++i)
275 // TODO check visible
277 ItemData& item_data = _layout_items[i];
278 LayoutItem const& item = *item_data.layout_item;
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();
285 if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
289 item_data.padding_orig = 0;
294 item_data.padding_orig = _padding;
295 _layout_data.padding += item_data.padding_orig;
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);
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)() );
312 _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
315 safeAdd(min_size.x(), _layout_data.padding);
316 safeAdd(max_size.x(), _layout_data.padding);
317 safeAdd(size_hint.x(), _layout_data.padding);
319 _layout_data.min_size = min_size.x();
320 _layout_data.max_size = max_size.x();
321 _layout_data.size_hint = size_hint.x();
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)();
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)();
331 _flags &= ~SIZE_INFO_DIRTY;
334 //----------------------------------------------------------------------------
335 void BoxLayout::updateWFHCache(int w) const
337 if( w == _hfw_width )
345 _layout_data.size = w;
346 const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
348 for(size_t i = 0; i < _layout_items.size(); ++i)
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));
357 for(size_t i = 0; i < _layout_items.size(); ++i)
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;
368 //----------------------------------------------------------------------------
369 SGVec2i BoxLayout::sizeHintImpl() const
375 //----------------------------------------------------------------------------
376 SGVec2i BoxLayout::minimumSizeImpl() const
382 //----------------------------------------------------------------------------
383 SGVec2i BoxLayout::maximumSizeImpl() const
389 //----------------------------------------------------------------------------
390 void BoxLayout::doLayout(const SGRecti& geom)
392 if( _flags & SIZE_INFO_DIRTY )
395 // Store current size hints because vertical layouts containing
396 // height-for-width items the size hints are update for the actual width of
398 int min_size_save = _layout_data.min_size,
399 size_hint_save = _layout_data.size_hint;
401 _layout_data.size = (geom.size().*_get_layout_coord)();
403 // update width dependent data for layouting of vertical layouts
404 if( _layout_data.has_hfw && !horiz() )
406 for(size_t i = 0; i < _layout_items.size(); ++i)
408 ItemData& data = _layout_items[i];
411 int w = SGMisc<int>::clip( geom.width(),
412 data.layout_item->minimumSize().x(),
413 data.layout_item->maximumSize().x() );
415 data.min_size = data.mhfw(w);
416 data.size_hint = data.hfw(w);
418 // Update size hints for layouting with difference to size hints
419 // calculated by using the size hints provided (without trading
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();
429 // now do the actual layouting
430 distribute(_layout_items, _layout_data);
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;
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)() );
441 bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
443 cur_pos.x() += (geom.size().*_get_layout_coord)();
445 for(size_t i = 0; i < _layout_items.size(); ++i)
447 ItemData const& data = _layout_items[i];
448 cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
452 std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(),
456 // Center in fixed direction (TODO allow specifying alignment)
457 int offset_fixed = (fixed_size - size.y()) / 2;
458 cur_pos.y() += offset_fixed;
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)()
468 cur_pos.x() += data.size;
469 cur_pos.y() -= offset_fixed;
473 //----------------------------------------------------------------------------
474 HBoxLayout::HBoxLayout():
475 BoxLayout(LeftToRight)
480 //----------------------------------------------------------------------------
481 VBoxLayout::VBoxLayout():
482 BoxLayout(TopToBottom)
487 } // namespace canvas
488 } // namespace simgear