]> git.mxchange.org Git - simgear.git/blobdiff - simgear/canvas/elements/CanvasElement.cxx
Canvas: Respect clipping while event handling.
[simgear.git] / simgear / canvas / elements / CanvasElement.cxx
index 7f5ba02d037f18ee7be5c788a53ae560ce91bfe8..373632fc4fbd4cbb79bcbbcd9bd3b482041046ea 100644 (file)
 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
 
 #include "CanvasElement.hxx"
-#include <simgear/canvas/Canvas.hxx>
-#include <simgear/canvas/CanvasEventListener.hxx>
 #include <simgear/canvas/CanvasEventVisitor.hxx>
 #include <simgear/canvas/MouseEvent.hxx>
+#include <simgear/math/SGMisc.hxx>
+#include <simgear/misc/strutils.hxx>
 #include <simgear/scene/material/parseBlendFunc.hxx>
 
 #include <osg/Drawable>
 
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/foreach.hpp>
-#include <boost/lexical_cast.hpp>
 #include <boost/make_shared.hpp>
-#include <boost/tokenizer.hpp>
 
 #include <cassert>
+#include <cmath>
 #include <cstring>
 
 namespace simgear
@@ -42,6 +41,83 @@ namespace canvas
 {
   const std::string NAME_TRANSFORM = "tf";
 
+  /**
+   * glScissor with coordinates relative to different reference frames.
+   */
+  class Element::RelativeScissor:
+    public osg::Scissor
+  {
+    public:
+
+      ReferenceFrame    _coord_reference;
+      osg::Matrix       _parent_inverse;
+
+      RelativeScissor():
+        _coord_reference(GLOBAL)
+      {}
+
+      bool contains(const osg::Vec2f& pos) const
+      {
+        return _x <= pos.x() && pos.x() <= _x + _width
+            && _y <= pos.y() && pos.y() <= _y + _height;
+      }
+
+      bool contains( const osg::Vec2f& global_pos,
+                     const osg::Vec2f& parent_pos,
+                     const osg::Vec2f& local_pos ) const
+      {
+        switch( _coord_reference )
+        {
+          case GLOBAL: return contains(global_pos);
+          case PARENT: return contains(parent_pos);
+          case LOCAL:  return contains(local_pos);
+        }
+
+        return false;
+      }
+
+      virtual void apply(osg::State& state) const
+      {
+        const osg::Viewport* vp = state.getCurrentViewport();
+        float w2 = 0.5 * vp->width(),
+              h2 = 0.5 * vp->height();
+
+        osg::Matrix model_view
+        (
+          w2, 0,  0, 0,
+          0,  h2, 0, 0,
+          0,  0,  1, 0,
+          w2, h2, 0, 1
+        );
+        model_view.preMult(state.getProjectionMatrix());
+
+        if( _coord_reference != GLOBAL )
+        {
+          model_view.preMult(state.getModelViewMatrix());
+
+          if( _coord_reference == PARENT )
+            model_view.preMult(_parent_inverse);
+        }
+
+        const osg::Vec2 scale( model_view(0,0), model_view(1,1)),
+                        offset(model_view(3,0), model_view(3,1));
+
+        // TODO check/warn for rotation?
+
+        GLint x = SGMiscf::roundToInt(scale.x() * _x + offset.x()),
+              y = SGMiscf::roundToInt(scale.y() * _y + offset.y()),
+              w = SGMiscf::roundToInt(std::fabs(scale.x()) * _width),
+              h = SGMiscf::roundToInt(std::fabs(scale.y()) * _height);
+
+        if( scale.x() < 0 )
+          x -= w;
+        if( scale.y() < 0 )
+          y -= h;
+
+        glScissor(x, y, w, h);
+      }
+  };
+
   //----------------------------------------------------------------------------
   Element::OSGUserData::OSGUserData(ElementPtr element):
     element(element)
@@ -52,7 +128,19 @@ namespace canvas
   //----------------------------------------------------------------------------
   Element::~Element()
   {
+    if( !_transform.valid() )
+      return;
+
+    for(unsigned int i = 0; i < _transform->getNumChildren(); ++i)
+    {
+      OSGUserData* ud =
+        static_cast<OSGUserData*>(_transform->getChild(i)->getUserData());
 
+      if( ud )
+        // Ensure parent is cleared to prevent accessing released memory if an
+        // element somehow survives longer than his parent.
+        ud->element->_parent = 0;
+    }
   }
 
   //----------------------------------------------------------------------------
@@ -86,71 +174,27 @@ namespace canvas
     return boost::static_pointer_cast<Element>(_self.lock());
   }
 
+  //----------------------------------------------------------------------------
+  ElementPtr Element::getParent()
+  {
+    return _parent ? _parent->getWeakPtr().lock() : ElementPtr();
+  }
+
   //----------------------------------------------------------------------------
   void Element::update(double dt)
   {
-    if( !_transform->getNodeMask() )
-      // Don't do anything if element is hidden
+    if( !isVisible() )
       return;
 
-    if( _transform_dirty )
-    {
-      osg::Matrix m;
-      for( size_t i = 0; i < _transform_types.size(); ++i )
-      {
-        // Skip unused indizes...
-        if( _transform_types[i] == TT_NONE )
-          continue;
+    // Trigger matrix update
+    getMatrix();
 
-        SGPropertyNode* tf_node = _node->getChild("tf", i, true);
+    if( _attributes_dirty & SCISSOR_COORDS )
+    {
+      if( _scissor && _scissor->_coord_reference != GLOBAL )
+        _scissor->_parent_inverse = _transform->getInverseMatrix();
 
-        // Build up the matrix representation of the current transform node
-        osg::Matrix tf;
-        switch( _transform_types[i] )
-        {
-          case TT_MATRIX:
-            tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
-                              tf_node->getDoubleValue("m[1]", 0),
-                              0,
-                              tf_node->getDoubleValue("m[6]", 0),
-
-                              tf_node->getDoubleValue("m[2]", 0),
-                              tf_node->getDoubleValue("m[3]", 1),
-                              0,
-                              tf_node->getDoubleValue("m[7]", 0),
-
-                              0,
-                              0,
-                              1,
-                              0,
-
-                              tf_node->getDoubleValue("m[4]", 0),
-                              tf_node->getDoubleValue("m[5]", 0),
-                              0,
-                              tf_node->getDoubleValue("m[8]", 1) );
-            break;
-          case TT_TRANSLATE:
-            tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
-                                          tf_node->getDoubleValue("t[1]", 0),
-                                          0 ) );
-            break;
-          case TT_ROTATE:
-            tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
-            break;
-          case TT_SCALE:
-          {
-            float sx = tf_node->getDoubleValue("s[0]", 1);
-            // sy defaults to sx...
-            tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
-            break;
-          }
-          default:
-            break;
-        }
-        m.postMult( tf );
-      }
-      _transform->setMatrix(m);
-      _transform_dirty = false;
+      _attributes_dirty &= ~SCISSOR_COORDS;
     }
 
     // Update bounding box on manual update (manual updates pass zero dt)
@@ -173,31 +217,28 @@ namespace canvas
   }
 
   //----------------------------------------------------------------------------
-  naRef Element::addEventListener(const nasal::CallContext& ctx)
+  bool Element::addEventListener( const std::string& type_str,
+                                  const EventListener& cb )
   {
-    const std::string type_str = ctx.requireArg<std::string>(0);
-    naRef code = ctx.requireArg<naRef>(1);
-
     SG_LOG
     (
-      SG_NASAL,
+      SG_GENERAL,
       SG_INFO,
       "addEventListener(" << _node->getPath() << ", " << type_str << ")"
     );
 
     Event::Type type = Event::strToType(type_str);
     if( type == Event::UNKNOWN )
-      naRuntimeError( ctx.c,
-                      "addEventListener: Unknown event type %s",
-                      type_str.c_str() );
+    {
+      SG_LOG( SG_GENERAL,
+              SG_WARN,
+              "addEventListener: Unknown event type " << type_str );
+      return false;
+    }
 
-    _listener[ type ].push_back
-    (
-      boost::make_shared<EventListener>( code,
-                                         _canvas.lock()->getSystemAdapter() )
-    );
+    _listener[ type ].push_back(cb);
 
-    return naNil();
+    return true;
   }
 
   //----------------------------------------------------------------------------
@@ -209,7 +250,7 @@ namespace canvas
   //----------------------------------------------------------------------------
   bool Element::accept(EventVisitor& visitor)
   {
-    if( !_transform.valid() )
+    if( !isVisible() )
       return false;
 
     return visitor.apply(*this);
@@ -236,30 +277,43 @@ namespace canvas
     if( listeners == _listener.end() )
       return false;
 
-    BOOST_FOREACH(EventListenerPtr listener, listeners->second)
-      listener->call(event);
+    BOOST_FOREACH(EventListener const& listener, listeners->second)
+      listener(event);
 
     return true;
   }
 
   //----------------------------------------------------------------------------
-  bool Element::hitBound( const osg::Vec2f& pos,
+  bool Element::hitBound( const osg::Vec2f& global_pos,
+                          const osg::Vec2f& parent_pos,
                           const osg::Vec2f& local_pos ) const
   {
-    const osg::Vec3f pos3(pos, 0);
+    if( _scissor && !_scissor->contains(global_pos, parent_pos, local_pos) )
+      return false;
+
+    const osg::Vec3f pos3(parent_pos, 0);
 
     // Drawables have a bounding box...
     if( _drawable )
       return _drawable->getBound().contains(osg::Vec3f(local_pos, 0));
-    // ... for other elements, i.e. groups only a bounding sphere is available
     else
-      return _transform->getBound().contains(osg::Vec3f(pos, 0));
+      // ... for other elements, i.e. groups only a bounding sphere is available
+      return _transform->getBound().contains(osg::Vec3f(parent_pos, 0));
+  }
+
+
+  //----------------------------------------------------------------------------
+  void Element::setVisible(bool visible)
+  {
+    if( _transform.valid() )
+      // TODO check if we need another nodemask
+      _transform->setNodeMask(visible ? 0xffffffff : 0);
   }
 
   //----------------------------------------------------------------------------
   bool Element::isVisible() const
   {
-    return _transform->getNodeMask() != 0;
+    return _transform.valid() && _transform->getNodeMask() != 0;
   }
 
   //----------------------------------------------------------------------------
@@ -274,6 +328,18 @@ namespace canvas
     return _transform.get();
   }
 
+  //----------------------------------------------------------------------------
+  osg::Vec2f Element::posToLocal(const osg::Vec2f& pos) const
+  {
+    getMatrix();
+    const osg::Matrix& m = _transform->getInverseMatrix();
+    return osg::Vec2f
+    (
+      m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
+      m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
+    );
+  }
+
   //----------------------------------------------------------------------------
   void Element::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
   {
@@ -284,7 +350,7 @@ namespace canvas
         _transform_types.resize( child->getIndex() + 1 );
 
       _transform_types[ child->getIndex() ] = TT_NONE;
-      _transform_dirty = true;
+      _attributes_dirty |= TRANSFORM;
       return;
     }
     else if(    parent->getParent() == _node
@@ -305,7 +371,7 @@ namespace canvas
       else if( name == "s" )
         type = TT_SCALE;
 
-      _transform_dirty = true;
+      _attributes_dirty |= TRANSFORM;
       return;
     }
 
@@ -315,29 +381,37 @@ namespace canvas
   //----------------------------------------------------------------------------
   void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
   {
-    if( parent == _node && child->getNameString() == NAME_TRANSFORM )
+    if( parent == _node )
     {
-      if( !_transform.valid() )
-        return;
-
-      if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
+      if( child->getNameString() == NAME_TRANSFORM )
       {
-        SG_LOG
-        (
-          SG_GENERAL,
-          SG_WARN,
-          "Element::childRemoved: unknown transform: " << child->getPath()
-        );
-        return;
-      }
+        if( !_transform.valid() )
+          return;
 
-      _transform_types[ child->getIndex() ] = TT_NONE;
+        if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
+        {
+          SG_LOG
+          (
+            SG_GENERAL,
+            SG_WARN,
+            "Element::childRemoved: unknown transform: " << child->getPath()
+          );
+          return;
+        }
 
-      while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
-        _transform_types.pop_back();
+        _transform_types[ child->getIndex() ] = TT_NONE;
 
-      _transform_dirty = true;
-      return;
+        while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
+          _transform_types.pop_back();
+
+        _attributes_dirty |= TRANSFORM;
+        return;
+      }
+      else if( StyleInfo const* style = getStyleInfo(child->getNameString()) )
+      {
+        if( setStyle(getParentStyle(child), style) )
+          return;
+      }
     }
 
     childRemoved(child);
@@ -350,20 +424,26 @@ namespace canvas
     if( parent == _node )
     {
       const std::string& name = child->getNameString();
-      if( setStyle(child) )
+      if( StyleInfo const* style_info = getStyleInfo(name) )
+      {
+        SGPropertyNode const* style = child;
+        if( isStyleEmpty(child) )
+        {
+          child->clearValue();
+          style = getParentStyle(child);
+        }
+        setStyle(style, style_info);
         return;
+      }
       else if( name == "update" )
         return update(0);
-      else if( name == "visible" )
-        // TODO check if we need another nodemask
-        return _transform->setNodeMask( child->getBoolValue() ? 0xffffffff : 0 );
       else if( boost::starts_with(name, "blend-") )
         return (void)(_attributes_dirty |= BLEND_FUNC);
     }
     else if(   parent->getParent() == _node
             && parent->getNameString() == NAME_TRANSFORM )
     {
-      _transform_dirty = true;
+      _attributes_dirty |= TRANSFORM;
       return;
     }
 
@@ -371,21 +451,10 @@ namespace canvas
   }
 
   //----------------------------------------------------------------------------
-  bool Element::setStyle(const SGPropertyNode* child)
+  bool Element::setStyle( const SGPropertyNode* child,
+                          const StyleInfo* style_info )
   {
-    StyleSetters::const_iterator setter =
-      _style_setters.find(child->getNameString());
-    if( setter == _style_setters.end() )
-      return false;
-
-    const StyleSetter* style_setter = &setter->second.setter;
-    while( style_setter )
-    {
-      if( style_setter->func(*this, child) )
-        return true;
-      style_setter = style_setter->next;
-    }
-    return false;
+    return canApplyStyle(child) && setStyleImpl(child, style_info);
   }
 
   //----------------------------------------------------------------------------
@@ -394,6 +463,7 @@ namespace canvas
     if( clip.empty() || clip == "auto" )
     {
       getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
+      _scissor = 0;
       return;
     }
 
@@ -406,17 +476,22 @@ namespace canvas
       return;
     }
 
-    typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
-    const boost::char_separator<char> del(", \t\npx");
-
-    tokenizer tokens(clip.begin() + RECT.size(), clip.end() - 1, del);
+    const std::string sep(", \t\npx");
     int comp = 0;
-    int values[4];
-    for( tokenizer::const_iterator tok = tokens.begin();
-         tok != tokens.end() && comp < 4;
-         ++tok, ++comp )
+    float values[4];
+
+    for(size_t pos = RECT.size(); comp < 4; ++comp)
     {
-      values[comp] = boost::lexical_cast<int>(*tok);
+      pos = clip.find_first_not_of(sep, pos);
+      if( pos == std::string::npos || pos == clip.size() - 1 )
+        break;
+
+      char *end = 0;
+      values[comp] = strtod(&clip[pos], &end);
+      if( end == &clip[pos] || !end )
+        break;
+
+      pos = end - &clip[0];
     }
 
     if( comp < 4 )
@@ -425,32 +500,37 @@ namespace canvas
       return;
     }
 
-    float scale_x = 1,
-          scale_y = 1;
+    float width = values[1] - values[3],
+          height = values[2] - values[0];
 
-    CanvasPtr canvas = _canvas.lock();
-    if( canvas )
+    if( width < 0 || height < 0 )
     {
-      // The scissor rectangle isn't affected by any transformation, so we need
-      // to convert to image/canvas coordinates on our selves.
-      scale_x = canvas->getSizeX()
-              / static_cast<float>(canvas->getViewWidth());
-      scale_y = canvas->getSizeY()
-              / static_cast<float>(canvas->getViewHeight());
+      SG_LOG(SG_GENERAL, SG_WARN, "Canvas: negative clip size: " << clip);
+      return;
     }
 
-    osg::Scissor* scissor = new osg::Scissor();
+    _scissor = new RelativeScissor();
     // <top>, <right>, <bottom>, <left>
-    scissor->x() = scale_x * values[3];
-    scissor->y() = scale_y * values[0];
-    scissor->width() = scale_x * (values[1] - values[3]);
-    scissor->height() = scale_y * (values[2] - values[0]);
+    _scissor->x() = SGMiscf::roundToInt(values[3]);
+    _scissor->y() = SGMiscf::roundToInt(values[0]);
+    _scissor->width() = SGMiscf::roundToInt(width);
+    _scissor->height() = SGMiscf::roundToInt(height);
 
-    if( canvas )
-      // Canvas has y axis upside down
-      scissor->y() = canvas->getSizeY() - scissor->y() - scissor->height();
+    getOrCreateStateSet()->setAttributeAndModes(_scissor);
 
-    getOrCreateStateSet()->setAttributeAndModes(scissor);
+    SGPropertyNode* clip_frame = _node->getChild("clip-frame", 0);
+    if( clip_frame )
+      valueChanged(clip_frame);
+  }
+
+  //----------------------------------------------------------------------------
+  void Element::setClipFrame(ReferenceFrame rf)
+  {
+    if( _scissor )
+    {
+      _scissor->_coord_reference = rf;
+      _attributes_dirty |= SCISSOR_COORDS;
+    }
   }
 
   //----------------------------------------------------------------------------
@@ -481,11 +561,78 @@ namespace canvas
     osg::BoundingBox transformed;
     const osg::BoundingBox& bb = _drawable->getBound();
     for(int i = 0; i < 4; ++i)
-      transformed.expandBy( m * bb.corner(i) );
+      transformed.expandBy( bb.corner(i) * m );
 
     return transformed;
   }
 
+  //----------------------------------------------------------------------------
+  osg::Matrix Element::getMatrix() const
+  {
+    if( !(_attributes_dirty & TRANSFORM) )
+      return _transform->getMatrix();
+
+    osg::Matrix m;
+    for( size_t i = 0; i < _transform_types.size(); ++i )
+    {
+      // Skip unused indizes...
+      if( _transform_types[i] == TT_NONE )
+        continue;
+
+      SGPropertyNode* tf_node = _node->getChild("tf", i, true);
+
+      // Build up the matrix representation of the current transform node
+      osg::Matrix tf;
+      switch( _transform_types[i] )
+      {
+        case TT_MATRIX:
+          tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
+                            tf_node->getDoubleValue("m[1]", 0),
+                            0,
+                            tf_node->getDoubleValue("m[6]", 0),
+
+                            tf_node->getDoubleValue("m[2]", 0),
+                            tf_node->getDoubleValue("m[3]", 1),
+                            0,
+                            tf_node->getDoubleValue("m[7]", 0),
+
+                            0,
+                            0,
+                            1,
+                            0,
+
+                            tf_node->getDoubleValue("m[4]", 0),
+                            tf_node->getDoubleValue("m[5]", 0),
+                            0,
+                            tf_node->getDoubleValue("m[8]", 1) );
+          break;
+        case TT_TRANSLATE:
+          tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
+                                        tf_node->getDoubleValue("t[1]", 0),
+                                        0 ) );
+          break;
+        case TT_ROTATE:
+          tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
+          break;
+        case TT_SCALE:
+        {
+          float sx = tf_node->getDoubleValue("s[0]", 1);
+          // sy defaults to sx...
+          tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
+          break;
+        }
+        default:
+          break;
+      }
+      m.postMult( tf );
+    }
+    _transform->setMatrix(m);
+    _attributes_dirty &= ~TRANSFORM;
+    _attributes_dirty |= SCISSOR_COORDS;
+
+    return m;
+  }
+
   //----------------------------------------------------------------------------
   Element::StyleSetters Element::_style_setters;
 
@@ -498,11 +645,13 @@ namespace canvas
     _canvas( canvas ),
     _parent( parent ),
     _attributes_dirty( 0 ),
-    _transform_dirty( false ),
     _transform( new osg::MatrixTransform ),
     _style( parent_style ),
+    _scissor( 0 ),
     _drawable( 0 )
   {
+    staticInit();
+
     SG_LOG
     (
       SG_GL,
@@ -510,10 +659,94 @@ namespace canvas
       "New canvas element " << node->getPath()
     );
 
-    if( !isInit<Element>() )
+    // Ensure elements are drawn in order they appear in the element tree
+    _transform->getOrCreateStateSet()
+              ->setRenderBinDetails
+              (
+                0,
+                "PreOrderBin",
+                osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
+              );
+  }
+
+  //----------------------------------------------------------------------------
+  void Element::staticInit()
+  {
+    if( isInit<Element>() )
+      return;
+
+    addStyle("clip", "", &Element::setClip, false);
+    addStyle("clip-frame", "", &Element::setClipFrame, false);
+    addStyle("visible", "", &Element::setVisible, false);
+  }
+
+  //----------------------------------------------------------------------------
+  bool Element::isStyleEmpty(const SGPropertyNode* child) const
+  {
+    return !child
+        || simgear::strutils::strip(child->getStringValue()).empty();
+  }
+
+  //----------------------------------------------------------------------------
+  bool Element::canApplyStyle(const SGPropertyNode* child) const
+  {
+    if( _node == child->getParent() )
+      return true;
+
+    // Parent values do not override if element has own value
+    return isStyleEmpty( _node->getChild(child->getName()) );
+  }
+
+  //----------------------------------------------------------------------------
+  bool Element::setStyleImpl( const SGPropertyNode* child,
+                              const StyleInfo* style_info )
+  {
+    const StyleSetter* style_setter = style_info
+                                    ? &style_info->setter
+                                    : getStyleSetter(child->getNameString());
+    while( style_setter )
+    {
+      if( style_setter->func(*this, child) )
+        return true;
+      style_setter = style_setter->next;
+    }
+    return false;
+  }
+
+  //----------------------------------------------------------------------------
+  const Element::StyleInfo*
+  Element::getStyleInfo(const std::string& name) const
+  {
+    StyleSetters::const_iterator setter = _style_setters.find(name);
+    if( setter == _style_setters.end() )
+      return 0;
+
+    return &setter->second;
+  }
+
+  //----------------------------------------------------------------------------
+  const Element::StyleSetter*
+  Element::getStyleSetter(const std::string& name) const
+  {
+    const StyleInfo* info = getStyleInfo(name);
+    return info ? &info->setter : 0;
+  }
+
+  //----------------------------------------------------------------------------
+  const SGPropertyNode*
+  Element::getParentStyle(const SGPropertyNode* child) const
+  {
+    // Try to get value from parent...
+    if( _parent )
     {
-      addStyle("clip", "", &Element::setClip);
+      Style::const_iterator style =
+        _parent->_style.find(child->getNameString());
+      if( style != _parent->_style.end() )
+        return style->second;
     }
+
+    // ...or reset to default if none is available
+    return child; // TODO somehow get default value for each style?
   }
 
   //----------------------------------------------------------------------------