]> git.mxchange.org Git - simgear.git/blobdiff - simgear/canvas/layout/BoxLayout.cxx
canvas::Layout: support for contents margins.
[simgear.git] / simgear / canvas / layout / BoxLayout.cxx
index cabe0ba90463032cfa7e88642a5d2643454a1775..3e1d37ffde09a9336e6019e8de3f9fbd6481454e 100644 (file)
@@ -17,6 +17,7 @@
 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
 
 #include "BoxLayout.hxx"
+#include "SpacerItem.hxx"
 #include <simgear/canvas/Canvas.hxx>
 
 namespace simgear
@@ -31,6 +32,13 @@ namespace canvas
     setDirection(dir);
   }
 
+  //----------------------------------------------------------------------------
+  BoxLayout::~BoxLayout()
+  {
+    _parent.reset(); // No need to invalidate parent again...
+    clear();
+  }
+
   //----------------------------------------------------------------------------
   void BoxLayout::addItem(const LayoutItemRef& item)
   {
@@ -39,19 +47,107 @@ namespace canvas
 
   //----------------------------------------------------------------------------
   void BoxLayout::addItem(const LayoutItemRef& item, int stretch)
+  {
+    insertItem(-1, item, stretch);
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::addStretch(int stretch)
+  {
+    insertStretch(-1, stretch);
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::addSpacing(int size)
+  {
+    insertSpacing(-1, size);
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::insertItem(int index, 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);
+    if( SGWeakReferenced::count(this) )
+      item->setParent(this);
+    else
+      SG_LOG( SG_GUI,
+              SG_WARN,
+              "Adding item to expired or non-refcounted layout" );
 
-    _layout_items.push_back(item_data);
+    if( index < 0 )
+      _layout_items.push_back(item_data);
+    else
+      _layout_items.insert(_layout_items.begin() + index, item_data);
 
     invalidate();
   }
 
+  //----------------------------------------------------------------------------
+  void BoxLayout::insertStretch(int index, int stretch)
+  {
+    insertItem(index, LayoutItemRef(new SpacerItem()), stretch);
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::insertSpacing(int index, int size)
+  {
+    SGVec2i size_hint = horiz()
+                          ? SGVec2i(size, 0)
+                          : SGVec2i(0, size),
+                max_size  = size_hint;
+
+    insertItem(index, LayoutItemRef(new SpacerItem(size_hint, max_size)));
+  }
+
+  //----------------------------------------------------------------------------
+  size_t BoxLayout::count() const
+  {
+    return _layout_items.size();
+  }
+
+  //----------------------------------------------------------------------------
+  LayoutItemRef BoxLayout::itemAt(size_t index)
+  {
+    if( index >= _layout_items.size() )
+      return LayoutItemRef();
+
+    return _layout_items[index].layout_item;
+  }
+
+  //----------------------------------------------------------------------------
+  LayoutItemRef BoxLayout::takeAt(size_t index)
+  {
+    if( index >= _layout_items.size() )
+      return LayoutItemRef();
+
+    LayoutItems::iterator it = _layout_items.begin() + index;
+    LayoutItemRef item = it->layout_item;
+    item->onRemove();
+    item->setParent(LayoutItemWeakRef());
+    _layout_items.erase(it);
+
+    invalidate();
+
+    return item;
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::clear()
+  {
+    for( LayoutItems::iterator it = _layout_items.begin();
+                               it != _layout_items.end();
+                             ++it )
+    {
+      it->layout_item->onRemove();
+      it->layout_item->setParent(LayoutItemWeakRef());
+    }
+    _layout_items.clear();
+    invalidate();
+  }
+
   //----------------------------------------------------------------------------
   void BoxLayout::setStretch(size_t index, int stretch)
   {
@@ -62,6 +158,24 @@ namespace canvas
     invalidate();
   }
 
+  //----------------------------------------------------------------------------
+  bool BoxLayout::setStretchFactor(const LayoutItemRef& item, int stretch)
+  {
+    for( LayoutItems::iterator it = _layout_items.begin();
+                               it != _layout_items.end();
+                             ++it )
+    {
+      if( item == it->layout_item )
+      {
+        it->stretch = std::max(0, stretch);
+        invalidate();
+        return true;
+      }
+    }
+
+    return false;
+  }
+
   //----------------------------------------------------------------------------
   int BoxLayout::stretch(size_t index) const
   {
@@ -90,24 +204,29 @@ namespace canvas
   //----------------------------------------------------------------------------
   void BoxLayout::setDirection(Direction dir)
   {
+    _direction = dir;
     _get_layout_coord = &SGVec2i::x;
     _get_fixed_coord = &SGVec2i::y;
 
-    if( dir == TopToBottom || dir == BottomToTop )
+    if( !horiz() )
       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<CoordGetter>(&SGVec2i::x) )
-      return _reverse ? RightToLeft : LeftToRight;
-    else
-      return _reverse ? BottomToTop : TopToBottom;
+    return _direction;
+  }
+
+  //----------------------------------------------------------------------------
+  bool BoxLayout::hasHeightForWidth() const
+  {
+    if( _flags & SIZE_INFO_DIRTY )
+      updateSizeHints();
+
+    return _layout_data.has_hfw;
   }
 
   //----------------------------------------------------------------------------
@@ -119,6 +238,12 @@ namespace canvas
       _layout_items[i].layout_item->setCanvas(canvas);
   }
 
+  //----------------------------------------------------------------------------
+  bool BoxLayout::horiz() const
+  {
+    return (_direction == LeftToRight) || (_direction == RightToLeft);
+  }
+
   //----------------------------------------------------------------------------
   void BoxLayout::updateSizeHints() const
   {
@@ -127,46 +252,57 @@ 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)
     {
-      // TODO check visible
-
       ItemData& item_data = _layout_items[i];
       LayoutItem const& item = *item_data.layout_item;
 
+      item_data.visible = item.isVisible();
+      if( !item_data.visible )
+        continue;
+
       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( is_first )
+      if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
       {
-        item_data.padding_orig = 0;
-        is_first = false;
+        if( is_first )
+        {
+          item_data.padding_orig = 0;
+          is_first = false;
+        }
+        else
+        {
+          item_data.padding_orig = _padding;
+          _layout_data.padding += item_data.padding_orig;
+        }
       }
-      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);
+      SGMisc<int>::addClipOverflowInplace(min_size.x(),  item_data.min_size);
+      SGMisc<int>::addClipOverflowInplace(max_size.x(),  item_data.max_size);
+      SGMisc<int>::addClipOverflowInplace(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(),
+      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);
-    safeAdd(max_size.x(),  _layout_data.padding);
-    safeAdd(size_hint.x(), _layout_data.padding);
+    SGMisc<int>::addClipOverflowInplace(min_size.x(),  _layout_data.padding);
+    SGMisc<int>::addClipOverflowInplace(max_size.x(),  _layout_data.padding);
+    SGMisc<int>::addClipOverflowInplace(size_hint.x(), _layout_data.padding);
 
     _layout_data.min_size = min_size.x();
     _layout_data.max_size = max_size.x();
@@ -183,6 +319,46 @@ 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];
+        if( !data.visible )
+          continue;
+
+        _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];
+        if( !data.visible )
+          continue;
+
+        _hfw_height += data.hfw(w) + data.padding_orig;
+        _hfw_min_height += data.mhfw(w) + data.padding_orig;
+      }
+    }
+
+    _hfw_width = w;
+  }
+
   //----------------------------------------------------------------------------
   SGVec2i BoxLayout::sizeHintImpl() const
   {
@@ -204,26 +380,93 @@ 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)
   {
     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.visible )
+          continue;
+
+        if( data.has_hfw )
+        {
+          int w = SGMisc<int>::clip( geom.width(),
+                                     data.layout_item->minimumSize().x(),
+                                     data.layout_item->maximumSize().x() );
+
+          data.min_size = data.mhfw(w);
+          data.size_hint = data.hfw(w);
+
+          // Update size hints for layouting with difference to size hints
+          // calculated by using the size hints provided (without trading
+          // height for width)
+          _layout_data.min_size  += data.min_size
+                                  - data.layout_item->minimumSize().y();
+          _layout_data.size_hint += data.size_hint
+                                  - data.layout_item->sizeHint().y();
+        }
+      }
+    }
+
+    // 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)() );
-    if( _reverse )
+
+    bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
+    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;
+      if( !data.visible )
+        continue;
+
+      cur_pos.x() += reverse ? -data.padding - data.size : data.padding;
 
       SGVec2i size(
         data.size,
@@ -242,12 +485,19 @@ namespace canvas
         (size.*_get_fixed_coord)()
       ));
 
-      if( !_reverse )
+      if( !reverse )
         cur_pos.x() += data.size;
       cur_pos.y() -= offset_fixed;
     }
   }
 
+  //----------------------------------------------------------------------------
+  void BoxLayout::visibilityChanged(bool visible)
+  {
+    for(size_t i = 0; i < _layout_items.size(); ++i)
+      callSetVisibleInternal(_layout_items[i].layout_item.get(), visible);
+  }
+
   //----------------------------------------------------------------------------
   HBoxLayout::HBoxLayout():
     BoxLayout(LeftToRight)