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