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