diff --git a/simgear/canvas/CMakeLists.txt b/simgear/canvas/CMakeLists.txt index 96a23eaa..be1a44ff 100644 --- a/simgear/canvas/CMakeLists.txt +++ b/simgear/canvas/CMakeLists.txt @@ -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 diff --git a/simgear/canvas/Canvas.cxx b/simgear/canvas/Canvas.cxx index 58c115a8..c5c8f1a5 100644 --- a/simgear/canvas/Canvas.cxx +++ b/simgear/canvas/Canvas.cxx @@ -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 #include @@ -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(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 diff --git a/simgear/canvas/Canvas.hxx b/simgear/canvas/Canvas.hxx index 594d67cf..ca897c26 100644 --- a/simgear/canvas/Canvas.hxx +++ b/simgear/canvas/Canvas.hxx @@ -134,6 +134,8 @@ namespace canvas SystemAdapterPtr _system_adapter; CanvasMgr *_canvas_mgr; + std::auto_ptr _event_manager; + int _size_x, _size_y, _view_width, @@ -155,7 +157,7 @@ namespace canvas _visible; ODGauge _texture; - std::auto_ptr _root_group; + GroupPtr _root_group; CullCallbackPtr _cull_callback; bool _render_always; // +// +// 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 + +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(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 index 00000000..f8e80571 --- /dev/null +++ b/simgear/canvas/CanvasEventManager.hxx @@ -0,0 +1,92 @@ +// Manage event handling inside a Canvas similar to the DOM Level 3 Event Model +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 + +namespace simgear +{ +namespace canvas +{ + + struct EventTarget + { + ElementWeakPtr element; + osg::Vec2f local_pos, + local_delta; + }; + typedef std::deque 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_ */ diff --git a/simgear/canvas/CanvasEventVisitor.cxx b/simgear/canvas/CanvasEventVisitor.cxx index b74ee171..777434a4 100644 --- a/simgear/canvas/CanvasEventVisitor.cxx +++ b/simgear/canvas/CanvasEventVisitor.cxx @@ -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 diff --git a/simgear/canvas/CanvasEventVisitor.hxx b/simgear/canvas/CanvasEventVisitor.hxx index e12b9088..9b1fe810 100644 --- a/simgear/canvas/CanvasEventVisitor.hxx +++ b/simgear/canvas/CanvasEventVisitor.hxx @@ -21,7 +21,7 @@ #define CANVAS_EVENT_VISITOR_HXX_ #include "canvas_fwd.hxx" -#include +#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 EventTargets; - EventTargets _target_path; - TraverseMode _traverse_mode; + TraverseMode _traverse_mode; + EventPropagationPath _target_path; + }; } // namespace canvas diff --git a/simgear/canvas/MouseEvent.hxx b/simgear/canvas/MouseEvent.hxx index 3193f278..31694171 100644 --- a/simgear/canvas/MouseEvent.hxx +++ b/simgear/canvas/MouseEvent.hxx @@ -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, // Style; diff --git a/simgear/canvas/elements/CanvasElement.cxx b/simgear/canvas/elements/CanvasElement.cxx index b20fbd4b..9395eec7 100644 --- a/simgear/canvas/elements/CanvasElement.cxx +++ b/simgear/canvas/elements/CanvasElement.cxx @@ -54,6 +54,12 @@ namespace canvas } } + //---------------------------------------------------------------------------- + ElementWeakPtr Element::getWeakPtr() const + { + return boost::static_pointer_cast(_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, diff --git a/simgear/canvas/elements/CanvasElement.hxx b/simgear/canvas/elements/CanvasElement.hxx index 0d70390e..d3a5bfa9 100644 --- a/simgear/canvas/elements/CanvasElement.hxx +++ b/simgear/canvas/elements/CanvasElement.hxx @@ -21,7 +21,7 @@ #include #include -#include +#include #include // for uint32_t #include @@ -42,7 +42,7 @@ namespace canvas { class Element: - public SGPropertyChangeListener + public PropertyBasedElement { public: typedef boost::function 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 _transform; std::vector _transform_types; - SGPropertyNode_ptr _node; Style _style; StyleSetters _style_setters; std::vector _bounding_box; diff --git a/simgear/canvas/elements/CanvasGroup.cxx b/simgear/canvas/elements/CanvasGroup.cxx index 176af793..5cd403cc 100644 --- a/simgear/canvas/elements/CanvasGroup.cxx +++ b/simgear/canvas/elements/CanvasGroup.cxx @@ -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; } //----------------------------------------------------------------------------