]> git.mxchange.org Git - simgear.git/commitdiff
canvas::Layout: support for contents margins.
authorThomas Geymayer <tomgey@gmail.com>
Mon, 21 Jul 2014 22:48:42 +0000 (00:48 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Tue, 22 Jul 2014 10:00:24 +0000 (12:00 +0200)
simgear/canvas/Canvas.cxx
simgear/canvas/Canvas.hxx
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

index c7898b4535df604b07de4b0ccda9fa545af708af..5ca1eb95e605929929361a378a0274d87c7b2d83 100644 (file)
@@ -196,7 +196,6 @@ namespace canvas
   {
     _layout = layout;
     _layout->setCanvas(this);
-    _status |= LAYOUT_DIRTY;
   }
 
   //----------------------------------------------------------------------------
@@ -256,15 +255,7 @@ namespace canvas
     }
 
     if( _layout )
-    {
-      if( (_status & LAYOUT_DIRTY) )
-      {
-        _layout->setGeometry(SGRecti(0, 0, _view_width, _view_height));
-        _status &= ~LAYOUT_DIRTY;
-      }
-      else
-        _layout->update();
-    }
+      _layout->setGeometry(SGRecti(0, 0, _view_width, _view_height));
 
     if( _visible || _render_always )
     {
@@ -410,7 +401,6 @@ namespace canvas
     if( _view_width == w )
       return;
     _view_width = w;
-    _status |= LAYOUT_DIRTY;
 
     _texture.setViewSize(_view_width, _view_height);
   }
@@ -421,7 +411,6 @@ namespace canvas
     if( _view_height == h )
       return;
     _view_height = h;
-    _status |= LAYOUT_DIRTY;
 
     _texture.setViewSize(_view_width, _view_height);
   }
index 2c817f61bcbf98fe17805b505847204df1d41f7f..cf9ae2a527401d91643f12b7e32a20d864e92766 100644 (file)
@@ -53,8 +53,7 @@ namespace canvas
       {
         STATUS_OK,
         STATUS_DIRTY   = 1,
-        LAYOUT_DIRTY   = STATUS_DIRTY << 1,
-        MISSING_SIZE_X = LAYOUT_DIRTY << 1,
+        MISSING_SIZE_X = STATUS_DIRTY << 1,
         MISSING_SIZE_Y = MISSING_SIZE_X << 1,
         MISSING_SIZE   = MISSING_SIZE_X | MISSING_SIZE_Y,
         CREATE_FAILED  = MISSING_SIZE_Y << 1
index 3da5d98e29242627c32c2f40efb2880e1ebd3127..3e1d37ffde09a9336e6019e8de3f9fbd6481454e 100644 (file)
@@ -229,26 +229,6 @@ namespace canvas
     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)
   {
@@ -400,6 +380,27 @@ namespace canvas
     return _max_size;
   }
 
+
+  //----------------------------------------------------------------------------
+  int BoxLayout::heightForWidthImpl(int w) const
+  {
+    if( !hasHeightForWidth() )
+      return -1;
+
+    updateWFHCache(w);
+    return _hfw_height;
+  }
+
+  //----------------------------------------------------------------------------
+  int BoxLayout::minimumHeightForWidthImpl(int w) const
+  {
+    if( !hasHeightForWidth() )
+      return -1;
+
+    updateWFHCache(w);
+    return _hfw_min_height;
+  }
+
   //----------------------------------------------------------------------------
   void BoxLayout::doLayout(const SGRecti& geom)
   {
index 639871e9868e776333a05a3bea5d44a1ef910a83..1978439a57a0bd293f1319e7d2c975d2b43185b5 100644 (file)
@@ -86,8 +86,6 @@ namespace canvas
       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);
 
@@ -121,6 +119,9 @@ namespace canvas
       virtual SGVec2i minimumSizeImpl() const;
       virtual SGVec2i maximumSizeImpl() const;
 
+      virtual int heightForWidthImpl(int w) const;
+      virtual int minimumHeightForWidthImpl(int w) const;
+
       virtual void doLayout(const SGRecti& geom);
 
       virtual void visibilityChanged(bool visible);
index d3ae38867007e97fb9dd23673b78eeaef1afb3a5..ee24be6e1853678768e5faf79e7766b2049307f5 100644 (file)
@@ -24,36 +24,6 @@ namespace simgear
 namespace canvas
 {
 
-  //----------------------------------------------------------------------------
-  void Layout::update()
-  {
-    if( !(_flags & (LAYOUT_DIRTY | SIZE_INFO_DIRTY)) || !isVisible() )
-      return;
-
-    doLayout(_geometry);
-
-    _flags &= ~LAYOUT_DIRTY;
-  }
-
-  //----------------------------------------------------------------------------
-  void Layout::invalidate()
-  {
-    LayoutItem::invalidate();
-    _flags |= LAYOUT_DIRTY;
-  }
-
-  //----------------------------------------------------------------------------
-  void Layout::setGeometry(const SGRecti& geom)
-  {
-    if( geom != _geometry )
-    {
-      _geometry = geom;
-      _flags |= LAYOUT_DIRTY;
-    }
-
-    update();
-  }
-
   //----------------------------------------------------------------------------
   void Layout::removeItem(const LayoutItemRef& item)
   {
@@ -108,6 +78,14 @@ namespace canvas
       return layout_item->minimumSize().y();
   }
 
+  //----------------------------------------------------------------------------
+  void Layout::contentsRectChanged(const SGRecti& rect)
+  {
+    doLayout(rect);
+
+    _flags &= ~LAYOUT_DIRTY;
+  }
+
   //----------------------------------------------------------------------------
   void Layout::distribute(std::vector<ItemData>& items, const ItemData& space)
   {
index f28763cbde4836d825c3cb83c76c90fc5a9c5a9b..e543db259676602ea187c0a68b905515c8de81da 100644 (file)
@@ -31,11 +31,6 @@ namespace canvas
     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;
@@ -74,8 +69,7 @@ namespace canvas
     protected:
       enum LayoutFlags
       {
-        LAYOUT_DIRTY = LayoutItem::LAST_FLAG << 1,
-        LAST_FLAG = LAYOUT_DIRTY
+        LAST_FLAG = LayoutItem::LAST_FLAG
       };
 
       struct ItemData
@@ -99,6 +93,8 @@ namespace canvas
         int mhfw(int w) const;
       };
 
+      virtual void contentsRectChanged(const SGRecti& rect);
+
       /**
        * Override to implement the actual layouting
        */
index a6e30dfbea8a8b6ffb523d25d6d41cd5973fc352..f585b257808e7f379da3ac77f512d7bcb39bbf67 100644 (file)
@@ -23,6 +23,54 @@ namespace simgear
 {
 namespace canvas
 {
+
+  //----------------------------------------------------------------------------
+  Margins::Margins(int m):
+    l(m), t(m), r(m), b(m)
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  Margins::Margins(int h, int v):
+    l(h), t(v),
+    r(h), b(v)
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  Margins::Margins(int l, int t, int r, int b):
+    l(l), t(t), r(r), b(b)
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  int Margins::horiz() const
+  {
+    return l + r;
+  }
+
+  //----------------------------------------------------------------------------
+  int Margins::vert() const
+  {
+    return t + b;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i Margins::size() const
+  {
+    return SGVec2i(horiz(), vert());
+  }
+
+  //----------------------------------------------------------------------------
+  bool Margins::isNull() const
+  {
+    return l == 0 && t == 0 && r == 0 && b == 0;
+  }
+
+  //----------------------------------------------------------------------------
   const SGVec2i LayoutItem::MAX_SIZE( SGLimits<int>::max(),
                                       SGLimits<int>::max() );
 
@@ -42,6 +90,44 @@ namespace canvas
 
   }
 
+  //----------------------------------------------------------------------------
+  void LayoutItem::setContentsMargins(const Margins& margins)
+  {
+    _margins = margins;
+  }
+
+  //----------------------------------------------------------------------------
+  void LayoutItem::setContentsMargins(int left, int top, int right, int bottom)
+  {
+    _margins.l = left;
+    _margins.t = top;
+    _margins.r = right;
+    _margins.b = bottom;
+  }
+
+  //----------------------------------------------------------------------------
+  void LayoutItem::setContentsMargin(int margin)
+  {
+    setContentsMargins(margin, margin, margin, margin);
+  }
+
+  //----------------------------------------------------------------------------
+  Margins LayoutItem::getContentsMargins() const
+  {
+    return _margins;
+  }
+
+  //----------------------------------------------------------------------------
+  SGRecti LayoutItem::contentsRect() const
+  {
+    return SGRecti(
+      _geometry.x() + _margins.l,
+      _geometry.y() + _margins.t,
+      std::max(0, _geometry.width() - _margins.horiz()),
+      std::max(0, _geometry.height() - _margins.vert())
+    );
+  }
+
   //----------------------------------------------------------------------------
   SGVec2i LayoutItem::sizeHint() const
   {
@@ -51,7 +137,7 @@ namespace canvas
       _flags &= ~SIZE_HINT_DIRTY;
     }
 
-    return _size_hint;
+    return addClipOverflow(_size_hint, _margins.size());
   }
 
   //----------------------------------------------------------------------------
@@ -63,7 +149,7 @@ namespace canvas
       _flags &= ~MINIMUM_SIZE_DIRTY;
     }
 
-    return _min_size;
+    return addClipOverflow(_min_size, _margins.size());
   }
 
   //----------------------------------------------------------------------------
@@ -75,7 +161,7 @@ namespace canvas
       _flags &= ~MAXIMUM_SIZE_DIRTY;
     }
 
-    return _max_size;
+    return addClipOverflow(_max_size, _margins.size());
   }
 
   //----------------------------------------------------------------------------
@@ -87,13 +173,15 @@ namespace canvas
   //----------------------------------------------------------------------------
   int LayoutItem::heightForWidth(int w) const
   {
-    return -1;
+    int h = heightForWidthImpl(w - _margins.horiz());
+    return h < 0 ? -1 : SGMisc<int>::addClipOverflow(h, _margins.vert());
   }
 
   //------------------------------------------------------------------------------
   int LayoutItem::minimumHeightForWidth(int w) const
   {
-    return heightForWidth(w);
+    int h = minimumHeightForWidthImpl(w - _margins.horiz());
+    return h < 0 ? -1 : SGMisc<int>::addClipOverflow(h, _margins.vert());
   }
 
   //----------------------------------------------------------------------------
@@ -122,7 +210,7 @@ namespace canvas
   //----------------------------------------------------------------------------
   void LayoutItem::invalidate()
   {
-    _flags |= SIZE_INFO_DIRTY;
+    _flags |= SIZE_INFO_DIRTY | LAYOUT_DIRTY;
     invalidateParent();
   }
 
@@ -134,10 +222,23 @@ namespace canvas
       parent->invalidate();
   }
 
+  //----------------------------------------------------------------------------
+  void LayoutItem::update()
+  {
+    if( (_flags & LAYOUT_DIRTY) && isVisible() )
+      contentsRectChanged( contentsRect() );
+  }
+
   //----------------------------------------------------------------------------
   void LayoutItem::setGeometry(const SGRecti& geom)
   {
-    _geometry = geom;
+    if( geom != _geometry )
+    {
+      _geometry = geom;
+      _flags |= LAYOUT_DIRTY;
+    }
+
+    update();
   }
 
   //----------------------------------------------------------------------------
@@ -197,6 +298,18 @@ namespace canvas
     return _max_size;
   }
 
+  //----------------------------------------------------------------------------
+  int LayoutItem::heightForWidthImpl(int w) const
+  {
+    return -1;
+  }
+
+  //------------------------------------------------------------------------------
+  int LayoutItem::minimumHeightForWidthImpl(int w) const
+  {
+    return heightForWidth(w);
+  }
+
   //----------------------------------------------------------------------------
   void LayoutItem::setVisibleInternal(bool visible)
   {
index e26e032fbcec80ace5046f5f93a15dcf9db8a60e..130c47c5f9957e152e45b6a4d9042fc3ac0284bc 100644 (file)
@@ -34,6 +34,53 @@ namespace canvas
   typedef SGSharedPtr<LayoutItem> LayoutItemRef;
   typedef SGWeakPtr<LayoutItem> LayoutItemWeakRef;
 
+  /**
+   * Holds the four margins for a rectangle.
+   */
+  struct Margins
+  {
+    int l, t, r, b;
+
+    /**
+     * Set all margins to the same value @a m.
+     */
+    explicit Margins(int m = 0);
+
+    /**
+     * Set horizontal and vertical margins to the same values @a h and @a v
+     * respectively.
+     *
+     * @param h Horizontal margins
+     * @param v Vertical margins
+     */
+    Margins(int h, int v);
+
+    /**
+     * Set the margins to the given values.
+     */
+    Margins(int left, int top, int right, int bottom);
+
+    /**
+     * Get the total horizontal margin (sum of left and right margin).
+     */
+    int horiz() const;
+
+    /**
+     * Get the total vertical margin (sum of top and bottom margin).
+     */
+    int vert() const;
+
+    /**
+     * Get total horizontal and vertical margin as vector.
+     */
+    SGVec2i size() const;
+
+    /**
+     * Returns true if all margins are 0.
+     */
+    bool isNull() const;
+  };
+
   /**
    * Base class for all layouting elements. Specializations either implement a
    * layouting algorithm or a widget.
@@ -49,6 +96,49 @@ namespace canvas
       LayoutItem();
       virtual ~LayoutItem();
 
+      /**
+       * Set the margins to use by the layout system around the item.
+       *
+       * The margins define the size of the clear area around an item. It
+       * increases the size hints and reduces the size of the geometry()
+       * available to child layouts and widgets.
+       *
+       * @see Margins
+       */
+      void setContentsMargins(const Margins& margins);
+
+      /**
+       * Set the individual margins.
+       *
+       * @see setContentsMargins(const Margins&)
+       */
+      void setContentsMargins(int left, int top, int right, int bottom);
+
+      /**
+       * Set all margins to the same value.
+       *
+       * @see setContentsMargins(const Margins&)
+       */
+      void setContentsMargin(int margin);
+
+      /**
+       * Get the currently used margins.
+       *
+       * @see setContentsMargins(const Margins&)
+       * @see Margins
+       */
+      Margins getContentsMargins() const;
+
+      /**
+       * Get the area available to the contents.
+       *
+       * This is equal to the geometry() reduced by the sizes of the margins.
+       *
+       * @see setContentsMargins(const Margins&)
+       * @see geometry()
+       */
+      SGRecti contentsRect() const;
+
       /**
        * Get the preferred size of this item.
        */
@@ -64,9 +154,36 @@ namespace canvas
        */
       SGVec2i maximumSize() const;
 
+      /**
+       * Returns true if this items preferred and minimum height depend on its
+       * width.
+       *
+       * The default implementation returns false. Reimplement for items
+       * providing height for width.
+       *
+       * @see heightForWidth()
+       * @see minimumHeightForWidth()
+       */
       virtual bool hasHeightForWidth() const;
-      virtual int heightForWidth(int w) const;
-      virtual int minimumHeightForWidth(int w) const;
+
+      /**
+       * Returns the preferred height for the given width @q w.
+       *
+       * Reimplement heightForWidthImpl() for items providing height for width.
+       *
+       * @see hasHeightForWidth()
+       */
+      int heightForWidth(int w) const;
+
+      /**
+       * Returns the minimum height for the given width @q w.
+       *
+       * Reimplement minimumHeightForWidthImpl() for items providing height for
+       * width.
+       *
+       * @see hasHeightForWidth()
+       */
+      int minimumHeightForWidth(int w) const;
 
       virtual void setVisible(bool visible);
       virtual bool isVisible() const;
@@ -82,10 +199,15 @@ namespace canvas
       virtual void invalidate();
 
       /**
-       * Mark all cached data of parent item as invalid (if it is known)
+       * Mark all cached data of parent item as invalid (if the parent is set).
        */
       void invalidateParent();
 
+      /**
+       * Apply any changes not applied yet.
+       */
+      void update();
+
       /**
        * Set position and size of this element. For layouts this triggers a
        * recalculation of the layout.
@@ -134,13 +256,15 @@ namespace canvas
                         | MAXIMUM_SIZE_DIRTY,
         EXPLICITLY_HIDDEN = MAXIMUM_SIZE_DIRTY << 1,
         VISIBLE = EXPLICITLY_HIDDEN << 1,
-        LAST_FLAG = VISIBLE
+        LAYOUT_DIRTY = VISIBLE << 1,
+        LAST_FLAG = LAYOUT_DIRTY
       };
 
       CanvasWeakPtr     _canvas;
       LayoutItemWeakRef _parent;
 
       SGRecti           _geometry;
+      Margins           _margins;
 
       mutable uint32_t  _flags;
       mutable SGVec2i   _size_hint,
@@ -151,11 +275,43 @@ namespace canvas
       virtual SGVec2i minimumSizeImpl() const;
       virtual SGVec2i maximumSizeImpl() const;
 
+      /**
+       * Returns the preferred height for the given width @q w.
+       *
+       * The default implementation returns -1, indicating that the preferred
+       * height is independent of the given width.
+       *
+       * Reimplement this function for items supporting height for width.
+       *
+       * @note Do not take margins into account, as this is already handled
+       *       before calling this function.
+       *
+       * @see hasHeightForWidth()
+       */
+      virtual int heightForWidthImpl(int w) const;
+
+      /**
+       * Returns the minimum height for the given width @q w.
+       *
+       * The default implementation returns -1, indicating that the minimum
+       * height is independent of the given width.
+       *
+       * Reimplement this function for items supporting height for width.
+       *
+       * @note Do not take margins into account, as this is already handled
+       *       before calling this function.
+       *
+       * @see hasHeightForWidth()
+       */
+      virtual int minimumHeightForWidthImpl(int w) const;
+
       /**
        * @return whether the visibility has changed.
        */
       void setVisibleInternal(bool visible);
 
+      virtual void contentsRectChanged(const SGRecti& rect) {};
+
       virtual void visibilityChanged(bool visible) {}
 
       /**
index 0cf172e137846e4f8d9a398513da054d09970495..5c027e8518359be9470a54f39c766ae63b5313e8 100644 (file)
@@ -46,41 +46,6 @@ namespace canvas
     onRemove();
   }
 
-  //----------------------------------------------------------------------------
-  void NasalWidget::invalidate()
-  {
-    LayoutItem::invalidate();
-    _flags |= LAYOUT_DIRTY;
-  }
-
-  //----------------------------------------------------------------------------
-  void NasalWidget::setGeometry(const SGRect<int>& geom)
-  {
-    if( _geometry != geom )
-    {
-      _geometry = geom;
-      _flags |= LAYOUT_DIRTY;
-    }
-
-    if( !_set_geometry || !(_flags & LAYOUT_DIRTY) )
-      return;
-
-    try
-    {
-      nasal::Context c;
-      _set_geometry(nasal::to_nasal(c, this), geom);
-      _flags &= ~LAYOUT_DIRTY;
-    }
-    catch( std::exception const& ex )
-    {
-      SG_LOG(
-        SG_GUI,
-        SG_WARN,
-        "NasalWidget::setGeometry: callback error: '" << ex.what() << "'"
-      );
-    }
-  }
-
   //----------------------------------------------------------------------------
   void NasalWidget::onRemove()
   {
@@ -186,22 +151,6 @@ namespace canvas
     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)
   {
@@ -285,6 +234,46 @@ namespace canvas
     );
   }
 
+
+  //----------------------------------------------------------------------------
+  int NasalWidget::heightForWidthImpl(int w) const
+  {
+    return callHeightForWidthFunc( _height_for_width.empty()
+                                 ? _min_height_for_width
+                                 : _height_for_width, w );
+  }
+
+  //----------------------------------------------------------------------------
+  int NasalWidget::minimumHeightForWidthImpl(int w) const
+  {
+    return callHeightForWidthFunc( _min_height_for_width.empty()
+                                 ? _height_for_width
+                                 : _min_height_for_width, w );
+  }
+
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::contentsRectChanged(const SGRect<int>& rect)
+  {
+    if( !_set_geometry )
+      return;
+
+    try
+    {
+      nasal::Context c;
+      _set_geometry(nasal::to_nasal(c, this), rect);
+      _flags &= ~LAYOUT_DIRTY;
+    }
+    catch( std::exception const& ex )
+    {
+      SG_LOG(
+        SG_GUI,
+        SG_WARN,
+        "NasalWidget::setGeometry: callback error: '" << ex.what() << "'"
+      );
+    }
+  }
+
   //----------------------------------------------------------------------------
   void NasalWidget::visibilityChanged(bool visible)
   {
index 34877e276e60251abafc9a30aecdc24f8e93f04b..504a41e7afa2346c073985f3c8f83ab53ed0125c 100644 (file)
@@ -51,8 +51,6 @@ namespace canvas
 
       ~NasalWidget();
 
-      virtual void invalidate();
-      virtual void setGeometry(const SGRecti& geom);
       virtual void onRemove();
 
       void setSetGeometryFunc(const SetGeometryFunc& func);
@@ -85,8 +83,6 @@ namespace canvas
       void setLayoutMaximumSize(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
@@ -118,6 +114,11 @@ namespace canvas
       virtual SGVec2i minimumSizeImpl() const;
       virtual SGVec2i maximumSizeImpl() const;
 
+      virtual int heightForWidthImpl(int w) const;
+      virtual int minimumHeightForWidthImpl(int w) const;
+
+      virtual void contentsRectChanged(const SGRecti& rect);
+
       virtual void visibilityChanged(bool visible);
 
   };
index ecab633397164be25d3fb957f52d2676877ae21e..a95aa76035db2ed0ed776a638123a933cc3d25f6 100644 (file)
@@ -64,13 +64,8 @@ class TestWidget:
     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; }
@@ -78,7 +73,7 @@ class TestWidget:
     virtual void visibilityChanged(bool visible)
     {
       if( !visible )
-        _geom.set(0, 0, 0, 0);
+        _geometry.set(0, 0, 0, 0);
     }
 };
 
@@ -99,12 +94,12 @@ class TestWidgetHFW:
       return true;
     }
 
-    virtual int heightForWidth(int w) const
+    virtual int heightForWidthImpl(int w) const
     {
       return _size_hint.x() * _size_hint.y() / w;
     }
 
-    virtual int minimumHeightForWidth(int w) const
+    virtual int minimumHeightForWidthImpl(int w) const
     {
       return _min_size.x() * _min_size.y() / w;
     }
@@ -411,6 +406,96 @@ BOOST_AUTO_TEST_CASE( boxlayout_visibility )
   BOOST_CHECK_EQUAL(w3->geometry(), SGRecti(50, 0, 19, 32));
 }
 
+//------------------------------------------------------------------------------
+BOOST_AUTO_TEST_CASE( boxlayout_contents_margins )
+{
+  sc::Margins m;
+
+  BOOST_REQUIRE(m.isNull());
+
+  m = sc::Margins(5);
+
+  BOOST_REQUIRE_EQUAL(m.l, 5);
+  BOOST_REQUIRE_EQUAL(m.t, 5);
+  BOOST_REQUIRE_EQUAL(m.r, 5);
+  BOOST_REQUIRE_EQUAL(m.b, 5);
+
+  m = sc::Margins(6, 7);
+
+  BOOST_REQUIRE_EQUAL(m.l, 6);
+  BOOST_REQUIRE_EQUAL(m.t, 7);
+  BOOST_REQUIRE_EQUAL(m.r, 6);
+  BOOST_REQUIRE_EQUAL(m.b, 7);
+
+  BOOST_REQUIRE_EQUAL(m.horiz(), 12);
+  BOOST_REQUIRE_EQUAL(m.vert(), 14);
+  BOOST_REQUIRE(!m.isNull());
+
+  m = sc::Margins(1, 2, 3, 4);
+
+  BOOST_REQUIRE_EQUAL(m.l, 1);
+  BOOST_REQUIRE_EQUAL(m.t, 2);
+  BOOST_REQUIRE_EQUAL(m.r, 3);
+  BOOST_REQUIRE_EQUAL(m.b, 4);
+
+  BOOST_REQUIRE_EQUAL(m.horiz(), 4);
+  BOOST_REQUIRE_EQUAL(m.vert(), 6);
+  BOOST_REQUIRE_EQUAL(m.size(), SGVec2i(4, 6));
+
+  sc::BoxLayoutRef hbox( new sc::HBoxLayout );
+
+  hbox->setContentsMargins(5, 10, 15, 20);
+
+  BOOST_CHECK_EQUAL(hbox->minimumSize(), SGVec2i(20, 30));
+  BOOST_CHECK_EQUAL(hbox->sizeHint(),    SGVec2i(20, 30));
+  BOOST_CHECK_EQUAL(hbox->maximumSize(), SGVec2i(20, 30));
+
+  hbox->setGeometry(SGRecti(0, 0, 30, 40));
+
+  BOOST_CHECK_EQUAL(hbox->contentsRect(), SGRecti(5, 10, 10, 10));
+
+  TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
+                                    SGVec2i(32, 32) ) ),
+                w2( new TestWidget(*w1) ),
+                w3( new TestWidget(*w1) );
+
+  w1->setContentsMargin(5);
+  w2->setContentsMargin(6);
+  w3->setContentsMargin(7);
+
+  BOOST_CHECK_EQUAL(w1->minimumSize(), SGVec2i(26, 26));
+  BOOST_CHECK_EQUAL(w1->sizeHint(),    SGVec2i(42, 42));
+  BOOST_CHECK_EQUAL(w1->maximumSize(), sc::LayoutItem::MAX_SIZE);
+
+  BOOST_CHECK_EQUAL(w2->minimumSize(), SGVec2i(28, 28));
+  BOOST_CHECK_EQUAL(w2->sizeHint(),    SGVec2i(44, 44));
+  BOOST_CHECK_EQUAL(w2->maximumSize(), sc::LayoutItem::MAX_SIZE);
+
+  BOOST_CHECK_EQUAL(w3->minimumSize(), SGVec2i(30, 30));
+  BOOST_CHECK_EQUAL(w3->sizeHint(),    SGVec2i(46, 46));
+  BOOST_CHECK_EQUAL(w3->maximumSize(), sc::LayoutItem::MAX_SIZE);
+
+  hbox->addItem(w1);
+  hbox->addItem(w2);
+  hbox->addItem(w3);
+
+  BOOST_CHECK_EQUAL(hbox->minimumSize(), SGVec2i(114, 60));
+  BOOST_CHECK_EQUAL(hbox->sizeHint(),    SGVec2i(162, 76));
+  BOOST_CHECK_EQUAL(hbox->maximumSize(), sc::LayoutItem::MAX_SIZE);
+
+  hbox->setGeometry(SGRecti(0, 0, hbox->sizeHint().x(), hbox->sizeHint().y()));
+
+  BOOST_CHECK_EQUAL(hbox->contentsRect(), SGRecti(5, 10, 142, 46));
+
+  BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(5,   10, 42, 46));
+  BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(52,  10, 44, 46));
+  BOOST_CHECK_EQUAL(w3->geometry(), SGRecti(101, 10, 46, 46));
+
+  BOOST_CHECK_EQUAL(w1->contentsRect(), SGRecti(10,  15, 32, 36));
+  BOOST_CHECK_EQUAL(w2->contentsRect(), SGRecti(58,  16, 32, 34));
+  BOOST_CHECK_EQUAL(w3->contentsRect(), SGRecti(108, 17, 32, 32));
+}
+
 //------------------------------------------------------------------------------
 BOOST_AUTO_TEST_CASE( boxlayout_hfw )
 {
@@ -502,6 +587,7 @@ BOOST_AUTO_TEST_CASE( boxlayout_hfw )
   BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32));
 }
 
+//------------------------------------------------------------------------------
 // TODO extend to_nasal_helper for automatic argument conversion
 static naRef f_Widget_visibilityChanged(nasal::CallContext ctx)
 {