]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/gui_mgr.cxx
de9cf2d0b056bc02b267c0e53ffcdd4b735f6bb6
[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& aa,
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   osg::Camera* camera =
118     flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
119   assert(camera);
120   camera->addChild( getMatrixTransform() );
121
122   simgear::canvas::Canvas::addPlacementFactory
123   (
124     "window",
125     boost::bind(&GUIMgr::addPlacement, this, _1, _2)
126   );
127
128   osg::StateSet* stateSet = _transform->getOrCreateStateSet();
129   stateSet->setDataVariance(osg::Object::STATIC);
130   stateSet->setRenderBinDetails(1000, "RenderBin");
131
132   // speed optimization?
133   stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
134   stateSet->setAttribute(new osg::BlendFunc(
135     osg::BlendFunc::SRC_ALPHA,
136     osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
137   );
138   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
139   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
140   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
141   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
142 }
143
144 //------------------------------------------------------------------------------
145 canvas::WindowPtr GUIMgr::createWindow(const std::string& name)
146 {
147   canvas::WindowPtr window = createChild<canvas::Window>(name);
148   if( name.empty() )
149     window->set<std::string>
150     (
151       "id",
152       boost::lexical_cast<std::string>(window->getProps()->getIndex())
153     );
154   return window;
155 }
156
157 //------------------------------------------------------------------------------
158 void GUIMgr::init()
159 {
160   handleResize
161   (
162     0,
163     0,
164     fgGetInt("/sim/startup/xsize"),
165     fgGetInt("/sim/startup/ysize")
166   );
167
168   globals->get_renderer()
169          ->getViewer()
170          ->getEventHandlers()
171          // GUI is on top of everything so lets install as first event handler
172          .push_front( _event_handler );
173
174   _node->addChangeListener(this);
175   _node->fireCreatedRecursive();
176 }
177
178 //------------------------------------------------------------------------------
179 void GUIMgr::shutdown()
180 {
181   _node->removeChangeListener(this);
182
183   globals->get_renderer()
184          ->getViewer()
185          ->removeEventHandler( _event_handler );
186 }
187
188 //------------------------------------------------------------------------------
189 void GUIMgr::update(double dt)
190 {
191   Group::update(dt);
192 }
193
194 //------------------------------------------------------------------------------
195 bool GUIMgr::handleEvent(const osgGA::GUIEventAdapter& ea)
196 {
197   switch( ea.getEventType() )
198   {
199     case osgGA::GUIEventAdapter::PUSH:
200     case osgGA::GUIEventAdapter::RELEASE:
201 //    case osgGA::GUIEventAdapter::DOUBLECLICK:
202 //    // DOUBLECLICK doesn't seem to be triggered...
203     case osgGA::GUIEventAdapter::DRAG:
204     case osgGA::GUIEventAdapter::MOVE:
205     case osgGA::GUIEventAdapter::SCROLL:
206       return handleMouse(ea);
207     case osgGA::GUIEventAdapter::RESIZE:
208       handleResize( ea.getWindowX(),
209                     ea.getWindowY(),
210                     ea.getWindowWidth(),
211                     ea.getWindowHeight() );
212       return false; // Let other event handlers also consume resize events
213     default:
214       return false;
215   }
216 }
217
218 //------------------------------------------------------------------------------
219 GUIMgr::ElementFactory GUIMgr::getChildFactory(const std::string& type) const
220 {
221   if( type == "window" )
222     return &Element::create<canvas::Window>;
223
224   return Group::getChildFactory(type);
225 }
226
227 //------------------------------------------------------------------------------
228 simgear::canvas::Placements
229 GUIMgr::addPlacement( SGPropertyNode* node,
230                       simgear::canvas::CanvasPtr canvas )
231 {
232   const std::string& id = node->getStringValue("id");
233
234   simgear::canvas::Placements placements;
235   canvas::WindowPtr window = getChild<canvas::Window>(id);
236   if( window )
237   {
238     window->setCanvasContent(canvas);
239     placements.push_back(
240       simgear::canvas::PlacementPtr(new WindowPlacement(node, window, canvas))
241     );
242   }
243   return placements;
244 }
245
246 /*
247 RESIZE AREAS
248 ============
249
250 |   || |      _ inside corner region (L-shaped part inside margin) both
251 |___||_|_ _ _/  directions can be resized (outside only one axis)
252 |   || |     |
253 |   || |
254 |   || |_____|__                  _
255 |   ||       |   } margin_neg      \
256 |    ========|== <-- window border  |_ area where resize
257 |            |   } margin_pos       |  can be initiated
258 |____________|__/                 _/
259 |<- corner ->|
260 */
261 const float resize_margin_pos = 12;
262 const float resize_margin_neg = 2;
263 const float resize_corner = 20;
264
265 //------------------------------------------------------------------------------
266 bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
267 {
268   if( !_transform->getNumChildren() || !_handle_events )
269     return false;
270
271   namespace sc = simgear::canvas;
272   sc::MouseEventPtr event(new sc::MouseEvent(ea));
273
274   event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
275   event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
276   if(    ea.getMouseYOrientation()
277       != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
278     event->screen_pos.y() = _height - event->screen_pos.y();
279
280   event->delta.x() = event->getScreenX() - _last_x;
281   event->delta.y() = event->getScreenY() - _last_y;
282
283   _last_x = event->getScreenX();
284   _last_y = event->getScreenY();
285
286   event->local_pos = event->client_pos = event->screen_pos;
287
288   if( !_resize_window.expired() )
289   {
290     switch( ea.getEventType() )
291     {
292       case osgGA::GUIEventAdapter::RELEASE:
293         _resize_window.lock()->handleResize(canvas::Window::NONE);
294         _resize_window.reset();
295         break;
296       case osgGA::GUIEventAdapter::DRAG:
297         _resize_window.lock()->handleResize(_resize, event->delta);
298         return true;
299       default:
300         return false;
301     }
302   }
303
304   canvas::WindowPtr window_at_cursor;
305   for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
306   {
307     osg::Group *element = _transform->getChild(i)->asGroup();
308
309     assert(element);
310     assert(element->getUserData());
311
312     canvas::WindowPtr window =
313       boost::static_pointer_cast<canvas::Window>
314       (
315         static_cast<sc::Element::OSGUserData*>(element->getUserData())->element
316       );
317
318     if( !window->isCapturingEvents() || !window->isVisible() )
319       continue;
320
321     float margin = window->isResizable() ? resize_margin_pos : 0;
322     if( window->getScreenRegion().contains( event->getScreenX(),
323                                             event->getScreenY(),
324                                             margin ) )
325     {
326       window_at_cursor = window;
327       break;
328     }
329   }
330
331   if( window_at_cursor )
332   {
333     const SGRect<float>& reg = window_at_cursor->getScreenRegion();
334
335     if(     window_at_cursor->isResizable()
336         && (  ea.getEventType() == osgGA::GUIEventAdapter::MOVE
337            || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
338            || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
339            )
340         && !reg.contains( event->getScreenX(),
341                           event->getScreenY(),
342                           -resize_margin_neg ) )
343     {
344       if( !_last_cursor )
345         _last_cursor = fgGetMouseCursor();
346
347       _resize = 0;
348
349       if( event->getScreenX() <= reg.l() + resize_corner )
350         _resize |= canvas::Window::LEFT;
351       else if( event->getScreenX() >= reg.r() - resize_corner )
352         _resize |= canvas::Window::RIGHT;
353
354       if( event->getScreenY() <= reg.t() + resize_corner )
355         _resize |= canvas::Window::TOP;
356       else if( event->getScreenY() >= reg.b() - resize_corner )
357         _resize |= canvas::Window::BOTTOM;
358
359       static const int cursor_mapping[] =
360       {
361         0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
362         MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
363         MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
364       };
365
366       if( !cursor_mapping[_resize] )
367         return false;
368
369       fgSetMouseCursor(cursor_mapping[_resize]);
370
371       if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
372       {
373         _resize_window = window_at_cursor;
374         window_at_cursor->raise();
375         window_at_cursor->handleResize( _resize | canvas::Window::INIT,
376                                         event->delta );
377       }
378
379       return true;
380     }
381   }
382
383   if( _last_cursor )
384   {
385     fgSetMouseCursor(_last_cursor);
386     _last_cursor = 0;
387     return true;
388   }
389
390   canvas::WindowPtr target_window = window_at_cursor;
391   switch( ea.getEventType() )
392   {
393     case osgGA::GUIEventAdapter::PUSH:
394       _last_push = window_at_cursor;
395       event->type = sc::Event::MOUSE_DOWN;
396       break;
397     case osgGA::GUIEventAdapter::SCROLL:
398       switch( ea.getScrollingMotion() )
399       {
400         case osgGA::GUIEventAdapter::SCROLL_UP:
401           event->delta.y() = 1;
402           break;
403         case osgGA::GUIEventAdapter::SCROLL_DOWN:
404           event->delta.y() = -1;
405           break;
406         default:
407           return false;
408       }
409
410       // osg sends two events for every scrolling motion. We don't need
411       // duplicate events, so lets ignore the second event with the same
412       // timestamp.
413       if( _last_scroll_time == ea.getTime() )
414         return window_at_cursor ? true : false;
415       _last_scroll_time = ea.getTime();
416
417       event->type = sc::Event::WHEEL;
418       break;
419     case osgGA::GUIEventAdapter::MOVE:
420     {
421       canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
422       if( last_mouse_over != window_at_cursor && last_mouse_over )
423       {
424         sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
425         move_event->type = sc::Event::MOUSE_LEAVE;
426         move_event->client_pos -= toOsg(last_mouse_over->getPosition());
427         move_event->local_pos = move_event->client_pos;
428
429         last_mouse_over->handleEvent(move_event);
430       }
431       _last_mouse_over = window_at_cursor;
432       event->type = sc::Event::MOUSE_MOVE;
433       break;
434     }
435     case osgGA::GUIEventAdapter::RELEASE:
436       target_window = _last_push.lock();
437       _last_push.reset();
438       event->type = sc::Event::MOUSE_UP;
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 GUIMgr::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 GUIMgr::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 }