]> git.mxchange.org Git - simgear.git/commitdiff
Implement Canvas single/double/tripple click handling.
authorThomas Geymayer <tomgey@gmail.com>
Thu, 6 Dec 2012 22:13:19 +0000 (23:13 +0100)
committerThomas Geymayer <tomgey@gmail.com>
Thu, 6 Dec 2012 22:16:36 +0000 (23:16 +0100)
 - Implement click event creation like specified in
   DOM Level 3:
   * Check for max move distance between mousedown/up
     and clicks
   * Check for click timeout
   * Count clicks and report double clicks

14 files changed:
simgear/canvas/CMakeLists.txt
simgear/canvas/Canvas.cxx
simgear/canvas/Canvas.hxx
simgear/canvas/CanvasEvent.cxx
simgear/canvas/CanvasEvent.hxx
simgear/canvas/CanvasEventManager.cxx [new file with mode: 0644]
simgear/canvas/CanvasEventManager.hxx [new file with mode: 0644]
simgear/canvas/CanvasEventVisitor.cxx
simgear/canvas/CanvasEventVisitor.hxx
simgear/canvas/MouseEvent.hxx
simgear/canvas/canvas_fwd.hxx
simgear/canvas/elements/CanvasElement.cxx
simgear/canvas/elements/CanvasElement.hxx
simgear/canvas/elements/CanvasGroup.cxx

index 96a23eaafa54dda05e9e6147faf0f9b90392889b..be1a44ff18028e4b24357990a03c95b58675b593 100644 (file)
@@ -5,6 +5,7 @@ set(HEADERS
   Canvas.hxx
   CanvasEvent.hxx
   CanvasEventListener.hxx
+  CanvasEventManager.hxx
   CanvasEventTypes.hxx
   CanvasEventVisitor.hxx
   CanvasMgr.hxx
@@ -19,6 +20,7 @@ set(SOURCES
   Canvas.cxx
   CanvasEvent.cxx
   CanvasEventListener.cxx
+  CanvasEventManager.cxx
   CanvasEventVisitor.cxx
   CanvasMgr.cxx
   CanvasPlacement.cxx
index 58c115a85eb74adf11c07e8c44319f4ae236b12e..c5c8f1a54953619ee3f6280cba23a0cf477fe9a3 100644 (file)
@@ -17,6 +17,7 @@
 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
 
 #include "Canvas.hxx"
+#include "CanvasEventManager.hxx"
 #include "CanvasEventVisitor.hxx"
 #include <simgear/canvas/MouseEvent.hxx>
 #include <simgear/canvas/CanvasPlacement.hxx>
@@ -58,6 +59,7 @@ namespace canvas
   Canvas::Canvas(SGPropertyNode* node):
     PropertyBasedElement(node),
     _canvas_mgr(0),
+    _event_manager(new EventManager),
     _size_x(-1),
     _size_y(-1),
     _view_width(-1),
@@ -356,9 +358,7 @@ namespace canvas
     if( !_root_group->accept(visitor) )
       return false;
 
-    // TODO create special events like click/dblclick etc.
-
-    return visitor.propagateEvent(event);
+    return _event_manager->handleEvent(event, visitor.getPropagationPath());
   }
 
   //----------------------------------------------------------------------------
@@ -507,6 +507,7 @@ namespace canvas
     CanvasPtr canvas = boost::static_pointer_cast<Canvas>(self);
 
     _root_group.reset( new Group(canvas, _node) );
+    _root_group->setSelf(_root_group);
 
     // Remove automatically created property listener as we forward them on our
     // own
index 594d67cf22ee937fda0e711fc27bff109672edd4..ca897c268bd331b5c2399e3e22cdfa62b8afeb64 100644 (file)
@@ -134,6 +134,8 @@ namespace canvas
       SystemAdapterPtr  _system_adapter;
       CanvasMgr        *_canvas_mgr;
 
+      std::auto_ptr<EventManager>   _event_manager;
+
       int _size_x,
           _size_y,
           _view_width,
@@ -155,7 +157,7 @@ namespace canvas
            _visible;
 
       ODGauge _texture;
-      std::auto_ptr<Group> _root_group;
+      GroupPtr _root_group;
 
       CullCallbackPtr _cull_callback;
       bool _render_always; //<! Used to disable automatic lazy rendering (culling)
index 1e9815e8bb0d1c2207c9bf22d1810679ae7f306f..d08cfc86f92c2a130730c45355da41d95aceb6a5 100644 (file)
@@ -62,6 +62,12 @@ namespace canvas
     return target;
   }
 
+  //----------------------------------------------------------------------------
+  double Event::getTime() const
+  {
+    return time;
+  }
+
   //----------------------------------------------------------------------------
   void Event::stopPropagation()
   {
index 9ac9d475675b98fe156d427fa8ae4f1d9ced56d6..39c65abc5b51ff24325240f009ba720ca36b18e9 100644 (file)
@@ -42,6 +42,7 @@ namespace canvas
 
       Type              type;
       ElementWeakPtr    target;
+      double            time;
       bool              propagation_stopped;
 
       Event();
@@ -54,6 +55,9 @@ namespace canvas
       std::string getTypeString() const;
 
       ElementWeakPtr getTarget() const;
+
+      double getTime() const;
+
       void stopPropagation();
 
       static Type strToType(const std::string& str);
diff --git a/simgear/canvas/CanvasEventManager.cxx b/simgear/canvas/CanvasEventManager.cxx
new file mode 100644 (file)
index 0000000..da10ba5
--- /dev/null
@@ -0,0 +1,187 @@
+// Manage event handling inside a Canvas similar to the DOM Level 3 Event Model
+//
+// Copyright (C) 2012  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 "CanvasEventManager.hxx"
+#include "MouseEvent.hxx"
+#include <simgear/canvas/elements/CanvasElement.hxx>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  const unsigned int drag_threshold = 8;
+  const double multi_click_timeout = 0.4;
+
+  //----------------------------------------------------------------------------
+  EventManager::EventManager():
+    _current_click_count(0)
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  bool EventManager::handleEvent( const MouseEventPtr& event,
+                                  const EventPropagationPath& path )
+  {
+    propagateEvent(event, path);
+    switch( event->type )
+    {
+      case Event::MOUSE_DOWN:
+        _last_mouse_down = StampedPropagationPath(path, event->getTime());
+        break;
+      case Event::MOUSE_UP:
+      {
+        if( _last_mouse_down.path.empty() )
+          // Ignore mouse up without any previous mouse down
+          return false;
+
+        if( checkClickDistance(path, _last_mouse_down.path) )
+          handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
+
+        break;
+      }
+      default:
+        return false;
+    }
+
+    return true;
+  }
+
+  //----------------------------------------------------------------------------
+  void EventManager::handleClick( const MouseEventPtr& event,
+                                  const EventPropagationPath& path )
+  {
+    MouseEventPtr click(new MouseEvent(*event));
+    click->type = Event::CLICK;
+
+    if( event->getTime() > _last_click.time + multi_click_timeout )
+      _current_click_count = 1;
+    else
+    {
+      // Maximum current click count is 3
+      _current_click_count = (_current_click_count % 3) + 1;
+
+      if( _current_click_count > 1 )
+      {
+        // Reset current click count if moved too far
+        if( !checkClickDistance(path, _last_click.path) )
+          _current_click_count = 1;
+      }
+    }
+
+    click->click_count = _current_click_count;
+
+    MouseEventPtr dbl_click;
+    if( _current_click_count == 2 )
+    {
+      dbl_click.reset(new MouseEvent(*click));
+      dbl_click->type = Event::DBL_CLICK;
+    }
+
+    propagateEvent(click, path);
+
+    if( dbl_click )
+      propagateEvent(dbl_click, getCommonAncestor(_last_click.path, path));
+
+    _last_click = StampedPropagationPath(path, event->getTime());
+  }
+
+  //----------------------------------------------------------------------------
+  bool EventManager::propagateEvent( const EventPtr& event,
+                                     const EventPropagationPath& path )
+  {
+    event->target = path.back().element;
+    MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
+
+    // Event propagation similar to DOM Level 3 event flow:
+    // http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
+
+    // Capturing phase
+//    for( EventTargets::iterator it = _target_path.begin();
+//                                it != _target_path.end();
+//                              ++it )
+//    {
+//      if( it->element )
+//        std::cout << it->element->getProps()->getPath() << " "
+//                  << "(" << it->local_pos.x() << "|" << it->local_pos.y() << ")\n";
+//    }
+
+    // Bubbling phase
+    for( EventPropagationPath::const_reverse_iterator
+           it = path.rbegin();
+           it != path.rend();
+         ++it )
+    {
+      ElementPtr el = it->element.lock();
+
+      if( !el )
+        // Ignore element if it has been destroyed while traversing the event
+        // (eg. removed by another event handler)
+        continue;
+
+      if( mouse_event )
+      {
+        // Position and delta are specified in local coordinate system of
+        // current element
+        mouse_event->pos = it->local_pos;
+        mouse_event->delta = it->local_delta;
+      }
+
+      el->callListeners(event);
+
+      if( event->propagation_stopped )
+        return true;
+    }
+
+    return true;
+  }
+
+  //----------------------------------------------------------------------------
+  bool
+  EventManager::checkClickDistance( const EventPropagationPath& path1,
+                                    const EventPropagationPath& path2 ) const
+  {
+    osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
+    return delta.x() < drag_threshold
+        && delta.y() < drag_threshold;
+  }
+
+  //----------------------------------------------------------------------------
+  EventPropagationPath
+  EventManager::getCommonAncestor( const EventPropagationPath& path1,
+                                   const EventPropagationPath& path2 ) const
+  {
+    if( path1.back().element.lock() == path2.back().element.lock() )
+      return path2;
+
+    EventPropagationPath path;
+
+    for( size_t i = 0; i < path1.size() && i < path2.size(); ++i )
+    {
+      if( path1[i].element.lock() != path2[i].element.lock() )
+        break;
+
+      path.push_back(path2[i]);
+    }
+
+    return path;
+  }
+
+} // namespace canvas
+} // namespace simgear
diff --git a/simgear/canvas/CanvasEventManager.hxx b/simgear/canvas/CanvasEventManager.hxx
new file mode 100644 (file)
index 0000000..f8e8057
--- /dev/null
@@ -0,0 +1,92 @@
+// Manage event handling inside a Canvas similar to the DOM Level 3 Event Model
+//
+// Copyright (C) 2012  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 CANVAS_EVENT_MANAGER_HXX_
+#define CANVAS_EVENT_MANAGER_HXX_
+
+#include "canvas_fwd.hxx"
+#include <deque>
+
+namespace simgear
+{
+namespace canvas
+{
+
+  struct EventTarget
+  {
+    ElementWeakPtr  element;
+    osg::Vec2f      local_pos,
+                    local_delta;
+  };
+  typedef std::deque<EventTarget> EventPropagationPath;
+
+  class EventManager
+  {
+    public:
+      EventManager();
+
+      bool handleEvent( const MouseEventPtr& event,
+                        const EventPropagationPath& path );
+
+    protected:
+      struct StampedPropagationPath
+      {
+        StampedPropagationPath():
+          time(0)
+        {}
+
+        StampedPropagationPath(const EventPropagationPath& path, double time):
+          path(path),
+          time(time)
+        {}
+
+        EventPropagationPath path;
+        double time;
+      };
+
+      // TODO if we really need the paths modify to not copy around the paths
+      //      that much.
+      StampedPropagationPath _last_mouse_down,
+                             _last_click;
+      size_t _current_click_count;
+
+      /**
+       * Propagate click event and handle multi-click (eg. create dblclick)
+       */
+      void handleClick( const MouseEventPtr& event,
+                        const EventPropagationPath& path );
+
+      bool propagateEvent( const EventPtr& event,
+                           const EventPropagationPath& path );
+
+      /**
+       * Check if two click events (either mousedown/up or two consecutive
+       * clicks) are inside a maximum distance to still create a click or
+       * dblclick event respectively.
+       */
+      bool checkClickDistance( const EventPropagationPath& path1,
+                               const EventPropagationPath& path2 ) const;
+      EventPropagationPath
+      getCommonAncestor( const EventPropagationPath& path1,
+                         const EventPropagationPath& path2 ) const;
+  };
+
+} // namespace canvas
+} // namespace simgear
+
+#endif /* CANVAS_EVENT_MANAGER_HXX_ */
index b74ee171690923b46ba5510d472bbbbb34b97524..777434a4b055d49b0db19d283a236ff04c9de0f7 100644 (file)
@@ -35,7 +35,7 @@ namespace canvas
   {
     if( mode == TRAVERSE_DOWN )
     {
-      EventTarget target = {0, pos, delta};
+      EventTarget target = {ElementWeakPtr(), pos, delta};
       _target_path.push_back(target);
     }
   }
@@ -83,7 +83,7 @@ namespace canvas
         m(0, 1) * delta[0] + m(1, 1) * delta[1]
       );
 
-      EventTarget target = {&el, local_pos, local_delta};
+      EventTarget target = {el.getWeakPtr(), local_pos, local_delta};
       _target_path.push_back(target);
 
       if( el.traverse(*this) || _target_path.size() <= 2 )
@@ -97,36 +97,9 @@ namespace canvas
   }
 
   //----------------------------------------------------------------------------
-  bool EventVisitor::propagateEvent(const EventPtr& event)
+  const EventPropagationPath& EventVisitor::getPropagationPath() const
   {
-    // Event propagation similar to DOM Level 3 event flow:
-    // http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
-
-    // Capturing phase
-//    for( EventTargets::iterator it = _target_path.begin();
-//                                it != _target_path.end();
-//                              ++it )
-//    {
-//      if( it->element )
-//        std::cout << it->element->getProps()->getPath() << " "
-//                  << "(" << it->local_pos.x() << "|" << it->local_pos.y() << ")\n";
-//    }
-
-    // Bubbling phase
-    for( EventTargets::reverse_iterator it = _target_path.rbegin();
-                                        it != _target_path.rend();
-                                      ++it )
-    {
-      if( !it->element )
-        continue;
-
-      it->element->callListeners(event);
-
-      if( event->propagation_stopped )
-        return true;
-    }
-
-    return true;
+    return _target_path;
   }
 
 } // namespace canvas
index e12b908856086abefd5bfc78884dbb8a3d4cfee6..9b1fe810ee65511ca705f2bd46721cfc0a7f499a 100644 (file)
@@ -21,7 +21,7 @@
 #define CANVAS_EVENT_VISITOR_HXX_
 
 #include "canvas_fwd.hxx"
-#include <deque>
+#include "CanvasEventManager.hxx"
 
 namespace simgear
 {
@@ -45,19 +45,13 @@ namespace canvas
       virtual bool traverse(Element& el);
       virtual bool apply(Element& el);
 
-      bool propagateEvent(const EventPtr& event);
+      const EventPropagationPath& getPropagationPath() const;
 
     protected:
-      struct EventTarget
-      {
-        Element*   element;
-        osg::Vec2f local_pos,
-                   local_delta;
-      };
-      typedef std::deque<EventTarget> EventTargets;
 
-      EventTargets  _target_path;
-      TraverseMode  _traverse_mode;
+      TraverseMode          _traverse_mode;
+      EventPropagationPath  _target_path;
+
   };
 
 } // namespace canvas
index 3193f27819e49cb7fa74374d77c1d05cfbc0e14b..316941714aac493ab855a74c898e270854a2e403 100644 (file)
@@ -34,7 +34,8 @@ namespace canvas
       MouseEvent():
         button(-1),
         state(-1),
-        mod(-1)
+        mod(-1),
+        click_count(0)
       {}
 
       osg::Vec2f getPos() const { return pos; }
@@ -47,11 +48,14 @@ namespace canvas
       float getDeltaX() const { return delta.x(); }
       float getDeltaY() const { return delta.y(); }
 
+      int getCurrentClickCount() const { return click_count; }
+
       osg::Vec2f  pos,
                   delta;
-      int         button, //<! Button for this event
-                  state,  //<! Current button state
-                  mod;    //<! Keyboard modifier state
+      int         button,       //<! Button for this event
+                  state,        //<! Current button state
+                  mod,          //<! Keyboard modifier state
+                  click_count;  //<! Current click count
   };
 
 } // namespace canvas
index 4b0e41e10ccc2acca7011550fe0a992e817da118..8694d86e929b70c127077e8821e86a815d7b7d47 100644 (file)
@@ -58,6 +58,7 @@ namespace canvas
 
 #undef SG_FWD_DECL
 
+  class EventManager;
   class EventVisitor;
 
   typedef std::map<std::string, const SGPropertyNode*> Style;
index b20fbd4bc54aa81a1d0aafcdee227282754a6944..9395eec7e51c628561ca57d66977860c7ba3834a 100644 (file)
@@ -54,6 +54,12 @@ namespace canvas
     }
   }
 
+  //----------------------------------------------------------------------------
+  ElementWeakPtr Element::getWeakPtr() const
+  {
+    return boost::static_pointer_cast<Element>(_self.lock());
+  }
+
   //----------------------------------------------------------------------------
   void Element::update(double dt)
   {
@@ -140,18 +146,6 @@ namespace canvas
     return naNil();
   }
 
-  //----------------------------------------------------------------------------
-  SGConstPropertyNode_ptr Element::getProps() const
-  {
-    return _node;
-  }
-
-  //----------------------------------------------------------------------------
-  SGPropertyNode_ptr Element::getProps()
-  {
-    return  _node;
-  }
-
   //----------------------------------------------------------------------------
   bool Element::accept(EventVisitor& visitor)
   {
@@ -339,17 +333,14 @@ namespace canvas
                     const SGPropertyNode_ptr& node,
                     const Style& parent_style,
                     Element* parent ):
+    PropertyBasedElement(node),
     _canvas( canvas ),
     _parent( parent ),
     _transform_dirty( false ),
     _transform( new osg::MatrixTransform ),
-    _node( node ),
     _style( parent_style ),
     _drawable( 0 )
   {
-    assert( _node );
-    _node->addChangeListener(this);
-
     SG_LOG
     (
       SG_GL,
index 0d70390ebe467edf1c522b6292005edd79718973..d3a5bfa9e01f417c52f6d24c59b7d046b3cdf994 100644 (file)
@@ -21,7 +21,7 @@
 
 #include <simgear/canvas/canvas_fwd.hxx>
 #include <simgear/canvas/CanvasEvent.hxx>
-#include <simgear/props/props.hxx>
+#include <simgear/props/PropertyBasedElement.hxx>
 #include <simgear/misc/stdint.hxx> // for uint32_t
 #include <simgear/nasal/cppbind/Ghost.hxx>
 
@@ -42,7 +42,7 @@ namespace canvas
 {
 
   class Element:
-    public SGPropertyChangeListener
+    public PropertyBasedElement
   {
     public:
       typedef boost::function<void(const SGPropertyNode*)> StyleSetter;
@@ -62,6 +62,8 @@ namespace canvas
        */
       virtual ~Element() = 0;
 
+      ElementWeakPtr getWeakPtr() const;
+
       /**
        * Called every frame to update internal state
        *
@@ -71,9 +73,6 @@ namespace canvas
 
       naRef addEventListener(const nasal::CallContext& ctx);
 
-      SGConstPropertyNode_ptr getProps() const;
-      SGPropertyNode_ptr getProps();
-
       virtual bool accept(EventVisitor& visitor);
       virtual bool ascend(EventVisitor& visitor);
       virtual bool traverse(EventVisitor& visitor);
@@ -121,13 +120,13 @@ namespace canvas
 
       CanvasWeakPtr _canvas;
       Element      *_parent;
+
       uint32_t _attributes_dirty;
 
       bool _transform_dirty;
       osg::ref_ptr<osg::MatrixTransform>    _transform;
       std::vector<TransformType>            _transform_types;
 
-      SGPropertyNode_ptr                _node;
       Style                             _style;
       StyleSetters                      _style_setters;
       std::vector<SGPropertyNode_ptr>   _bounding_box;
index 176af793e2e9d1cf5ff1c254d7366bf4c993e03b..5cd403ccd2b5876bca04cbd22154ad88b6edf6e3 100644 (file)
@@ -41,7 +41,9 @@ namespace canvas
                             const Style& style,
                             Element* parent )
   {
-    return ElementPtr( new T(canvas, node, style, parent) );
+    ElementPtr el( new T(canvas, node, style, parent) );
+    el->setSelf(el);
+    return el;
   }
 
   //----------------------------------------------------------------------------