]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/gui_mgr.cxx
Canvas GUI: completely reset on reinit
[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   // Do not change values on reinit
199   _width.node()->setAttribute(SGPropertyNode::PRESERVE, true);
200   _height.node()->setAttribute(SGPropertyNode::PRESERVE, true);
201
202   // Do not restore windows on reinit (all windows will need to be recreated,
203   // but hey it's a reset ;-))
204   _node->setAttribute(SGPropertyNode::PRESERVE, true);
205 }
206
207 //------------------------------------------------------------------------------
208 bool DesktopGroup::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 RESIZE AREAS
233 ============
234
235 |   || |      _ inside corner region (L-shaped part inside margin) both
236 |___||_|_ _ _/  directions can be resized (outside only one axis)
237 |   || |     |
238 |   || |
239 |   || |_____|__                  _
240 |   ||       |   } margin_neg      \
241 |    ========|== <-- window border  |_ area where resize
242 |            |   } margin_pos       |  can be initiated
243 |____________|__/                 _/
244 |<- corner ->|
245 */
246 const float resize_margin_pos = 12;
247 const float resize_margin_neg = 2;
248 const float resize_corner = 20;
249
250 //------------------------------------------------------------------------------
251 bool DesktopGroup::handleMouse(const osgGA::GUIEventAdapter& ea)
252 {
253   if( !_transform->getNumChildren() || !_handle_events )
254     return false;
255
256   namespace sc = simgear::canvas;
257   sc::MouseEventPtr event(new sc::MouseEvent(ea));
258
259   event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
260   event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
261   if(    ea.getMouseYOrientation()
262       != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
263     event->screen_pos.y() = _height - event->screen_pos.y();
264
265   event->delta.x() = event->getScreenX() - _last_x;
266   event->delta.y() = event->getScreenY() - _last_y;
267
268   _last_x = event->getScreenX();
269   _last_y = event->getScreenY();
270
271   event->local_pos = event->client_pos = event->screen_pos;
272
273   if( !_resize_window.expired() )
274   {
275     switch( ea.getEventType() )
276     {
277       case osgGA::GUIEventAdapter::RELEASE:
278         _resize_window.lock()->handleResize(canvas::Window::NONE);
279         _resize_window.reset();
280         break;
281       case osgGA::GUIEventAdapter::DRAG:
282         _resize_window.lock()->handleResize( _resize,
283                                              event->screen_pos - _drag_start );
284         return true;
285       default:
286         return false;
287     }
288   }
289
290   canvas::WindowPtr window_at_cursor;
291   for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
292   {
293     osg::Group *element = _transform->getChild(i)->asGroup();
294
295     assert(element);
296     assert(element->getUserData());
297
298     canvas::WindowPtr window =
299       boost::dynamic_pointer_cast<canvas::Window>
300       (
301         static_cast<sc::Element::OSGUserData*>(element->getUserData())->element
302       );
303
304     if( !window || !window->isCapturingEvents() || !window->isVisible() )
305       continue;
306
307     float margin = window->isResizable() ? resize_margin_pos : 0;
308     if( window->getScreenRegion().contains( event->getScreenX(),
309                                             event->getScreenY(),
310                                             margin ) )
311     {
312       window_at_cursor = window;
313       break;
314     }
315   }
316
317   if( window_at_cursor )
318   {
319     const SGRect<float>& reg = window_at_cursor->getScreenRegion();
320
321     if(     window_at_cursor->isResizable()
322         && (  ea.getEventType() == osgGA::GUIEventAdapter::MOVE
323            || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
324            || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
325            )
326         && !reg.contains( event->getScreenX(),
327                           event->getScreenY(),
328                           -resize_margin_neg ) )
329     {
330       if( !_last_cursor )
331         _last_cursor = fgGetMouseCursor();
332
333       _resize = 0;
334
335       if( event->getScreenX() <= reg.l() + resize_corner )
336         _resize |= canvas::Window::LEFT;
337       else if( event->getScreenX() >= reg.r() - resize_corner )
338         _resize |= canvas::Window::RIGHT;
339
340       if( event->getScreenY() <= reg.t() + resize_corner )
341         _resize |= canvas::Window::TOP;
342       else if( event->getScreenY() >= reg.b() - resize_corner )
343         _resize |= canvas::Window::BOTTOM;
344
345       static const int cursor_mapping[] =
346       {
347         0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
348         MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
349         MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
350       };
351
352       if( !cursor_mapping[_resize] )
353         return false;
354
355       fgSetMouseCursor(cursor_mapping[_resize]);
356
357       if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
358       {
359         _resize_window = window_at_cursor;
360         _drag_start = event->screen_pos;
361
362         window_at_cursor->raise();
363         window_at_cursor->handleResize(_resize | canvas::Window::INIT);
364       }
365
366       return true;
367     }
368   }
369
370   if( _last_cursor )
371   {
372     fgSetMouseCursor(_last_cursor);
373     _last_cursor = 0;
374     return true;
375   }
376
377   canvas::WindowPtr target_window = window_at_cursor;
378   switch( ea.getEventType() )
379   {
380     case osgGA::GUIEventAdapter::PUSH:
381       _last_push = window_at_cursor;
382       event->type = sc::Event::MOUSE_DOWN;
383       break;
384     case osgGA::GUIEventAdapter::SCROLL:
385       switch( ea.getScrollingMotion() )
386       {
387         case osgGA::GUIEventAdapter::SCROLL_UP:
388           event->delta.y() = 1;
389           break;
390         case osgGA::GUIEventAdapter::SCROLL_DOWN:
391           event->delta.y() = -1;
392           break;
393         default:
394           return false;
395       }
396
397       // osg sends two events for every scrolling motion. We don't need
398       // duplicate events, so lets ignore the second event with the same
399       // timestamp.
400       if( _last_scroll_time == ea.getTime() )
401         return window_at_cursor ? true : false;
402       _last_scroll_time = ea.getTime();
403
404       event->type = sc::Event::WHEEL;
405       break;
406     case osgGA::GUIEventAdapter::MOVE:
407     {
408       canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
409       if( last_mouse_over != window_at_cursor && last_mouse_over )
410       {
411         sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
412         move_event->type = sc::Event::MOUSE_LEAVE;
413         move_event->client_pos -= toOsg(last_mouse_over->getPosition());
414         move_event->local_pos = move_event->client_pos;
415
416         last_mouse_over->handleEvent(move_event);
417       }
418       _last_mouse_over = window_at_cursor;
419       event->type = sc::Event::MOUSE_MOVE;
420       break;
421     }
422     case osgGA::GUIEventAdapter::RELEASE:
423     {
424       event->type = sc::Event::MOUSE_UP;
425
426       canvas::WindowPtr last_push = _last_push.lock();
427       _last_push.reset();
428
429       if( last_push && last_push != target_window )
430       {
431         // Leave old window
432         sc::MouseEventPtr leave_event( new sc::MouseEvent(*event) );
433         leave_event->type = sc::Event::MOUSE_LEAVE;
434         leave_event->client_pos -= toOsg(last_push->getPosition());
435         leave_event->local_pos = leave_event->client_pos;
436
437         last_push->handleEvent(leave_event);
438       }
439       break;
440     }
441     case osgGA::GUIEventAdapter::DRAG:
442       target_window = _last_push.lock();
443       event->type = sc::Event::DRAG;
444       break;
445
446     default:
447       return false;
448   }
449
450   if( target_window )
451   {
452     event->client_pos -= toOsg(target_window->getPosition());
453     event->local_pos = event->client_pos;
454     return target_window->handleEvent(event);
455   }
456   else
457     return false;
458 }
459
460 //------------------------------------------------------------------------------
461 void DesktopGroup::handleResize(int x, int y, int width, int height)
462 {
463   if( _width == width && _height == height )
464     return;
465
466   _width = width;
467   _height = height;
468
469   // Origin should be at top left corner, therefore we need to mirror the y-axis
470   _transform->setMatrix(osg::Matrix(
471     1,  0, 0, 0,
472     0, -1, 0, 0,
473     0,  0, 1, 0,
474     0, _height, 0, 1
475   ));
476 }
477
478 //------------------------------------------------------------------------------
479 void DesktopGroup::handleMouseMode(SGPropertyNode* node)
480 {
481   // pass-through indicates events should pass through to the UI
482   _handle_events = fgGetNode("/input/mice/mouse[0]/mode", node->getIntValue())
483                      ->getBoolValue("pass-through");
484 }
485
486 //------------------------------------------------------------------------------
487 GUIMgr::GUIMgr()
488 {
489
490 }
491
492 //------------------------------------------------------------------------------
493 canvas::WindowPtr GUIMgr::createWindow(const std::string& name)
494 {
495   canvas::WindowPtr window = _desktop->createChild<canvas::Window>(name);
496   if( name.empty() )
497     window->set<std::string>
498     (
499       "id",
500       boost::lexical_cast<std::string>(window->getProps()->getIndex())
501     );
502   return window;
503 }
504
505 //------------------------------------------------------------------------------
506 void GUIMgr::init()
507 {
508   DesktopPtr desktop( new DesktopGroup );
509   desktop->handleResize
510   (
511     0,
512     0,
513     fgGetInt("/sim/startup/xsize"),
514     fgGetInt("/sim/startup/ysize")
515   );
516   _desktop = desktop;
517
518   _event_handler = new GUIEventHandler(desktop);
519   globals->get_renderer()
520          ->getViewer()
521          ->getEventHandlers()
522          // GUI is on top of everything so lets install as first event handler
523          .push_front( _event_handler );
524
525   simgear::canvas::Canvas::addPlacementFactory
526   (
527     "window",
528     boost::bind(&GUIMgr::addWindowPlacement, this, _1, _2)
529   );
530
531   _desktop->getProps()->fireCreatedRecursive();
532 }
533
534 //------------------------------------------------------------------------------
535 void GUIMgr::shutdown()
536 {
537   _desktop->destroy();
538   _desktop.reset();
539   simgear::canvas::Canvas::removePlacementFactory("window");
540
541   globals->get_renderer()
542          ->getViewer()
543          ->removeEventHandler( _event_handler );
544   _event_handler = 0;
545 }
546
547 //------------------------------------------------------------------------------
548 void GUIMgr::update(double dt)
549 {
550   _desktop->update(dt);
551 }
552
553 //------------------------------------------------------------------------------
554 simgear::canvas::GroupPtr GUIMgr::getDesktop()
555 {
556   return _desktop;
557 }
558
559 //------------------------------------------------------------------------------
560 simgear::canvas::Placements
561 GUIMgr::addWindowPlacement( SGPropertyNode* placement,
562                             simgear::canvas::CanvasPtr canvas )
563 {
564   const std::string& id = placement->getStringValue("id");
565
566   simgear::canvas::Placements placements;
567   canvas::WindowPtr window = _desktop->getChild<canvas::Window>(id);
568   if( window )
569   {
570     window->setCanvasContent(canvas);
571     placements.push_back(
572       simgear::canvas::PlacementPtr(
573         new WindowPlacement(placement, window, canvas)
574     ));
575   }
576   return placements;
577 }