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>
30 #include <simgear/scene/util/OsgMath.hxx>
32 #include <osg/BlendFunc>
33 #include <osgViewer/Viewer>
34 #include <osgGA/GUIEventHandler>
36 #include <boost/bind.hpp>
37 #include <boost/lexical_cast.hpp>
40 typedef boost::shared_ptr<DesktopGroup> DesktopPtr;
41 typedef boost::weak_ptr<DesktopGroup> DesktopWeakPtr;
46 class GUIEventHandler:
47 public osgGA::GUIEventHandler
50 GUIEventHandler(const DesktopWeakPtr& desktop_group);
52 bool handle( const osgGA::GUIEventAdapter& ea,
53 osgGA::GUIActionAdapter&,
58 DesktopWeakPtr _desktop;
62 * Track a canvas placement on a window
64 class WindowPlacement:
65 public simgear::canvas::Placement
68 WindowPlacement( SGPropertyNode* node,
69 canvas::WindowPtr window,
70 simgear::canvas::CanvasPtr canvas ):
77 * Remove placement from window
79 virtual ~WindowPlacement()
81 canvas::WindowPtr window = _window.lock();
82 simgear::canvas::CanvasPtr canvas = _canvas.lock();
84 if( window && canvas && canvas == window->getCanvasContent().lock() )
85 window->setCanvasContent( simgear::canvas::CanvasPtr() );
89 canvas::WindowWeakPtr _window;
90 simgear::canvas::CanvasWeakPtr _canvas;
97 public simgear::canvas::Group
101 bool handleEvent(const osgGA::GUIEventAdapter& ea);
107 SGPropertyChangeCallback<DesktopGroup> _cb_mouse_mode;
110 simgear::PropertyObject<int> _width,
113 canvas::WindowWeakPtr _last_push,
119 osg::Vec2 _drag_start;
122 double _last_scroll_time;
124 bool handleMouse(const osgGA::GUIEventAdapter& ea);
125 void handleResize(int x, int y, int width, int height);
126 void handleMouseMode(SGPropertyNode* node);
131 simgear::canvas::ElementFactory
132 getChildFactory(const std::string& type) const
134 if( type == "window" )
135 return &Element::create<canvas::Window>;
137 return Group::getChildFactory(type);
141 //------------------------------------------------------------------------------
142 GUIEventHandler::GUIEventHandler(const DesktopWeakPtr& desktop_group):
143 _desktop( desktop_group )
148 //------------------------------------------------------------------------------
149 bool GUIEventHandler::handle( const osgGA::GUIEventAdapter& ea,
150 osgGA::GUIActionAdapter&,
154 if( ea.getHandled() )
157 DesktopPtr desktop = _desktop.lock();
158 return desktop && desktop->handleEvent(ea);
161 //------------------------------------------------------------------------------
162 DesktopGroup::DesktopGroup():
163 Group(simgear::canvas::CanvasPtr(), fgGetNode("/sim/gui/canvas", true)),
164 _cb_mouse_mode( this,
165 &DesktopGroup::handleMouseMode,
166 fgGetNode("/devices/status/mice/mouse[0]/mode") ),
167 _handle_events(true),
168 _width(_node, "size[0]"),
169 _height(_node, "size[1]"),
170 _resize(canvas::Window::NONE),
171 _last_cursor(MOUSE_CURSOR_NONE),
176 osg::Camera* camera =
177 flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
179 camera->addChild( getMatrixTransform() );
181 osg::StateSet* stateSet = _transform->getOrCreateStateSet();
182 stateSet->setDataVariance(osg::Object::STATIC);
183 stateSet->setRenderBinDetails(1000, "RenderBin");
185 // speed optimization?
186 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
187 stateSet->setAttribute(new osg::BlendFunc(
188 osg::BlendFunc::SRC_ALPHA,
189 osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
191 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
192 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
193 stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
194 stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
196 _width = _height = -1;
198 // Do not change values on reinit
199 _width.node()->setAttribute(SGPropertyNode::PRESERVE, true);
200 _height.node()->setAttribute(SGPropertyNode::PRESERVE, true);
203 //------------------------------------------------------------------------------
204 bool DesktopGroup::handleEvent(const osgGA::GUIEventAdapter& ea)
206 switch( ea.getEventType() )
208 case osgGA::GUIEventAdapter::PUSH:
209 case osgGA::GUIEventAdapter::RELEASE:
210 // case osgGA::GUIEventAdapter::DOUBLECLICK:
211 // // DOUBLECLICK doesn't seem to be triggered...
212 case osgGA::GUIEventAdapter::DRAG:
213 case osgGA::GUIEventAdapter::MOVE:
214 case osgGA::GUIEventAdapter::SCROLL:
215 return handleMouse(ea);
216 case osgGA::GUIEventAdapter::RESIZE:
217 handleResize( ea.getWindowX(),
220 ea.getWindowHeight() );
221 return false; // Let other event handlers also consume resize events
231 | || | _ inside corner region (L-shaped part inside margin) both
232 |___||_|_ _ _/ directions can be resized (outside only one axis)
236 | || | } margin_neg \
237 | ========|== <-- window border |_ area where resize
238 | | } margin_pos | can be initiated
242 const float resize_margin_pos = 12;
243 const float resize_margin_neg = 2;
244 const float resize_corner = 20;
246 //------------------------------------------------------------------------------
247 bool DesktopGroup::handleMouse(const osgGA::GUIEventAdapter& ea)
249 if( !_transform->getNumChildren() || !_handle_events )
252 namespace sc = simgear::canvas;
253 sc::MouseEventPtr event(new sc::MouseEvent(ea));
255 event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
256 event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
257 if( ea.getMouseYOrientation()
258 != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
259 event->screen_pos.y() = _height - event->screen_pos.y();
261 event->delta.x() = event->getScreenX() - _last_x;
262 event->delta.y() = event->getScreenY() - _last_y;
264 _last_x = event->getScreenX();
265 _last_y = event->getScreenY();
267 event->local_pos = event->client_pos = event->screen_pos;
269 if( !_resize_window.expired() )
271 switch( ea.getEventType() )
273 case osgGA::GUIEventAdapter::RELEASE:
274 _resize_window.lock()->handleResize(canvas::Window::NONE);
275 _resize_window.reset();
277 case osgGA::GUIEventAdapter::DRAG:
278 _resize_window.lock()->handleResize( _resize,
279 event->screen_pos - _drag_start );
286 canvas::WindowPtr window_at_cursor;
287 for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
289 osg::Group *element = _transform->getChild(i)->asGroup();
292 assert(element->getUserData());
294 canvas::WindowPtr window =
295 boost::dynamic_pointer_cast<canvas::Window>
297 static_cast<sc::Element::OSGUserData*>(element->getUserData())->element
300 if( !window || !window->isCapturingEvents() || !window->isVisible() )
303 float margin = window->isResizable() ? resize_margin_pos : 0;
304 if( window->getScreenRegion().contains( event->getScreenX(),
308 window_at_cursor = window;
313 if( window_at_cursor )
315 const SGRect<float>& reg = window_at_cursor->getScreenRegion();
317 if( window_at_cursor->isResizable()
318 && ( ea.getEventType() == osgGA::GUIEventAdapter::MOVE
319 || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
320 || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
322 && !reg.contains( event->getScreenX(),
324 -resize_margin_neg ) )
327 _last_cursor = fgGetMouseCursor();
331 if( event->getScreenX() <= reg.l() + resize_corner )
332 _resize |= canvas::Window::LEFT;
333 else if( event->getScreenX() >= reg.r() - resize_corner )
334 _resize |= canvas::Window::RIGHT;
336 if( event->getScreenY() <= reg.t() + resize_corner )
337 _resize |= canvas::Window::TOP;
338 else if( event->getScreenY() >= reg.b() - resize_corner )
339 _resize |= canvas::Window::BOTTOM;
341 static const int cursor_mapping[] =
343 0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
344 MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
345 MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
348 if( !cursor_mapping[_resize] )
351 fgSetMouseCursor(cursor_mapping[_resize]);
353 if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
355 _resize_window = window_at_cursor;
356 _drag_start = event->screen_pos;
358 window_at_cursor->raise();
359 window_at_cursor->handleResize(_resize | canvas::Window::INIT);
368 fgSetMouseCursor(_last_cursor);
373 canvas::WindowPtr target_window = window_at_cursor;
374 switch( ea.getEventType() )
376 case osgGA::GUIEventAdapter::PUSH:
377 _last_push = window_at_cursor;
378 event->type = sc::Event::MOUSE_DOWN;
380 case osgGA::GUIEventAdapter::SCROLL:
381 switch( ea.getScrollingMotion() )
383 case osgGA::GUIEventAdapter::SCROLL_UP:
384 event->delta.y() = 1;
386 case osgGA::GUIEventAdapter::SCROLL_DOWN:
387 event->delta.y() = -1;
393 // osg sends two events for every scrolling motion. We don't need
394 // duplicate events, so lets ignore the second event with the same
396 if( _last_scroll_time == ea.getTime() )
397 return window_at_cursor ? true : false;
398 _last_scroll_time = ea.getTime();
400 event->type = sc::Event::WHEEL;
402 case osgGA::GUIEventAdapter::MOVE:
404 canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
405 if( last_mouse_over != window_at_cursor && last_mouse_over )
407 sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
408 move_event->type = sc::Event::MOUSE_LEAVE;
409 move_event->client_pos -= toOsg(last_mouse_over->getPosition());
410 move_event->local_pos = move_event->client_pos;
412 last_mouse_over->handleEvent(move_event);
414 _last_mouse_over = window_at_cursor;
415 event->type = sc::Event::MOUSE_MOVE;
418 case osgGA::GUIEventAdapter::RELEASE:
420 event->type = sc::Event::MOUSE_UP;
422 canvas::WindowPtr last_push = _last_push.lock();
425 if( last_push && last_push != target_window )
428 sc::MouseEventPtr leave_event( new sc::MouseEvent(*event) );
429 leave_event->type = sc::Event::MOUSE_LEAVE;
430 leave_event->client_pos -= toOsg(last_push->getPosition());
431 leave_event->local_pos = leave_event->client_pos;
433 last_push->handleEvent(leave_event);
437 case osgGA::GUIEventAdapter::DRAG:
438 target_window = _last_push.lock();
439 event->type = sc::Event::DRAG;
448 event->client_pos -= toOsg(target_window->getPosition());
449 event->local_pos = event->client_pos;
450 return target_window->handleEvent(event);
456 //------------------------------------------------------------------------------
457 void DesktopGroup::handleResize(int x, int y, int width, int height)
459 if( _width == width && _height == height )
465 // Origin should be at top left corner, therefore we need to mirror the y-axis
466 _transform->setMatrix(osg::Matrix(
474 //------------------------------------------------------------------------------
475 void DesktopGroup::handleMouseMode(SGPropertyNode* node)
477 // pass-through indicates events should pass through to the UI
478 _handle_events = fgGetNode("/input/mice/mouse[0]/mode", node->getIntValue())
479 ->getBoolValue("pass-through");
482 //------------------------------------------------------------------------------
484 _desktop( new DesktopGroup ),
485 _event_handler( new GUIEventHandler(
486 boost::static_pointer_cast<DesktopGroup>(_desktop)
489 // We handle the property listener manually within ::init and ::shutdown.
490 _desktop->removeListener();
493 //------------------------------------------------------------------------------
494 canvas::WindowPtr GUIMgr::createWindow(const std::string& name)
496 canvas::WindowPtr window = _desktop->createChild<canvas::Window>(name);
498 window->set<std::string>
501 boost::lexical_cast<std::string>(window->getProps()->getIndex())
506 //------------------------------------------------------------------------------
509 boost::static_pointer_cast<DesktopGroup>(_desktop)->handleResize
513 fgGetInt("/sim/startup/xsize"),
514 fgGetInt("/sim/startup/ysize")
517 globals->get_renderer()
520 // GUI is on top of everything so lets install as first event handler
521 .push_front( _event_handler );
524 simgear::canvas::Canvas::addPlacementFactory
527 boost::bind(&GUIMgr::addWindowPlacement, this, _1, _2)
529 _desktop->getProps()->addChangeListener(_desktop.get());
530 _desktop->getProps()->fireCreatedRecursive();
533 //------------------------------------------------------------------------------
534 void GUIMgr::shutdown()
536 _desktop->getProps()->removeChangeListener(_desktop.get());
537 simgear::canvas::Canvas::removePlacementFactory("window");
539 globals->get_renderer()
541 ->removeEventHandler( _event_handler );
544 //------------------------------------------------------------------------------
545 void GUIMgr::update(double dt)
547 _desktop->update(dt);
550 //------------------------------------------------------------------------------
551 simgear::canvas::GroupPtr GUIMgr::getDesktop()
556 //------------------------------------------------------------------------------
557 simgear::canvas::Placements
558 GUIMgr::addWindowPlacement( SGPropertyNode* placement,
559 simgear::canvas::CanvasPtr canvas )
561 const std::string& id = placement->getStringValue("id");
563 simgear::canvas::Placements placements;
564 canvas::WindowPtr window = _desktop->getChild<canvas::Window>(id);
567 window->setCanvasContent(canvas);
568 placements.push_back(
569 simgear::canvas::PlacementPtr(
570 new WindowPlacement(placement, window, canvas)