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