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,
53 insertItem(-1, item, stretch, alignment);
56 //----------------------------------------------------------------------------
57 void BoxLayout::addStretch(int stretch)
59 insertStretch(-1, stretch);
62 //----------------------------------------------------------------------------
63 void BoxLayout::addSpacing(int size)
65 insertSpacing(-1, size);
68 //----------------------------------------------------------------------------
69 void BoxLayout::insertItem( int index,
70 const LayoutItemRef& item,
74 ItemData item_data = {0};
75 item_data.layout_item = item;
76 item_data.stretch = std::max(0, stretch);
78 if( alignment != AlignFill )
79 item->setAlignment(alignment);
81 if( SGWeakReferenced::count(this) )
82 item->setParent(this);
86 "Adding item to expired or non-refcounted layout" );
89 _layout_items.push_back(item_data);
91 _layout_items.insert(_layout_items.begin() + index, item_data);
96 //----------------------------------------------------------------------------
97 void BoxLayout::insertStretch(int index, int stretch)
99 insertItem(index, LayoutItemRef(new SpacerItem()), stretch);
102 //----------------------------------------------------------------------------
103 void BoxLayout::insertSpacing(int index, int size)
105 SGVec2i size_hint = horiz()
108 max_size = size_hint;
110 insertItem(index, LayoutItemRef(new SpacerItem(size_hint, max_size)));
113 //----------------------------------------------------------------------------
114 size_t BoxLayout::count() const
116 return _layout_items.size();
119 //----------------------------------------------------------------------------
120 LayoutItemRef BoxLayout::itemAt(size_t index)
122 if( index >= _layout_items.size() )
123 return LayoutItemRef();
125 return _layout_items[index].layout_item;
128 //----------------------------------------------------------------------------
129 LayoutItemRef BoxLayout::takeAt(size_t index)
131 if( index >= _layout_items.size() )
132 return LayoutItemRef();
134 LayoutItems::iterator it = _layout_items.begin() + index;
135 LayoutItemRef item = it->layout_item;
137 item->setParent(LayoutItemWeakRef());
138 _layout_items.erase(it);
145 //----------------------------------------------------------------------------
146 void BoxLayout::clear()
148 for( LayoutItems::iterator it = _layout_items.begin();
149 it != _layout_items.end();
152 it->layout_item->onRemove();
153 it->layout_item->setParent(LayoutItemWeakRef());
155 _layout_items.clear();
159 //----------------------------------------------------------------------------
160 void BoxLayout::setStretch(size_t index, int stretch)
162 if( index >= _layout_items.size() )
165 _layout_items.at(index).stretch = std::max(0, stretch);
169 //----------------------------------------------------------------------------
170 bool BoxLayout::setStretchFactor(const LayoutItemRef& item, int stretch)
172 for( LayoutItems::iterator it = _layout_items.begin();
173 it != _layout_items.end();
176 if( item == it->layout_item )
178 it->stretch = std::max(0, stretch);
187 //----------------------------------------------------------------------------
188 int BoxLayout::stretch(size_t index) const
190 if( index >= _layout_items.size() )
193 return _layout_items.at(index).stretch;
196 //----------------------------------------------------------------------------
197 void BoxLayout::setSpacing(int spacing)
199 if( spacing == _padding )
206 //----------------------------------------------------------------------------
207 int BoxLayout::spacing() const
212 //----------------------------------------------------------------------------
213 void BoxLayout::setDirection(Direction dir)
216 _get_layout_coord = &SGVec2i::x;
217 _get_fixed_coord = &SGVec2i::y;
220 std::swap(_get_layout_coord, _get_fixed_coord);
225 //----------------------------------------------------------------------------
226 BoxLayout::Direction BoxLayout::direction() const
231 //----------------------------------------------------------------------------
232 bool BoxLayout::hasHeightForWidth() const
234 if( _flags & SIZE_INFO_DIRTY )
237 return _layout_data.has_hfw;
240 //----------------------------------------------------------------------------
241 void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
245 for(size_t i = 0; i < _layout_items.size(); ++i)
246 _layout_items[i].layout_item->setCanvas(canvas);
249 //----------------------------------------------------------------------------
250 bool BoxLayout::horiz() const
252 return (_direction == LeftToRight) || (_direction == RightToLeft);
255 //----------------------------------------------------------------------------
256 void BoxLayout::updateSizeHints() const
258 SGVec2i min_size(0, 0),
262 _layout_data.reset();
263 _hfw_width = _hfw_height = _hfw_min_height = -1;
265 bool is_first = true;
267 for(size_t i = 0; i < _layout_items.size(); ++i)
269 ItemData& item_data = _layout_items[i];
270 LayoutItem const& item = *item_data.layout_item;
272 item_data.visible = item.isVisible();
273 if( !item_data.visible )
276 item_data.min_size = (item.minimumSize().*_get_layout_coord)();
277 item_data.max_size = (item.maximumSize().*_get_layout_coord)();
278 item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
279 item_data.has_hfw = item.hasHeightForWidth();
281 uint8_t alignment_mask = horiz()
282 ? AlignHorizontal_Mask
283 : AlignVertical_Mask;
284 item_data.has_align = (item.alignment() & alignment_mask) != 0;
286 if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
290 item_data.padding_orig = 0;
295 item_data.padding_orig = _padding;
296 _layout_data.padding += item_data.padding_orig;
300 // Add sizes of all children in layout direction
301 SGMisc<int>::addClipOverflowInplace(min_size.x(), item_data.min_size);
302 SGMisc<int>::addClipOverflowInplace(max_size.x(), item_data.max_size);
303 SGMisc<int>::addClipOverflowInplace(size_hint.x(), item_data.size_hint);
305 // Take maximum in fixed (non-layouted) direction
306 min_size.y() = std::max( min_size.y(),
307 (item.minimumSize().*_get_fixed_coord)() );
308 max_size.y() = std::max( max_size.y(),
309 (item.maximumSize().*_get_fixed_coord)() );
310 size_hint.y() = std::max( size_hint.y(),
311 (item.sizeHint().*_get_fixed_coord)() );
313 _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
316 SGMisc<int>::addClipOverflowInplace(min_size.x(), _layout_data.padding);
317 SGMisc<int>::addClipOverflowInplace(max_size.x(), _layout_data.padding);
318 SGMisc<int>::addClipOverflowInplace(size_hint.x(), _layout_data.padding);
320 _layout_data.min_size = min_size.x();
321 _layout_data.max_size = max_size.x();
322 _layout_data.size_hint = size_hint.x();
324 _min_size.x() = (min_size.*_get_layout_coord)();
325 _max_size.x() = (max_size.*_get_layout_coord)();
326 _size_hint.x() = (size_hint.*_get_layout_coord)();
328 _min_size.y() = (min_size.*_get_fixed_coord)();
329 _max_size.y() = (max_size.*_get_fixed_coord)();
330 _size_hint.y() = (size_hint.*_get_fixed_coord)();
332 _flags &= ~SIZE_INFO_DIRTY;
335 //----------------------------------------------------------------------------
336 void BoxLayout::updateWFHCache(int w) const
338 if( w == _hfw_width )
346 _layout_data.size = w;
347 const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
349 for(size_t i = 0; i < _layout_items.size(); ++i)
351 ItemData const& data = _layout_items[i];
355 _hfw_height = std::max(_hfw_height, data.hfw(data.size));
356 _hfw_min_height = std::max(_hfw_min_height, data.mhfw(data.size));
361 for(size_t i = 0; i < _layout_items.size(); ++i)
363 ItemData const& data = _layout_items[i];
367 _hfw_height += data.hfw(w) + data.padding_orig;
368 _hfw_min_height += data.mhfw(w) + data.padding_orig;
375 //----------------------------------------------------------------------------
376 SGVec2i BoxLayout::sizeHintImpl() const
382 //----------------------------------------------------------------------------
383 SGVec2i BoxLayout::minimumSizeImpl() const
389 //----------------------------------------------------------------------------
390 SGVec2i BoxLayout::maximumSizeImpl() const
397 //----------------------------------------------------------------------------
398 int BoxLayout::heightForWidthImpl(int w) const
400 if( !hasHeightForWidth() )
407 //----------------------------------------------------------------------------
408 int BoxLayout::minimumHeightForWidthImpl(int w) const
410 if( !hasHeightForWidth() )
414 return _hfw_min_height;
417 //----------------------------------------------------------------------------
418 void BoxLayout::doLayout(const SGRecti& geom)
420 if( _flags & SIZE_INFO_DIRTY )
423 // Store current size hints because vertical layouts containing
424 // height-for-width items the size hints are update for the actual width of
426 int min_size_save = _layout_data.min_size,
427 size_hint_save = _layout_data.size_hint;
429 _layout_data.size = (geom.size().*_get_layout_coord)();
431 // update width dependent data for layouting of vertical layouts
432 if( _layout_data.has_hfw && !horiz() )
434 for(size_t i = 0; i < _layout_items.size(); ++i)
436 ItemData& data = _layout_items[i];
442 int w = SGMisc<int>::clip( geom.width(),
443 data.layout_item->minimumSize().x(),
444 data.layout_item->maximumSize().x() );
446 data.min_size = data.mhfw(w);
447 data.size_hint = data.hfw(w);
449 // Update size hints for layouting with difference to size hints
450 // calculated by using the size hints provided (without trading
452 _layout_data.min_size += data.min_size
453 - data.layout_item->minimumSize().y();
454 _layout_data.size_hint += data.size_hint
455 - data.layout_item->sizeHint().y();
460 // now do the actual layouting
461 distribute(_layout_items, _layout_data);
463 // Restore size hints possibly changed by vertical layouting
464 _layout_data.min_size = min_size_save;
465 _layout_data.size_hint = size_hint_save;
467 // and finally set the layouted geometry for each item
469 // Always assign all available space. Alignment handles final
471 (geom.size().*_get_fixed_coord)() );
472 SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
473 (geom.pos().*_get_fixed_coord)() );
475 bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
477 cur_pos.x() += (geom.size().*_get_layout_coord)();
479 for(size_t i = 0; i < _layout_items.size(); ++i)
481 ItemData const& data = _layout_items[i];
485 cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
486 size.x() = data.size;
488 data.layout_item->setGeometry(SGRecti(
489 (cur_pos.*_get_layout_coord)(),
490 (cur_pos.*_get_fixed_coord)(),
491 (size.*_get_layout_coord)(),
492 (size.*_get_fixed_coord)()
496 cur_pos.x() += data.size;
500 //----------------------------------------------------------------------------
501 void BoxLayout::visibilityChanged(bool visible)
503 for(size_t i = 0; i < _layout_items.size(); ++i)
504 callSetVisibleInternal(_layout_items[i].layout_item.get(), visible);
507 //----------------------------------------------------------------------------
508 HBoxLayout::HBoxLayout():
509 BoxLayout(LeftToRight)
514 //----------------------------------------------------------------------------
515 VBoxLayout::VBoxLayout():
516 BoxLayout(TopToBottom)
521 } // namespace canvas
522 } // namespace simgear