]> git.mxchange.org Git - simgear.git/commitdiff
Canvas: basic layouting system.
authorThomas Geymayer <tomgey@gmail.com>
Sat, 31 May 2014 00:40:59 +0000 (02:40 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Sat, 31 May 2014 00:40:59 +0000 (02:40 +0200)
Support for vertical and horizontal layouts. More layouts and
widgets can be created using Nasal.

19 files changed:
simgear/canvas/CMakeLists.txt
simgear/canvas/Canvas.cxx
simgear/canvas/Canvas.hxx
simgear/canvas/CanvasWindow.cxx
simgear/canvas/CanvasWindow.hxx
simgear/canvas/elements/CanvasElement.hxx
simgear/canvas/layout/BoxLayout.cxx [new file with mode: 0644]
simgear/canvas/layout/BoxLayout.hxx [new file with mode: 0644]
simgear/canvas/layout/CMakeLists.txt [new file with mode: 0644]
simgear/canvas/layout/Layout.cxx [new file with mode: 0644]
simgear/canvas/layout/Layout.hxx [new file with mode: 0644]
simgear/canvas/layout/LayoutItem.cxx [new file with mode: 0644]
simgear/canvas/layout/LayoutItem.hxx [new file with mode: 0644]
simgear/canvas/layout/NasalWidget.cxx [new file with mode: 0644]
simgear/canvas/layout/NasalWidget.hxx [new file with mode: 0644]
simgear/canvas/layout/canvas_layout_test.cxx [new file with mode: 0644]
simgear/debug/debug_types.h
simgear/debug/logstream.cxx
simgear/props/PropertyBasedElement.hxx

index fea5a22d7896f81f5e7596b497debf0f35eaf28f..e2a9652b7ac84aa688ab18b9efe1d0139ce4c9fd 100644 (file)
@@ -32,5 +32,6 @@ set(SOURCES
 add_subdirectory(ShivaVG/src)
 add_subdirectory(elements)
 add_subdirectory(events)
+add_subdirectory(layout)
 
 simgear_scene_component(canvas canvas "${SOURCES}" "${HEADERS}")
index 46f2e27fa07ec9b5aec01c140f58d50432d0d6c8..e01eb8a2c07ebe221eb7b77fc20645f814aa033c 100644 (file)
@@ -191,6 +191,14 @@ namespace canvas
     return _root_group;
   }
 
+  //----------------------------------------------------------------------------
+  void Canvas::setLayout(const LayoutRef& layout)
+  {
+    _layout = layout;
+    _layout->setCanvas(this);
+    _status |= LAYOUT_DIRTY;
+  }
+
   //----------------------------------------------------------------------------
   void Canvas::enableRendering(bool force)
   {
@@ -202,11 +210,10 @@ namespace canvas
   //----------------------------------------------------------------------------
   void Canvas::update(double delta_time_sec)
   {
-    if(    (!_texture.serviceable() && _status != STATUS_DIRTY)
-        || (_status & CREATE_FAILED) )
+    if( _status & (CREATE_FAILED | MISSING_SIZE) )
       return;
 
-    if( _status == STATUS_DIRTY )
+    if( _status & STATUS_DIRTY )
     {
       _texture.setSize(_size_x, _size_y);
 
@@ -248,6 +255,17 @@ namespace canvas
       }
     }
 
+    if( _layout )
+    {
+      if( (_status & LAYOUT_DIRTY) )
+      {
+        _layout->setGeometry(SGRecti(0, 0, _view_width, _view_height));
+        _status &= ~LAYOUT_DIRTY;
+      }
+      else
+        _layout->update();
+    }
+
     if( _visible || _render_always )
     {
       BOOST_FOREACH(CanvasWeakPtr canvas_weak, _child_canvases)
@@ -392,6 +410,7 @@ namespace canvas
     if( _view_width == w )
       return;
     _view_width = w;
+    _status |= LAYOUT_DIRTY;
 
     _texture.setViewSize(_view_width, _view_height);
   }
@@ -402,6 +421,7 @@ namespace canvas
     if( _view_height == h )
       return;
     _view_height = h;
+    _status |= LAYOUT_DIRTY;
 
     _texture.setViewSize(_view_width, _view_height);
   }
@@ -657,7 +677,7 @@ namespace canvas
       _status_msg = "Missing size-y";
     else if( _status & CREATE_FAILED )
       _status_msg = "Creating render target failed";
-    else if( _status == STATUS_DIRTY )
+    else if( _status & STATUS_DIRTY )
       _status_msg = "Creation pending...";
     else
       _status_msg = "Ok";
index 10872f22d7f0574e35066cb9ec96257ebb7d2955..c2de8d483f7ec496ad78a4eb1088636e5b820a54 100644 (file)
@@ -23,6 +23,7 @@
 #include "ODGauge.hxx"
 
 #include <simgear/canvas/elements/CanvasGroup.hxx>
+#include <simgear/canvas/layout/Layout.hxx>
 #include <simgear/math/SGRect.hxx>
 #include <simgear/props/PropertyBasedElement.hxx>
 #include <simgear/props/propertyObject.hxx>
@@ -48,9 +49,11 @@ namespace canvas
       enum StatusFlags
       {
         STATUS_OK,
-        STATUS_DIRTY     = 1,
-        MISSING_SIZE_X = STATUS_DIRTY << 1,
+        STATUS_DIRTY   = 1,
+        LAYOUT_DIRTY   = STATUS_DIRTY << 1,
+        MISSING_SIZE_X = LAYOUT_DIRTY << 1,
         MISSING_SIZE_Y = MISSING_SIZE_X << 1,
+        MISSING_SIZE   = MISSING_SIZE_X | MISSING_SIZE_Y,
         CREATE_FAILED  = MISSING_SIZE_Y << 1
       };
 
@@ -121,6 +124,12 @@ namespace canvas
        */
       GroupPtr getRootGroup();
 
+      /**
+       * Set the layout of the canvas (the layout will automatically update with
+       * the viewport size of the canvas)
+       */
+      void setLayout(const LayoutRef& layout);
+
       /**
        * Enable rendering for the next frame
        *
@@ -195,7 +204,9 @@ namespace canvas
            _visible;
 
       ODGauge _texture;
-      GroupPtr _root_group;
+
+      GroupPtr  _root_group;
+      LayoutRef _layout;
 
       CullCallbackPtr _cull_callback;
       bool _render_always; //<! Used to disable automatic lazy rendering (culling)
index 55f4d9b7480b7aa774f454e1987e9c0e62be741e..f4bf38dc06ea05448149e2a82b71046e86c11103 100644 (file)
@@ -125,6 +125,11 @@ namespace canvas
   void Window::setCanvasContent(CanvasPtr canvas)
   {
     _canvas_content = canvas;
+    if( _layout )
+    {
+      canvas->setLayout(_layout);
+      _layout.clear();
+    }
 
     if( _image_content )
       // Placement within decoration canvas
@@ -139,6 +144,16 @@ namespace canvas
     return _canvas_content;
   }
 
+  //----------------------------------------------------------------------------
+  void Window::setLayout(const LayoutRef& layout)
+  {
+    CanvasPtr canvas = _canvas_content.lock();
+    if( canvas )
+      canvas->setLayout(layout);
+    else
+      _layout = layout; // keep layout until content canvas is set
+  }
+
   //----------------------------------------------------------------------------
   CanvasPtr Window::getCanvasDecoration() const
   {
index 57068a84e914b66fba986e648e1e187804780dc3..81917ed7c66828be45ff94634abc1627a7a893d5 100644 (file)
@@ -20,6 +20,7 @@
 #define CANVAS_WINDOW_HXX_
 
 #include <simgear/canvas/elements/CanvasImage.hxx>
+#include <simgear/canvas/layout/Layout.hxx>
 #include <simgear/canvas/events/MouseEvent.hxx>
 #include <simgear/props/PropertyBasedElement.hxx>
 #include <simgear/props/propertyObject.hxx>
@@ -34,7 +35,8 @@ namespace canvas
 {
 
   class Window:
-    public Image
+    public Image,
+    public LayoutItem
   {
     public:
       static const std::string TYPE_NAME;
@@ -75,6 +77,8 @@ namespace canvas
       void setCanvasContent(CanvasPtr canvas);
       simgear::canvas::CanvasWeakPtr getCanvasContent() const;
 
+      void setLayout(const LayoutRef& layout);
+
       CanvasPtr getCanvasDecoration() const;
 
       bool isResizable() const;
@@ -101,6 +105,7 @@ namespace canvas
 
       CanvasPtr        _canvas_decoration;
       CanvasWeakPtr    _canvas_content;
+      LayoutRef        _layout;
 
       ImagePtr _image_content,
                _image_shadow;
index 01858f4b402db65b912d6ca37ffce8a3dbe0987f..9e686739fcd05f068ee9888cc2cad12e63f22287 100644 (file)
@@ -167,7 +167,7 @@ namespace canvas
 
       /**
        * Get bounding box (may not be as tight as bounding box returned by
-       * #getTightBounds)
+       * #getTightBoundingBox)
        */
       osg::BoundingBox getBoundingBox() const;
 
diff --git a/simgear/canvas/layout/BoxLayout.cxx b/simgear/canvas/layout/BoxLayout.cxx
new file mode 100644 (file)
index 0000000..cabe0ba
--- /dev/null
@@ -0,0 +1,266 @@
+// Align items horizontally or vertically in a box
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "BoxLayout.hxx"
+#include <simgear/canvas/Canvas.hxx>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  //----------------------------------------------------------------------------
+  BoxLayout::BoxLayout(Direction dir):
+    _padding(5)
+  {
+    setDirection(dir);
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::addItem(const LayoutItemRef& item)
+  {
+    return addItem(item, 0);
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::addItem(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);
+
+    _layout_items.push_back(item_data);
+
+    invalidate();
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::setStretch(size_t index, int stretch)
+  {
+    if( index >= _layout_items.size() )
+      return;
+
+    _layout_items.at(index).stretch = std::max(0, stretch);
+    invalidate();
+  }
+
+  //----------------------------------------------------------------------------
+  int BoxLayout::stretch(size_t index) const
+  {
+    if( index >= _layout_items.size() )
+      return 0;
+
+    return _layout_items.at(index).stretch;
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::setSpacing(int spacing)
+  {
+    if( spacing == _padding )
+      return;
+
+    _padding = spacing;
+    invalidate();
+  }
+
+  //----------------------------------------------------------------------------
+  int BoxLayout::spacing() const
+  {
+    return _padding;
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::setDirection(Direction dir)
+  {
+    _get_layout_coord = &SGVec2i::x;
+    _get_fixed_coord = &SGVec2i::y;
+
+    if( dir == TopToBottom || dir == BottomToTop )
+      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;
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
+  {
+    _canvas = canvas;
+
+    for(size_t i = 0; i < _layout_items.size(); ++i)
+      _layout_items[i].layout_item->setCanvas(canvas);
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::updateSizeHints() const
+  {
+    SGVec2i min_size(0, 0),
+            max_size(0, 0),
+            size_hint(0, 0);
+
+    _layout_data.reset();
+    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.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)();
+
+      if( is_first )
+      {
+        item_data.padding_orig = 0;
+        is_first = false;
+      }
+      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);
+
+      // 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(),
+                                (item.sizeHint().*_get_fixed_coord)() );
+    }
+
+    safeAdd(min_size.x(),  _layout_data.padding);
+    safeAdd(max_size.x(),  _layout_data.padding);
+    safeAdd(size_hint.x(), _layout_data.padding);
+
+    _layout_data.min_size = min_size.x();
+    _layout_data.max_size = max_size.x();
+    _layout_data.size_hint = size_hint.x();
+
+    _min_size.x()  = (min_size.*_get_layout_coord)();
+    _max_size.x()  = (max_size.*_get_layout_coord)();
+    _size_hint.x() = (size_hint.*_get_layout_coord)();
+
+    _min_size.y()  = (min_size.*_get_fixed_coord)();
+    _max_size.y()  = (max_size.*_get_fixed_coord)();
+    _size_hint.y() = (size_hint.*_get_fixed_coord)();
+
+    _flags &= ~SIZE_INFO_DIRTY;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i BoxLayout::sizeHintImpl() const
+  {
+    updateSizeHints();
+    return _size_hint;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i BoxLayout::minimumSizeImpl() const
+  {
+    updateSizeHints();
+    return _min_size;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i BoxLayout::maximumSizeImpl() const
+  {
+    updateSizeHints();
+    return _max_size;
+  }
+
+  //----------------------------------------------------------------------------
+  void BoxLayout::doLayout(const SGRecti& geom)
+  {
+    if( _flags & SIZE_INFO_DIRTY )
+      updateSizeHints();
+
+    _layout_data.size = (geom.size().*_get_layout_coord)();
+    distribute(_layout_items, _layout_data);
+
+    int fixed_size = (geom.size().*_get_fixed_coord)();
+    SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
+                     (geom.pos().*_get_fixed_coord)() );
+    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;
+
+      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;
+
+      data.layout_item->setGeometry(SGRecti(
+        (cur_pos.*_get_layout_coord)(),
+        (cur_pos.*_get_fixed_coord)(),
+        (size.*_get_layout_coord)(),
+        (size.*_get_fixed_coord)()
+      ));
+
+      if( !_reverse )
+        cur_pos.x() += data.size;
+      cur_pos.y() -= offset_fixed;
+    }
+  }
+
+  //----------------------------------------------------------------------------
+  HBoxLayout::HBoxLayout():
+    BoxLayout(LeftToRight)
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  VBoxLayout::VBoxLayout():
+    BoxLayout(TopToBottom)
+  {
+
+  }
+
+} // namespace canvas
+} // namespace simgear
diff --git a/simgear/canvas/layout/BoxLayout.hxx b/simgear/canvas/layout/BoxLayout.hxx
new file mode 100644 (file)
index 0000000..9fc4141
--- /dev/null
@@ -0,0 +1,114 @@
+// Align items horizontally or vertically in a box
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_CANVAS_BOX_LAYOUT_HXX_
+#define SG_CANVAS_BOX_LAYOUT_HXX_
+
+#include "Layout.hxx"
+
+namespace simgear
+{
+namespace canvas
+{
+
+  class BoxLayout:
+    public Layout
+  {
+    public:
+
+      enum Direction
+      {
+        LeftToRight,
+        RightToLeft,
+        TopToBottom,
+        BottomToTop
+      };
+
+      BoxLayout(Direction dir);
+
+      virtual void addItem(const LayoutItemRef& item);
+
+      void addItem(const LayoutItemRef& item, int stretch);
+
+      /**
+       * Set the stretch factor of the item at position @a index to @a stretch.
+       */
+      void setStretch(size_t index, int stretch);
+
+      /**
+       * Get the stretch factor of the item at position @a index
+       */
+      int stretch(size_t index) const;
+
+      virtual void setSpacing(int spacing);
+      virtual int spacing() const;
+
+      void setDirection(Direction dir);
+      Direction direction() const;
+
+      virtual void setCanvas(const CanvasWeakPtr& canvas);
+
+    protected:
+
+      typedef const int& (SGVec2i::*CoordGetter)() const;
+      CoordGetter _get_layout_coord,    //<! getter for coordinate in layout
+                                        //   direction
+                  _get_fixed_coord;     //<! getter for coordinate in secondary
+                                        //   (fixed) direction
+
+      int _padding;
+      bool _reverse; //<! if true, right-to-left/bottom-to-top layouting
+
+      mutable std::vector<ItemData> _layout_items;
+      mutable ItemData _layout_data;
+
+      void updateSizeHints() const;
+
+      virtual SGVec2i sizeHintImpl() const;
+      virtual SGVec2i minimumSizeImpl() const;
+      virtual SGVec2i maximumSizeImpl() const;
+
+      virtual void doLayout(const SGRecti& geom);
+  };
+
+  /**
+   * Shortcut for creating a horizontal box layout
+   */
+  class HBoxLayout:
+    public BoxLayout
+  {
+    public:
+      HBoxLayout();
+  };
+
+  /**
+   * Shortcut for creating a vertical box layout
+   */
+  class VBoxLayout:
+    public BoxLayout
+  {
+    public:
+      VBoxLayout();
+  };
+
+} // namespace canvas
+} // namespace simgear
+
+
+#endif /* SG_CANVAS_BOX_LAYOUT_HXX_ */
+
diff --git a/simgear/canvas/layout/CMakeLists.txt b/simgear/canvas/layout/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f02c30f
--- /dev/null
@@ -0,0 +1,22 @@
+include (SimGearComponent)
+
+set(HEADERS
+  BoxLayout.hxx
+  Layout.hxx
+  LayoutItem.hxx
+  NasalWidget.hxx
+)
+
+set(SOURCES
+  BoxLayout.cxx
+  Layout.cxx
+  LayoutItem.cxx
+  NasalWidget.cxx
+)
+
+simgear_scene_component(canvas-layout canvas/layout "${SOURCES}" "${HEADERS}")
+
+add_boost_test(canvas_layout
+  SOURCES canvas_layout_test.cxx
+  LIBRARIES ${TEST_LIBS}
+)
\ No newline at end of file
diff --git a/simgear/canvas/layout/Layout.cxx b/simgear/canvas/layout/Layout.cxx
new file mode 100644 (file)
index 0000000..8c10a52
--- /dev/null
@@ -0,0 +1,243 @@
+// Basic class for canvas layouts
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "Layout.hxx"
+#include <simgear/debug/logstream.hxx>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  //----------------------------------------------------------------------------
+  void Layout::update()
+  {
+    if( !(_flags & (LAYOUT_DIRTY | SIZE_INFO_DIRTY)) )
+      return;
+
+    doLayout(_geometry);
+
+    _flags &= ~LAYOUT_DIRTY;
+  }
+
+  //----------------------------------------------------------------------------
+  void Layout::invalidate()
+  {
+    LayoutItem::invalidate();
+    _flags |= LAYOUT_DIRTY;
+  }
+
+  //----------------------------------------------------------------------------
+  void Layout::setGeometry(const SGRecti& geom)
+  {
+    if( geom == _geometry )
+      return;
+
+    _geometry = geom;
+    _flags |= LAYOUT_DIRTY;
+
+    update();
+  }
+
+  //----------------------------------------------------------------------------
+  void Layout::ItemData::reset()
+  {
+    layout_item = 0;
+    size_hint   = 0;
+    min_size    = 0;
+    max_size    = 0;
+    padding_orig= 0;
+    padding     = 0;
+    size        = 0;
+    stretch     = 0;
+    done        = false;
+  }
+
+  //----------------------------------------------------------------------------
+  void Layout::safeAdd(int& a, int b)
+  {
+    if( SGLimits<int>::max() - b < a )
+      a = SGLimits<int>::max();
+    else
+      a += b;
+  }
+
+  //----------------------------------------------------------------------------
+  void Layout::distribute(std::vector<ItemData>& items, const ItemData& space)
+  {
+    const int num_children = static_cast<int>(items.size());
+    _num_not_done = num_children;
+
+    SG_LOG( SG_GUI,
+            SG_DEBUG,
+            "Layout::distribute(" << num_children << " items)" );
+
+    if( space.size < space.min_size )
+    {
+      // TODO
+      SG_LOG( SG_GUI, SG_WARN, "Layout: not enough size (not implemented)");
+    }
+    else if( space.size < space.max_size )
+    {
+      _sum_stretch = 0;
+      _space_stretch = 0;
+
+      bool less_then_hint = space.size < space.size_hint;
+
+      // Give min_size/size_hint to all items
+      _space_left = space.size
+                  - (less_then_hint ? space.min_size : space.size_hint);
+      for(int i = 0; i < num_children; ++i)
+      {
+        ItemData& d = items[i];
+        d.size = less_then_hint ? d.min_size : d.size_hint;
+        d.padding = d.padding_orig;
+        d.done = d.size >= (less_then_hint ? d.size_hint : d.max_size);
+
+        if( d.done )
+        {
+          _num_not_done -= 1;
+          continue;
+        }
+
+        if( d.stretch > 0 )
+        {
+          _sum_stretch += d.stretch;
+          _space_stretch += d.size;
+        }
+      }
+
+      // Distribute remaining space to increase the size of each item up to its
+      // size_hint/max_size
+      while( _space_left > 0 )
+      {
+        if( _num_not_done <= 0 )
+        {
+          SG_LOG(SG_GUI, SG_WARN, "space left, but no more items?");
+          break;
+        }
+
+        int space_per_element = std::max(1, _space_left / _num_not_done);
+
+        SG_LOG(SG_GUI, SG_DEBUG, "space/element=" << space_per_element);
+
+        for(int i = 0; i < num_children; ++i)
+        {
+          ItemData& d = items[i];
+
+          SG_LOG(
+            SG_GUI,
+            SG_DEBUG,
+            i << ") left=" << _space_left
+              << ", not_done=" << _num_not_done
+              << ", sum=" << _sum_stretch
+              << ", stretch=" << _space_stretch
+              << ", stretch/unit=" << _space_stretch / std::max(1, _sum_stretch)
+          );
+
+          if( d.done )
+            continue;
+
+          if( _sum_stretch > 0 && d.stretch <= 0 )
+            d.done = true;
+          else
+          {
+            int target_size = 0;
+            int max_size = less_then_hint ? d.size_hint : d.max_size;
+
+            if( _sum_stretch > 0 )
+            {
+              target_size = (d.stretch * (_space_left + _space_stretch))
+                          / _sum_stretch;
+
+              // Item would be smaller than minimum size or larger than maximum
+              // size, so just keep bounded size and ignore stretch factor
+              if( target_size <= d.size || target_size >= max_size )
+              {
+                d.done = true;
+                _sum_stretch -= d.stretch;
+                _space_stretch -= d.size;
+
+                if( target_size >= max_size )
+                  target_size = max_size;
+                else
+                  target_size = d.size;
+              }
+              else
+                _space_stretch += target_size - d.size;
+            }
+            else
+            {
+              // Give space evenly to all remaining elements in this round
+              target_size = d.size + std::min(_space_left, space_per_element);
+
+              if( target_size >= max_size )
+              {
+                d.done = true;
+                target_size = max_size;
+              }
+            }
+
+            int old_size = d.size;
+            d.size = target_size;
+            _space_left -= d.size - old_size;
+          }
+
+          if( d.done )
+          {
+            _num_not_done -= 1;
+
+            if( _sum_stretch <= 0 && d.stretch > 0 )
+              // Distribute remaining space evenly to all non-stretchable items
+              // in a new round
+              break;
+          }
+        }
+      }
+    }
+    else
+    {
+      _space_left = space.size - space.max_size;
+      for(int i = 0; i < num_children; ++i)
+      {
+        ItemData& d = items[i];
+        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);
+
+        _space_left -= d.padding - d.padding_orig;
+        _num_not_done -= 1;
+      }
+    }
+
+    SG_LOG(SG_GUI, SG_DEBUG, "distribute:");
+    for(int i = 0; i < num_children; ++i)
+    {
+      ItemData const& d = items[i];
+      SG_LOG( SG_GUI,
+              SG_DEBUG,
+              i << ") pad=" << d.padding
+                << ", size = " << d.size );
+    }
+  }
+
+} // namespace canvas
+} // namespace simgear
diff --git a/simgear/canvas/layout/Layout.hxx b/simgear/canvas/layout/Layout.hxx
new file mode 100644 (file)
index 0000000..6d4aa57
--- /dev/null
@@ -0,0 +1,98 @@
+// Basic class for canvas layouts
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_CANVAS_LAYOUT_HXX_
+#define SG_CANVAS_LAYOUT_HXX_
+
+#include "LayoutItem.hxx"
+#include <vector>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  class Layout:
+    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;
+
+    protected:
+      enum LayoutFlags
+      {
+        LAYOUT_DIRTY = LayoutItem::LAST_FLAG << 1,
+        LAST_FLAG = LAYOUT_DIRTY
+      };
+
+      struct ItemData
+      {
+        LayoutItemRef layout_item;
+        int     size_hint,
+                min_size,
+                max_size,
+                padding_orig, //<! original padding as specified by the user
+                padding,      //<! padding before element (layouted)
+                size,         //<! layouted size
+                stretch;      //<! stretch factor
+        bool    done;         //<! layouting done
+
+        /** Clear values (reset to default/empty state) */
+        void reset();
+      };
+
+      /**
+       * Override to implement the actual layouting
+       */
+      virtual void doLayout(const SGRecti& geom) = 0;
+
+      /**
+       * Add two integers taking care of overflow (limit to INT_MAX)
+       */
+      static void safeAdd(int& a, int b);
+
+      /**
+       * Distribute the available @a space to all @a items
+       */
+      void distribute(std::vector<ItemData>& items, const ItemData& space);
+
+    private:
+
+      int _num_not_done, //<! number of children not layouted yet
+          _sum_stretch,  //<! sum of stretch factors of all not yet layouted
+                         //   children
+          _space_stretch,//<! space currently assigned to all not yet layouted
+                         //   stretchable children
+          _space_left;   //<! remaining space not used by any child yet
+
+  };
+
+  typedef SGSharedPtr<Layout> LayoutRef;
+
+} // namespace canvas
+} // namespace simgear
+
+
+#endif /* SG_CANVAS_LAYOUT_HXX_ */
diff --git a/simgear/canvas/layout/LayoutItem.cxx b/simgear/canvas/layout/LayoutItem.cxx
new file mode 100644 (file)
index 0000000..31be629
--- /dev/null
@@ -0,0 +1,151 @@
+// Basic element for layouting canvas elements
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "LayoutItem.hxx"
+#include <simgear/canvas/Canvas.hxx>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  //----------------------------------------------------------------------------
+  LayoutItem::LayoutItem():
+    _flags(0)
+  {
+    invalidate();
+  }
+
+  //----------------------------------------------------------------------------
+  LayoutItem::~LayoutItem()
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i LayoutItem::sizeHint() const
+  {
+    if( _flags & SIZE_HINT_DIRTY )
+    {
+      _size_hint = sizeHintImpl();
+      _flags &= ~SIZE_HINT_DIRTY;
+    }
+
+    return _size_hint;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i LayoutItem::minimumSize() const
+  {
+    if( _flags & MINIMUM_SIZE_DIRTY )
+    {
+      _min_size = minimumSizeImpl();
+      _flags &= ~MINIMUM_SIZE_DIRTY;
+    }
+
+    return _min_size;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i LayoutItem::maximumSize() const
+  {
+    if( _flags & MAXIMUM_SIZE_DIRTY )
+    {
+      _max_size = maximumSizeImpl();
+      _flags &= ~MAXIMUM_SIZE_DIRTY;
+    }
+
+    return _max_size;
+  }
+
+  //----------------------------------------------------------------------------
+  void LayoutItem::invalidate()
+  {
+    _flags |= SIZE_HINT_DIRTY
+           |  MINIMUM_SIZE_DIRTY
+           |  MAXIMUM_SIZE_DIRTY;
+
+    invalidateParent();
+  }
+
+  //----------------------------------------------------------------------------
+  void LayoutItem::invalidateParent()
+  {
+    LayoutItemRef parent = _parent.lock();
+    if( parent )
+      parent->invalidate();
+  }
+
+  //----------------------------------------------------------------------------
+  void LayoutItem::setGeometry(const SGRecti& geom)
+  {
+    _geometry = geom;
+  }
+
+  //----------------------------------------------------------------------------
+  SGRecti LayoutItem::geometry() const
+  {
+    return _geometry;
+  }
+
+  //----------------------------------------------------------------------------
+  void LayoutItem::setCanvas(const CanvasWeakPtr& canvas)
+  {
+    _canvas = canvas;
+  }
+
+  //----------------------------------------------------------------------------
+  CanvasPtr LayoutItem::getCanvas() const
+  {
+    return _canvas.lock();
+  }
+
+  //----------------------------------------------------------------------------
+  void LayoutItem::setParent(const LayoutItemWeakRef& parent)
+  {
+    _parent = parent;
+    LayoutItemRef parent_ref = parent.lock();
+    setCanvas(parent_ref ? parent_ref->_canvas : CanvasWeakPtr());
+  }
+
+  //----------------------------------------------------------------------------
+  LayoutItemRef LayoutItem::getParent() const
+  {
+    return _parent.lock();
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i LayoutItem::sizeHintImpl() const
+  {
+    return SGVec2i(16, 16);
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i LayoutItem::minimumSizeImpl() const
+  {
+    return SGVec2i(0, 0);
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i LayoutItem::maximumSizeImpl() const
+  {
+    return SGVec2i(SGLimits<int>::max(), SGLimits<int>::max());
+  }
+
+} // namespace canvas
+} // namespace simgear
diff --git a/simgear/canvas/layout/LayoutItem.hxx b/simgear/canvas/layout/LayoutItem.hxx
new file mode 100644 (file)
index 0000000..18e56b9
--- /dev/null
@@ -0,0 +1,139 @@
+///@file Basic element for layouting canvas elements
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_CANVAS_LAYOUT_ITEM_HXX_
+#define SG_CANVAS_LAYOUT_ITEM_HXX_
+
+#include <simgear/canvas/canvas_fwd.hxx>
+#include <simgear/math/SGMath.hxx>
+#include <simgear/math/SGRect.hxx>
+#include <simgear/misc/stdint.hxx>
+#include <simgear/structure/SGWeakReferenced.hxx>
+#include <simgear/structure/SGSharedPtr.hxx>
+
+namespace simgear
+{
+namespace canvas
+{
+  class LayoutItem;
+  typedef SGSharedPtr<LayoutItem> LayoutItemRef;
+  typedef SGWeakPtr<LayoutItem> LayoutItemWeakRef;
+
+  /**
+   * Base class for all layouting elements. Specializations either implement a
+   * layouting algorithm or a widget.
+   */
+  class LayoutItem:
+    public virtual SGVirtualWeakReferenced
+  {
+    public:
+
+      LayoutItem();
+      virtual ~LayoutItem();
+
+      /**
+       * Get the preferred size of this item.
+       */
+      SGVec2i sizeHint() const;
+
+      /**
+       * Get the minimum amount of the space this item requires.
+       */
+      SGVec2i minimumSize() const;
+
+      /**
+       * Get the maximum amount of space this item can use.
+       */
+      SGVec2i maximumSize() const;
+
+      /**
+       * Mark all cached data as invalid and require it to be recalculated.
+       */
+      virtual void invalidate();
+
+      /**
+       * Mark all cached data of parent item as invalid (if it is known)
+       */
+      void invalidateParent();
+
+      /**
+       * Set position and size of this element. For layouts this triggers a
+       * recalculation of the layout.
+       */
+      virtual void setGeometry(const SGRecti& geom);
+
+      /**
+       * Get position and size of this element.
+       */
+      virtual SGRecti geometry() const;
+
+      /**
+       * Set the canvas this item is attached to.
+       */
+      virtual void setCanvas(const CanvasWeakPtr& canvas);
+
+      /**
+       * Get the canvas this item is attached to.
+       */
+      CanvasPtr getCanvas() const;
+
+      /**
+       * Set the parent layout item (usally this is a layout).
+       */
+      void setParent(const LayoutItemWeakRef& parent);
+
+      /**
+       * Get the parent layout.
+       */
+      LayoutItemRef getParent() const;
+
+    protected:
+
+      friend class Canvas;
+
+      enum Flags
+      {
+        SIZE_HINT_DIRTY = 1,
+        MINIMUM_SIZE_DIRTY = SIZE_HINT_DIRTY << 1,
+        MAXIMUM_SIZE_DIRTY = MINIMUM_SIZE_DIRTY << 1,
+        SIZE_INFO_DIRTY = SIZE_HINT_DIRTY
+                        | MINIMUM_SIZE_DIRTY
+                        | MAXIMUM_SIZE_DIRTY,
+        LAST_FLAG = MAXIMUM_SIZE_DIRTY
+      };
+
+      CanvasWeakPtr     _canvas;
+      LayoutItemWeakRef _parent;
+
+      SGRecti           _geometry;
+
+      mutable uint32_t  _flags;
+      mutable SGVec2i   _size_hint,
+                        _min_size,
+                        _max_size;
+
+      virtual SGVec2i sizeHintImpl() const;
+      virtual SGVec2i minimumSizeImpl() const;
+      virtual SGVec2i maximumSizeImpl() const;
+
+  };
+
+} // namespace canvas
+} // namespace simgear
+
+#endif /* SG_CANVAS_LAYOUT_ITEM_HXX_ */
diff --git a/simgear/canvas/layout/NasalWidget.cxx b/simgear/canvas/layout/NasalWidget.cxx
new file mode 100644 (file)
index 0000000..a8532c9
--- /dev/null
@@ -0,0 +1,158 @@
+// Glue for GUI layout items implemented in Nasal space
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "NasalWidget.hxx"
+
+#include <simgear/canvas/Canvas.hxx>
+#include <simgear/nasal/cppbind/Ghost.hxx>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  //----------------------------------------------------------------------------
+  NasalWidget::NasalWidget(naRef impl):
+    _nasal_impl(impl)
+  {
+    assert( naIsHash(_nasal_impl.get_naRef()) );
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setGeometry(const SGRect<int>& geom)
+  {
+    if( _geometry == geom )
+      return;
+
+    _geometry = geom;
+
+    if( _set_geometry )
+      _set_geometry(_nasal_impl.get_naRef(), geom);
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setSetGeometryFunc(const SetGeometryFunc& func)
+  {
+    _set_geometry = func;
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setImpl(naRef obj)
+  {
+    _nasal_impl.reset(obj);
+  }
+
+  //----------------------------------------------------------------------------
+  naRef NasalWidget::getImpl() const
+  {
+    return _nasal_impl.get_naRef();
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setSizeHint(const SGVec2i& s)
+  {
+    if( _size_hint == s )
+      return;
+
+    _size_hint = s;
+
+    // TODO just invalidate size_hint? Probably not a performance issue...
+    invalidateParent();
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setMinimumSize(const SGVec2i& s)
+  {
+    if( _min_size == s )
+      return;
+
+    _min_size = s;
+    invalidateParent();
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setMaximumSize(const SGVec2i& s)
+  {
+    if( _max_size == s )
+      return;
+
+    _max_size = s;
+    invalidateParent();
+  }
+
+  //----------------------------------------------------------------------------
+  bool NasalWidget::_set(naContext c, const std::string& key, naRef val)
+  {
+    if( !_nasal_impl.valid() )
+      return false;
+
+    nasal::Hash(_nasal_impl.get_naRef(), c).set(key, val);
+    return true;
+  }
+
+  //----------------------------------------------------------------------------
+  static naRef f_makeNasalWidget(const nasal::CallContext& ctx)
+  {
+    return ctx.to_nasal(NasalWidgetRef(
+      new NasalWidget( ctx.requireArg<naRef>(0) )
+    ));
+  }
+
+  //----------------------------------------------------------------------------
+  void NasalWidget::setupGhost(nasal::Hash& ns)
+  {
+    nasal::Ghost<NasalWidgetRef>::init("canvas.Widget")
+      .bases<LayoutItemRef>()
+      ._set(&NasalWidget::_set)
+      .member("parents", &NasalWidget::getParents)
+      .method("setSetGeometryFunc", &NasalWidget::setSetGeometryFunc)
+      .method("setSizeHint", &NasalWidget::setSizeHint)
+      .method("setMinimumSize", &NasalWidget::setMinimumSize)
+      .method("setMaximumSize", &NasalWidget::setMaximumSize);
+
+    nasal::Hash widget_hash = ns.createHash("Widget");
+    widget_hash.set("new", &f_makeNasalWidget);
+  }
+
+  //----------------------------------------------------------------------------
+  naRef NasalWidget::getParents(NasalWidget& w, naContext c)
+  {
+    naRef parents[] = { w._nasal_impl.get_naRef() };
+    return nasal::to_nasal(c, parents);
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i NasalWidget::sizeHintImpl() const
+  {
+    return _size_hint;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i NasalWidget::minimumSizeImpl() const
+  {
+    return _min_size;
+  }
+
+  //----------------------------------------------------------------------------
+  SGVec2i NasalWidget::maximumSizeImpl() const
+  {
+    return _max_size;
+  }
+
+} // namespace canvas
+} // namespace simgear
diff --git a/simgear/canvas/layout/NasalWidget.hxx b/simgear/canvas/layout/NasalWidget.hxx
new file mode 100644 (file)
index 0000000..d9e8045
--- /dev/null
@@ -0,0 +1,85 @@
+///@file Glue for GUI widgets implemented in Nasal space
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_CANVAS_NASAL_WIDGET_HXX_
+#define SG_CANVAS_NASAL_WIDGET_HXX_
+
+#include "LayoutItem.hxx"
+
+#include <simgear/nasal/cppbind/from_nasal.hxx>
+#include <simgear/nasal/cppbind/NasalHash.hxx>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  /**
+   * Baseclass/ghost to create widgets with Nasal.
+   */
+  class NasalWidget:
+    public LayoutItem
+  {
+    public:
+
+      typedef boost::function<void (nasal::Me, const SGRecti&)> SetGeometryFunc;
+
+      /**
+       *
+       * @param impl    Initial implementation hash (nasal part of
+       *                implementation)
+       */
+      NasalWidget(naRef impl);
+
+      virtual void setGeometry(const SGRecti& geom);
+
+      void setSetGeometryFunc(const SetGeometryFunc& func);
+
+      void setImpl(naRef obj);
+      naRef getImpl() const;
+
+      void setSizeHint(const SGVec2i& s);
+      void setMinimumSize(const SGVec2i& s);
+      void setMaximumSize(const SGVec2i& s);
+
+      bool _set(naContext c, const std::string& key, naRef val);
+
+      /**
+       * @param ns  Namespace to register the class interface
+       */
+      static void setupGhost(nasal::Hash& ns);
+
+    protected:
+      SetGeometryFunc       _set_geometry;
+      nasal::ObjectHolder<> _nasal_impl;
+
+      static naRef getParents(NasalWidget&, naContext);
+
+      virtual SGVec2i sizeHintImpl() const;
+      virtual SGVec2i minimumSizeImpl() const;
+      virtual SGVec2i maximumSizeImpl() const;
+
+  };
+
+  typedef SGSharedPtr<NasalWidget> NasalWidgetRef;
+
+} // namespace canvas
+} // namespace simgear
+
+
+#endif /* SG_CANVAS_NASAL_WIDGET_HXX_ */
diff --git a/simgear/canvas/layout/canvas_layout_test.cxx b/simgear/canvas/layout/canvas_layout_test.cxx
new file mode 100644 (file)
index 0000000..ffbc1c9
--- /dev/null
@@ -0,0 +1,231 @@
+// Testing canvas layouting system
+//
+// Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#define BOOST_TEST_MODULE canvas_layout
+#include <BoostTestTargetConfig.h>
+
+#include "BoxLayout.hxx"
+#include "NasalWidget.hxx"
+
+#include <simgear/debug/logstream.hxx>
+#include <cstdlib>
+
+//------------------------------------------------------------------------------
+struct SetLogLevelFixture
+{
+  SetLogLevelFixture()
+  {
+    sglog().set_log_priority(SG_DEBUG);
+  }
+};
+BOOST_GLOBAL_FIXTURE(SetLogLevelFixture);
+
+//------------------------------------------------------------------------------
+namespace sc = simgear::canvas;
+
+class TestWidget:
+  public sc::LayoutItem
+{
+  public:
+    TestWidget( const SGVec2i& min_size,
+                const SGVec2i& size_hint,
+                const SGVec2i& max_size )
+    {
+      _size_hint = size_hint;
+      _min_size = min_size;
+      _max_size = max_size;
+    }
+
+    TestWidget(const TestWidget& rhs)
+    {
+      _size_hint = rhs._size_hint;
+      _min_size = rhs._min_size;
+      _max_size = rhs._max_size;
+    }
+
+    void setMinSize(const SGVec2i& size) { _min_size = size; }
+    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; }
+};
+
+typedef SGSharedPtr<TestWidget> TestWidgetRef;
+
+//------------------------------------------------------------------------------
+BOOST_AUTO_TEST_CASE( horizontal_layout )
+{
+  sc::BoxLayout box_layout(sc::BoxLayout::BottomToTop);
+  box_layout.setSpacing(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);
+
+  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);
+
+  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);
+
+  // 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));
+
+  // Test with different spacing/padding
+  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));
+
+  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));
+  BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 107, 32));
+
+  TestWidgetRef stretch_widget( new TestWidget( SGVec2i(16, 16),
+                                                SGVec2i(32, 32),
+                                                SGVec2i(128, 32) ) );
+  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));
+
+  // Due to the stretch factor only the last widget gets additional space. All
+  // other widgets get the preferred size.
+  BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16));
+  BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 32, 32));
+  BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(58, 0, 70, 32));
+
+  // Test stretch factor
+  TestWidgetRef fast_stretch( new TestWidget(*stretch_widget) );
+  sc::BoxLayout box_layout_stretch(sc::BoxLayout::LeftToRight);
+
+  box_layout_stretch.addItem(stretch_widget, 1);
+  box_layout_stretch.addItem(fast_stretch, 2);
+
+  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));
+
+  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));
+
+  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) );
+
+    hbox.addItem(w1);
+    hbox.addItem(w2);
+
+    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));
+
+    hbox.setStretch(0, 1);
+    hbox.setStretch(1, 1);
+
+    BOOST_CHECK_EQUAL(hbox.stretch(0), 1);
+    BOOST_CHECK_EQUAL(hbox.stretch(1), 1);
+
+    hbox.update();
+
+    BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0,   0, 125, 32));
+    BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(130, 0, 126, 32));
+  }
+}
+
+//------------------------------------------------------------------------------
+BOOST_AUTO_TEST_CASE( vertical_layout)
+{
+  sc::BoxLayout vbox(sc::BoxLayout::TopToBottom);
+  vbox.setSpacing(7);
+
+  TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16),
+                                                   SGVec2i(16, 16),
+                                                   SGVec2i(16, 16) ) );
+  TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16),
+                                                       SGVec2i(32, 32),
+                                                       SGVec2i(256, 64) ) );
+
+  vbox.addItem(fixed_size_widget);
+  vbox.addItem(limited_resize_widget);
+
+  BOOST_CHECK_EQUAL(vbox.minimumSize(), SGVec2i(16, 39));
+  BOOST_CHECK_EQUAL(vbox.sizeHint(), SGVec2i(32, 55));
+  BOOST_CHECK_EQUAL(vbox.maximumSize(), SGVec2i(256, 87));
+
+  vbox.setGeometry(SGRecti(10, 20, 16, 55));
+
+  BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(10, 20, 16, 16));
+  BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(10, 43, 16, 32));
+
+  vbox.setDirection(sc::BoxLayout::BottomToTop);
+}
+
+//------------------------------------------------------------------------------
+BOOST_AUTO_TEST_CASE( nasal_layout )
+{
+  naContext c = naNewContext();
+  naRef me = naNewHash(c);
+
+  sc::LayoutItemRef nasal_item( new sc::NasalWidget(me) );
+
+  naFreeContext(c);
+}
index f37aaaa5a71551164d9f0aec5572665cb5740a47..82773287058b5815cf3b82ea8e2918ac89cca3c5 100644 (file)
@@ -31,7 +31,8 @@ typedef enum {
     SG_ENVIRONMENT = 0x00100000,
     SG_SOUND       = 0x00200000,
     SG_NAVAID      = 0x00400000,
-    SG_UNDEFD      = 0x00800000, // For range checking
+    SG_GUI         = 0x00800000,
+    SG_UNDEFD      = 0x01000000, // For range checking
 
     SG_ALL         = 0xFFFFFFFF
 } sgDebugClass;
index d3972ae7b67632e42f129a3d959650e135891052..4290f5ca9d0c3fc6a60c9df3e073501053504563 100644 (file)
@@ -70,6 +70,7 @@ const char* debugClassToString(sgDebugClass c)
     case SG_ENVIRONMENT:return "environment";
     case SG_SOUND:      return "sound";
     case SG_NAVAID:     return "navaid";
+    case SG_GUI:        return "gui";
     default:            return "unknown";
     }
 }
@@ -438,4 +439,4 @@ void requestConsole()
        global_privateLogstream->requestConsole();
 }
 
-} // of namespace simgear 
\ No newline at end of file
+} // of namespace simgear
index 52af9b30bb79fcd3cc033edf3823be011d1af6fe..27eff170ce731a32d49f31d4272662ddedd8563a 100644 (file)
@@ -30,7 +30,7 @@ namespace simgear
    */
   class PropertyBasedElement:
     public SGPropertyChangeListener,
-    public SGWeakReferenced
+    public virtual SGVirtualWeakReferenced
   {
     public:
       PropertyBasedElement(SGPropertyNode* node);