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