Implement Canvas single/double/tripple click handling.
- 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
This commit is contained in:
parent
d06d94c767
commit
fc49be1e05
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -62,6 +62,12 @@ namespace canvas
|
||||
return target;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
double Event::getTime() const
|
||||
{
|
||||
return time;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Event::stopPropagation()
|
||||
{
|
||||
|
@ -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);
|
||||
|
187
simgear/canvas/CanvasEventManager.cxx
Normal file
187
simgear/canvas/CanvasEventManager.cxx
Normal file
@ -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
|
92
simgear/canvas/CanvasEventManager.hxx
Normal file
92
simgear/canvas/CanvasEventManager.hxx
Normal file
@ -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_ */
|
@ -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
|
||||
|
@ -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;
|
||||
EventPropagationPath _target_path;
|
||||
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
|
@ -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
|
||||
mod, //<! Keyboard modifier state
|
||||
click_count; //<! Current click count
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
|
@ -58,6 +58,7 @@ namespace canvas
|
||||
|
||||
#undef SG_FWD_DECL
|
||||
|
||||
class EventManager;
|
||||
class EventVisitor;
|
||||
|
||||
typedef std::map<std::string, const SGPropertyNode*> Style;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
Loading…
Reference in New Issue
Block a user