From b36cdebe8b2da3fd008bf6ab4fb4212626a87a79 Mon Sep 17 00:00:00 2001 From: Thomas Geymayer Date: Sat, 31 May 2014 02:40:59 +0200 Subject: [PATCH] Canvas: basic layouting system. Support for vertical and horizontal layouts. More layouts and widgets can be created using Nasal. --- simgear/canvas/CMakeLists.txt | 1 + simgear/canvas/Canvas.cxx | 28 +- simgear/canvas/Canvas.hxx | 17 +- simgear/canvas/CanvasWindow.cxx | 15 ++ simgear/canvas/CanvasWindow.hxx | 7 +- simgear/canvas/elements/CanvasElement.hxx | 2 +- simgear/canvas/layout/BoxLayout.cxx | 266 +++++++++++++++++++ simgear/canvas/layout/BoxLayout.hxx | 114 ++++++++ simgear/canvas/layout/CMakeLists.txt | 22 ++ simgear/canvas/layout/Layout.cxx | 243 +++++++++++++++++ simgear/canvas/layout/Layout.hxx | 98 +++++++ simgear/canvas/layout/LayoutItem.cxx | 151 +++++++++++ simgear/canvas/layout/LayoutItem.hxx | 139 ++++++++++ simgear/canvas/layout/NasalWidget.cxx | 158 +++++++++++ simgear/canvas/layout/NasalWidget.hxx | 85 ++++++ simgear/canvas/layout/canvas_layout_test.cxx | 231 ++++++++++++++++ simgear/debug/debug_types.h | 3 +- simgear/debug/logstream.cxx | 3 +- simgear/props/PropertyBasedElement.hxx | 2 +- 19 files changed, 1573 insertions(+), 12 deletions(-) create mode 100644 simgear/canvas/layout/BoxLayout.cxx create mode 100644 simgear/canvas/layout/BoxLayout.hxx create mode 100644 simgear/canvas/layout/CMakeLists.txt create mode 100644 simgear/canvas/layout/Layout.cxx create mode 100644 simgear/canvas/layout/Layout.hxx create mode 100644 simgear/canvas/layout/LayoutItem.cxx create mode 100644 simgear/canvas/layout/LayoutItem.hxx create mode 100644 simgear/canvas/layout/NasalWidget.cxx create mode 100644 simgear/canvas/layout/NasalWidget.hxx create mode 100644 simgear/canvas/layout/canvas_layout_test.cxx diff --git a/simgear/canvas/CMakeLists.txt b/simgear/canvas/CMakeLists.txt index fea5a22d..e2a9652b 100644 --- a/simgear/canvas/CMakeLists.txt +++ b/simgear/canvas/CMakeLists.txt @@ -32,5 +32,6 @@ set(SOURCES add_subdirectory(ShivaVG/src) add_subdirectory(elements) add_subdirectory(events) +add_subdirectory(layout) simgear_scene_component(canvas canvas "${SOURCES}" "${HEADERS}") diff --git a/simgear/canvas/Canvas.cxx b/simgear/canvas/Canvas.cxx index 46f2e27f..e01eb8a2 100644 --- a/simgear/canvas/Canvas.cxx +++ b/simgear/canvas/Canvas.cxx @@ -191,6 +191,14 @@ namespace canvas return _root_group; } + //---------------------------------------------------------------------------- + void Canvas::setLayout(const LayoutRef& layout) + { + _layout = layout; + _layout->setCanvas(this); + _status |= LAYOUT_DIRTY; + } + //---------------------------------------------------------------------------- void Canvas::enableRendering(bool force) { @@ -202,11 +210,10 @@ namespace canvas //---------------------------------------------------------------------------- void Canvas::update(double delta_time_sec) { - if( (!_texture.serviceable() && _status != STATUS_DIRTY) - || (_status & CREATE_FAILED) ) + if( _status & (CREATE_FAILED | MISSING_SIZE) ) return; - if( _status == STATUS_DIRTY ) + if( _status & STATUS_DIRTY ) { _texture.setSize(_size_x, _size_y); @@ -248,6 +255,17 @@ namespace canvas } } + if( _layout ) + { + if( (_status & LAYOUT_DIRTY) ) + { + _layout->setGeometry(SGRecti(0, 0, _view_width, _view_height)); + _status &= ~LAYOUT_DIRTY; + } + else + _layout->update(); + } + if( _visible || _render_always ) { BOOST_FOREACH(CanvasWeakPtr canvas_weak, _child_canvases) @@ -392,6 +410,7 @@ namespace canvas if( _view_width == w ) return; _view_width = w; + _status |= LAYOUT_DIRTY; _texture.setViewSize(_view_width, _view_height); } @@ -402,6 +421,7 @@ namespace canvas if( _view_height == h ) return; _view_height = h; + _status |= LAYOUT_DIRTY; _texture.setViewSize(_view_width, _view_height); } @@ -657,7 +677,7 @@ namespace canvas _status_msg = "Missing size-y"; else if( _status & CREATE_FAILED ) _status_msg = "Creating render target failed"; - else if( _status == STATUS_DIRTY ) + else if( _status & STATUS_DIRTY ) _status_msg = "Creation pending..."; else _status_msg = "Ok"; diff --git a/simgear/canvas/Canvas.hxx b/simgear/canvas/Canvas.hxx index 10872f22..c2de8d48 100644 --- a/simgear/canvas/Canvas.hxx +++ b/simgear/canvas/Canvas.hxx @@ -23,6 +23,7 @@ #include "ODGauge.hxx" #include +#include #include #include #include @@ -48,9 +49,11 @@ namespace canvas enum StatusFlags { STATUS_OK, - STATUS_DIRTY = 1, - MISSING_SIZE_X = STATUS_DIRTY << 1, + STATUS_DIRTY = 1, + LAYOUT_DIRTY = STATUS_DIRTY << 1, + MISSING_SIZE_X = LAYOUT_DIRTY << 1, MISSING_SIZE_Y = MISSING_SIZE_X << 1, + MISSING_SIZE = MISSING_SIZE_X | MISSING_SIZE_Y, CREATE_FAILED = MISSING_SIZE_Y << 1 }; @@ -121,6 +124,12 @@ namespace canvas */ GroupPtr getRootGroup(); + /** + * Set the layout of the canvas (the layout will automatically update with + * the viewport size of the canvas) + */ + void setLayout(const LayoutRef& layout); + /** * Enable rendering for the next frame * @@ -195,7 +204,9 @@ namespace canvas _visible; ODGauge _texture; - GroupPtr _root_group; + + GroupPtr _root_group; + LayoutRef _layout; CullCallbackPtr _cull_callback; bool _render_always; //setLayout(_layout); + _layout.clear(); + } if( _image_content ) // Placement within decoration canvas @@ -139,6 +144,16 @@ namespace canvas return _canvas_content; } + //---------------------------------------------------------------------------- + void Window::setLayout(const LayoutRef& layout) + { + CanvasPtr canvas = _canvas_content.lock(); + if( canvas ) + canvas->setLayout(layout); + else + _layout = layout; // keep layout until content canvas is set + } + //---------------------------------------------------------------------------- CanvasPtr Window::getCanvasDecoration() const { diff --git a/simgear/canvas/CanvasWindow.hxx b/simgear/canvas/CanvasWindow.hxx index 57068a84..81917ed7 100644 --- a/simgear/canvas/CanvasWindow.hxx +++ b/simgear/canvas/CanvasWindow.hxx @@ -20,6 +20,7 @@ #define CANVAS_WINDOW_HXX_ #include +#include #include #include #include @@ -34,7 +35,8 @@ namespace canvas { class Window: - public Image + public Image, + public LayoutItem { public: static const std::string TYPE_NAME; @@ -75,6 +77,8 @@ namespace canvas void setCanvasContent(CanvasPtr canvas); simgear::canvas::CanvasWeakPtr getCanvasContent() const; + void setLayout(const LayoutRef& layout); + CanvasPtr getCanvasDecoration() const; bool isResizable() const; @@ -101,6 +105,7 @@ namespace canvas CanvasPtr _canvas_decoration; CanvasWeakPtr _canvas_content; + LayoutRef _layout; ImagePtr _image_content, _image_shadow; diff --git a/simgear/canvas/elements/CanvasElement.hxx b/simgear/canvas/elements/CanvasElement.hxx index 01858f4b..9e686739 100644 --- a/simgear/canvas/elements/CanvasElement.hxx +++ b/simgear/canvas/elements/CanvasElement.hxx @@ -167,7 +167,7 @@ namespace canvas /** * Get bounding box (may not be as tight as bounding box returned by - * #getTightBounds) + * #getTightBoundingBox) */ osg::BoundingBox getBoundingBox() const; diff --git a/simgear/canvas/layout/BoxLayout.cxx b/simgear/canvas/layout/BoxLayout.cxx new file mode 100644 index 00000000..cabe0ba9 --- /dev/null +++ b/simgear/canvas/layout/BoxLayout.cxx @@ -0,0 +1,266 @@ +// Align items horizontally or vertically in a box +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "BoxLayout.hxx" +#include + +namespace simgear +{ +namespace canvas +{ + + //---------------------------------------------------------------------------- + BoxLayout::BoxLayout(Direction dir): + _padding(5) + { + setDirection(dir); + } + + //---------------------------------------------------------------------------- + void BoxLayout::addItem(const LayoutItemRef& item) + { + return addItem(item, 0); + } + + //---------------------------------------------------------------------------- + void BoxLayout::addItem(const LayoutItemRef& item, int stretch) + { + ItemData item_data = {0}; + item_data.layout_item = item; + item_data.stretch = std::max(0, stretch); + + item->setCanvas(_canvas); + item->setParent(this); + + _layout_items.push_back(item_data); + + invalidate(); + } + + //---------------------------------------------------------------------------- + void BoxLayout::setStretch(size_t index, int stretch) + { + if( index >= _layout_items.size() ) + return; + + _layout_items.at(index).stretch = std::max(0, stretch); + invalidate(); + } + + //---------------------------------------------------------------------------- + int BoxLayout::stretch(size_t index) const + { + if( index >= _layout_items.size() ) + return 0; + + return _layout_items.at(index).stretch; + } + + //---------------------------------------------------------------------------- + void BoxLayout::setSpacing(int spacing) + { + if( spacing == _padding ) + return; + + _padding = spacing; + invalidate(); + } + + //---------------------------------------------------------------------------- + int BoxLayout::spacing() const + { + return _padding; + } + + //---------------------------------------------------------------------------- + void BoxLayout::setDirection(Direction dir) + { + _get_layout_coord = &SGVec2i::x; + _get_fixed_coord = &SGVec2i::y; + + if( dir == TopToBottom || dir == BottomToTop ) + std::swap(_get_layout_coord, _get_fixed_coord); + + _reverse = (dir == RightToLeft || dir == BottomToTop ); + + invalidate(); + } + + //---------------------------------------------------------------------------- + BoxLayout::Direction BoxLayout::direction() const + { + if( _get_layout_coord == static_cast(&SGVec2i::x) ) + return _reverse ? RightToLeft : LeftToRight; + else + return _reverse ? BottomToTop : TopToBottom; + } + + //---------------------------------------------------------------------------- + void BoxLayout::setCanvas(const CanvasWeakPtr& canvas) + { + _canvas = canvas; + + for(size_t i = 0; i < _layout_items.size(); ++i) + _layout_items[i].layout_item->setCanvas(canvas); + } + + //---------------------------------------------------------------------------- + void BoxLayout::updateSizeHints() const + { + SGVec2i min_size(0, 0), + max_size(0, 0), + size_hint(0, 0); + + _layout_data.reset(); + bool is_first = true; + + for(size_t i = 0; i < _layout_items.size(); ++i) + { + // TODO check visible + + ItemData& item_data = _layout_items[i]; + LayoutItem const& item = *item_data.layout_item; + + item_data.min_size = (item.minimumSize().*_get_layout_coord)(); + item_data.max_size = (item.maximumSize().*_get_layout_coord)(); + item_data.size_hint = (item.sizeHint().*_get_layout_coord)(); + + if( is_first ) + { + item_data.padding_orig = 0; + is_first = false; + } + else + item_data.padding_orig = _padding; + + _layout_data.padding += item_data.padding_orig; + + // Add sizes of all children in layout direction + safeAdd(min_size.x(), item_data.min_size); + safeAdd(max_size.x(), item_data.max_size); + safeAdd(size_hint.x(), item_data.size_hint); + + // Take maximum in fixed (non-layouted) direction + min_size.y() = std::max( min_size.y(), + (item.minimumSize().*_get_fixed_coord)() ); + max_size.y() = std::max( max_size.y(), + (item.maximumSize().*_get_fixed_coord)() ); + size_hint.y() = std::max( min_size.y(), + (item.sizeHint().*_get_fixed_coord)() ); + } + + safeAdd(min_size.x(), _layout_data.padding); + safeAdd(max_size.x(), _layout_data.padding); + safeAdd(size_hint.x(), _layout_data.padding); + + _layout_data.min_size = min_size.x(); + _layout_data.max_size = max_size.x(); + _layout_data.size_hint = size_hint.x(); + + _min_size.x() = (min_size.*_get_layout_coord)(); + _max_size.x() = (max_size.*_get_layout_coord)(); + _size_hint.x() = (size_hint.*_get_layout_coord)(); + + _min_size.y() = (min_size.*_get_fixed_coord)(); + _max_size.y() = (max_size.*_get_fixed_coord)(); + _size_hint.y() = (size_hint.*_get_fixed_coord)(); + + _flags &= ~SIZE_INFO_DIRTY; + } + + //---------------------------------------------------------------------------- + SGVec2i BoxLayout::sizeHintImpl() const + { + updateSizeHints(); + return _size_hint; + } + + //---------------------------------------------------------------------------- + SGVec2i BoxLayout::minimumSizeImpl() const + { + updateSizeHints(); + return _min_size; + } + + //---------------------------------------------------------------------------- + SGVec2i BoxLayout::maximumSizeImpl() const + { + updateSizeHints(); + return _max_size; + } + + //---------------------------------------------------------------------------- + void BoxLayout::doLayout(const SGRecti& geom) + { + if( _flags & SIZE_INFO_DIRTY ) + updateSizeHints(); + + _layout_data.size = (geom.size().*_get_layout_coord)(); + distribute(_layout_items, _layout_data); + + int fixed_size = (geom.size().*_get_fixed_coord)(); + SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(), + (geom.pos().*_get_fixed_coord)() ); + if( _reverse ) + cur_pos.x() += (geom.size().*_get_layout_coord)(); + + // TODO handle reverse layouting (rtl/btt) + for(size_t i = 0; i < _layout_items.size(); ++i) + { + ItemData const& data = _layout_items[i]; + cur_pos.x() += _reverse ? -data.padding - data.size : data.padding; + + SGVec2i size( + data.size, + std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(), + fixed_size ) + ); + + // Center in fixed direction (TODO allow specifying alignment) + int offset_fixed = (fixed_size - size.y()) / 2; + cur_pos.y() += offset_fixed; + + data.layout_item->setGeometry(SGRecti( + (cur_pos.*_get_layout_coord)(), + (cur_pos.*_get_fixed_coord)(), + (size.*_get_layout_coord)(), + (size.*_get_fixed_coord)() + )); + + if( !_reverse ) + cur_pos.x() += data.size; + cur_pos.y() -= offset_fixed; + } + } + + //---------------------------------------------------------------------------- + HBoxLayout::HBoxLayout(): + BoxLayout(LeftToRight) + { + + } + + //---------------------------------------------------------------------------- + VBoxLayout::VBoxLayout(): + BoxLayout(TopToBottom) + { + + } + +} // namespace canvas +} // namespace simgear diff --git a/simgear/canvas/layout/BoxLayout.hxx b/simgear/canvas/layout/BoxLayout.hxx new file mode 100644 index 00000000..9fc41415 --- /dev/null +++ b/simgear/canvas/layout/BoxLayout.hxx @@ -0,0 +1,114 @@ +// Align items horizontally or vertically in a box +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_CANVAS_BOX_LAYOUT_HXX_ +#define SG_CANVAS_BOX_LAYOUT_HXX_ + +#include "Layout.hxx" + +namespace simgear +{ +namespace canvas +{ + + class BoxLayout: + public Layout + { + public: + + enum Direction + { + LeftToRight, + RightToLeft, + TopToBottom, + BottomToTop + }; + + BoxLayout(Direction dir); + + virtual void addItem(const LayoutItemRef& item); + + void addItem(const LayoutItemRef& item, int stretch); + + /** + * Set the stretch factor of the item at position @a index to @a stretch. + */ + void setStretch(size_t index, int stretch); + + /** + * Get the stretch factor of the item at position @a index + */ + int stretch(size_t index) const; + + virtual void setSpacing(int spacing); + virtual int spacing() const; + + void setDirection(Direction dir); + Direction direction() const; + + virtual void setCanvas(const CanvasWeakPtr& canvas); + + protected: + + typedef const int& (SGVec2i::*CoordGetter)() const; + CoordGetter _get_layout_coord, // _layout_items; + mutable ItemData _layout_data; + + void updateSizeHints() const; + + virtual SGVec2i sizeHintImpl() const; + virtual SGVec2i minimumSizeImpl() const; + virtual SGVec2i maximumSizeImpl() const; + + virtual void doLayout(const SGRecti& geom); + }; + + /** + * Shortcut for creating a horizontal box layout + */ + class HBoxLayout: + public BoxLayout + { + public: + HBoxLayout(); + }; + + /** + * Shortcut for creating a vertical box layout + */ + class VBoxLayout: + public BoxLayout + { + public: + VBoxLayout(); + }; + +} // namespace canvas +} // namespace simgear + + +#endif /* SG_CANVAS_BOX_LAYOUT_HXX_ */ + diff --git a/simgear/canvas/layout/CMakeLists.txt b/simgear/canvas/layout/CMakeLists.txt new file mode 100644 index 00000000..f02c30fb --- /dev/null +++ b/simgear/canvas/layout/CMakeLists.txt @@ -0,0 +1,22 @@ +include (SimGearComponent) + +set(HEADERS + BoxLayout.hxx + Layout.hxx + LayoutItem.hxx + NasalWidget.hxx +) + +set(SOURCES + BoxLayout.cxx + Layout.cxx + LayoutItem.cxx + NasalWidget.cxx +) + +simgear_scene_component(canvas-layout canvas/layout "${SOURCES}" "${HEADERS}") + +add_boost_test(canvas_layout + SOURCES canvas_layout_test.cxx + LIBRARIES ${TEST_LIBS} +) \ No newline at end of file diff --git a/simgear/canvas/layout/Layout.cxx b/simgear/canvas/layout/Layout.cxx new file mode 100644 index 00000000..8c10a520 --- /dev/null +++ b/simgear/canvas/layout/Layout.cxx @@ -0,0 +1,243 @@ +// Basic class for canvas layouts +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "Layout.hxx" +#include + +namespace simgear +{ +namespace canvas +{ + + //---------------------------------------------------------------------------- + void Layout::update() + { + if( !(_flags & (LAYOUT_DIRTY | SIZE_INFO_DIRTY)) ) + return; + + doLayout(_geometry); + + _flags &= ~LAYOUT_DIRTY; + } + + //---------------------------------------------------------------------------- + void Layout::invalidate() + { + LayoutItem::invalidate(); + _flags |= LAYOUT_DIRTY; + } + + //---------------------------------------------------------------------------- + void Layout::setGeometry(const SGRecti& geom) + { + if( geom == _geometry ) + return; + + _geometry = geom; + _flags |= LAYOUT_DIRTY; + + update(); + } + + //---------------------------------------------------------------------------- + void Layout::ItemData::reset() + { + layout_item = 0; + size_hint = 0; + min_size = 0; + max_size = 0; + padding_orig= 0; + padding = 0; + size = 0; + stretch = 0; + done = false; + } + + //---------------------------------------------------------------------------- + void Layout::safeAdd(int& a, int b) + { + if( SGLimits::max() - b < a ) + a = SGLimits::max(); + else + a += b; + } + + //---------------------------------------------------------------------------- + void Layout::distribute(std::vector& items, const ItemData& space) + { + const int num_children = static_cast(items.size()); + _num_not_done = num_children; + + SG_LOG( SG_GUI, + SG_DEBUG, + "Layout::distribute(" << num_children << " items)" ); + + if( space.size < space.min_size ) + { + // TODO + SG_LOG( SG_GUI, SG_WARN, "Layout: not enough size (not implemented)"); + } + else if( space.size < space.max_size ) + { + _sum_stretch = 0; + _space_stretch = 0; + + bool less_then_hint = space.size < space.size_hint; + + // Give min_size/size_hint to all items + _space_left = space.size + - (less_then_hint ? space.min_size : space.size_hint); + for(int i = 0; i < num_children; ++i) + { + ItemData& d = items[i]; + d.size = less_then_hint ? d.min_size : d.size_hint; + d.padding = d.padding_orig; + d.done = d.size >= (less_then_hint ? d.size_hint : d.max_size); + + if( d.done ) + { + _num_not_done -= 1; + continue; + } + + if( d.stretch > 0 ) + { + _sum_stretch += d.stretch; + _space_stretch += d.size; + } + } + + // Distribute remaining space to increase the size of each item up to its + // size_hint/max_size + while( _space_left > 0 ) + { + if( _num_not_done <= 0 ) + { + SG_LOG(SG_GUI, SG_WARN, "space left, but no more items?"); + break; + } + + int space_per_element = std::max(1, _space_left / _num_not_done); + + SG_LOG(SG_GUI, SG_DEBUG, "space/element=" << space_per_element); + + for(int i = 0; i < num_children; ++i) + { + ItemData& d = items[i]; + + SG_LOG( + SG_GUI, + SG_DEBUG, + i << ") left=" << _space_left + << ", not_done=" << _num_not_done + << ", sum=" << _sum_stretch + << ", stretch=" << _space_stretch + << ", stretch/unit=" << _space_stretch / std::max(1, _sum_stretch) + ); + + if( d.done ) + continue; + + if( _sum_stretch > 0 && d.stretch <= 0 ) + d.done = true; + else + { + int target_size = 0; + int max_size = less_then_hint ? d.size_hint : d.max_size; + + if( _sum_stretch > 0 ) + { + target_size = (d.stretch * (_space_left + _space_stretch)) + / _sum_stretch; + + // Item would be smaller than minimum size or larger than maximum + // size, so just keep bounded size and ignore stretch factor + if( target_size <= d.size || target_size >= max_size ) + { + d.done = true; + _sum_stretch -= d.stretch; + _space_stretch -= d.size; + + if( target_size >= max_size ) + target_size = max_size; + else + target_size = d.size; + } + else + _space_stretch += target_size - d.size; + } + else + { + // Give space evenly to all remaining elements in this round + target_size = d.size + std::min(_space_left, space_per_element); + + if( target_size >= max_size ) + { + d.done = true; + target_size = max_size; + } + } + + int old_size = d.size; + d.size = target_size; + _space_left -= d.size - old_size; + } + + if( d.done ) + { + _num_not_done -= 1; + + if( _sum_stretch <= 0 && d.stretch > 0 ) + // Distribute remaining space evenly to all non-stretchable items + // in a new round + break; + } + } + } + } + else + { + _space_left = space.size - space.max_size; + for(int i = 0; i < num_children; ++i) + { + ItemData& d = items[i]; + d.size = d.max_size; + + // Add superfluous space as padding + d.padding = d.padding_orig + _space_left + // Padding after last child... + / (_num_not_done + 1); + + _space_left -= d.padding - d.padding_orig; + _num_not_done -= 1; + } + } + + SG_LOG(SG_GUI, SG_DEBUG, "distribute:"); + for(int i = 0; i < num_children; ++i) + { + ItemData const& d = items[i]; + SG_LOG( SG_GUI, + SG_DEBUG, + i << ") pad=" << d.padding + << ", size = " << d.size ); + } + } + +} // namespace canvas +} // namespace simgear diff --git a/simgear/canvas/layout/Layout.hxx b/simgear/canvas/layout/Layout.hxx new file mode 100644 index 00000000..6d4aa575 --- /dev/null +++ b/simgear/canvas/layout/Layout.hxx @@ -0,0 +1,98 @@ +// Basic class for canvas layouts +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_CANVAS_LAYOUT_HXX_ +#define SG_CANVAS_LAYOUT_HXX_ + +#include "LayoutItem.hxx" +#include + +namespace simgear +{ +namespace canvas +{ + + class Layout: + public LayoutItem + { + public: + void update(); + + virtual void invalidate(); + virtual void setGeometry(const SGRecti& geom); + + virtual void addItem(const LayoutItemRef& item) = 0; + virtual void setSpacing(int spacing) = 0; + virtual int spacing() const = 0; + + protected: + enum LayoutFlags + { + LAYOUT_DIRTY = LayoutItem::LAST_FLAG << 1, + LAST_FLAG = LAYOUT_DIRTY + }; + + struct ItemData + { + LayoutItemRef layout_item; + int size_hint, + min_size, + max_size, + padding_orig, //& items, const ItemData& space); + + private: + + int _num_not_done, // LayoutRef; + +} // namespace canvas +} // namespace simgear + + +#endif /* SG_CANVAS_LAYOUT_HXX_ */ diff --git a/simgear/canvas/layout/LayoutItem.cxx b/simgear/canvas/layout/LayoutItem.cxx new file mode 100644 index 00000000..31be629e --- /dev/null +++ b/simgear/canvas/layout/LayoutItem.cxx @@ -0,0 +1,151 @@ +// Basic element for layouting canvas elements +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "LayoutItem.hxx" +#include + +namespace simgear +{ +namespace canvas +{ + + //---------------------------------------------------------------------------- + LayoutItem::LayoutItem(): + _flags(0) + { + invalidate(); + } + + //---------------------------------------------------------------------------- + LayoutItem::~LayoutItem() + { + + } + + //---------------------------------------------------------------------------- + SGVec2i LayoutItem::sizeHint() const + { + if( _flags & SIZE_HINT_DIRTY ) + { + _size_hint = sizeHintImpl(); + _flags &= ~SIZE_HINT_DIRTY; + } + + return _size_hint; + } + + //---------------------------------------------------------------------------- + SGVec2i LayoutItem::minimumSize() const + { + if( _flags & MINIMUM_SIZE_DIRTY ) + { + _min_size = minimumSizeImpl(); + _flags &= ~MINIMUM_SIZE_DIRTY; + } + + return _min_size; + } + + //---------------------------------------------------------------------------- + SGVec2i LayoutItem::maximumSize() const + { + if( _flags & MAXIMUM_SIZE_DIRTY ) + { + _max_size = maximumSizeImpl(); + _flags &= ~MAXIMUM_SIZE_DIRTY; + } + + return _max_size; + } + + //---------------------------------------------------------------------------- + void LayoutItem::invalidate() + { + _flags |= SIZE_HINT_DIRTY + | MINIMUM_SIZE_DIRTY + | MAXIMUM_SIZE_DIRTY; + + invalidateParent(); + } + + //---------------------------------------------------------------------------- + void LayoutItem::invalidateParent() + { + LayoutItemRef parent = _parent.lock(); + if( parent ) + parent->invalidate(); + } + + //---------------------------------------------------------------------------- + void LayoutItem::setGeometry(const SGRecti& geom) + { + _geometry = geom; + } + + //---------------------------------------------------------------------------- + SGRecti LayoutItem::geometry() const + { + return _geometry; + } + + //---------------------------------------------------------------------------- + void LayoutItem::setCanvas(const CanvasWeakPtr& canvas) + { + _canvas = canvas; + } + + //---------------------------------------------------------------------------- + CanvasPtr LayoutItem::getCanvas() const + { + return _canvas.lock(); + } + + //---------------------------------------------------------------------------- + void LayoutItem::setParent(const LayoutItemWeakRef& parent) + { + _parent = parent; + LayoutItemRef parent_ref = parent.lock(); + setCanvas(parent_ref ? parent_ref->_canvas : CanvasWeakPtr()); + } + + //---------------------------------------------------------------------------- + LayoutItemRef LayoutItem::getParent() const + { + return _parent.lock(); + } + + //---------------------------------------------------------------------------- + SGVec2i LayoutItem::sizeHintImpl() const + { + return SGVec2i(16, 16); + } + + //---------------------------------------------------------------------------- + SGVec2i LayoutItem::minimumSizeImpl() const + { + return SGVec2i(0, 0); + } + + //---------------------------------------------------------------------------- + SGVec2i LayoutItem::maximumSizeImpl() const + { + return SGVec2i(SGLimits::max(), SGLimits::max()); + } + +} // namespace canvas +} // namespace simgear diff --git a/simgear/canvas/layout/LayoutItem.hxx b/simgear/canvas/layout/LayoutItem.hxx new file mode 100644 index 00000000..18e56b92 --- /dev/null +++ b/simgear/canvas/layout/LayoutItem.hxx @@ -0,0 +1,139 @@ +///@file Basic element for layouting canvas elements +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_CANVAS_LAYOUT_ITEM_HXX_ +#define SG_CANVAS_LAYOUT_ITEM_HXX_ + +#include +#include +#include +#include +#include +#include + +namespace simgear +{ +namespace canvas +{ + class LayoutItem; + typedef SGSharedPtr LayoutItemRef; + typedef SGWeakPtr LayoutItemWeakRef; + + /** + * Base class for all layouting elements. Specializations either implement a + * layouting algorithm or a widget. + */ + class LayoutItem: + public virtual SGVirtualWeakReferenced + { + public: + + LayoutItem(); + virtual ~LayoutItem(); + + /** + * Get the preferred size of this item. + */ + SGVec2i sizeHint() const; + + /** + * Get the minimum amount of the space this item requires. + */ + SGVec2i minimumSize() const; + + /** + * Get the maximum amount of space this item can use. + */ + SGVec2i maximumSize() const; + + /** + * Mark all cached data as invalid and require it to be recalculated. + */ + virtual void invalidate(); + + /** + * Mark all cached data of parent item as invalid (if it is known) + */ + void invalidateParent(); + + /** + * Set position and size of this element. For layouts this triggers a + * recalculation of the layout. + */ + virtual void setGeometry(const SGRecti& geom); + + /** + * Get position and size of this element. + */ + virtual SGRecti geometry() const; + + /** + * Set the canvas this item is attached to. + */ + virtual void setCanvas(const CanvasWeakPtr& canvas); + + /** + * Get the canvas this item is attached to. + */ + CanvasPtr getCanvas() const; + + /** + * Set the parent layout item (usally this is a layout). + */ + void setParent(const LayoutItemWeakRef& parent); + + /** + * Get the parent layout. + */ + LayoutItemRef getParent() const; + + protected: + + friend class Canvas; + + enum Flags + { + SIZE_HINT_DIRTY = 1, + MINIMUM_SIZE_DIRTY = SIZE_HINT_DIRTY << 1, + MAXIMUM_SIZE_DIRTY = MINIMUM_SIZE_DIRTY << 1, + SIZE_INFO_DIRTY = SIZE_HINT_DIRTY + | MINIMUM_SIZE_DIRTY + | MAXIMUM_SIZE_DIRTY, + LAST_FLAG = MAXIMUM_SIZE_DIRTY + }; + + CanvasWeakPtr _canvas; + LayoutItemWeakRef _parent; + + SGRecti _geometry; + + mutable uint32_t _flags; + mutable SGVec2i _size_hint, + _min_size, + _max_size; + + virtual SGVec2i sizeHintImpl() const; + virtual SGVec2i minimumSizeImpl() const; + virtual SGVec2i maximumSizeImpl() const; + + }; + +} // namespace canvas +} // namespace simgear + +#endif /* SG_CANVAS_LAYOUT_ITEM_HXX_ */ diff --git a/simgear/canvas/layout/NasalWidget.cxx b/simgear/canvas/layout/NasalWidget.cxx new file mode 100644 index 00000000..a8532c9e --- /dev/null +++ b/simgear/canvas/layout/NasalWidget.cxx @@ -0,0 +1,158 @@ +// Glue for GUI layout items implemented in Nasal space +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "NasalWidget.hxx" + +#include +#include + +namespace simgear +{ +namespace canvas +{ + + //---------------------------------------------------------------------------- + NasalWidget::NasalWidget(naRef impl): + _nasal_impl(impl) + { + assert( naIsHash(_nasal_impl.get_naRef()) ); + } + + //---------------------------------------------------------------------------- + void NasalWidget::setGeometry(const SGRect& geom) + { + if( _geometry == geom ) + return; + + _geometry = geom; + + if( _set_geometry ) + _set_geometry(_nasal_impl.get_naRef(), geom); + } + + //---------------------------------------------------------------------------- + void NasalWidget::setSetGeometryFunc(const SetGeometryFunc& func) + { + _set_geometry = func; + } + + //---------------------------------------------------------------------------- + void NasalWidget::setImpl(naRef obj) + { + _nasal_impl.reset(obj); + } + + //---------------------------------------------------------------------------- + naRef NasalWidget::getImpl() const + { + return _nasal_impl.get_naRef(); + } + + //---------------------------------------------------------------------------- + void NasalWidget::setSizeHint(const SGVec2i& s) + { + if( _size_hint == s ) + return; + + _size_hint = s; + + // TODO just invalidate size_hint? Probably not a performance issue... + invalidateParent(); + } + + //---------------------------------------------------------------------------- + void NasalWidget::setMinimumSize(const SGVec2i& s) + { + if( _min_size == s ) + return; + + _min_size = s; + invalidateParent(); + } + + //---------------------------------------------------------------------------- + void NasalWidget::setMaximumSize(const SGVec2i& s) + { + if( _max_size == s ) + return; + + _max_size = s; + invalidateParent(); + } + + //---------------------------------------------------------------------------- + bool NasalWidget::_set(naContext c, const std::string& key, naRef val) + { + if( !_nasal_impl.valid() ) + return false; + + nasal::Hash(_nasal_impl.get_naRef(), c).set(key, val); + return true; + } + + //---------------------------------------------------------------------------- + static naRef f_makeNasalWidget(const nasal::CallContext& ctx) + { + return ctx.to_nasal(NasalWidgetRef( + new NasalWidget( ctx.requireArg(0) ) + )); + } + + //---------------------------------------------------------------------------- + void NasalWidget::setupGhost(nasal::Hash& ns) + { + nasal::Ghost::init("canvas.Widget") + .bases() + ._set(&NasalWidget::_set) + .member("parents", &NasalWidget::getParents) + .method("setSetGeometryFunc", &NasalWidget::setSetGeometryFunc) + .method("setSizeHint", &NasalWidget::setSizeHint) + .method("setMinimumSize", &NasalWidget::setMinimumSize) + .method("setMaximumSize", &NasalWidget::setMaximumSize); + + nasal::Hash widget_hash = ns.createHash("Widget"); + widget_hash.set("new", &f_makeNasalWidget); + } + + //---------------------------------------------------------------------------- + naRef NasalWidget::getParents(NasalWidget& w, naContext c) + { + naRef parents[] = { w._nasal_impl.get_naRef() }; + return nasal::to_nasal(c, parents); + } + + //---------------------------------------------------------------------------- + SGVec2i NasalWidget::sizeHintImpl() const + { + return _size_hint; + } + + //---------------------------------------------------------------------------- + SGVec2i NasalWidget::minimumSizeImpl() const + { + return _min_size; + } + + //---------------------------------------------------------------------------- + SGVec2i NasalWidget::maximumSizeImpl() const + { + return _max_size; + } + +} // namespace canvas +} // namespace simgear diff --git a/simgear/canvas/layout/NasalWidget.hxx b/simgear/canvas/layout/NasalWidget.hxx new file mode 100644 index 00000000..d9e80450 --- /dev/null +++ b/simgear/canvas/layout/NasalWidget.hxx @@ -0,0 +1,85 @@ +///@file Glue for GUI widgets implemented in Nasal space +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_CANVAS_NASAL_WIDGET_HXX_ +#define SG_CANVAS_NASAL_WIDGET_HXX_ + +#include "LayoutItem.hxx" + +#include +#include + +namespace simgear +{ +namespace canvas +{ + + /** + * Baseclass/ghost to create widgets with Nasal. + */ + class NasalWidget: + public LayoutItem + { + public: + + typedef boost::function SetGeometryFunc; + + /** + * + * @param impl Initial implementation hash (nasal part of + * implementation) + */ + NasalWidget(naRef impl); + + virtual void setGeometry(const SGRecti& geom); + + void setSetGeometryFunc(const SetGeometryFunc& func); + + void setImpl(naRef obj); + naRef getImpl() const; + + void setSizeHint(const SGVec2i& s); + void setMinimumSize(const SGVec2i& s); + void setMaximumSize(const SGVec2i& s); + + bool _set(naContext c, const std::string& key, naRef val); + + /** + * @param ns Namespace to register the class interface + */ + static void setupGhost(nasal::Hash& ns); + + protected: + SetGeometryFunc _set_geometry; + nasal::ObjectHolder<> _nasal_impl; + + static naRef getParents(NasalWidget&, naContext); + + virtual SGVec2i sizeHintImpl() const; + virtual SGVec2i minimumSizeImpl() const; + virtual SGVec2i maximumSizeImpl() const; + + }; + + typedef SGSharedPtr NasalWidgetRef; + +} // namespace canvas +} // namespace simgear + + +#endif /* SG_CANVAS_NASAL_WIDGET_HXX_ */ diff --git a/simgear/canvas/layout/canvas_layout_test.cxx b/simgear/canvas/layout/canvas_layout_test.cxx new file mode 100644 index 00000000..ffbc1c90 --- /dev/null +++ b/simgear/canvas/layout/canvas_layout_test.cxx @@ -0,0 +1,231 @@ +// Testing canvas layouting system +// +// Copyright (C) 2014 Thomas Geymayer +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#define BOOST_TEST_MODULE canvas_layout +#include + +#include "BoxLayout.hxx" +#include "NasalWidget.hxx" + +#include +#include + +//------------------------------------------------------------------------------ +struct SetLogLevelFixture +{ + SetLogLevelFixture() + { + sglog().set_log_priority(SG_DEBUG); + } +}; +BOOST_GLOBAL_FIXTURE(SetLogLevelFixture); + +//------------------------------------------------------------------------------ +namespace sc = simgear::canvas; + +class TestWidget: + public sc::LayoutItem +{ + public: + TestWidget( const SGVec2i& min_size, + const SGVec2i& size_hint, + const SGVec2i& max_size ) + { + _size_hint = size_hint; + _min_size = min_size; + _max_size = max_size; + } + + TestWidget(const TestWidget& rhs) + { + _size_hint = rhs._size_hint; + _min_size = rhs._min_size; + _max_size = rhs._max_size; + } + + void setMinSize(const SGVec2i& size) { _min_size = size; } + void setMaxSize(const SGVec2i& size) { _max_size = size; } + void setSizeHint(const SGVec2i& size) { _size_hint = size; } + + virtual void setGeometry(const SGRecti& geom) { _geom = geom; } + virtual SGRecti geometry() const { return _geom; } + + protected: + + SGRecti _geom; + + virtual SGVec2i sizeHintImpl() const { return _size_hint; } + virtual SGVec2i minimumSizeImpl() const { return _min_size; } + virtual SGVec2i maximumSizeImpl() const { return _max_size; } +}; + +typedef SGSharedPtr TestWidgetRef; + +//------------------------------------------------------------------------------ +BOOST_AUTO_TEST_CASE( horizontal_layout ) +{ + sc::BoxLayout box_layout(sc::BoxLayout::BottomToTop); + box_layout.setSpacing(5); + + BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::BottomToTop); + BOOST_CHECK_EQUAL(box_layout.spacing(), 5); + + box_layout.setDirection(sc::BoxLayout::LeftToRight); + box_layout.setSpacing(9); + + BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::LeftToRight); + BOOST_CHECK_EQUAL(box_layout.spacing(), 9); + + TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16), + SGVec2i(16, 16), + SGVec2i(16, 16) ) ); + box_layout.addItem(fixed_size_widget); + + BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(16, 16)); + BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(16, 16)); + BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(16, 16)); + + TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16), + SGVec2i(32, 32), + SGVec2i(256, 64) ) ); + box_layout.addItem(limited_resize_widget); + + // Combined sizes of both widget plus the padding between them + BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(41, 16)); + BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(57, 32)); + BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(281, 64)); + + // Test with different spacing/padding + box_layout.setSpacing(5); + + BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(37, 16)); + BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(53, 32)); + BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(277, 64)); + + box_layout.setGeometry(SGRecti(0, 0, 128, 32)); + + // Fixed size for first widget and remaining space goes to second widget + BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16)); + BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 107, 32)); + + TestWidgetRef stretch_widget( new TestWidget( SGVec2i(16, 16), + SGVec2i(32, 32), + SGVec2i(128, 32) ) ); + box_layout.addItem(stretch_widget, 1); + box_layout.update(); + + BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(58, 16)); + BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(90, 32)); + BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(410, 64)); + + // Due to the stretch factor only the last widget gets additional space. All + // other widgets get the preferred size. + BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16)); + BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 32, 32)); + BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(58, 0, 70, 32)); + + // Test stretch factor + TestWidgetRef fast_stretch( new TestWidget(*stretch_widget) ); + sc::BoxLayout box_layout_stretch(sc::BoxLayout::LeftToRight); + + box_layout_stretch.addItem(stretch_widget, 1); + box_layout_stretch.addItem(fast_stretch, 2); + + box_layout_stretch.setGeometry(SGRecti(0,0,128,32)); + + BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 41, 32)); + BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(46, 0, 82, 32)); + + box_layout_stretch.setGeometry(SGRecti(0,0,256,32)); + + BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 123, 32)); + BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(128, 0, 128, 32)); + + // Test superflous space to padding + box_layout_stretch.setGeometry(SGRecti(0,0,512,32)); + + BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(83, 0, 128, 32)); + BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(300, 0, 128, 32)); + + // Test more space then preferred, but less than maximum + { + sc::HBoxLayout hbox; + TestWidgetRef w1( new TestWidget( SGVec2i(16, 16), + SGVec2i(32, 32), + SGVec2i(9999, 32) ) ), + w2( new TestWidget(*w1) ); + + hbox.addItem(w1); + hbox.addItem(w2); + + hbox.setGeometry( SGRecti(0, 0, 256, 32) ); + + BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 126, 32)); + BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(131, 0, 125, 32)); + + hbox.setStretch(0, 1); + hbox.setStretch(1, 1); + + BOOST_CHECK_EQUAL(hbox.stretch(0), 1); + BOOST_CHECK_EQUAL(hbox.stretch(1), 1); + + hbox.update(); + + BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 125, 32)); + BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(130, 0, 126, 32)); + } +} + +//------------------------------------------------------------------------------ +BOOST_AUTO_TEST_CASE( vertical_layout) +{ + sc::BoxLayout vbox(sc::BoxLayout::TopToBottom); + vbox.setSpacing(7); + + TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16), + SGVec2i(16, 16), + SGVec2i(16, 16) ) ); + TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16), + SGVec2i(32, 32), + SGVec2i(256, 64) ) ); + + vbox.addItem(fixed_size_widget); + vbox.addItem(limited_resize_widget); + + BOOST_CHECK_EQUAL(vbox.minimumSize(), SGVec2i(16, 39)); + BOOST_CHECK_EQUAL(vbox.sizeHint(), SGVec2i(32, 55)); + BOOST_CHECK_EQUAL(vbox.maximumSize(), SGVec2i(256, 87)); + + vbox.setGeometry(SGRecti(10, 20, 16, 55)); + + BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(10, 20, 16, 16)); + BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(10, 43, 16, 32)); + + vbox.setDirection(sc::BoxLayout::BottomToTop); +} + +//------------------------------------------------------------------------------ +BOOST_AUTO_TEST_CASE( nasal_layout ) +{ + naContext c = naNewContext(); + naRef me = naNewHash(c); + + sc::LayoutItemRef nasal_item( new sc::NasalWidget(me) ); + + naFreeContext(c); +} diff --git a/simgear/debug/debug_types.h b/simgear/debug/debug_types.h index f37aaaa5..82773287 100644 --- a/simgear/debug/debug_types.h +++ b/simgear/debug/debug_types.h @@ -31,7 +31,8 @@ typedef enum { SG_ENVIRONMENT = 0x00100000, SG_SOUND = 0x00200000, SG_NAVAID = 0x00400000, - SG_UNDEFD = 0x00800000, // For range checking + SG_GUI = 0x00800000, + SG_UNDEFD = 0x01000000, // For range checking SG_ALL = 0xFFFFFFFF } sgDebugClass; diff --git a/simgear/debug/logstream.cxx b/simgear/debug/logstream.cxx index d3972ae7..4290f5ca 100644 --- a/simgear/debug/logstream.cxx +++ b/simgear/debug/logstream.cxx @@ -70,6 +70,7 @@ const char* debugClassToString(sgDebugClass c) case SG_ENVIRONMENT:return "environment"; case SG_SOUND: return "sound"; case SG_NAVAID: return "navaid"; + case SG_GUI: return "gui"; default: return "unknown"; } } @@ -438,4 +439,4 @@ void requestConsole() global_privateLogstream->requestConsole(); } -} // of namespace simgear \ No newline at end of file +} // of namespace simgear diff --git a/simgear/props/PropertyBasedElement.hxx b/simgear/props/PropertyBasedElement.hxx index 52af9b30..27eff170 100644 --- a/simgear/props/PropertyBasedElement.hxx +++ b/simgear/props/PropertyBasedElement.hxx @@ -30,7 +30,7 @@ namespace simgear */ class PropertyBasedElement: public SGPropertyChangeListener, - public SGWeakReferenced + public virtual SGVirtualWeakReferenced { public: PropertyBasedElement(SGPropertyNode* node); -- 2.39.5