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"
21 #include <Main/fg_os.hxx>
22 #include <Main/fg_props.hxx>
23 #include <Main/globals.hxx>
24 #include <Viewer/CameraGroup.hxx>
25 #include <Viewer/renderer.hxx>
27 #include <simgear/canvas/Canvas.hxx>
28 #include <simgear/canvas/CanvasPlacement.hxx>
29 #include <simgear/canvas/CanvasWindow.hxx>
30 #include <simgear/canvas/events/KeyboardEvent.hxx>
31 #include <simgear/scene/util/OsgMath.hxx>
33 #include <osg/BlendFunc>
34 #include <osgViewer/Viewer>
35 #include <osgGA/GUIEventHandler>
37 #include <boost/bind.hpp>
38 #include <boost/lexical_cast.hpp>
41 typedef SGSharedPtr<DesktopGroup> DesktopPtr;
42 typedef SGWeakPtr<DesktopGroup> DesktopWeakPtr;
44 namespace sc = simgear::canvas;
49 class GUIEventHandler:
50 public osgGA::GUIEventHandler
53 GUIEventHandler(const DesktopWeakPtr& desktop_group);
55 bool handle( const osgGA::GUIEventAdapter& ea,
56 osgGA::GUIActionAdapter&,
61 DesktopWeakPtr _desktop;
65 * Track a canvas placement on a window
67 class WindowPlacement:
71 WindowPlacement( SGPropertyNode* node,
73 sc::CanvasPtr canvas ):
80 * Remove placement from window
82 virtual ~WindowPlacement()
84 sc::WindowPtr window = _window.lock();
85 sc::CanvasPtr canvas = _canvas.lock();
87 if( window && canvas && canvas == window->getCanvasContent().lock() )
88 window->setCanvasContent( sc::CanvasPtr() );
92 sc::WindowWeakPtr _window;
93 sc::CanvasWeakPtr _canvas;
105 void setFocusWindow(const sc::WindowPtr& window);
107 bool grabPointer(const sc::WindowPtr& window);
108 void ungrabPointer(const sc::WindowPtr& window);
110 bool handleEvent(const osgGA::GUIEventAdapter& ea);
116 SGPropertyChangeCallback<DesktopGroup> _cb_mouse_mode;
119 simgear::PropertyObject<int> _width,
122 sc::WindowWeakPtr _last_push,
126 _pointer_grab_window;
131 osg::Vec2 _drag_start;
134 double _last_scroll_time;
136 uint32_t _last_key_down_no_mod; // Key repeat for non modifier keys
138 bool handleMouse(const osgGA::GUIEventAdapter& ea);
139 bool handleKeyboard(const osgGA::GUIEventAdapter& ea);
141 bool handleEvent( const sc::EventPtr& event,
142 const sc::WindowPtr& active_window );
143 bool handleRootEvent(const sc::EventPtr& event);
145 void handleResize(int x, int y, int width, int height);
146 void handleMouseMode(SGPropertyNode* node);
152 getChildFactory(const std::string& type) const
154 if( type == "window" )
155 return &Element::create<sc::Window>;
157 return Group::getChildFactory(type);
161 //------------------------------------------------------------------------------
162 GUIEventHandler::GUIEventHandler(const DesktopWeakPtr& desktop_group):
163 _desktop( desktop_group )
168 //------------------------------------------------------------------------------
169 bool GUIEventHandler::handle( const osgGA::GUIEventAdapter& ea,
170 osgGA::GUIActionAdapter&,
174 if( ea.getHandled() )
177 DesktopPtr desktop = _desktop.lock();
178 return desktop && desktop->handleEvent(ea);
181 //------------------------------------------------------------------------------
182 DesktopGroup::DesktopGroup():
183 Group(sc::CanvasPtr(), fgGetNode("/sim/gui/canvas", true)),
184 _cb_mouse_mode( this,
185 &DesktopGroup::handleMouseMode,
186 fgGetNode("/devices/status/mice/mouse[0]/mode") ),
187 _handle_events(true),
188 _width(_node, "size[0]"),
189 _height(_node, "size[1]"),
190 _resize(sc::Window::NONE),
191 _last_cursor(MOUSE_CURSOR_NONE),
194 _last_scroll_time(0),
195 _last_key_down_no_mod(-1)
197 osg::Camera* camera =
198 flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
200 camera->addChild( getMatrixTransform() );
202 osg::StateSet* stateSet = _transform->getOrCreateStateSet();
203 stateSet->setDataVariance(osg::Object::STATIC);
204 stateSet->setRenderBinDetails(1000, "RenderBin");
206 // speed optimization?
207 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
208 stateSet->setAttribute(new osg::BlendFunc(
209 osg::BlendFunc::SRC_ALPHA,
210 osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
212 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
213 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
214 stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
215 stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
217 _width = _height = -1;
220 //------------------------------------------------------------------------------
221 void DesktopGroup::setFocusWindow(const sc::WindowPtr& window)
223 _focus_window = window;
226 //------------------------------------------------------------------------------
227 bool DesktopGroup::grabPointer(const sc::WindowPtr& window)
229 sc::WindowPtr resize = _resize_window.lock();
230 if( (resize && resize != window) || !_pointer_grab_window.expired() )
231 // Already grabbed (resize -> implicit grab)
234 _pointer_grab_window = window;
238 //------------------------------------------------------------------------------
239 void DesktopGroup::ungrabPointer(const sc::WindowPtr& window)
241 if( _pointer_grab_window.expired() )
242 SG_LOG(SG_GUI, SG_WARN, "ungrabPointer: no active grab.");
243 else if( window != _pointer_grab_window.lock() )
244 SG_LOG(SG_GUI, SG_WARN, "ungrabPointer: window is not owner of the grab.");
246 _pointer_grab_window.reset();
249 //------------------------------------------------------------------------------
250 bool DesktopGroup::handleEvent(const osgGA::GUIEventAdapter& ea)
252 switch( ea.getEventType() )
254 case osgGA::GUIEventAdapter::PUSH:
255 case osgGA::GUIEventAdapter::RELEASE:
256 // case osgGA::GUIEventAdapter::DOUBLECLICK:
257 // // DOUBLECLICK doesn't seem to be triggered...
258 case osgGA::GUIEventAdapter::DRAG:
259 case osgGA::GUIEventAdapter::MOVE:
260 case osgGA::GUIEventAdapter::SCROLL:
261 return handleMouse(ea);
262 case osgGA::GUIEventAdapter::KEYDOWN:
263 case osgGA::GUIEventAdapter::KEYUP:
264 return handleKeyboard(ea);
265 case osgGA::GUIEventAdapter::RESIZE:
266 handleResize( ea.getWindowX(),
269 ea.getWindowHeight() );
270 return false; // Let other event handlers also consume resize events
280 | || | _ inside corner region (L-shaped part inside margin) both
281 |___||_|_ _ _/ directions can be resized (outside only one axis)
285 | || | } margin_neg \
286 | ========|== <-- window border |_ area where resize
287 | | } margin_pos | can be initiated
291 const float resize_margin_pos = 12;
292 const float resize_margin_neg = 2;
293 const float resize_corner = 20;
295 //------------------------------------------------------------------------------
296 bool DesktopGroup::handleMouse(const osgGA::GUIEventAdapter& ea)
298 if( !_transform->getNumChildren() || !_handle_events )
301 sc::MouseEventPtr event(new sc::MouseEvent(ea));
303 event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
304 event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
305 if( ea.getMouseYOrientation()
306 != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
307 event->screen_pos.y() = _height - event->screen_pos.y();
309 event->delta.x() = event->getScreenX() - _last_x;
310 event->delta.y() = event->getScreenY() - _last_y;
312 _last_x = event->getScreenX();
313 _last_y = event->getScreenY();
315 event->local_pos = event->client_pos = event->screen_pos;
317 if( !_resize_window.expired() )
319 switch( ea.getEventType() )
321 case osgGA::GUIEventAdapter::RELEASE:
322 _resize_window.lock()->handleResize(sc::Window::NONE);
323 _resize_window.reset();
325 case osgGA::GUIEventAdapter::DRAG:
326 _resize_window.lock()->handleResize( _resize,
327 event->screen_pos - _drag_start );
334 sc::WindowPtr window_at_cursor = _pointer_grab_window.lock();
335 if( !window_at_cursor )
337 for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
339 osg::Group *element = _transform->getChild(i)->asGroup();
342 assert(element->getUserData());
344 sc::WindowPtr window =
345 dynamic_cast<sc::Window*>
347 static_cast<sc::Element::OSGUserData*>(
348 element->getUserData()
352 if( !window || !window->isCapturingEvents() || !window->isVisible() )
355 float margin = window->isResizable() ? resize_margin_pos : 0;
356 if( window->getScreenRegion().contains( event->getScreenX(),
360 window_at_cursor = window;
366 if( window_at_cursor )
368 const SGRect<float>& reg = window_at_cursor->getScreenRegion();
370 if( window_at_cursor->isResizable()
371 && ( ea.getEventType() == osgGA::GUIEventAdapter::MOVE
372 || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
373 || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
375 && !reg.contains( event->getScreenX(),
377 -resize_margin_neg ) )
380 _last_cursor = fgGetMouseCursor();
384 if( event->getScreenX() <= reg.l() + resize_corner )
385 _resize |= sc::Window::LEFT;
386 else if( event->getScreenX() >= reg.r() - resize_corner )
387 _resize |= sc::Window::RIGHT;
389 if( event->getScreenY() <= reg.t() + resize_corner )
390 _resize |= sc::Window::TOP;
391 else if( event->getScreenY() >= reg.b() - resize_corner )
392 _resize |= sc::Window::BOTTOM;
394 static const int cursor_mapping[] =
396 0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
397 MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
398 MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
401 if( !cursor_mapping[_resize] )
404 fgSetMouseCursor(cursor_mapping[_resize]);
406 if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
408 _resize_window = window_at_cursor;
409 _drag_start = event->screen_pos;
411 window_at_cursor->raise();
412 window_at_cursor->handleResize(_resize | sc::Window::INIT);
421 fgSetMouseCursor(_last_cursor);
426 sc::WindowPtr target_window = window_at_cursor;
427 switch( ea.getEventType() )
429 case osgGA::GUIEventAdapter::PUSH:
430 _last_push = window_at_cursor;
431 event->type = sc::Event::MOUSE_DOWN;
433 case osgGA::GUIEventAdapter::SCROLL:
434 switch( ea.getScrollingMotion() )
436 case osgGA::GUIEventAdapter::SCROLL_UP:
437 event->delta.y() = 1;
439 case osgGA::GUIEventAdapter::SCROLL_DOWN:
440 event->delta.y() = -1;
446 // osg sends two events for every scrolling motion. We don't need
447 // duplicate events, so lets ignore the second event with the same
449 if( _last_scroll_time == ea.getTime() )
450 return window_at_cursor ? true : false;
451 _last_scroll_time = ea.getTime();
453 event->type = sc::Event::WHEEL;
455 case osgGA::GUIEventAdapter::MOVE:
457 sc::WindowPtr last_mouse_over = _last_mouse_over.lock();
458 if( last_mouse_over != window_at_cursor && last_mouse_over )
460 sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
461 move_event->type = sc::Event::MOUSE_LEAVE;
462 move_event->client_pos -= toOsg(last_mouse_over->getPosition());
463 move_event->local_pos = move_event->client_pos;
465 last_mouse_over->handleEvent(move_event);
467 _last_mouse_over = window_at_cursor;
468 event->type = sc::Event::MOUSE_MOVE;
471 case osgGA::GUIEventAdapter::RELEASE:
473 event->type = sc::Event::MOUSE_UP;
475 sc::WindowPtr last_push = _last_push.lock();
478 if( last_push && last_push != target_window )
481 sc::MouseEventPtr leave_event( new sc::MouseEvent(*event) );
482 leave_event->type = sc::Event::MOUSE_LEAVE;
483 leave_event->client_pos -= toOsg(last_push->getPosition());
484 leave_event->local_pos = leave_event->client_pos;
486 last_push->handleEvent(leave_event);
490 case osgGA::GUIEventAdapter::DRAG:
491 target_window = _last_push.lock();
492 event->type = sc::Event::DRAG;
501 event->client_pos -= toOsg(target_window->getPosition());
502 event->local_pos = event->client_pos;
503 return target_window->handleEvent(event);
506 return handleRootEvent(event);
509 //------------------------------------------------------------------------------
510 bool DesktopGroup::handleKeyboard(const osgGA::GUIEventAdapter& ea)
512 if( !_transform->getNumChildren() || !_handle_events )
515 sc::KeyboardEventPtr event(new sc::KeyboardEvent(ea));
517 // Detect key repeat (of non modifier keys)
518 if( !event->isModifier() )
520 if( event->getType() == sc::Event::KEY_DOWN )
522 if( event->keyCode() == _last_key_down_no_mod )
523 event->setRepeat(true);
524 _last_key_down_no_mod = event->keyCode();
528 if( event->keyCode() == _last_key_down_no_mod )
529 _last_key_down_no_mod = -1;
533 sc::WindowPtr active_window = _focus_window.lock();
534 bool handled = handleEvent(event, active_window);
536 if( event->getType() == sc::Event::KEY_DOWN
537 && !event->defaultPrevented()
538 && event->isPrint() )
540 sc::KeyboardEventPtr keypress( new sc::KeyboardEvent(*event) );
541 keypress->type = sc::Event::KEY_PRESS;
543 handled |= handleEvent(keypress, active_window);
549 //------------------------------------------------------------------------------
550 bool DesktopGroup::handleEvent( const sc::EventPtr& event,
551 const sc::WindowPtr& active_window )
554 ? active_window->handleEvent(event)
555 : handleRootEvent(event);
558 //------------------------------------------------------------------------------
559 bool DesktopGroup::handleRootEvent(const sc::EventPtr& event)
561 sc::Element::handleEvent(event);
563 // stopPropagation() on DesktopGroup stops propagation to internal event
565 return event->propagation_stopped;
568 //------------------------------------------------------------------------------
569 void DesktopGroup::handleResize(int x, int y, int width, int height)
571 if( _width == width && _height == height )
577 // Origin should be at top left corner, therefore we need to mirror the y-axis
578 _transform->setMatrix(osg::Matrix(
586 //------------------------------------------------------------------------------
587 void DesktopGroup::handleMouseMode(SGPropertyNode* node)
589 // pass-through indicates events should pass through to the UI
590 _handle_events = fgGetNode("/input/mice/mouse[0]/mode", node->getIntValue())
591 ->getBoolValue("pass-through");
594 //------------------------------------------------------------------------------
600 //------------------------------------------------------------------------------
601 sc::WindowPtr GUIMgr::createWindow(const std::string& name)
603 sc::WindowPtr window = _desktop->createChild<sc::Window>(name);
605 window->set<std::string>
608 boost::lexical_cast<std::string>(window->getProps()->getIndex())
613 //------------------------------------------------------------------------------
616 if( _desktop && _event_handler )
618 SG_LOG(SG_GUI, SG_WARN, "GUIMgr::init() already initialized.");
622 DesktopPtr desktop( new DesktopGroup );
623 desktop->handleResize
627 fgGetInt("/sim/startup/xsize"),
628 fgGetInt("/sim/startup/ysize")
632 _event_handler = new GUIEventHandler(desktop);
633 globals->get_renderer()
636 // GUI is on top of everything so lets install as first event handler
637 .push_front( _event_handler );
639 sc::Canvas::addPlacementFactory
642 boost::bind(&GUIMgr::addWindowPlacement, this, _1, _2)
645 _desktop->getProps()->fireCreatedRecursive();
648 //------------------------------------------------------------------------------
649 void GUIMgr::shutdown()
651 if( !_desktop && !_event_handler )
653 SG_LOG(SG_GUI, SG_WARN, "GUIMgr::shutdown() not running.");
657 sc::Canvas::removePlacementFactory("window");
667 globals->get_renderer()
669 ->removeEventHandler( _event_handler );
674 //------------------------------------------------------------------------------
675 void GUIMgr::update(double dt)
677 _desktop->update(dt);
680 //------------------------------------------------------------------------------
681 sc::GroupPtr GUIMgr::getDesktop()
686 //------------------------------------------------------------------------------
687 void GUIMgr::setInputFocus(const simgear::canvas::WindowPtr& window)
689 static_cast<DesktopGroup*>(_desktop.get())->setFocusWindow(window);
692 //------------------------------------------------------------------------------
693 bool GUIMgr::grabPointer(const sc::WindowPtr& window)
695 return static_cast<DesktopGroup*>(_desktop.get())->grabPointer(window);
698 //------------------------------------------------------------------------------
699 void GUIMgr::ungrabPointer(const sc::WindowPtr& window)
701 static_cast<DesktopGroup*>(_desktop.get())->ungrabPointer(window);
704 //------------------------------------------------------------------------------
706 GUIMgr::addWindowPlacement( SGPropertyNode* placement,
707 sc::CanvasPtr canvas )
709 const std::string& id = placement->getStringValue("id");
711 sc::Placements placements;
712 sc::WindowPtr window = _desktop->getChild<sc::Window>(id);
715 window->setCanvasContent(canvas);
716 placements.push_back(
718 new WindowPlacement(placement, window, canvas)