]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/gui_mgr.cxx
006dd2d689ee601f1e5ca9db2400aa916f774d3b
[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   _cb_mouse_mode( this,
122                   &GUIMgr::handleMouseMode,
123                   fgGetNode("/devices/status/mice/mouse[0]/mode") ),
124   _handle_events(true),
125   _width(_props, "size[0]"),
126   _height(_props, "size[1]"),
127   _resize(canvas::Window::NONE),
128   _last_cursor(MOUSE_CURSOR_NONE),
129   _last_scroll_time(0)
130 {
131   _width = _height = -1;
132
133   osg::Camera* camera =
134     flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
135   assert(camera);
136   camera->addChild(_transform);
137
138   simgear::canvas::Canvas::addPlacementFactory
139   (
140     "window",
141     boost::bind(&GUIMgr::addPlacement, this, _1, _2)
142   );
143
144   osg::StateSet* stateSet = _transform->getOrCreateStateSet();
145   stateSet->setDataVariance(osg::Object::STATIC);
146   stateSet->setRenderBinDetails(1000, "RenderBin");
147
148   // speed optimization?
149   stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
150   stateSet->setAttribute(new osg::BlendFunc(
151     osg::BlendFunc::SRC_ALPHA,
152     osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
153   );
154   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
155   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
156   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
157   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
158 }
159
160 //------------------------------------------------------------------------------
161 void GUIMgr::init()
162 {
163   handleResize
164   (
165     0,
166     0,
167     fgGetInt("/sim/startup/xsize"),
168     fgGetInt("/sim/startup/ysize")
169   );
170
171   PropertyBasedMgr::init();
172
173   globals->get_renderer()
174          ->getViewer()
175          ->getEventHandlers()
176          // GUI is on top of everything so lets install as first event handler
177          .push_front( _event_handler );
178 }
179
180 //------------------------------------------------------------------------------
181 void GUIMgr::shutdown()
182 {
183   PropertyBasedMgr::shutdown();
184
185   globals->get_renderer()
186          ->getViewer()
187          ->removeEventHandler( _event_handler );
188 }
189
190 //------------------------------------------------------------------------------
191 void GUIMgr::elementCreated(simgear::PropertyBasedElementPtr element)
192 {
193   canvas::WindowPtr window =
194     boost::static_pointer_cast<canvas::Window>(element);
195
196   size_t layer_index = std::max(0, window->getProps()->getIntValue("layer", 1));
197   osg::Group *layer = 0;
198
199   if( layer_index < _transform->getNumChildren() )
200   {
201     layer = _transform->getChild(layer_index)->asGroup();
202     assert(layer);
203   }
204   else
205   {
206     while( _transform->getNumChildren() <= layer_index )
207     {
208       layer = new osg::Group;
209       _transform->addChild(layer);
210     }
211   }
212   window->getGroup()->setUserData(new WindowUserData(window));
213   layer->addChild(window->getGroup());
214 }
215
216 //------------------------------------------------------------------------------
217 bool GUIMgr::handleEvent(const osgGA::GUIEventAdapter& ea)
218 {
219   switch( ea.getEventType() )
220   {
221     case osgGA::GUIEventAdapter::PUSH:
222     case osgGA::GUIEventAdapter::RELEASE:
223 //    case osgGA::GUIEventAdapter::DOUBLECLICK:
224 //    // DOUBLECLICK doesn't seem to be triggered...
225     case osgGA::GUIEventAdapter::DRAG:
226     case osgGA::GUIEventAdapter::MOVE:
227     case osgGA::GUIEventAdapter::SCROLL:
228       return handleMouse(ea);
229     case osgGA::GUIEventAdapter::RESIZE:
230       handleResize( ea.getWindowX(),
231                     ea.getWindowY(),
232                     ea.getWindowWidth(),
233                     ea.getWindowHeight() );
234       return false; // Let other event handlers also consume resize events
235     default:
236       return false;
237   }
238 }
239
240 //------------------------------------------------------------------------------
241 canvas::WindowPtr GUIMgr::getWindow(size_t i)
242 {
243   return boost::static_pointer_cast<canvas::Window>(_elements[i]);
244 }
245
246 //------------------------------------------------------------------------------
247 simgear::canvas::Placements
248 GUIMgr::addPlacement( SGPropertyNode* node,
249                       simgear::canvas::CanvasPtr canvas )
250 {
251   int placement_index = node->getIntValue("index", -1);
252
253   simgear::canvas::Placements placements;
254   for( size_t i = 0; i < _elements.size(); ++i )
255   {
256     if( placement_index >= 0 && static_cast<int>(i) != placement_index )
257       continue;
258
259     canvas::WindowPtr window = getWindow(i);
260     if( !window )
261       continue;
262
263     window->setCanvas(canvas);
264     placements.push_back(
265       simgear::canvas::PlacementPtr(new WindowPlacement(node, window, canvas))
266     );
267   }
268   return placements;
269 }
270
271 /*
272 RESIZE AREAS
273 ============
274
275 |   || |      _ inside corner region (L-shaped part inside margin) both
276 |___||_|_ _ _/  directions can be resized (outside only one axis)
277 |   || |     |
278 |   || |
279 |   || |_____|__                  _
280 |   ||       |   } margin_neg      \
281 |    ========|== <-- window border  |_ area where resize
282 |            |   } margin_pos       |  can be initiated
283 |____________|__/                 _/
284 |<- corner ->|
285 */
286 const float resize_margin_pos = 12;
287 const float resize_margin_neg = 2;
288 const float resize_corner = 20;
289
290 //------------------------------------------------------------------------------
291 bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
292 {
293   if( !_transform->getNumChildren() || !_handle_events )
294     return false;
295
296   namespace sc = simgear::canvas;
297   sc::MouseEventPtr event(new sc::MouseEvent(ea));
298
299   event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
300   event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
301   if(    ea.getMouseYOrientation()
302       != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
303     event->screen_pos.y() = _height - event->screen_pos.y();
304
305   event->delta.x() = event->getScreenX() - _last_x;
306   event->delta.y() = event->getScreenY() - _last_y;
307
308   _last_x = event->getScreenX();
309   _last_y = event->getScreenY();
310
311   event->local_pos = event->client_pos = event->screen_pos;
312
313   if( !_resize_window.expired() )
314   {
315     switch( ea.getEventType() )
316     {
317       case osgGA::GUIEventAdapter::RELEASE:
318         _resize_window.lock()->handleResize(canvas::Window::NONE);
319         _resize_window.reset();
320         break;
321       case osgGA::GUIEventAdapter::DRAG:
322         _resize_window.lock()->handleResize(_resize, event->delta);
323         return true;
324       default:
325         return false;
326     }
327   }
328
329   canvas::WindowPtr window_at_cursor;
330   for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
331   {
332     osg::Group *layer = _transform->getChild(i)->asGroup();
333     assert(layer);
334     if( !layer->getNumChildren() )
335       continue;
336
337     for( int j = layer->getNumChildren() - 1; j >= 0; --j )
338     {
339       assert(layer->getChild(j)->getUserData());
340       canvas::WindowPtr window =
341         static_cast<WindowUserData*>(layer->getChild(j)->getUserData())
342           ->window.lock();
343
344       if( !window->isCapturingEvents() || !window->isVisible() )
345         continue;
346
347       float margin = window->isResizable() ? resize_margin_pos : 0;
348       if( window->getRegion().contains( event->getScreenX(),
349                                         event->getScreenY(),
350                                         margin ) )
351       {
352         window_at_cursor = window;
353         break;
354       }
355     }
356
357     if( window_at_cursor )
358       break;
359   }
360
361   if( window_at_cursor )
362   {
363     const SGRect<float>& reg = window_at_cursor->getRegion();
364
365     if(     window_at_cursor->isResizable()
366         && (  ea.getEventType() == osgGA::GUIEventAdapter::MOVE
367            || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
368            || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
369            )
370         && !reg.contains( event->getScreenX(),
371                           event->getScreenY(),
372                           -resize_margin_neg ) )
373     {
374       if( !_last_cursor )
375         _last_cursor = fgGetMouseCursor();
376
377       _resize = 0;
378
379       if( event->getScreenX() <= reg.l() + resize_corner )
380         _resize |= canvas::Window::LEFT;
381       else if( event->getScreenX() >= reg.r() - resize_corner )
382         _resize |= canvas::Window::RIGHT;
383
384       if( event->getScreenY() <= reg.t() + resize_corner )
385         _resize |= canvas::Window::TOP;
386       else if( event->getScreenY() >= reg.b() - resize_corner )
387         _resize |= canvas::Window::BOTTOM;
388
389       static const int cursor_mapping[] =
390       {
391         0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
392         MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
393         MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
394       };
395
396       if( !cursor_mapping[_resize] )
397         return false;
398
399       fgSetMouseCursor(cursor_mapping[_resize]);
400
401       if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
402       {
403         _resize_window = window_at_cursor;
404         window_at_cursor->doRaise();
405         window_at_cursor->handleResize( _resize | canvas::Window::INIT,
406                                         event->delta );
407       }
408
409       return true;
410     }
411   }
412
413   if( _last_cursor )
414   {
415     fgSetMouseCursor(_last_cursor);
416     _last_cursor = 0;
417     return true;
418   }
419
420   canvas::WindowPtr target_window = window_at_cursor;
421   switch( ea.getEventType() )
422   {
423     case osgGA::GUIEventAdapter::PUSH:
424       _last_push = window_at_cursor;
425       event->type = sc::Event::MOUSE_DOWN;
426       break;
427     case osgGA::GUIEventAdapter::SCROLL:
428       switch( ea.getScrollingMotion() )
429       {
430         case osgGA::GUIEventAdapter::SCROLL_UP:
431           event->delta.y() = 1;
432           break;
433         case osgGA::GUIEventAdapter::SCROLL_DOWN:
434           event->delta.y() = -1;
435           break;
436         default:
437           return false;
438       }
439
440       // osg sends two events for every scrolling motion. We don't need
441       // duplicate events, so lets ignore the second event with the same
442       // timestamp.
443       if( _last_scroll_time == ea.getTime() )
444         return window_at_cursor ? true : false;
445       _last_scroll_time = ea.getTime();
446
447       event->type = sc::Event::WHEEL;
448       break;
449     case osgGA::GUIEventAdapter::MOVE:
450     {
451       canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
452       if( last_mouse_over != window_at_cursor && last_mouse_over )
453       {
454         sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
455         move_event->type = sc::Event::MOUSE_LEAVE;
456
457         last_mouse_over->handleMouseEvent(move_event);
458       }
459       _last_mouse_over = window_at_cursor;
460       event->type = sc::Event::MOUSE_MOVE;
461       break;
462     }
463     case osgGA::GUIEventAdapter::RELEASE:
464       target_window = _last_push.lock();
465       _last_push.reset();
466       event->type = sc::Event::MOUSE_UP;
467       break;
468
469     case osgGA::GUIEventAdapter::DRAG:
470       target_window = _last_push.lock();
471       event->type = sc::Event::DRAG;
472       break;
473
474     default:
475       return false;
476   }
477
478   if( target_window )
479     return target_window->handleMouseEvent(event);
480   else
481     return false;
482 }
483
484 //------------------------------------------------------------------------------
485 void GUIMgr::handleResize(int x, int y, int width, int height)
486 {
487   if( _width == width && _height == height )
488     return;
489
490   _width = width;
491   _height = height;
492
493   // Origin should be at top left corner, therefore we need to mirror the y-axis
494   _transform->setMatrix(osg::Matrix(
495     1,  0, 0, 0,
496     0, -1, 0, 0,
497     0,  0, 1, 0,
498     0, _height, 0, 1
499   ));
500 }
501
502 //------------------------------------------------------------------------------
503 void GUIMgr::handleMouseMode(SGPropertyNode* node)
504 {
505   // pass-through indicates events should pass through to the UI
506   _handle_events = fgGetNode("/input/mice/mouse[0]/mode", node->getIntValue())
507                      ->getBoolValue("pass-through");
508 }