1 // Canvas gui/dialog manager
3 // Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
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.
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.
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.
19 #include "gui_mgr.hxx"
20 #include <Canvas/window.hxx>
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>
28 #include <simgear/canvas/Canvas.hxx>
29 #include <simgear/canvas/CanvasPlacement.hxx>
31 #include <osg/BlendFunc>
32 #include <osgViewer/Viewer>
33 #include <osgGA/GUIEventHandler>
35 #include <boost/bind.hpp>
40 class GUIEventHandler:
41 public osgGA::GUIEventHandler
44 GUIEventHandler(GUIMgr* gui_mgr):
48 bool handle( const osgGA::GUIEventAdapter& ea,
49 osgGA::GUIActionAdapter& aa,
55 return _gui_mgr->handleEvent(ea);
63 * Track a canvas placement on a window
65 class WindowPlacement:
66 public simgear::canvas::Placement
69 WindowPlacement( SGPropertyNode* node,
70 canvas::WindowPtr window,
71 simgear::canvas::CanvasPtr canvas ):
78 * Remove placement from window
80 virtual ~WindowPlacement()
82 canvas::WindowPtr window = _window.lock();
83 simgear::canvas::CanvasPtr canvas = _canvas.lock();
85 if( window && canvas && canvas == window->getCanvas().lock() )
86 window->setCanvas( simgear::canvas::CanvasPtr() );
90 canvas::WindowWeakPtr _window;
91 simgear::canvas::CanvasWeakPtr _canvas;
95 * Store pointer to window as user data
98 public osg::Referenced
101 canvas::WindowWeakPtr window;
102 WindowUserData(canvas::WindowPtr window):
107 //------------------------------------------------------------------------------
108 typedef boost::shared_ptr<canvas::Window> WindowPtr;
109 WindowPtr windowFactory(SGPropertyNode* node)
111 return WindowPtr(new canvas::Window(node));
114 //------------------------------------------------------------------------------
116 PropertyBasedMgr( fgGetNode("/sim/gui/canvas", true),
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),
131 _width = _height = -1;
133 osg::Camera* camera =
134 flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
136 camera->addChild(_transform);
138 simgear::canvas::Canvas::addPlacementFactory
141 boost::bind(&GUIMgr::addPlacement, this, _1, _2)
144 osg::StateSet* stateSet = _transform->getOrCreateStateSet();
145 stateSet->setDataVariance(osg::Object::STATIC);
146 stateSet->setRenderBinDetails(1000, "RenderBin");
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)
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);
160 //------------------------------------------------------------------------------
167 fgGetInt("/sim/startup/xsize"),
168 fgGetInt("/sim/startup/ysize")
171 PropertyBasedMgr::init();
173 globals->get_renderer()
176 // GUI is on top of everything so lets install as first event handler
177 .push_front( _event_handler );
180 //------------------------------------------------------------------------------
181 void GUIMgr::shutdown()
183 PropertyBasedMgr::shutdown();
185 globals->get_renderer()
187 ->removeEventHandler( _event_handler );
190 //------------------------------------------------------------------------------
191 void GUIMgr::elementCreated(simgear::PropertyBasedElementPtr element)
193 canvas::WindowPtr window =
194 boost::static_pointer_cast<canvas::Window>(element);
196 size_t layer_index = std::max(0, window->getProps()->getIntValue("layer", 1));
197 osg::Group *layer = 0;
199 if( layer_index < _transform->getNumChildren() )
201 layer = _transform->getChild(layer_index)->asGroup();
206 while( _transform->getNumChildren() <= layer_index )
208 layer = new osg::Group;
209 _transform->addChild(layer);
212 window->getGroup()->setUserData(new WindowUserData(window));
213 layer->addChild(window->getGroup());
216 //------------------------------------------------------------------------------
217 bool GUIMgr::handleEvent(const osgGA::GUIEventAdapter& ea)
219 switch( ea.getEventType() )
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(),
233 ea.getWindowHeight() );
234 return false; // Let other event handlers also consume resize events
240 //------------------------------------------------------------------------------
241 canvas::WindowPtr GUIMgr::getWindow(size_t i)
243 return boost::static_pointer_cast<canvas::Window>(_elements[i]);
246 //------------------------------------------------------------------------------
247 simgear::canvas::Placements
248 GUIMgr::addPlacement( SGPropertyNode* node,
249 simgear::canvas::CanvasPtr canvas )
251 int placement_index = node->getIntValue("index", -1);
253 simgear::canvas::Placements placements;
254 for( size_t i = 0; i < _elements.size(); ++i )
256 if( placement_index >= 0 && static_cast<int>(i) != placement_index )
259 canvas::WindowPtr window = getWindow(i);
263 window->setCanvas(canvas);
264 placements.push_back(
265 simgear::canvas::PlacementPtr(new WindowPlacement(node, window, canvas))
275 | || | _ inside corner region (L-shaped part inside margin) both
276 |___||_|_ _ _/ directions can be resized (outside only one axis)
280 | || | } margin_neg \
281 | ========|== <-- window border |_ area where resize
282 | | } margin_pos | can be initiated
286 const float resize_margin_pos = 12;
287 const float resize_margin_neg = 2;
288 const float resize_corner = 20;
290 //------------------------------------------------------------------------------
291 bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
293 if( !_transform->getNumChildren() || !_handle_events )
296 namespace sc = simgear::canvas;
297 sc::MouseEventPtr event(new sc::MouseEvent(ea));
299 event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
300 event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
301 if( ea.getMouseYOrientation()
302 != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
303 event->screen_pos.y() = _height - event->screen_pos.y();
305 event->delta.x() = event->getScreenX() - _last_x;
306 event->delta.y() = event->getScreenY() - _last_y;
308 _last_x = event->getScreenX();
309 _last_y = event->getScreenY();
311 event->client_pos = event->screen_pos;
313 if( !_resize_window.expired() )
315 switch( ea.getEventType() )
317 case osgGA::GUIEventAdapter::RELEASE:
318 _resize_window.lock()->handleResize(canvas::Window::NONE);
319 _resize_window.reset();
321 case osgGA::GUIEventAdapter::DRAG:
322 _resize_window.lock()->handleResize(_resize, event->delta);
329 canvas::WindowPtr window_at_cursor;
330 for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
332 osg::Group *layer = _transform->getChild(i)->asGroup();
334 if( !layer->getNumChildren() )
337 for( int j = layer->getNumChildren() - 1; j >= 0; --j )
339 assert(layer->getChild(j)->getUserData());
340 canvas::WindowPtr window =
341 static_cast<WindowUserData*>(layer->getChild(j)->getUserData())
344 if( !window->isCapturingEvents() || !window->isVisible() )
347 float margin = window->isResizable() ? resize_margin_pos : 0;
348 if( window->getRegion().contains( event->getScreenX(),
352 window_at_cursor = window;
357 if( window_at_cursor )
361 if( window_at_cursor )
363 const SGRect<float>& reg = window_at_cursor->getRegion();
365 if( window_at_cursor->isResizable()
366 && ( ea.getEventType() == osgGA::GUIEventAdapter::MOVE
367 || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
368 || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
370 && !reg.contains( event->getScreenX(),
372 -resize_margin_neg ) )
375 _last_cursor = fgGetMouseCursor();
379 if( event->getScreenX() <= reg.l() + resize_corner )
380 _resize |= canvas::Window::LEFT;
381 else if( event->getScreenX() >= reg.r() - resize_corner )
382 _resize |= canvas::Window::RIGHT;
384 if( event->getScreenY() <= reg.t() + resize_corner )
385 _resize |= canvas::Window::TOP;
386 else if( event->getScreenY() >= reg.b() - resize_corner )
387 _resize |= canvas::Window::BOTTOM;
389 static const int cursor_mapping[] =
391 0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
392 MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
393 MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
396 if( !cursor_mapping[_resize] )
399 fgSetMouseCursor(cursor_mapping[_resize]);
401 if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
403 _resize_window = window_at_cursor;
404 window_at_cursor->doRaise();
405 window_at_cursor->handleResize( _resize | canvas::Window::INIT,
415 fgSetMouseCursor(_last_cursor);
420 canvas::WindowPtr target_window = window_at_cursor;
421 switch( ea.getEventType() )
423 case osgGA::GUIEventAdapter::PUSH:
424 _last_push = window_at_cursor;
425 event->type = sc::Event::MOUSE_DOWN;
427 case osgGA::GUIEventAdapter::SCROLL:
428 switch( ea.getScrollingMotion() )
430 case osgGA::GUIEventAdapter::SCROLL_UP:
431 event->delta.y() = 1;
433 case osgGA::GUIEventAdapter::SCROLL_DOWN:
434 event->delta.y() = -1;
440 // osg sends two events for every scrolling motion. We don't need
441 // duplicate events, so lets ignore the second event with the same
443 if( _last_scroll_time == ea.getTime() )
444 return window_at_cursor ? true : false;
445 _last_scroll_time = ea.getTime();
447 event->type = sc::Event::WHEEL;
449 case osgGA::GUIEventAdapter::MOVE:
451 canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
452 if( last_mouse_over != window_at_cursor && last_mouse_over )
454 sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
455 move_event->type = sc::Event::MOUSE_LEAVE;
457 // Let the event position be always relative to the top left window
459 move_event->client_pos.x() -= last_mouse_over->getRegion().x();
460 move_event->client_pos.y() -= last_mouse_over->getRegion().y();
462 last_mouse_over->handleMouseEvent(move_event);
464 _last_mouse_over = window_at_cursor;
465 event->type = sc::Event::MOUSE_MOVE;
468 case osgGA::GUIEventAdapter::RELEASE:
469 target_window = _last_push.lock();
471 event->type = sc::Event::MOUSE_UP;
474 case osgGA::GUIEventAdapter::DRAG:
475 target_window = _last_push.lock();
476 event->type = sc::Event::DRAG;
485 // Let the event position be always relative to the top left window corner
486 event->client_pos.x() -= target_window->getRegion().x();
487 event->client_pos.y() -= target_window->getRegion().y();
489 return target_window->handleMouseEvent(event);
495 //------------------------------------------------------------------------------
496 void GUIMgr::handleResize(int x, int y, int width, int height)
498 if( _width == width && _height == height )
504 // Origin should be at top left corner, therefore we need to mirror the y-axis
505 _transform->setMatrix(osg::Matrix(
513 //------------------------------------------------------------------------------
514 void GUIMgr::handleMouseMode(SGPropertyNode* node)
516 // pass-through indicates events should pass through to the UI
517 _handle_events = fgGetNode("/input/mice/mouse[0]/mode", node->getIntValue())
518 ->getBoolValue("pass-through");