]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/CanvasEventManager.cxx
Implement Canvas single/double/tripple click handling.
[simgear.git] / simgear / canvas / CanvasEventManager.cxx
1 // Manage event handling inside a Canvas similar to the DOM Level 3 Event Model
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
18
19 #include "CanvasEventManager.hxx"
20 #include "MouseEvent.hxx"
21 #include <simgear/canvas/elements/CanvasElement.hxx>
22
23 namespace simgear
24 {
25 namespace canvas
26 {
27
28   const unsigned int drag_threshold = 8;
29   const double multi_click_timeout = 0.4;
30
31   //----------------------------------------------------------------------------
32   EventManager::EventManager():
33     _current_click_count(0)
34   {
35
36   }
37
38   //----------------------------------------------------------------------------
39   bool EventManager::handleEvent( const MouseEventPtr& event,
40                                   const EventPropagationPath& path )
41   {
42     propagateEvent(event, path);
43     switch( event->type )
44     {
45       case Event::MOUSE_DOWN:
46         _last_mouse_down = StampedPropagationPath(path, event->getTime());
47         break;
48       case Event::MOUSE_UP:
49       {
50         if( _last_mouse_down.path.empty() )
51           // Ignore mouse up without any previous mouse down
52           return false;
53
54         if( checkClickDistance(path, _last_mouse_down.path) )
55           handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
56
57         break;
58       }
59       default:
60         return false;
61     }
62
63     return true;
64   }
65
66   //----------------------------------------------------------------------------
67   void EventManager::handleClick( const MouseEventPtr& event,
68                                   const EventPropagationPath& path )
69   {
70     MouseEventPtr click(new MouseEvent(*event));
71     click->type = Event::CLICK;
72
73     if( event->getTime() > _last_click.time + multi_click_timeout )
74       _current_click_count = 1;
75     else
76     {
77       // Maximum current click count is 3
78       _current_click_count = (_current_click_count % 3) + 1;
79
80       if( _current_click_count > 1 )
81       {
82         // Reset current click count if moved too far
83         if( !checkClickDistance(path, _last_click.path) )
84           _current_click_count = 1;
85       }
86     }
87
88     click->click_count = _current_click_count;
89
90     MouseEventPtr dbl_click;
91     if( _current_click_count == 2 )
92     {
93       dbl_click.reset(new MouseEvent(*click));
94       dbl_click->type = Event::DBL_CLICK;
95     }
96
97     propagateEvent(click, path);
98
99     if( dbl_click )
100       propagateEvent(dbl_click, getCommonAncestor(_last_click.path, path));
101
102     _last_click = StampedPropagationPath(path, event->getTime());
103   }
104
105   //----------------------------------------------------------------------------
106   bool EventManager::propagateEvent( const EventPtr& event,
107                                      const EventPropagationPath& path )
108   {
109     event->target = path.back().element;
110     MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
111
112     // Event propagation similar to DOM Level 3 event flow:
113     // http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
114
115     // Capturing phase
116 //    for( EventTargets::iterator it = _target_path.begin();
117 //                                it != _target_path.end();
118 //                              ++it )
119 //    {
120 //      if( it->element )
121 //        std::cout << it->element->getProps()->getPath() << " "
122 //                  << "(" << it->local_pos.x() << "|" << it->local_pos.y() << ")\n";
123 //    }
124
125     // Bubbling phase
126     for( EventPropagationPath::const_reverse_iterator
127            it = path.rbegin();
128            it != path.rend();
129          ++it )
130     {
131       ElementPtr el = it->element.lock();
132
133       if( !el )
134         // Ignore element if it has been destroyed while traversing the event
135         // (eg. removed by another event handler)
136         continue;
137
138       if( mouse_event )
139       {
140         // Position and delta are specified in local coordinate system of
141         // current element
142         mouse_event->pos = it->local_pos;
143         mouse_event->delta = it->local_delta;
144       }
145
146       el->callListeners(event);
147
148       if( event->propagation_stopped )
149         return true;
150     }
151
152     return true;
153   }
154
155   //----------------------------------------------------------------------------
156   bool
157   EventManager::checkClickDistance( const EventPropagationPath& path1,
158                                     const EventPropagationPath& path2 ) const
159   {
160     osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
161     return delta.x() < drag_threshold
162         && delta.y() < drag_threshold;
163   }
164
165   //----------------------------------------------------------------------------
166   EventPropagationPath
167   EventManager::getCommonAncestor( const EventPropagationPath& path1,
168                                    const EventPropagationPath& path2 ) const
169   {
170     if( path1.back().element.lock() == path2.back().element.lock() )
171       return path2;
172
173     EventPropagationPath path;
174
175     for( size_t i = 0; i < path1.size() && i < path2.size(); ++i )
176     {
177       if( path1[i].element.lock() != path2[i].element.lock() )
178         break;
179
180       path.push_back(path2[i]);
181     }
182
183     return path;
184   }
185
186 } // namespace canvas
187 } // namespace simgear