]> git.mxchange.org Git - simgear.git/commitdiff
canvas::Layout: support for alignment.
authorThomas Geymayer <tomgey@gmail.com>
Sun, 3 Aug 2014 10:02:39 +0000 (12:02 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Sun, 3 Aug 2014 10:02:39 +0000 (12:02 +0200)
Set alignment inside layouts, taking care of where
excess space is distributed.

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/canvas_layout_test.cxx

index 3e1d37ffde09a9336e6019e8de3f9fbd6481454e..1c470f4ff33bf70c3de407d3cc36fb8a171d6920 100644 (file)
@@ -46,9 +46,11 @@ namespace canvas
   }
 
   //----------------------------------------------------------------------------
-  void BoxLayout::addItem(const LayoutItemRef& item, int stretch)
+  void BoxLayout::addItem( const LayoutItemRef& item,
+                           int stretch,
+                           uint8_t alignment )
   {
-    insertItem(-1, item, stretch);
+    insertItem(-1, item, stretch, alignment);
   }
 
   //----------------------------------------------------------------------------
@@ -64,12 +66,18 @@ namespace canvas
   }
 
   //----------------------------------------------------------------------------
-  void BoxLayout::insertItem(int index, const LayoutItemRef& item, int stretch)
+  void BoxLayout::insertItem( int index,
+                              const LayoutItemRef& item,
+                              int stretch,
+                              uint8_t alignment )
   {
     ItemData item_data = {0};
     item_data.layout_item = item;
     item_data.stretch = std::max(0, stretch);
 
+    if( alignment != AlignFill )
+      item->setAlignment(alignment);
+
     if( SGWeakReferenced::count(this) )
       item->setParent(this);
     else
@@ -270,6 +278,11 @@ namespace canvas
       item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
       item_data.has_hfw = item.hasHeightForWidth();
 
+      uint8_t alignment_mask = horiz()
+                             ? AlignHorizontal_Mask
+                             : AlignVertical_Mask;
+      item_data.has_align = (item.alignment() & alignment_mask) != 0;
+
       if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
       {
         if( is_first )
@@ -452,7 +465,10 @@ namespace canvas
     _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 size( 0,
+                 // Always assign all available space. Alignment handles final
+                 // size.
+                 (geom.size().*_get_fixed_coord)() );
     SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
                      (geom.pos().*_get_fixed_coord)() );
 
@@ -467,16 +483,7 @@ namespace canvas
         continue;
 
       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;
+      size.x() = data.size;
 
       data.layout_item->setGeometry(SGRecti(
         (cur_pos.*_get_layout_coord)(),
@@ -487,7 +494,6 @@ namespace canvas
 
       if( !reverse )
         cur_pos.x() += data.size;
-      cur_pos.y() -= offset_fixed;
     }
   }
 
index 2d33c0162d682025f555e40d33471bc5e7c658a7..c5fe6d289e33a59cc0456638ef270c39b08a094a 100644 (file)
@@ -49,13 +49,18 @@ namespace canvas
 
       virtual void addItem(const LayoutItemRef& item);
 
-      void addItem(const LayoutItemRef& item, int stretch);
+      void addItem( const LayoutItemRef& item,
+                    int stretch,
+                    uint8_t alignment = 0 );
 
       void addStretch(int stretch = 0);
 
       void addSpacing(int size);
 
-      void insertItem(int index, const LayoutItemRef& item, int stretch = 0);
+      void insertItem( int index,
+                       const LayoutItemRef& item,
+                       int stretch = 0,
+                       uint8_t alignment = 0 );
 
       void insertStretch(int index, int stretch = 0);
 
index ee24be6e1853678768e5faf79e7766b2049307f5..b7e5b76bd2b15f0c4a72738190bc3a30530d2750 100644 (file)
@@ -44,6 +44,19 @@ namespace canvas
       takeAt(0);
   }
 
+  //----------------------------------------------------------------------------
+  SGRecti Layout::alignmentRect(const SGRecti& geom) const
+  {
+    return alignment() == AlignFill
+         // Without explicit alignment (default == AlignFill) use the whole
+         // available space and let the layout and its items distribute the
+         // excess space.
+         ? geom
+
+         // Otherwise align according to flags.
+         : LayoutItem::alignmentRect(geom);
+  }
+
   //----------------------------------------------------------------------------
   void Layout::ItemData::reset()
   {
@@ -56,6 +69,7 @@ namespace canvas
     size        = 0;
     stretch     = 0;
     visible     = false;
+    has_align   = false;
     has_hfw     = false;
     done        = false;
   }
@@ -78,6 +92,16 @@ namespace canvas
       return layout_item->minimumSize().y();
   }
 
+  //----------------------------------------------------------------------------
+  Layout::Layout():
+    _num_not_done(0),
+    _sum_stretch(0),
+    _space_stretch(0),
+    _space_left(0)
+  {
+
+  }
+
   //----------------------------------------------------------------------------
   void Layout::contentsRectChanged(const SGRecti& rect)
   {
@@ -238,27 +262,59 @@ namespace canvas
     else
     {
       _space_left = space.size - space.max_size;
+      int num_align = 0;
       for(int i = 0; i < num_children; ++i)
       {
-        if( items[i].visible )
-          _num_not_done += 1;
+        if( !items[i].visible )
+          continue;
+
+        _num_not_done += 1;
+
+        if( items[i].has_align )
+          num_align += 1;
       }
 
+      SG_LOG(
+        SG_GUI,
+        SG_DEBUG,
+        "Distributing excess space:"
+             " not_done=" << _num_not_done
+         << ", num_align=" << num_align
+         << ", space_left=" << _space_left
+      );
+
       for(int i = 0; i < num_children; ++i)
       {
         ItemData& d = items[i];
         if( !d.visible )
           continue;
 
+        d.padding = d.padding_orig;
         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);
+        int space_add = 0;
+
+        if( d.has_align )
+        {
+          // Equally distribute superfluous space and let each child items
+          // alignment handle the exact usage.
+          space_add = _space_left / num_align;
+          num_align -= 1;
+
+          d.size += space_add;
+        }
+        else if( num_align <= 0 )
+        {
+          // Add superfluous space as padding
+          space_add = _space_left
+                    // Padding after last child...
+                    / (_num_not_done + 1);
+          _num_not_done -= 1;
+
+          d.padding += space_add;
+        }
 
-        _space_left -= d.padding - d.padding_orig;
-        _num_not_done -= 1;
+        _space_left -= space_add;
       }
     }
 
index 0fee1471f7b9df997533436fcccbeebf1f236703..5abbf07d428b1c10114351926895f7200df25396 100644 (file)
@@ -69,6 +69,18 @@ namespace canvas
        */
       virtual void clear();
 
+      /**
+       * Get the actual geometry of this layout given the rectangle \a geom
+       * taking into account the alignment flags and size hints. For layouts,
+       * if no alignment (different to AlignFill) is set, the whole area is
+       * used. Excess space is distributed by the layouting algorithm and
+       * handled by the individual children.
+       *
+       * @param geom    Area available to this layout.
+       * @return The resulting geometry for this layout.
+       */
+      virtual SGRecti alignmentRect(const SGRecti& geom) const;
+
     protected:
       enum LayoutFlags
       {
@@ -86,6 +98,7 @@ namespace canvas
                 size,         //!< layouted size
                 stretch;      //!< stretch factor
         bool    visible : 1,
+                has_align: 1, //!< Has alignment factor set (!= AlignFill)
                 has_hfw : 1,  //!< height for width
                 done : 1;     //!< layouting done
 
@@ -96,6 +109,8 @@ namespace canvas
         int mhfw(int w) const;
       };
 
+      Layout();
+
       virtual void contentsRectChanged(const SGRecti& rect);
 
       /**
index f585b257808e7f379da3ac77f512d7bcb39bbf67..8db90285604638f0d4292cb741450a9b0aca914b 100644 (file)
@@ -76,6 +76,7 @@ namespace canvas
 
   //----------------------------------------------------------------------------
   LayoutItem::LayoutItem():
+    _alignment(AlignFill),
     _flags(VISIBLE),
     _size_hint(0, 0),
     _min_size(0, 0),
@@ -184,6 +185,22 @@ namespace canvas
     return h < 0 ? -1 : SGMisc<int>::addClipOverflow(h, _margins.vert());
   }
 
+  //----------------------------------------------------------------------------
+  void LayoutItem::setAlignment(uint8_t align)
+  {
+    if( align == _alignment )
+      return;
+
+    _alignment = align;
+    invalidateParent();
+  }
+
+  //----------------------------------------------------------------------------
+  uint8_t LayoutItem::alignment() const
+  {
+    return _alignment;
+  }
+
   //----------------------------------------------------------------------------
   void LayoutItem::setVisible(bool visible)
   {
@@ -232,9 +249,10 @@ namespace canvas
   //----------------------------------------------------------------------------
   void LayoutItem::setGeometry(const SGRecti& geom)
   {
-    if( geom != _geometry )
+    SGRecti ar = alignmentRect(geom);
+    if( ar != _geometry )
     {
-      _geometry = geom;
+      _geometry = ar;
       _flags |= LAYOUT_DIRTY;
     }
 
@@ -247,6 +265,41 @@ namespace canvas
     return _geometry;
   }
 
+  //----------------------------------------------------------------------------
+  SGRecti LayoutItem::alignmentRect(const SGRecti& geom) const
+  {
+    uint8_t halign = alignment() & AlignHorizontal_Mask,
+            valign = alignment() & AlignVertical_Mask;
+
+    // Size
+    SGVec2i size = sizeHint();
+
+    if( halign == AlignFill )
+      size.x() = maximumSize().x();
+    size.x() = std::min(size.x(), geom.width());
+
+    if( valign == AlignFill )
+      size.y() = maximumSize().y();
+    else if( hasHeightForWidth() )
+      size.y() = heightForWidth(size.x());
+    size.y() = std::min(size.y(), geom.height());
+
+    // Position
+    SGVec2i pos = geom.pos();
+
+    if( halign & AlignRight )
+      pos.x() += geom.width() - size.x();
+    else if( !(halign & AlignLeft) )
+      pos.x() += (geom.width() - size.x()) / 2;
+
+    if( valign & AlignBottom )
+      pos.y() += geom.height() - size.y();
+    else if( !(valign & AlignTop) )
+      pos.y() += (geom.height() - size.y()) / 2;
+
+    return SGRecti(pos, pos + size);
+  }
+
   //----------------------------------------------------------------------------
   void LayoutItem::setCanvas(const CanvasWeakPtr& canvas)
   {
index 79347ccef45f39425fe00f4426c3b3ae8accd4c4..2807daa6f938a84f0636f154e6ee621f5d148290 100644 (file)
@@ -82,6 +82,34 @@ namespace canvas
     bool isNull() const;
   };
 
+  /**
+   * Flags for LayoutItem alignment inside Layouts.
+   *
+   * @note You can only use one horizontal and one vertical flag at the same.
+   */
+  enum AlignmentFlag
+  {
+    AlignFill = 0,            //!< Use all available space
+
+    AlignLeft = 0x01,         //!< Align with left edge
+    AlignRight = 0x02,        //!< Align with right edge
+    AlignHCenter = 0x04,      //!< Center horizontally in available space
+
+    AlignTop = 0x20,          //!< Align with top edge
+    AlignBottom = 0x40,       //!< Align with bottom edge
+    AlignVCenter = 0x80,      //!< Center vertically in available space
+
+    AlignCenter = AlignVCenter  //!< Center both vertically and horizontally
+                | AlignHCenter,
+
+    AlignHorizontal_Mask = AlignLeft
+                         | AlignRight
+                         | AlignHCenter,
+    AlignVertical_Mask = AlignTop
+                       | AlignBottom
+                       | AlignVCenter
+  };
+
   /**
    * Base class for all layouting elements. Specializations either implement a
    * layouting algorithm or a widget.
@@ -186,6 +214,22 @@ namespace canvas
        */
       int minimumHeightForWidth(int w) const;
 
+      /**
+       * Set alignment of item within @link{Layout Layouts}.
+       *
+       * @param alignment Bitwise combination of vertical and horizontal
+       *                  alignment flags.
+       * @see AlignmentFlag
+       */
+      void setAlignment(uint8_t alignment);
+
+      /**
+       * Get all alignment flags.
+       *
+       * @see AlignmentFlag
+       */
+      uint8_t alignment() const;
+
       virtual void setVisible(bool visible);
       virtual bool isVisible() const;
 
@@ -220,6 +264,20 @@ namespace canvas
        */
       virtual SGRecti geometry() const;
 
+      /**
+       * Get the actual geometry of this item given the rectangle \a geom
+       * taking into account the alignment flags and size hints.
+       *
+       * @param geom    Area available to this item.
+       * @return The resulting geometry for this item.
+       *
+       * @see setAlignment()
+       * @see minimumSize()
+       * @see maximumSize()
+       * @see sizeHint()
+       */
+      virtual SGRecti alignmentRect(const SGRecti& geom) const;
+
       /**
        * Set the canvas this item is attached to.
        */
@@ -266,6 +324,7 @@ namespace canvas
 
       SGRecti           _geometry;
       Margins           _margins;
+      uint8_t           _alignment;
 
       mutable uint32_t  _flags;
       mutable SGVec2i   _size_hint,
index a95aa76035db2ed0ed776a638123a933cc3d25f6..d695f2aa74377b761fc9c000e02b936f3fbefb17 100644 (file)
@@ -110,45 +110,45 @@ typedef SGSharedPtr<TestWidget> TestWidgetRef;
 //------------------------------------------------------------------------------
 BOOST_AUTO_TEST_CASE( horizontal_layout )
 {
-  sc::BoxLayout box_layout(sc::BoxLayout::BottomToTop);
-  box_layout.setSpacing(5);
+  sc::BoxLayoutRef box_layout(new sc::BoxLayout(sc::BoxLayout::BottomToTop));
+  box_layout->setSpacing(5);
 
-  BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::BottomToTop);
-  BOOST_CHECK_EQUAL(box_layout.spacing(), 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);
+  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);
+  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);
+  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));
+  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);
+  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));
+  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);
+  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));
+  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));
+  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));
@@ -157,12 +157,12 @@ BOOST_AUTO_TEST_CASE( horizontal_layout )
   TestWidgetRef stretch_widget( new TestWidget( SGVec2i(16, 16),
                                                 SGVec2i(32, 32),
                                                 SGVec2i(128, 32) ) );
-  box_layout.addItem(stretch_widget, 1);
-  box_layout.update();
+  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));
+  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.
@@ -172,63 +172,101 @@ BOOST_AUTO_TEST_CASE( horizontal_layout )
 
   // Test stretch factor
   TestWidgetRef fast_stretch( new TestWidget(*stretch_widget) );
-  sc::BoxLayout box_layout_stretch(sc::BoxLayout::LeftToRight);
+  sc::BoxLayoutRef box_layout_stretch(
+    new sc::BoxLayout(sc::BoxLayout::LeftToRight)
+  );
 
-  box_layout_stretch.addItem(stretch_widget, 1);
-  box_layout_stretch.addItem(fast_stretch, 2);
+  box_layout_stretch->addItem(stretch_widget, 1);
+  box_layout_stretch->addItem(fast_stretch, 2);
 
-  box_layout_stretch.setGeometry(SGRecti(0,0,128,32));
+  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));
+  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));
+  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) );
+  // ...and now with alignment
+  //
+  // All widgets without alignment get their maximum space and the remaining
+  // space is equally distributed to the remaining items. All items with
+  // alignment are set to their size hint and positioned according to their
+  // alignment.
+
+  // Left widget: size hint and positioned on the left
+  // Right widget: maximum size and positioned on the right
+  stretch_widget->setAlignment(sc::AlignLeft);
+  box_layout_stretch->update();
+  BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 32, 32));
+  BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(384, 0, 128, 32));
+
+  // Left widget: align right
+  stretch_widget->setAlignment(sc::AlignRight);
+  box_layout_stretch->update();
+  BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(347, 0, 32, 32));
+  BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(384, 0, 128, 32));
+
+  // Left widget: size hint and positioned on the right
+  // Right widget: size hint and positioned on the left of the right half
+  fast_stretch->setAlignment(sc::AlignLeft);
+  box_layout_stretch->update();
+  BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(221, 0, 32, 32));
+  BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(258, 0, 32, 32));
+
+  // Also check vertical alignment
+  stretch_widget->setAlignment(sc::AlignLeft | sc::AlignTop);
+  fast_stretch->setAlignment(sc::AlignLeft | sc::AlignBottom);
+  box_layout_stretch->setGeometry(SGRecti(0,0,512,64));
+  BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 32, 32));
+  BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(258, 32, 32, 32));
+}
+
+//------------------------------------------------------------------------------
+// Test more space then preferred, but less than maximum
+BOOST_AUTO_TEST_CASE( hbox_pref_to_max )
+{
+  sc::BoxLayoutRef hbox(new sc::HBoxLayout());
+  TestWidgetRef w1( new TestWidget( SGVec2i(16,   16),
+                                    SGVec2i(32,   32),
+                                    SGVec2i(9999, 32) ) ),
+                w2( new TestWidget(*w1) );
 
-    hbox.addItem(w1);
-    hbox.addItem(w2);
+  hbox->addItem(w1);
+  hbox->addItem(w2);
 
-    hbox.setGeometry( SGRecti(0, 0, 256, 32) );
+  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));
+  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);
+  hbox->setStretch(0, 1);
+  hbox->setStretch(1, 1);
 
-    BOOST_CHECK_EQUAL(hbox.stretch(0), 1);
-    BOOST_CHECK_EQUAL(hbox.stretch(1), 1);
+  BOOST_CHECK_EQUAL(hbox->stretch(0), 1);
+  BOOST_CHECK_EQUAL(hbox->stretch(1), 1);
 
-    hbox.update();
+  hbox->update();
 
-    BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0,   0, 125, 32));
-    BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(130, 0, 126, 32));
+  BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0,   0, 125, 32));
+  BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(130, 0, 126, 32));
 
-    BOOST_REQUIRE( hbox.setStretchFactor(w1, 2) );
-    BOOST_REQUIRE( hbox.setStretchFactor(w2, 3) );
-    BOOST_CHECK_EQUAL(hbox.stretch(0), 2);
-    BOOST_CHECK_EQUAL(hbox.stretch(1), 3);
+  BOOST_REQUIRE( hbox->setStretchFactor(w1, 2) );
+  BOOST_REQUIRE( hbox->setStretchFactor(w2, 3) );
+  BOOST_CHECK_EQUAL(hbox->stretch(0), 2);
+  BOOST_CHECK_EQUAL(hbox->stretch(1), 3);
 
-    hbox.removeItem(w1);
+  hbox->removeItem(w1);
 
-    BOOST_CHECK( !hbox.setStretchFactor(w1, 0) );
-  }
+  BOOST_CHECK( !hbox->setStretchFactor(w1, 0) );
 }
 
 //------------------------------------------------------------------------------
@@ -452,6 +490,7 @@ BOOST_AUTO_TEST_CASE( boxlayout_contents_margins )
 
   hbox->setGeometry(SGRecti(0, 0, 30, 40));
 
+  BOOST_CHECK_EQUAL(hbox->geometry(), SGRecti(0, 0, 30, 40));
   BOOST_CHECK_EQUAL(hbox->contentsRect(), SGRecti(5, 10, 10, 10));
 
   TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
@@ -587,6 +626,52 @@ BOOST_AUTO_TEST_CASE( boxlayout_hfw )
   BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32));
 }
 
+//------------------------------------------------------------------------------
+BOOST_AUTO_TEST_CASE( item_alignment_rect )
+{
+  TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
+                                    SGVec2i(32, 32) ) );
+
+  const SGRecti r(10, 10, 64, 64);
+
+  // Default: AlignFill -> fill up to maximum size
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), r);
+
+  // Horizontal
+
+  // AlignLeft -> width from size hint, positioned on the left
+  w1->setAlignment(sc::AlignLeft);
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), SGRecti(10, 10, 32, 64));
+
+  // AlignRight -> width from size hint, positioned on the left
+  w1->setAlignment(sc::AlignRight);
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), SGRecti(42, 10, 32, 64));
+
+  // AlignHCenter -> width from size hint, positioned in the center
+  w1->setAlignment(sc::AlignHCenter);
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), SGRecti(26, 10, 32, 64));
+
+
+  // Vertical
+
+  // AlignTop -> height from size hint, positioned on the top
+  w1->setAlignment(sc::AlignTop);
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), SGRecti(10, 10, 64, 32));
+
+  // AlignBottom -> height from size hint, positioned on the bottom
+  w1->setAlignment(sc::AlignBottom);
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), SGRecti(10, 42, 64, 32));
+
+  // AlignVCenter -> height from size hint, positioned in the center
+  w1->setAlignment(sc::AlignVCenter);
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), SGRecti(10, 26, 64, 32));
+
+
+  // Vertical + Horizontal
+  w1->setAlignment(sc::AlignCenter);
+  BOOST_CHECK_EQUAL(w1->alignmentRect(r), SGRecti(26, 26, 32, 32));
+}
+
 //------------------------------------------------------------------------------
 // TODO extend to_nasal_helper for automatic argument conversion
 static naRef f_Widget_visibilityChanged(nasal::CallContext ctx)