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>
42 class GUIEventHandler:
43 public osgGA::GUIEventHandler
46 GUIEventHandler(GUIMgr* gui_mgr):
50 bool handle( const osgGA::GUIEventAdapter& ea,
51 osgGA::GUIActionAdapter& aa,
57 return _gui_mgr->handleEvent(ea);
65 * Track a canvas placement on a window
67 class WindowPlacement:
68 public simgear::canvas::Placement
71 WindowPlacement( SGPropertyNode* node,
72 canvas::WindowPtr window,
73 simgear::canvas::CanvasPtr canvas ):
80 * Remove placement from window
82 virtual ~WindowPlacement()
84 canvas::WindowPtr window = _window.lock();
85 simgear::canvas::CanvasPtr canvas = _canvas.lock();
87 if( window && canvas && canvas == window->getCanvasContent().lock() )
88 window->setCanvasContent( simgear::canvas::CanvasPtr() );
92 canvas::WindowWeakPtr _window;
93 simgear::canvas::CanvasWeakPtr _canvas;
96 //------------------------------------------------------------------------------
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),
112 // We handle the property listener manually within ::init and ::shutdown.
115 _width = _height = -1;
117 osg::Camera* camera =
118 flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
120 camera->addChild( getMatrixTransform() );
122 simgear::canvas::Canvas::addPlacementFactory
125 boost::bind(&GUIMgr::addPlacement, this, _1, _2)
128 osg::StateSet* stateSet = _transform->getOrCreateStateSet();
129 stateSet->setDataVariance(osg::Object::STATIC);
130 stateSet->setRenderBinDetails(1000, "RenderBin");
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)
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);
144 //------------------------------------------------------------------------------
145 canvas::WindowPtr GUIMgr::createWindow(const std::string& name)
147 canvas::WindowPtr window = createChild<canvas::Window>(name);
149 window->set<std::string>
152 boost::lexical_cast<std::string>(window->getProps()->getIndex())
157 //------------------------------------------------------------------------------
164 fgGetInt("/sim/startup/xsize"),
165 fgGetInt("/sim/startup/ysize")
168 globals->get_renderer()
171 // GUI is on top of everything so lets install as first event handler
172 .push_front( _event_handler );
174 _node->addChangeListener(this);
175 _node->fireCreatedRecursive();
178 //------------------------------------------------------------------------------
179 void GUIMgr::shutdown()
181 _node->removeChangeListener(this);
183 globals->get_renderer()
185 ->removeEventHandler( _event_handler );
188 //------------------------------------------------------------------------------
189 void GUIMgr::update(double dt)
194 //------------------------------------------------------------------------------
195 bool GUIMgr::handleEvent(const osgGA::GUIEventAdapter& ea)
197 switch( ea.getEventType() )
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(),
211 ea.getWindowHeight() );
212 return false; // Let other event handlers also consume resize events
218 //------------------------------------------------------------------------------
219 GUIMgr::ElementFactory GUIMgr::getChildFactory(const std::string& type) const
221 if( type == "window" )
222 return &Element::create<canvas::Window>;
224 return Group::getChildFactory(type);
227 //------------------------------------------------------------------------------
228 simgear::canvas::Placements
229 GUIMgr::addPlacement( SGPropertyNode* node,
230 simgear::canvas::CanvasPtr canvas )
232 const std::string& id = node->getStringValue("id");
234 simgear::canvas::Placements placements;
235 canvas::WindowPtr window = getChild<canvas::Window>(id);
238 window->setCanvasContent(canvas);
239 placements.push_back(
240 simgear::canvas::PlacementPtr(new WindowPlacement(node, window, canvas))
250 | || | _ inside corner region (L-shaped part inside margin) both
251 |___||_|_ _ _/ directions can be resized (outside only one axis)
255 | || | } margin_neg \
256 | ========|== <-- window border |_ area where resize
257 | | } margin_pos | can be initiated
261 const float resize_margin_pos = 12;
262 const float resize_margin_neg = 2;
263 const float resize_corner = 20;
265 //------------------------------------------------------------------------------
266 bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
268 if( !_transform->getNumChildren() || !_handle_events )
271 namespace sc = simgear::canvas;
272 sc::MouseEventPtr event(new sc::MouseEvent(ea));
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();
280 event->delta.x() = event->getScreenX() - _last_x;
281 event->delta.y() = event->getScreenY() - _last_y;
283 _last_x = event->getScreenX();
284 _last_y = event->getScreenY();
286 event->local_pos = event->client_pos = event->screen_pos;
288 if( !_resize_window.expired() )
290 switch( ea.getEventType() )
292 case osgGA::GUIEventAdapter::RELEASE:
293 _resize_window.lock()->handleResize(canvas::Window::NONE);
294 _resize_window.reset();
296 case osgGA::GUIEventAdapter::DRAG:
297 _resize_window.lock()->handleResize(_resize, event->delta);
304 canvas::WindowPtr window_at_cursor;
305 for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
307 osg::Group *element = _transform->getChild(i)->asGroup();
310 assert(element->getUserData());
312 canvas::WindowPtr window =
313 boost::static_pointer_cast<canvas::Window>
315 static_cast<sc::Element::OSGUserData*>(element->getUserData())->element
318 if( !window->isCapturingEvents() || !window->isVisible() )
321 float margin = window->isResizable() ? resize_margin_pos : 0;
322 if( window->getScreenRegion().contains( event->getScreenX(),
326 window_at_cursor = window;
331 if( window_at_cursor )
333 const SGRect<float>& reg = window_at_cursor->getScreenRegion();
335 if( window_at_cursor->isResizable()
336 && ( ea.getEventType() == osgGA::GUIEventAdapter::MOVE
337 || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
338 || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
340 && !reg.contains( event->getScreenX(),
342 -resize_margin_neg ) )
345 _last_cursor = fgGetMouseCursor();
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;
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;
359 static const int cursor_mapping[] =
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,
366 if( !cursor_mapping[_resize] )
369 fgSetMouseCursor(cursor_mapping[_resize]);
371 if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
373 _resize_window = window_at_cursor;
374 window_at_cursor->raise();
375 window_at_cursor->handleResize( _resize | canvas::Window::INIT,
385 fgSetMouseCursor(_last_cursor);
390 canvas::WindowPtr target_window = window_at_cursor;
391 switch( ea.getEventType() )
393 case osgGA::GUIEventAdapter::PUSH:
394 _last_push = window_at_cursor;
395 event->type = sc::Event::MOUSE_DOWN;
397 case osgGA::GUIEventAdapter::SCROLL:
398 switch( ea.getScrollingMotion() )
400 case osgGA::GUIEventAdapter::SCROLL_UP:
401 event->delta.y() = 1;
403 case osgGA::GUIEventAdapter::SCROLL_DOWN:
404 event->delta.y() = -1;
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
413 if( _last_scroll_time == ea.getTime() )
414 return window_at_cursor ? true : false;
415 _last_scroll_time = ea.getTime();
417 event->type = sc::Event::WHEEL;
419 case osgGA::GUIEventAdapter::MOVE:
421 canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
422 if( last_mouse_over != window_at_cursor && last_mouse_over )
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;
429 last_mouse_over->handleEvent(move_event);
431 _last_mouse_over = window_at_cursor;
432 event->type = sc::Event::MOUSE_MOVE;
435 case osgGA::GUIEventAdapter::RELEASE:
436 target_window = _last_push.lock();
438 event->type = sc::Event::MOUSE_UP;
441 case osgGA::GUIEventAdapter::DRAG:
442 target_window = _last_push.lock();
443 event->type = sc::Event::DRAG;
452 event->client_pos -= toOsg(target_window->getPosition());
453 event->local_pos = event->client_pos;
454 return target_window->handleEvent(event);
460 //------------------------------------------------------------------------------
461 void GUIMgr::handleResize(int x, int y, int width, int height)
463 if( _width == width && _height == height )
469 // Origin should be at top left corner, therefore we need to mirror the y-axis
470 _transform->setMatrix(osg::Matrix(
478 //------------------------------------------------------------------------------
479 void GUIMgr::handleMouseMode(SGPropertyNode* node)
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");