]> git.mxchange.org Git - simgear.git/commitdiff
canvas::Layout: support height-for-width layouting.
authorThomas Geymayer <tomgey@gmail.com>
Thu, 12 Jun 2014 22:03:40 +0000 (00:03 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Thu, 12 Jun 2014 22:03:40 +0000 (00:03 +0200)
simgear/canvas/layout/BoxLayout.cxx
simgear/canvas/layout/BoxLayout.hxx
simgear/canvas/layout/Layout.cxx
simgear/canvas/layout/Layout.hxx
simgear/canvas/layout/LayoutItem.cxx
simgear/canvas/layout/LayoutItem.hxx
simgear/canvas/layout/NasalWidget.cxx
simgear/canvas/layout/NasalWidget.hxx
simgear/canvas/layout/canvas_layout_test.cxx
simgear/math/SGRect.hxx
simgear/nasal/cppbind/detail/from_nasal_helper.hxx

index a4dbd54e109af3277b07a5387a562345cc50b79d..a981db99c726020325b9add8a3e0837eca1aa06b 100644 (file)
@@ -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<SpacerItem*>(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<BoxLayout*>(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<int>::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];
index 4bf9fd1a1bda9580b3ff96aa581e63a238de95b1..4ee16c3cd6e581419480f09e3fbba1ec0eaba7a0 100644 (file)
@@ -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;
index a61d2780427e729c7e43ebd20760c161078349a0..b7e29c361c43c52d11c919513ca0df5918ad6308 100644 (file)
@@ -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;
index dc563023cb2402d2b527637a0cb59dfe93cba521..37ab1424fac600dfbd82a239caeb8644095e55f4 100644 (file)
@@ -83,10 +83,14 @@ namespace canvas
                 padding,      //<! padding before element (layouted)
                 size,         //<! layouted size
                 stretch;      //<! stretch factor
-        bool    done;         //<! layouting done
+        bool    has_hfw : 1,  //<! height for width
+                done : 1;     //<! layouting done
 
         /** Clear values (reset to default/empty state) */
         void reset();
+
+        int hfw(int w) const;
+        int mhfw(int w) const;
       };
 
       /**
index 3ebeeddc6109f1d8a9677cc7e62ba9bdfa042cff..1dfc07d935a9e872455101c57c98c9774172139c 100644 (file)
@@ -78,6 +78,24 @@ namespace canvas
     return _max_size;
   }
 
+  //----------------------------------------------------------------------------
+  bool LayoutItem::hasHeightForWidth() const
+  {
+    return false;
+  }
+
+  //----------------------------------------------------------------------------
+  int LayoutItem::heightForWidth(int w) const
+  {
+    return -1;
+  }
+
+  //------------------------------------------------------------------------------
+  int LayoutItem::minimumHeightForWidth(int w) const
+  {
+    return heightForWidth(w);
+  }
+
   //----------------------------------------------------------------------------
   void LayoutItem::invalidate()
   {
index 6654349a7a4a7218726bcfbc177025de794d589b..810c1a553e3f789320ac8ae624e248c50593a52c 100644 (file)
@@ -63,6 +63,10 @@ namespace canvas
        */
       SGVec2i maximumSize() const;
 
+      virtual bool hasHeightForWidth() const;
+      virtual int heightForWidth(int w) const;
+      virtual int minimumHeightForWidth(int w) const;
+
       /**
        * Mark all cached data as invalid and require it to be recalculated.
        */
index 62e0b78f6fe137aa7b65814d3cca32000803f7b3..d512b7fe96d7657db052816531e723b93e819e5b 100644 (file)
@@ -65,6 +65,20 @@ namespace canvas
     _set_geometry = func;
   }
 
+  //----------------------------------------------------------------------------
+  void NasalWidget::setHeightForWidthFunc(const HeightForWidthFunc& func)
+  {
+    _height_for_width = func;
+    invalidateParent();
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setMinimumHeightForWidthFunc(const HeightForWidthFunc& func)
+  {
+    _min_height_for_width = func;
+    invalidateParent();
+  }
+
   //----------------------------------------------------------------------------
   void NasalWidget::setSizeHint(const SGVec2i& s)
   {
@@ -97,6 +111,28 @@ namespace canvas
     invalidateParent();
   }
 
+  //----------------------------------------------------------------------------
+  bool NasalWidget::hasHeightForWidth() const
+  {
+    return !_height_for_width.empty() || !_min_height_for_width.empty();
+  }
+
+  //----------------------------------------------------------------------------
+  int NasalWidget::heightForWidth(int w) const
+  {
+    return callHeightForWidthFunc( _height_for_width.empty()
+                                 ? _min_height_for_width
+                                 : _height_for_width, w );
+  }
+
+  //----------------------------------------------------------------------------
+  int NasalWidget::minimumHeightForWidth(int w) const
+  {
+    return callHeightForWidthFunc( _min_height_for_width.empty()
+                                 ? _height_for_width
+                                 : _min_height_for_width, w );
+  }
+
   //----------------------------------------------------------------------------
   static naRef f_makeNasalWidget(const nasal::CallContext& ctx)
   {
@@ -112,6 +148,9 @@ namespace canvas
       .bases<LayoutItemRef>()
       .bases<nasal::ObjectRef>()
       .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<NasalWidget*>(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
   {
index e07ffb5389d034a6099e1151b771ef0f970695de..1fc4446a1cdd1e961d6b11b41a70a49b453ef3f5 100644 (file)
@@ -40,6 +40,7 @@ namespace canvas
     public:
 
       typedef boost::function<void (nasal::Me, const SGRecti&)> SetGeometryFunc;
+      typedef boost::function<int (nasal::Me, int)> 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;
index 4750aae879aac664ec7cc85898826a3882ca882e..42524e85204febc06e20e06b3e525deecf66e8ad 100644 (file)
@@ -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<TestWidget> 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 )
 {
index 9c51f091def6d591d6099f920e19812dc3522a91..da5cdcfe65dcbace4949ea6a7174cd4764d8a32f 100644 (file)
@@ -34,7 +34,7 @@ class SGRect
 
     }
 
-    SGRect(const SGVec2<T>& pt):
+    explicit SGRect(const SGVec2<T>& pt):
       _min(pt),
       _max(pt)
     {
index 6d84bf8329a92ae1837255a910ea9b5bf5478585..86d3c55e635e3ac1582d4ca6762f332b0b348773 100644 (file)
@@ -144,6 +144,9 @@ namespace nasal
   boost::function<Sig>
   from_nasal_helper(naContext c, naRef ref, boost::function<Sig>*)
   {
+    if( naIsNil(ref) )
+      return boost::function<Sig>();
+
     if(    !naIsCode(ref)
         && !naIsCCode(ref)
         && !naIsFunc(ref) )