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