]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/gui_mgr.cxx
efd3f94e1cfc90453f4db58c30bcf9a908bac0a8
[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   _width(_props, "size[0]"),
122   _height(_props, "size[1]"),
123   _resize(canvas::Window::NONE),
124   _last_cursor(MOUSE_CURSOR_NONE),
125   _last_scroll_time(0)
126 {
127   _width = _height = -1;
128
129   osg::Camera* camera =
130     flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
131   assert(camera);
132   camera->addChild(_transform);
133
134   osg::Viewport* vp = camera->getViewport();
135   handleResize(vp->x(), vp->y(), vp->width(), vp->height());
136
137   simgear::canvas::Canvas::addPlacementFactory
138   (
139     "window",
140     boost::bind(&GUIMgr::addPlacement, this, _1, _2)
141   );
142
143   osg::StateSet* stateSet = _transform->getOrCreateStateSet();
144   stateSet->setDataVariance(osg::Object::STATIC);
145   stateSet->setRenderBinDetails(1000, "RenderBin");
146
147   // speed optimization?
148   stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
149   stateSet->setAttribute(new osg::BlendFunc(
150     osg::BlendFunc::SRC_ALPHA,
151     osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
152   );
153   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
154   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
155   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
156   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
157 }
158
159 //------------------------------------------------------------------------------
160 void GUIMgr::init()
161 {
162   PropertyBasedMgr::init();
163
164   globals->get_renderer()
165          ->getViewer()
166          ->getEventHandlers()
167          // GUI is on top of everything so lets install as first event handler
168          .push_front( _event_handler );
169 }
170
171 //------------------------------------------------------------------------------
172 void GUIMgr::shutdown()
173 {
174   PropertyBasedMgr::shutdown();
175
176   globals->get_renderer()
177          ->getViewer()
178          ->removeEventHandler( _event_handler );
179 }
180
181 //------------------------------------------------------------------------------
182 void GUIMgr::elementCreated(simgear::PropertyBasedElementPtr element)
183 {
184   canvas::WindowPtr window =
185     boost::static_pointer_cast<canvas::Window>(element);
186
187   size_t layer_index = std::max(0, window->getProps()->getIntValue("layer", 1));
188   osg::Group *layer = 0;
189
190   if( layer_index < _transform->getNumChildren() )
191   {
192     layer = _transform->getChild(layer_index)->asGroup();
193     assert(layer);
194   }
195   else
196   {
197     while( _transform->getNumChildren() <= layer_index )
198     {
199       layer = new osg::Group;
200       _transform->addChild(layer);
201     }
202   }
203   window->getGroup()->setUserData(new WindowUserData(window));
204   layer->addChild(window->getGroup());
205 }
206
207 //------------------------------------------------------------------------------
208 bool GUIMgr::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 canvas::WindowPtr GUIMgr::getWindow(size_t i)
233 {
234   return boost::static_pointer_cast<canvas::Window>(_elements[i]);
235 }
236
237 //------------------------------------------------------------------------------
238 simgear::canvas::Placements
239 GUIMgr::addPlacement( SGPropertyNode* node,
240                       simgear::canvas::CanvasPtr canvas )
241 {
242   int placement_index = node->getIntValue("index", -1);
243
244   simgear::canvas::Placements placements;
245   for( size_t i = 0; i < _elements.size(); ++i )
246   {
247     if( placement_index >= 0 && static_cast<int>(i) != placement_index )
248       continue;
249
250     canvas::WindowPtr window = getWindow(i);
251     if( !window )
252       continue;
253
254     window->setCanvas(canvas);
255     placements.push_back(
256       simgear::canvas::PlacementPtr(new WindowPlacement(node, window, canvas))
257     );
258   }
259   return placements;
260 }
261
262 /*
263 RESIZE AREAS
264 ============
265
266 |   || |      _ inside corner region (L-shaped part inside margin) both
267 |___||_|_ _ _/  directions can be resized (outside only one axis)
268 |   || |     |
269 |   || |
270 |   || |_____|__                  _
271 |   ||       |   } margin_neg      \
272 |    ========|== <-- window border  |_ area where resize
273 |            |   } margin_pos       |  can be initiated
274 |____________|__/                 _/
275 |<- corner ->|
276 */
277 const float resize_margin_pos = 12;
278 const float resize_margin_neg = 2;
279 const float resize_corner = 20;
280
281 //------------------------------------------------------------------------------
282 bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
283 {
284   if( !_transform->getNumChildren() )
285     return false;
286
287   namespace sc = simgear::canvas;
288   sc::MouseEventPtr event(new sc::MouseEvent);
289   event->time = ea.getTime();
290
291   event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
292   event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
293   if(    ea.getMouseYOrientation()
294       != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
295     event->screen_pos.y() = _height - event->screen_pos.y();
296
297   event->delta.x() = event->getScreenX() - _last_x;
298   event->delta.y() = event->getScreenY() - _last_y;
299
300   _last_x = event->getScreenX();
301   _last_y = event->getScreenY();
302
303   event->client_pos = event->screen_pos;
304   event->button = ea.getButton();
305   event->state = ea.getButtonMask();
306   event->mod = ea.getModKeyMask();
307
308   static simgear::Rect<float> resize_region;
309
310   if( !_resize_window.expired() )
311   {
312     switch( ea.getEventType() )
313     {
314       case osgGA::GUIEventAdapter::RELEASE:
315         _resize_window.lock()->handleResize(canvas::Window::NONE);
316         _resize_window.reset();
317         break;
318       case osgGA::GUIEventAdapter::DRAG:
319         _resize_window.lock()->handleResize(_resize, event->delta);
320         return true;
321       default:
322         return false;
323     }
324   }
325
326   canvas::WindowPtr window_at_cursor;
327   for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
328   {
329     osg::Group *layer = _transform->getChild(i)->asGroup();
330     assert(layer);
331     if( !layer->getNumChildren() )
332       continue;
333
334     for( int j = layer->getNumChildren() - 1; j >= 0; --j )
335     {
336       assert(layer->getChild(j)->getUserData());
337       canvas::WindowPtr window =
338         static_cast<WindowUserData*>(layer->getChild(j)->getUserData())
339           ->window.lock();
340       float margin = window->isResizable() ? resize_margin_pos : 0;
341       if( window->getRegion().contains( event->getScreenX(),
342                                         event->getScreenY(),
343                                         margin ) )
344       {
345         window_at_cursor = window;
346         break;
347       }
348     }
349
350     if( window_at_cursor )
351       break;
352   }
353
354   if( window_at_cursor )
355   {
356     const simgear::Rect<float>& reg = window_at_cursor->getRegion();
357
358     if(     window_at_cursor->isResizable()
359         && (  ea.getEventType() == osgGA::GUIEventAdapter::MOVE
360            || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
361            || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
362            )
363         && !reg.contains( event->getScreenX(),
364                           event->getScreenY(),
365                           -resize_margin_neg ) )
366     {
367       if( !_last_cursor )
368         _last_cursor = fgGetMouseCursor();
369
370       _resize = 0;
371
372       if( event->getScreenX() <= reg.l() + resize_corner )
373         _resize |= canvas::Window::LEFT;
374       else if( event->getScreenX() >= reg.r() - resize_corner )
375         _resize |= canvas::Window::RIGHT;
376
377       if( event->getScreenY() <= reg.t() + resize_corner )
378         _resize |= canvas::Window::TOP;
379       else if( event->getScreenY() >= reg.b() - resize_corner )
380         _resize |= canvas::Window::BOTTOM;
381
382       static const int cursor_mapping[] =
383       {
384         0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
385         MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
386         MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
387       };
388
389       if( !cursor_mapping[_resize] )
390         return false;
391
392       fgSetMouseCursor(cursor_mapping[_resize]);
393
394       if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
395       {
396         _resize_window = window_at_cursor;
397         window_at_cursor->doRaise();
398         window_at_cursor->handleResize( _resize | canvas::Window::INIT,
399                                         event->delta );
400       }
401
402       return true;
403     }
404   }
405
406   if( _last_cursor )
407   {
408     fgSetMouseCursor(_last_cursor);
409     _last_cursor = 0;
410     return true;
411   }
412
413   canvas::WindowPtr target_window = window_at_cursor;
414   switch( ea.getEventType() )
415   {
416     case osgGA::GUIEventAdapter::PUSH:
417       _last_push = window_at_cursor;
418       event->type = sc::Event::MOUSE_DOWN;
419       break;
420     case osgGA::GUIEventAdapter::SCROLL:
421       switch( ea.getScrollingMotion() )
422       {
423         case osgGA::GUIEventAdapter::SCROLL_UP:
424           event->delta.y() = 1;
425           break;
426         case osgGA::GUIEventAdapter::SCROLL_DOWN:
427           event->delta.y() = -1;
428           break;
429         default:
430           return false;
431       }
432
433       // osg sends two events for every scrolling motion. We don't need
434       // duplicate events, so lets ignore the second event with the same
435       // timestamp.
436       if( _last_scroll_time == ea.getTime() )
437         return true;
438       _last_scroll_time = ea.getTime();
439
440       event->type = sc::Event::WHEEL;
441       break;
442     case osgGA::GUIEventAdapter::MOVE:
443     {
444       canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
445       if( last_mouse_over != window_at_cursor && last_mouse_over )
446       {
447         sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
448         move_event->type = sc::Event::MOUSE_LEAVE;
449
450         // Let the event position be always relative to the top left window
451         // corner
452         move_event->client_pos.x() -= last_mouse_over->getRegion().x();
453         move_event->client_pos.y() -= last_mouse_over->getRegion().y();
454
455         last_mouse_over->handleMouseEvent(move_event);
456       }
457       _last_mouse_over = window_at_cursor;
458       event->type = sc::Event::MOUSE_MOVE;
459       break;
460     }
461     case osgGA::GUIEventAdapter::RELEASE:
462       target_window = _last_push.lock();
463       _last_push.reset();
464       event->type = sc::Event::MOUSE_UP;
465       break;
466
467     case osgGA::GUIEventAdapter::DRAG:
468       target_window = _last_push.lock();
469       event->type = sc::Event::DRAG;
470       break;
471
472     default:
473       return false;
474   }
475
476   if( target_window )
477   {
478     // Let the event position be always relative to the top left window corner
479     event->client_pos.x() -= target_window->getRegion().x();
480     event->client_pos.y() -= target_window->getRegion().y();
481
482     return target_window->handleMouseEvent(event);
483   }
484   else
485     return false;
486 }
487
488 //------------------------------------------------------------------------------
489 void GUIMgr::handleResize(int x, int y, int width, int height)
490 {
491   if( _width == width && _height == height )
492     return;
493
494   _width = width;
495   _height = height;
496
497   // Origin should be at top left corner, therefore we need to mirror the y-axis
498   _transform->setMatrix(osg::Matrix(
499     1,  0, 0, 0,
500     0, -1, 0, 0,
501     0,  0, 1, 0,
502     0, _height, 0, 1
503   ));
504 }