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