From d3b211e7873e7754e8385937a417187724733e70 Mon Sep 17 00:00:00 2001 From: Thomas Geymayer Date: Fri, 13 Jun 2014 00:03:40 +0200 Subject: [PATCH] canvas::Layout: support height-for-width layouting. --- simgear/canvas/layout/BoxLayout.cxx | 105 +++++++++++++++- simgear/canvas/layout/BoxLayout.hxx | 10 ++ simgear/canvas/layout/Layout.cxx | 28 +++++ simgear/canvas/layout/Layout.hxx | 6 +- simgear/canvas/layout/LayoutItem.cxx | 18 +++ simgear/canvas/layout/LayoutItem.hxx | 4 + simgear/canvas/layout/NasalWidget.cxx | 64 ++++++++++ simgear/canvas/layout/NasalWidget.hxx | 12 ++ simgear/canvas/layout/canvas_layout_test.cxx | 114 +++++++++++++++++- simgear/math/SGRect.hxx | 2 +- .../cppbind/detail/from_nasal_helper.hxx | 3 + 11 files changed, 362 insertions(+), 4 deletions(-) diff --git a/simgear/canvas/layout/BoxLayout.cxx b/simgear/canvas/layout/BoxLayout.cxx index a4dbd54e..a981db99 100644 --- a/simgear/canvas/layout/BoxLayout.cxx +++ b/simgear/canvas/layout/BoxLayout.cxx @@ -175,6 +175,35 @@ namespace canvas return _direction; } + //---------------------------------------------------------------------------- + bool BoxLayout::hasHeightForWidth() const + { + if( _flags & SIZE_INFO_DIRTY ) + updateSizeHints(); + + return _layout_data.has_hfw; + } + + //---------------------------------------------------------------------------- + int BoxLayout::heightForWidth(int w) const + { + if( !hasHeightForWidth() ) + return -1; + + updateWFHCache(w); + return _hfw_height; + } + + //---------------------------------------------------------------------------- + int BoxLayout::minimumHeightForWidth(int w) const + { + if( !hasHeightForWidth() ) + return -1; + + updateWFHCache(w); + return _hfw_min_height; + } + //---------------------------------------------------------------------------- void BoxLayout::setCanvas(const CanvasWeakPtr& canvas) { @@ -198,6 +227,8 @@ namespace canvas size_hint(0, 0); _layout_data.reset(); + _hfw_width = _hfw_height = _hfw_min_height = -1; + bool is_first = true; for(size_t i = 0; i < _layout_items.size(); ++i) @@ -210,6 +241,7 @@ namespace canvas 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)(); + item_data.has_hfw = item.hasHeightForWidth(); if( !dynamic_cast(item_data.layout_item.get()) ) { @@ -237,6 +269,8 @@ namespace canvas (item.maximumSize().*_get_fixed_coord)() ); size_hint.y() = std::max( size_hint.y(), (item.sizeHint().*_get_fixed_coord)() ); + + _layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth(); } safeAdd(min_size.x(), _layout_data.padding); @@ -258,6 +292,40 @@ namespace canvas _flags &= ~SIZE_INFO_DIRTY; } + //---------------------------------------------------------------------------- + void BoxLayout::updateWFHCache(int w) const + { + if( w == _hfw_width ) + return; + + _hfw_height = 0; + _hfw_min_height = 0; + + if( horiz() ) + { + _layout_data.size = w; + const_cast(this)->distribute(_layout_items, _layout_data); + + for(size_t i = 0; i < _layout_items.size(); ++i) + { + ItemData const& data = _layout_items[i]; + _hfw_height = std::max(_hfw_height, data.hfw(data.size)); + _hfw_min_height = std::max(_hfw_min_height, data.mhfw(data.size)); + } + } + else + { + for(size_t i = 0; i < _layout_items.size(); ++i) + { + ItemData const& data = _layout_items[i]; + _hfw_height += data.hfw(w) + data.padding_orig; + _hfw_min_height += data.mhfw(w) + data.padding_orig; + } + } + + _hfw_width = w; + } + //---------------------------------------------------------------------------- SGVec2i BoxLayout::sizeHintImpl() const { @@ -285,9 +353,45 @@ namespace canvas if( _flags & SIZE_INFO_DIRTY ) updateSizeHints(); + // Store current size hints because vertical layouts containing + // height-for-width items the size hints are update for the actual width of + // the layout + int min_size_save = _layout_data.min_size, + size_hint_save = _layout_data.size_hint; + _layout_data.size = (geom.size().*_get_layout_coord)(); + + // update width dependent data for layouting of vertical layouts + if( _layout_data.has_hfw && !horiz() ) + { + for(size_t i = 0; i < _layout_items.size(); ++i) + { + ItemData& data = _layout_items[i]; + if( data.has_hfw ) + { + int w = SGMisc::clip( geom.width(), + data.layout_item->minimumSize().x(), + data.layout_item->maximumSize().x() ); + + int d = data.layout_item->minimumHeightForWidth(w) - data.min_size; + data.min_size += d; + _layout_data.min_size += d; + + d = data.layout_item->heightForWidth(w) - data.size_hint; + data.size_hint += d; + _layout_data.size_hint += d; + } + } + } + + // now do the actual layouting distribute(_layout_items, _layout_data); + // Restore size hints possibly changed by vertical layouting + _layout_data.min_size = min_size_save; + _layout_data.size_hint = size_hint_save; + + // and finally set the layouted geometry for each item int fixed_size = (geom.size().*_get_fixed_coord)(); SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(), (geom.pos().*_get_fixed_coord)() ); @@ -296,7 +400,6 @@ namespace canvas 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]; diff --git a/simgear/canvas/layout/BoxLayout.hxx b/simgear/canvas/layout/BoxLayout.hxx index 4bf9fd1a..4ee16c3c 100644 --- a/simgear/canvas/layout/BoxLayout.hxx +++ b/simgear/canvas/layout/BoxLayout.hxx @@ -75,6 +75,10 @@ namespace canvas void setDirection(Direction dir); Direction direction() const; + virtual bool hasHeightForWidth() const; + virtual int heightForWidth(int w) const; + virtual int minimumHeightForWidth(int w) const; + virtual void setCanvas(const CanvasWeakPtr& canvas); bool horiz() const; @@ -95,7 +99,13 @@ namespace canvas mutable LayoutItems _layout_items; mutable ItemData _layout_data; + // Cache for last height-for-width query + mutable int _hfw_width, + _hfw_height, + _hfw_min_height; + void updateSizeHints() const; + void updateWFHCache(int w) const; virtual SGVec2i sizeHintImpl() const; virtual SGVec2i minimumSizeImpl() const; diff --git a/simgear/canvas/layout/Layout.cxx b/simgear/canvas/layout/Layout.cxx index a61d2780..b7e29c36 100644 --- a/simgear/canvas/layout/Layout.cxx +++ b/simgear/canvas/layout/Layout.cxx @@ -78,9 +78,28 @@ namespace canvas padding = 0; size = 0; stretch = 0; + has_hfw = false; done = false; } + //---------------------------------------------------------------------------- + int Layout::ItemData::hfw(int w) const + { + if( has_hfw ) + return layout_item->heightForWidth(w); + else + return layout_item->sizeHint().y(); + } + + //---------------------------------------------------------------------------- + int Layout::ItemData::mhfw(int w) const + { + if( has_hfw ) + return layout_item->minimumHeightForWidth(w); + else + return layout_item->minimumSize().y(); + } + //---------------------------------------------------------------------------- void Layout::safeAdd(int& a, int b) { @@ -122,6 +141,15 @@ namespace canvas d.padding = d.padding_orig; d.done = d.size >= (less_then_hint ? d.size_hint : d.max_size); + SG_LOG( + SG_GUI, + SG_DEBUG, + i << ") initial=" << d.size + << ", min=" << d.min_size + << ", hint=" << d.size_hint + << ", max=" << d.max_size + ); + if( d.done ) { _num_not_done -= 1; diff --git a/simgear/canvas/layout/Layout.hxx b/simgear/canvas/layout/Layout.hxx index dc563023..37ab1424 100644 --- a/simgear/canvas/layout/Layout.hxx +++ b/simgear/canvas/layout/Layout.hxx @@ -83,10 +83,14 @@ namespace canvas padding, //() .bases() .method("setSetGeometryFunc", &NasalWidget::setSetGeometryFunc) + .method("setMinimumHeightForWidthFunc", + &NasalWidget::setMinimumHeightForWidthFunc) + .method("setHeightForWidthFunc", &NasalWidget::setHeightForWidthFunc) .method("setSizeHint", &NasalWidget::setSizeHint) .method("setMinimumSize", &NasalWidget::setMinimumSize) .method("setMaximumSize", &NasalWidget::setMaximumSize); @@ -120,6 +159,31 @@ namespace canvas widget_hash.set("new", &f_makeNasalWidget); } + //---------------------------------------------------------------------------- + int NasalWidget::callHeightForWidthFunc( const HeightForWidthFunc& hfw, + int w ) const + { + if( hfw.empty() ) + return -1; + + naContext c = naNewContext(); + try + { + return hfw(nasal::to_nasal(c, const_cast(this)), w); + } + catch( std::exception const& ex ) + { + SG_LOG( + SG_GUI, + SG_WARN, + "NasalWidget.heightForWidth: callback error: '" << ex.what() << "'" + ); + } + naFreeContext(c); + + return -1; + } + //---------------------------------------------------------------------------- SGVec2i NasalWidget::sizeHintImpl() const { diff --git a/simgear/canvas/layout/NasalWidget.hxx b/simgear/canvas/layout/NasalWidget.hxx index e07ffb53..1fc4446a 100644 --- a/simgear/canvas/layout/NasalWidget.hxx +++ b/simgear/canvas/layout/NasalWidget.hxx @@ -40,6 +40,7 @@ namespace canvas public: typedef boost::function SetGeometryFunc; + typedef boost::function HeightForWidthFunc; /** * @@ -51,11 +52,17 @@ namespace canvas virtual void setGeometry(const SGRecti& geom); void setSetGeometryFunc(const SetGeometryFunc& func); + void setHeightForWidthFunc(const HeightForWidthFunc& func); + void setMinimumHeightForWidthFunc(const HeightForWidthFunc& func); void setSizeHint(const SGVec2i& s); void setMinimumSize(const SGVec2i& s); void setMaximumSize(const SGVec2i& s); + virtual bool hasHeightForWidth() const; + virtual int heightForWidth(int w) const; + virtual int minimumHeightForWidth(int w) const; + /** * @param ns Namespace to register the class interface */ @@ -63,6 +70,11 @@ namespace canvas protected: SetGeometryFunc _set_geometry; + HeightForWidthFunc _height_for_width, + _min_height_for_width; + + int callHeightForWidthFunc( const HeightForWidthFunc& hfw, + int w ) const; virtual SGVec2i sizeHintImpl() const; virtual SGVec2i minimumSizeImpl() const; diff --git a/simgear/canvas/layout/canvas_layout_test.cxx b/simgear/canvas/layout/canvas_layout_test.cxx index 4750aae8..42524e85 100644 --- a/simgear/canvas/layout/canvas_layout_test.cxx +++ b/simgear/canvas/layout/canvas_layout_test.cxx @@ -44,7 +44,7 @@ class TestWidget: public: TestWidget( const SGVec2i& min_size, const SGVec2i& size_hint, - const SGVec2i& max_size ) + const SGVec2i& max_size = MAX_SIZE ) { _size_hint = size_hint; _min_size = min_size; @@ -74,6 +74,34 @@ class TestWidget: virtual SGVec2i maximumSizeImpl() const { return _max_size; } }; +class TestWidgetHFW: + public TestWidget +{ + public: + TestWidgetHFW( const SGVec2i& min_size, + const SGVec2i& size_hint, + const SGVec2i& max_size = MAX_SIZE ): + TestWidget(min_size, size_hint, max_size) + { + + } + + virtual bool hasHeightForWidth() const + { + return true; + } + + virtual int heightForWidth(int w) const + { + return _size_hint.x() * _size_hint.y() / w; + } + + virtual int minimumHeightForWidth(int w) const + { + return _min_size.x() * _min_size.y() / w; + } +}; + typedef SGSharedPtr TestWidgetRef; //------------------------------------------------------------------------------ @@ -255,6 +283,7 @@ BOOST_AUTO_TEST_CASE( vertical_layout) vbox.setDirection(sc::BoxLayout::BottomToTop); } +//------------------------------------------------------------------------------ BOOST_AUTO_TEST_CASE( boxlayout_insert_remove ) { sc::HBoxLayout hbox; @@ -282,6 +311,89 @@ BOOST_AUTO_TEST_CASE( boxlayout_insert_remove ) BOOST_CHECK_EQUAL(hbox.itemAt(0), w1); } +//------------------------------------------------------------------------------ +BOOST_AUTO_TEST_CASE( boxlayout_hfw ) +{ + TestWidgetRef w1( new TestWidgetHFW( SGVec2i(16, 16), + SGVec2i(32, 32) ) ), + w2( new TestWidgetHFW( SGVec2i(24, 24), + SGVec2i(48, 48) ) ); + + BOOST_CHECK_EQUAL(w1->heightForWidth(16), 64); + BOOST_CHECK_EQUAL(w1->minimumHeightForWidth(16), 16); + BOOST_CHECK_EQUAL(w2->heightForWidth(24), 96); + BOOST_CHECK_EQUAL(w2->minimumHeightForWidth(24), 24); + + TestWidgetRef w_no_hfw( new TestWidget( SGVec2i(16, 16), + SGVec2i(32, 32) ) ); + BOOST_CHECK(!w_no_hfw->hasHeightForWidth()); + BOOST_CHECK_EQUAL(w_no_hfw->heightForWidth(16), -1); + BOOST_CHECK_EQUAL(w_no_hfw->minimumHeightForWidth(16), -1); + + // horizontal + sc::HBoxLayout hbox; + hbox.setSpacing(5); + hbox.addItem(w1); + hbox.addItem(w2); + + BOOST_CHECK_EQUAL(hbox.heightForWidth(45), w2->heightForWidth(24)); + BOOST_CHECK_EQUAL(hbox.heightForWidth(85), w2->heightForWidth(48)); + + hbox.addItem(w_no_hfw); + + BOOST_CHECK_EQUAL(hbox.heightForWidth(66), 96); + BOOST_CHECK_EQUAL(hbox.heightForWidth(122), 48); + BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(66), 24); + BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(122), 16); + + hbox.setGeometry(SGRecti(0, 0, 66, 24)); + + BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 16, 24)); + BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(21, 0, 24, 24)); + BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(50, 0, 16, 24)); + + // vertical + sc::VBoxLayout vbox; + vbox.setSpacing(5); + vbox.addItem(w1); + vbox.addItem(w2); + + BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 143); + BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 74); + BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 39); + BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 22); + + vbox.addItem(w_no_hfw); + + BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 180); + BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 111); + BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 60); + BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 43); + + SGVec2i min_size = vbox.minimumSize(), + size_hint = vbox.sizeHint(); + + BOOST_CHECK_EQUAL(min_size, SGVec2i(24, 66)); + BOOST_CHECK_EQUAL(size_hint, SGVec2i(48, 122)); + + vbox.setGeometry(SGRecti(0, 0, 24, 122)); + + BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 24, 33)); + BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 38, 24, 47)); + BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32)); + + // Vertical layouting modifies size hints, so check if they are correctly + // restored + BOOST_CHECK_EQUAL(min_size, vbox.minimumSize()); + BOOST_CHECK_EQUAL(size_hint, vbox.sizeHint()); + + vbox.setGeometry(SGRecti(0, 0, 50, 122)); + + BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 50, 44)); + BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 49, 50, 70)); + BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 124, 50, 56)); +} + //------------------------------------------------------------------------------ BOOST_AUTO_TEST_CASE( nasal_layout ) { diff --git a/simgear/math/SGRect.hxx b/simgear/math/SGRect.hxx index 9c51f091..da5cdcfe 100644 --- a/simgear/math/SGRect.hxx +++ b/simgear/math/SGRect.hxx @@ -34,7 +34,7 @@ class SGRect } - SGRect(const SGVec2& pt): + explicit SGRect(const SGVec2& pt): _min(pt), _max(pt) { diff --git a/simgear/nasal/cppbind/detail/from_nasal_helper.hxx b/simgear/nasal/cppbind/detail/from_nasal_helper.hxx index 6d84bf83..86d3c55e 100644 --- a/simgear/nasal/cppbind/detail/from_nasal_helper.hxx +++ b/simgear/nasal/cppbind/detail/from_nasal_helper.hxx @@ -144,6 +144,9 @@ namespace nasal boost::function from_nasal_helper(naContext c, naRef ref, boost::function*) { + if( naIsNil(ref) ) + return boost::function(); + if( !naIsCode(ref) && !naIsCCode(ref) && !naIsFunc(ref) ) -- 2.39.5