]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/gui_mgr.cxx
Update to latest SimGear and fix eating up every 2nd scroll event
[flightgear.git] / src / Canvas / gui_mgr.cxx
1 // Canvas gui/dialog manager
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 #include "gui_mgr.hxx"
20 #include <Canvas/window.hxx>
21
22 #include <Main/fg_os.hxx>
23 #include <Main/fg_props.hxx>
24 #include <Main/globals.hxx>
25 #include <Viewer/CameraGroup.hxx>
26 #include <Viewer/renderer.hxx>
27
28 #include <simgear/canvas/Canvas.hxx>
29 #include <simgear/canvas/CanvasPlacement.hxx>
30
31 #include <osg/BlendFunc>
32 #include <osgViewer/Viewer>
33 #include <osgGA/GUIEventHandler>
34
35 #include <boost/bind.hpp>
36
37 /**
38  * Event handler
39  */
40 class GUIEventHandler:
41   public osgGA::GUIEventHandler
42 {
43   public:
44     GUIEventHandler(GUIMgr* gui_mgr):
45       _gui_mgr( gui_mgr )
46     {}
47
48     bool handle( const osgGA::GUIEventAdapter& ea,
49                  osgGA::GUIActionAdapter& aa,
50                  osg::Object*,
51                  osg::NodeVisitor* )
52     {
53       if( ea.getHandled() )
54         return false;
55       return _gui_mgr->handleEvent(ea);
56     }
57
58   protected:
59     GUIMgr *_gui_mgr;
60 };
61
62 /**
63  * Track a canvas placement on a window
64  */
65 class WindowPlacement:
66   public simgear::canvas::Placement
67 {
68   public:
69     WindowPlacement( SGPropertyNode* node,
70                      canvas::WindowPtr window,
71                      simgear::canvas::CanvasPtr canvas ):
72       Placement(node),
73       _window(window),
74       _canvas(canvas)
75     {}
76
77     /**
78      * Remove placement from window
79      */
80     virtual ~WindowPlacement()
81     {
82       canvas::WindowPtr window = _window.lock();
83       simgear::canvas::CanvasPtr canvas = _canvas.lock();
84
85       if( window && canvas && canvas == window->getCanvas().lock() )
86         window->setCanvas( simgear::canvas::CanvasPtr() );
87     }
88
89   private:
90     canvas::WindowWeakPtr _window;
91     simgear::canvas::CanvasWeakPtr _canvas;
92 };
93
94 /**
95  * Store pointer to window as user data
96  */
97 class WindowUserData:
98   public osg::Referenced
99 {
100   public:
101     canvas::WindowWeakPtr window;
102     WindowUserData(canvas::WindowPtr window):
103       window(window)
104     {}
105 };
106
107 //------------------------------------------------------------------------------
108 typedef boost::shared_ptr<canvas::Window> WindowPtr;
109 WindowPtr windowFactory(SGPropertyNode* node)
110 {
111   return WindowPtr(new canvas::Window(node));
112 }
113
114 //------------------------------------------------------------------------------
115 GUIMgr::GUIMgr():
116   PropertyBasedMgr( fgGetNode("/sim/gui/canvas", true),
117                     "window",
118                     &windowFactory ),
119   _event_handler( new GUIEventHandler(this) ),
120   _transform( new osg::MatrixTransform ),
121   _width(_props, "size[0]"),
122   _height(_props, "size[1]"),
123   _resize(canvas::Window::NONE),
124   _last_cursor(MOUSE_CURSOR_NONE),
125   _last_scroll_time(0)
126 {
127   _width = _height = -1;
128
129   osg::Camera* camera =
130     flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
131   assert(camera);
132   camera->addChild(_transform);
133
134   osg::Viewport* vp = camera->getViewport();
135   handleResize(vp->x(), vp->y(), vp->width(), vp->height());
136
137   simgear::canvas::Canvas::addPlacementFactory
138   (
139     "window",
140     boost::bind(&GUIMgr::addPlacement, this, _1, _2)
141   );
142
143   osg::StateSet* stateSet = _transform->getOrCreateStateSet();
144   stateSet->setDataVariance(osg::Object::STATIC);
145   stateSet->setRenderBinDetails(1000, "RenderBin");
146
147   // speed optimization?
148   stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
149   stateSet->setAttribute(new osg::BlendFunc(
150     osg::BlendFunc::SRC_ALPHA,
151     osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
152   );
153   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
154   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
155   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
156   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
157 }
158
159 //------------------------------------------------------------------------------
160 void GUIMgr::init()
161 {
162   PropertyBasedMgr::init();
163
164   globals->get_renderer()
165          ->getViewer()
166          ->getEventHandlers()
167          // GUI is on top of everything so lets install as first event handler
168          .push_front( _event_handler );
169 }
170
171 //------------------------------------------------------------------------------
172 void GUIMgr::shutdown()
173 {
174   PropertyBasedMgr::shutdown();
175
176   globals->get_renderer()
177          ->getViewer()
178          ->removeEventHandler( _event_handler );
179 }
180
181 //------------------------------------------------------------------------------
182 void GUIMgr::elementCreated(simgear::PropertyBasedElementPtr element)
183 {
184   canvas::WindowPtr window =
185     boost::static_pointer_cast<canvas::Window>(element);
186
187   size_t layer_index = std::max(0, window->getProps()->getIntValue("layer", 1));
188   osg::Group *layer = 0;
189
190   if( layer_index < _transform->getNumChildren() )
191   {
192     layer = _transform->getChild(layer_index)->asGroup();
193     assert(layer);
194   }
195   else
196   {
197     while( _transform->getNumChildren() <= layer_index )
198     {
199       layer = new osg::Group;
200       _transform->addChild(layer);
201     }
202   }
203   window->getGroup()->setUserData(new WindowUserData(window));
204   layer->addChild(window->getGroup());
205 }
206
207 //------------------------------------------------------------------------------
208 bool GUIMgr::handleEvent(const osgGA::GUIEventAdapter& ea)
209 {
210   switch( ea.getEventType() )
211   {
212     case osgGA::GUIEventAdapter::PUSH:
213     case osgGA::GUIEventAdapter::RELEASE:
214 //    case osgGA::GUIEventAdapter::DOUBLECLICK:
215 //    // DOUBLECLICK doesn't seem to be triggered...
216     case osgGA::GUIEventAdapter::DRAG:
217     case osgGA::GUIEventAdapter::MOVE:
218     case osgGA::GUIEventAdapter::SCROLL:
219       return handleMouse(ea);
220     case osgGA::GUIEventAdapter::RESIZE:
221       handleResize( ea.getWindowX(),
222                     ea.getWindowY(),
223                     ea.getWindowWidth(),
224                     ea.getWindowHeight() );
225       return false; // Let other event handlers also consume resize events
226     default:
227       return false;
228   }
229 }
230
231 //------------------------------------------------------------------------------
232 canvas::WindowPtr GUIMgr::getWindow(size_t i)
233 {
234   return boost::static_pointer_cast<canvas::Window>(_elements[i]);
235 }
236
237 //------------------------------------------------------------------------------
238 simgear::canvas::Placements
239 GUIMgr::addPlacement( SGPropertyNode* node,
240                       simgear::canvas::CanvasPtr canvas )
241 {
242   int placement_index = node->getIntValue("index", -1);
243
244   simgear::canvas::Placements placements;
245   for( size_t i = 0; i < _elements.size(); ++i )
246   {
247     if( placement_index >= 0 && static_cast<int>(i) != placement_index )
248       continue;
249
250     canvas::WindowPtr window = getWindow(i);
251     if( !window )
252       continue;
253
254     window->setCanvas(canvas);
255     placements.push_back(
256       simgear::canvas::PlacementPtr(new WindowPlacement(node, window, canvas))
257     );
258   }
259   return placements;
260 }
261
262 /*
263 RESIZE AREAS
264 ============
265
266 |   || |      _ inside corner region (L-shaped part inside margin) both
267 |___||_|_ _ _/  directions can be resized (outside only one axis)
268 |   || |     |
269 |   || |
270 |   || |_____|__                  _
271 |   ||       |   } margin_neg      \
272 |    ========|== <-- window border  |_ area where resize
273 |            |   } margin_pos       |  can be initiated
274 |____________|__/                 _/
275 |<- corner ->|
276 */
277 const float resize_margin_pos = 12;
278 const float resize_margin_neg = 2;
279 const float resize_corner = 20;
280
281 //------------------------------------------------------------------------------
282 bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
283 {
284   if( !_transform->getNumChildren() )
285     return false;
286
287   namespace sc = simgear::canvas;
288   sc::MouseEventPtr event(new sc::MouseEvent);
289   event->time = ea.getTime();
290
291   event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
292   event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
293   if(    ea.getMouseYOrientation()
294       != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
295     event->screen_pos.y() = _height - event->screen_pos.y();
296
297   event->delta.x() = event->getScreenX() - _last_x;
298   event->delta.y() = event->getScreenY() - _last_y;
299
300   _last_x = event->getScreenX();
301   _last_y = event->getScreenY();
302
303   event->client_pos = event->screen_pos;
304   event->button = ea.getButton();
305   event->state = ea.getButtonMask();
306   event->mod = ea.getModKeyMask();
307
308   if( !_resize_window.expired() )
309   {
310     switch( ea.getEventType() )
311     {
312       case osgGA::GUIEventAdapter::RELEASE:
313         _resize_window.lock()->handleResize(canvas::Window::NONE);
314         _resize_window.reset();
315         break;
316       case osgGA::GUIEventAdapter::DRAG:
317         _resize_window.lock()->handleResize(_resize, event->delta);
318         return true;
319       default:
320         return false;
321     }
322   }
323
324   canvas::WindowPtr window_at_cursor;
325   for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
326   {
327     osg::Group *layer = _transform->getChild(i)->asGroup();
328     assert(layer);
329     if( !layer->getNumChildren() )
330       continue;
331
332     for( int j = layer->getNumChildren() - 1; j >= 0; --j )
333     {
334       assert(layer->getChild(j)->getUserData());
335       canvas::WindowPtr window =
336         static_cast<WindowUserData*>(layer->getChild(j)->getUserData())
337           ->window.lock();
338       float margin = window->isResizable() ? resize_margin_pos : 0;
339       if( window->getRegion().contains( event->getScreenX(),
340                                         event->getScreenY(),
341                                         margin ) )
342       {
343         window_at_cursor = window;
344         break;
345       }
346     }
347
348     if( window_at_cursor )
349       break;
350   }
351
352   if( window_at_cursor )
353   {
354     const SGRect<float>& reg = window_at_cursor->getRegion();
355
356     if(     window_at_cursor->isResizable()
357         && (  ea.getEventType() == osgGA::GUIEventAdapter::MOVE
358            || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
359            || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
360            )
361         && !reg.contains( event->getScreenX(),
362                           event->getScreenY(),
363                           -resize_margin_neg ) )
364     {
365       if( !_last_cursor )
366         _last_cursor = fgGetMouseCursor();
367
368       _resize = 0;
369
370       if( event->getScreenX() <= reg.l() + resize_corner )
371         _resize |= canvas::Window::LEFT;
372       else if( event->getScreenX() >= reg.r() - resize_corner )
373         _resize |= canvas::Window::RIGHT;
374
375       if( event->getScreenY() <= reg.t() + resize_corner )
376         _resize |= canvas::Window::TOP;
377       else if( event->getScreenY() >= reg.b() - resize_corner )
378         _resize |= canvas::Window::BOTTOM;
379
380       static const int cursor_mapping[] =
381       {
382         0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
383         MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
384         MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
385       };
386
387       if( !cursor_mapping[_resize] )
388         return false;
389
390       fgSetMouseCursor(cursor_mapping[_resize]);
391
392       if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
393       {
394         _resize_window = window_at_cursor;
395         window_at_cursor->doRaise();
396         window_at_cursor->handleResize( _resize | canvas::Window::INIT,
397                                         event->delta );
398       }
399
400       return true;
401     }
402   }
403
404   if( _last_cursor )
405   {
406     fgSetMouseCursor(_last_cursor);
407     _last_cursor = 0;
408     return true;
409   }
410
411   canvas::WindowPtr target_window = window_at_cursor;
412   switch( ea.getEventType() )
413   {
414     case osgGA::GUIEventAdapter::PUSH:
415       _last_push = window_at_cursor;
416       event->type = sc::Event::MOUSE_DOWN;
417       break;
418     case osgGA::GUIEventAdapter::SCROLL:
419       switch( ea.getScrollingMotion() )
420       {
421         case osgGA::GUIEventAdapter::SCROLL_UP:
422           event->delta.y() = 1;
423           break;
424         case osgGA::GUIEventAdapter::SCROLL_DOWN:
425           event->delta.y() = -1;
426           break;
427         default:
428           return false;
429       }
430
431       // osg sends two events for every scrolling motion. We don't need
432       // duplicate events, so lets ignore the second event with the same
433       // timestamp.
434       if( _last_scroll_time == ea.getTime() )
435         return window_at_cursor ? true : false;
436       _last_scroll_time = ea.getTime();
437
438       event->type = sc::Event::WHEEL;
439       break;
440     case osgGA::GUIEventAdapter::MOVE:
441     {
442       canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
443       if( last_mouse_over != window_at_cursor && last_mouse_over )
444       {
445         sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
446         move_event->type = sc::Event::MOUSE_LEAVE;
447
448         // Let the event position be always relative to the top left window
449         // corner
450         move_event->client_pos.x() -= last_mouse_over->getRegion().x();
451         move_event->client_pos.y() -= last_mouse_over->getRegion().y();
452
453         last_mouse_over->handleMouseEvent(move_event);
454       }
455       _last_mouse_over = window_at_cursor;
456       event->type = sc::Event::MOUSE_MOVE;
457       break;
458     }
459     case osgGA::GUIEventAdapter::RELEASE:
460       target_window = _last_push.lock();
461       _last_push.reset();
462       event->type = sc::Event::MOUSE_UP;
463       break;
464
465     case osgGA::GUIEventAdapter::DRAG:
466       target_window = _last_push.lock();
467       event->type = sc::Event::DRAG;
468       break;
469
470     default:
471       return false;
472   }
473
474   if( target_window )
475   {
476     // Let the event position be always relative to the top left window corner
477     event->client_pos.x() -= target_window->getRegion().x();
478     event->client_pos.y() -= target_window->getRegion().y();
479
480     return target_window->handleMouseEvent(event);
481   }
482   else
483     return false;
484 }
485
486 //------------------------------------------------------------------------------
487 void GUIMgr::handleResize(int x, int y, int width, int height)
488 {
489   if( _width == width && _height == height )
490     return;
491
492   _width = width;
493   _height = height;
494
495   // Origin should be at top left corner, therefore we need to mirror the y-axis
496   _transform->setMatrix(osg::Matrix(
497     1,  0, 0, 0,
498     0, -1, 0, 0,
499     0,  0, 1, 0,
500     0, _height, 0, 1
501   ));
502 }