From: Flightgear-commitlogs <ma...@hy...> - 2012-07-31 22:09:15
|
The branch, next has been updated - Log ----------------------------------------------------------------- commit 8a6a2346539aa901e4f1301dba5331c40269188b Author: Thomas Geymayer Date: Fri Jul 27 13:17:42 2012 +0200 Canvas: First version of new Canvas GUI system. - Refactor CanvasMgr into PropertyBasedMgr to be also used for the Canvas GUI system. - Get rid of tied properties in the Canvas system. - Add new placement type 'window' for placing canvases onto windows - Pass mouse events to Window class (only if cursor is over window) - Refactor canvas placement clean up commit 5f08e10c0a0e383d7974bf69f5f51d4df28beb1e Author: Thomas Geymayer Date: Wed Jul 25 00:11:55 2012 +0200 Canvas: Support for text selection. - Expose nearest hit for text/character selection - Fix culling commit 91c3f6311027d65a235162d7408f1b486ab52eb6 Author: Thomas Geymayer Date: Tue Jul 17 01:02:03 2012 +0200 Canvas: UTF-8 support and more settings exposed. - Allow hiding objects - Use UTF-8 encoding for text elements - Add paramter max-width to text elements - Support stroke-linecap for path elements - Fix mouse coords - Set better mipmap filter commit e81db175f41ddf47e80f9a0b2a4e7fea515d3f1f Author: Thomas Geymayer Date: Thu Jul 12 00:23:29 2012 +0200 Canvas: Add new element type map for geo mapping. - The new map element automatically transforms geo coordinates (lat, lon) to the according screen coordinates. - Currently one type of projection is supported (Sanson-Flamsteed projection) commit a876ff93e158c444b4585efd73c248b45c56336c Author: Thomas Geymayer Date: Thu Jul 12 00:10:00 2012 +0200 NasalPositioned: Expose actual postion of parkings commit 373d511c6948ae4737b183e0ac38471544b6f499 Author: Thomas Geymayer Date: Wed Jul 4 13:15:12 2012 +0200 Canvas: Allow using canvases as PUI widgets. - Add new widget type canvas - Set canvas view dimension from the gui xml. - Expose mouse events to canvas widget properties. - Summary ------------------------------------------------------------- src/Canvas/CMakeLists.txt | 12 + src/Canvas/canvas.cxx | 404 +++++++++---------- src/Canvas/canvas.hxx | 103 ++++-- src/Canvas/canvas_fwd.hpp | 53 +++ src/Canvas/canvas_mgr.cxx | 129 +----- src/Canvas/canvas_mgr.hxx | 46 +-- src/Canvas/elements/element.cxx | 14 +- src/Canvas/elements/group.cxx | 6 +- src/Canvas/elements/map.cxx | 232 +++++++++++ src/Canvas/elements/map.hxx | 77 ++++ src/Canvas/elements/map/geo_node_pair.hxx | 120 ++++++ src/Canvas/elements/map/projection.hxx | 165 ++++++++ src/Canvas/elements/path.cxx | 20 + src/Canvas/elements/text.cxx | 183 +++++++--- src/Canvas/elements/text.hxx | 9 +- src/Canvas/gui_mgr.cxx | 297 ++++++++++++++ src/Canvas/gui_mgr.hxx | 71 ++++ src/Canvas/{elements/group.hxx => mouse_event.hxx} | 50 ++-- src/{Scenery/redout.hxx => Canvas/placement.cxx} | 27 +- .../HTTPClient.hxx => Canvas/placement.hxx} | 33 +- .../property_based_element.cxx} | 30 +- .../group.hxx => property_based_element.hxx} | 44 +-- src/Canvas/property_based_mgr.cxx | 118 ++++++ .../{canvas_mgr.hxx => property_based_mgr.hxx} | 56 ++-- src/Canvas/property_helper.cxx | 12 + src/Canvas/property_helper.hxx | 6 + src/Canvas/rect.hxx | 68 ++++ src/Canvas/window.cxx | 177 +++++++++ src/Canvas/window.hxx | 66 ++++ src/GUI/CMakeLists.txt | 2 + src/GUI/CanvasWidget.cxx | 168 ++++++++ src/GUI/CanvasWidget.hxx | 47 +++ src/GUI/FGPUIDialog.cxx | 8 + src/Instrumentation/od_gauge.cxx | 49 ++- src/Instrumentation/od_gauge.hxx | 21 +- src/Main/fg_init.cxx | 4 +- src/Scripting/NasalPositioned.cxx | 9 +- src/Scripting/NasalSys.cxx | 20 +- src/Scripting/NasalSys.hxx | 4 + 39 files changed, 2371 insertions(+), 589 deletions(-) create mode 100644 src/Canvas/canvas_fwd.hpp create mode 100644 src/Canvas/elements/map.cxx create mode 100644 src/Canvas/elements/map.hxx create mode 100644 src/Canvas/elements/map/geo_node_pair.hxx create mode 100644 src/Canvas/elements/map/projection.hxx create mode 100644 src/Canvas/gui_mgr.cxx create mode 100644 src/Canvas/gui_mgr.hxx copy src/Canvas/{elements/group.hxx => mouse_event.hxx} (53%) copy src/{Scenery/redout.hxx => Canvas/placement.cxx} (65%) copy src/{Network/HTTPClient.hxx => Canvas/placement.hxx} (62%) copy src/{Environment/metarairportfilter.cxx => Canvas/property_based_element.cxx} (58%) copy src/Canvas/{elements/group.hxx => property_based_element.hxx} (54%) create mode 100644 src/Canvas/property_based_mgr.cxx copy src/Canvas/{canvas_mgr.hxx => property_based_mgr.hxx} (50%) create mode 100644 src/Canvas/rect.hxx create mode 100644 src/Canvas/window.cxx create mode 100644 src/Canvas/window.hxx create mode 100644 src/GUI/CanvasWidget.cxx create mode 100644 src/GUI/CanvasWidget.hxx - Diff ---------------------------------------------------------------- diff --git a/src/Canvas/CMakeLists.txt b/src/Canvas/CMakeLists.txt index 6927c04..d8c45bd 100644 --- a/src/Canvas/CMakeLists.txt +++ b/src/Canvas/CMakeLists.txt @@ -5,9 +5,15 @@ set(SOURCES canvas_mgr.cxx elements/element.cxx elements/group.cxx + elements/map.cxx elements/path.cxx elements/text.cxx + gui_mgr.cxx + placement.cxx + property_based_element.cxx + property_based_mgr.cxx property_helper.cxx + window.cxx ) set(HEADERS @@ -15,9 +21,15 @@ set(HEADERS canvas_mgr.hxx elements/element.hxx elements/group.hxx + elements/map.hxx elements/path.hxx elements/text.hxx + gui_mgr.hxx + placement.hxx + property_based_element.hxx + property_based_mgr.hxx property_helper.hxx + window.hxx ) flightgear_component(Canvas "${SOURCES}" "${HEADERS}") diff --git a/src/Canvas/canvas.cxx b/src/Canvas/canvas.cxx index 7f2c859..4c6504f 100644 --- a/src/Canvas/canvas.cxx +++ b/src/Canvas/canvas.cxx @@ -18,172 +18,109 @@ #include "canvas.hxx" #include "elements/group.hxx" +#include "mouse_event.hxx" + #include <Canvas/property_helper.hxx> +#include <Main/globals.hxx> +#include <Viewer/CameraGroup.hxx> +#include <Viewer/renderer.hxx> + +#include <simgear/scene/util/RenderConstants.hxx> #include <osg/Camera> #include <osg/Geode> #include <osgText/Text> +#include <osgViewer/Viewer> +#include <boost/algorithm/string/predicate.hpp> #include <iostream> -//------------------------------------------------------------------------------ -/** - * Callback used to disable/enable rendering to the texture if it is not - * visible - */ -class CameraCullCallback: - public osg::NodeCallback +//---------------------------------------------------------------------------- +Canvas::CameraCullCallback::CameraCullCallback(): + _render( true ), + _render_frame( 0 ) { - public: - CameraCullCallback(): - _render( true ) - {} +} - /** - * Enable rendering for the next frame - */ - void enableRendering() - { - _render = true; - } +//---------------------------------------------------------------------------- +void Canvas::CameraCullCallback::enableRendering() +{ + _render = true; +} - private: +//---------------------------------------------------------------------------- +void Canvas::CameraCullCallback::operator()( osg::Node* node, + osg::NodeVisitor* nv ) +{ + if( !_render && nv->getTraversalNumber() != _render_frame ) + return; - bool _render; + traverse(node, nv); - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - if( _render ) - { - traverse(node, nv); - _render = false; - } - } -}; - -/** - * This callback is installed on every placement of the canvas in the scene to - * only render the canvas if at least one placement is visible - */ -class PlacementCullCallback: - public osg::NodeCallback -{ - public: + _render = false; + _render_frame = nv->getTraversalNumber(); +} - PlacementCullCallback(Canvas* canvas, CameraCullCallback* camera_cull): - _canvas( canvas ), - _camera_cull( camera_cull ) - {} +//---------------------------------------------------------------------------- +Canvas::CullCallback::CullCallback(CameraCullCallback* camera_cull): + _camera_cull( camera_cull ) +{ - private: +} - Canvas *_canvas; - CameraCullCallback *_camera_cull; +//---------------------------------------------------------------------------- +void Canvas::CullCallback::operator()( osg::Node* node, + osg::NodeVisitor* nv ) +{ + if( nv->getTraversalMask() & simgear::MODEL_BIT ) + _camera_cull->enableRendering(); - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - _camera_cull->enableRendering(); - traverse(node, nv); - } -}; + traverse(node, nv); +} //------------------------------------------------------------------------------ -Canvas::Canvas(): +Canvas::Canvas(SGPropertyNode* node): + PropertyBasedElement(node), _size_x(-1), _size_y(-1), _view_width(-1), _view_height(-1), - _status(0), + _status(node, "status"), + _status_msg(node, "status-msg"), + _mouse_x(node, "mouse/x"), + _mouse_y(node, "mouse/y"), + _mouse_dx(node, "mouse/dx"), + _mouse_dy(node, "mouse/dy"), + _mouse_button(node, "mouse/button"), + _mouse_state(node, "mouse/state"), + _mouse_mod(node, "mouse/mod"), + _mouse_scroll(node, "mouse/scroll"), + _mouse_event(node, "mouse/event"), _sampling_dirty(false), _color_dirty(true), - _node(0) + _root_group( new canvas::Group(node) ), + _render_always(false) { + _status = 0; setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y); - CameraCullCallback *camera_callback = new CameraCullCallback; - _camera_callback = camera_callback; - _cull_callback = new PlacementCullCallback(this, camera_callback); -} - -//------------------------------------------------------------------------------ -Canvas::~Canvas() -{ - clearPlacements(); + _camera_callback = new CameraCullCallback; + _cull_callback = new CullCallback(_camera_callback); - unbind(); - _node = 0; + canvas::linkColorNodes + ( + "color-background", + _node, + _color_background, + osg::Vec4f(0,0,0,1) + ); } //------------------------------------------------------------------------------ -int Canvas::getStatus() const -{ - return _status; -} - -//------------------------------------------------------------------------------ -void Canvas::reset(SGPropertyNode* node) +Canvas::~Canvas() { - if( node ) - SG_LOG - ( - SG_GL, - SG_INFO, - "Canvas::reset() texture[" << node->getIndex() << "]" - ); - - unbind(); - - _node = node; - setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y); - - if( _node ) - { - _root_group.reset( new canvas::Group(_node) ); - _node->tie - ( - "size[0]", - SGRawValueMethods<Canvas, int>( *this, &Canvas::getSizeX, - &Canvas::setSizeX ) - ); - _node->tie - ( - "size[1]", - SGRawValueMethods<Canvas, int>( *this, &Canvas::getSizeY, - &Canvas::setSizeY ) - ); - _node->tie - ( - "view[0]", - SGRawValueMethods<Canvas, int>( *this, &Canvas::getViewWidth, - &Canvas::setViewWidth ) - ); - _node->tie - ( - "view[1]", - SGRawValueMethods<Canvas, int>( *this, &Canvas::getViewHeight, - &Canvas::setViewHeight ) - ); - _node->tie - ( - "status", - SGRawValueMethods<Canvas, int>(*this, &Canvas::getStatus) - ); - _node->tie - ( - "status-msg", - SGRawValueMethods<Canvas, const char*>(*this, &Canvas::getStatusMsg) - ); - _node->addChangeListener(this); - canvas::linkColorNodes - ( - "color-background", - _node, - _color_background, - osg::Vec4f(0,0,0,1) - ); - } } //------------------------------------------------------------------------------ @@ -244,16 +181,33 @@ void Canvas::update(double delta_time_sec) // New placement _placements.resize(node->getIndex() + 1); else - // Remove maybe already existing placements - clearPlacements(node->getIndex()); - - // add new placements - _placements[node->getIndex()] = _texture.set_texture( - node, - _texture.getTexture(), - _cull_callback - ); + // Remove possibly existing placements + _placements[ node->getIndex() ].clear(); + + // Get new placements + PlacementFactoryMap::const_iterator placement_factory = + _placement_factories.find( node->getStringValue("type", "object") ); + if( placement_factory != _placement_factories.end() ) + { + canvas::Placements& placements = + _placements[ node->getIndex() ] = + placement_factory->second + ( + node, + boost::shared_static_cast<Canvas>(_self.lock()) + ); + node->setStringValue + ( + "status-msg", + placements.empty() ? "No match" : "Ok" + ); + } + else + node->setStringValue("status-msg", "Unknown placement type"); } + + if( _render_always ) + _camera_callback->enableRendering(); } //------------------------------------------------------------------------------ @@ -275,12 +229,6 @@ void Canvas::setSizeX(int sx) } //------------------------------------------------------------------------------ -int Canvas::getSizeX() const -{ - return _size_x; -} - -//------------------------------------------------------------------------------ void Canvas::setSizeY(int sy) { if( _size_y == sy ) @@ -299,12 +247,6 @@ void Canvas::setSizeY(int sy) } //------------------------------------------------------------------------------ -int Canvas::getSizeY() const -{ - return _size_y; -} - -//------------------------------------------------------------------------------ void Canvas::setViewWidth(int w) { if( _view_width == w ) @@ -315,12 +257,6 @@ void Canvas::setViewWidth(int w) } //------------------------------------------------------------------------------ -int Canvas::getViewWidth() const -{ - return _view_width; -} - -//------------------------------------------------------------------------------ void Canvas::setViewHeight(int h) { if( _view_height == h ) @@ -331,15 +267,19 @@ void Canvas::setViewHeight(int h) } //------------------------------------------------------------------------------ -int Canvas::getViewHeight() const +bool Canvas::handleMouseEvent(const canvas::MouseEvent& event) { - return _view_height; -} - -//------------------------------------------------------------------------------ -const char* Canvas::getStatusMsg() const -{ - return _status_msg.c_str(); + _mouse_x = event.x; + _mouse_y = event.y; + _mouse_dx = event.dx; + _mouse_dy = event.dy; + _mouse_button = event.button; + _mouse_state = event.state; + _mouse_mod = event.mod; + _mouse_scroll = event.scroll; + // Always set event type last because all listeners are attached to it + _mouse_event = event.type; + return true; } //------------------------------------------------------------------------------ @@ -364,7 +304,7 @@ void Canvas::childRemoved( SGPropertyNode * parent, return; if( child->getNameString() == "placement" ) - clearPlacements(child->getIndex()); + _placements[ child->getIndex() ].clear(); else static_cast<canvas::Element*>(_root_group.get()) ->childRemoved(parent, child); @@ -373,6 +313,9 @@ void Canvas::childRemoved( SGPropertyNode * parent, //---------------------------------------------------------------------------- void Canvas::valueChanged(SGPropertyNode* node) { + if( boost::starts_with(node->getNameString(), "status") ) + return; + if( node->getParent()->getParent() == _node ) { if( !_color_background.empty() @@ -398,18 +341,96 @@ void Canvas::valueChanged(SGPropertyNode* node) || node->getNameString() == "coverage-samples" || node->getNameString() == "color-samples" ) _sampling_dirty = true; + else if( node->getNameString() == "render-always" ) + _render_always = node->getBoolValue(); + else if( node->getNameString() == "size" ) + { + if( node->getIndex() == 0 ) + setSizeX( node->getIntValue() ); + else if( node->getIndex() == 1 ) + setSizeY( node->getIntValue() ); + } + else if( node->getNameString() == "view" ) + { + if( node->getIndex() == 0 ) + setViewWidth( node->getIntValue() ); + else if( node->getIndex() == 1 ) + setViewHeight( node->getIntValue() ); + } } _root_group->valueChanged(node); } //------------------------------------------------------------------------------ +osg::Texture2D* Canvas::getTexture() const +{ + return _texture.getTexture(); +} + +//------------------------------------------------------------------------------ +GLuint Canvas::getTexId() const +{ + osg::Texture2D* tex = _texture.getTexture(); + if( !tex ) + return 0; + +// osgViewer::Viewer::Contexts contexts; +// globals->get_renderer()->getViewer()->getContexts(contexts); +// +// if( contexts.empty() ) +// return 0; + + osg::Camera* guiCamera = + flightgear::getGUICamera(flightgear::CameraGroup::getDefault()); + + osg::State* state = guiCamera->getGraphicsContext()->getState(); //contexts[0]->getState(); + if( !state ) + return 0; + + osg::Texture::TextureObject* tobj = + tex->getTextureObject( state->getContextID() ); + if( !tobj ) + return 0; + + return tobj->_id; +} + +//------------------------------------------------------------------------------ +Canvas::CameraCullCallbackPtr Canvas::getCameraCullCallback() const +{ + return _camera_callback; +} + +//---------------------------------------------------------------------------- +Canvas::CullCallbackPtr Canvas::getCullCallback() const +{ + return _cull_callback; +} + +//------------------------------------------------------------------------------ +void Canvas::addPlacementFactory( const std::string& type, + canvas::PlacementFactory factory ) +{ + if( _placement_factories.find(type) != _placement_factories.end() ) + SG_LOG + ( + SG_GENERAL, + SG_WARN, + "Canvas::addPlacementFactory: replace existing factor for type " << type + ); + + _placement_factories[type] = factory; +} + +//------------------------------------------------------------------------------ void Canvas::setStatusFlags(unsigned int flags, bool set) { if( set ) - _status |= flags; + _status = _status | flags; else - _status &= ~flags; + _status = _status & ~flags; + // TODO maybe extend simgear::PropertyObject to allow |=, &= etc. if( (_status & MISSING_SIZE_X) && (_status & MISSING_SIZE_Y) ) _status_msg = "Missing size"; @@ -426,47 +447,4 @@ void Canvas::setStatusFlags(unsigned int flags, bool set) } //------------------------------------------------------------------------------ -void Canvas::clearPlacements(int index) -{ - Placements& placements = _placements.at(index); - while( !placements.empty() ) - { - osg::ref_ptr<osg::Group> group = placements.back(); - placements.pop_back(); - - assert( group->getNumChildren() == 1 ); - osg::Node *child = group->getChild(0); - - if( group->getNumParents() ) - { - osg::Group *parent = group->getParent(0); - parent->addChild(child); - parent->removeChild(group); - } - - group->removeChild(child); - } -} - -//------------------------------------------------------------------------------ -void Canvas::clearPlacements() -{ - for(size_t i = 0; i < _placements.size(); ++i) - clearPlacements(i); - _placements.clear(); -} - -//------------------------------------------------------------------------------ -void Canvas::unbind() -{ - if( !_node ) - return; - - _node->untie("size[0]"); - _node->untie("size[1]"); - _node->untie("view[0]"); - _node->untie("view[1]"); - _node->untie("status"); - _node->untie("status-msg"); - _node->removeChangeListener(this); -} +Canvas::PlacementFactoryMap Canvas::_placement_factories; diff --git a/src/Canvas/canvas.hxx b/src/Canvas/canvas.hxx index 25f8701..fa75f95 100644 --- a/src/Canvas/canvas.hxx +++ b/src/Canvas/canvas.hxx @@ -19,20 +19,20 @@ #ifndef CANVAS_HXX_ #define CANVAS_HXX_ +#include "placement.hxx" +#include "property_based_element.hxx" + +#include <Canvas/canvas_fwd.hpp> #include <Instrumentation/od_gauge.hxx> -#include <simgear/props/props.hxx> + +#include <simgear/props/propertyObject.hxx> #include <osg/NodeCallback> #include <memory> #include <string> -namespace canvas -{ - class Group; -} - class Canvas: - public SGPropertyChangeListener + public PropertyBasedElement { public: @@ -44,26 +44,58 @@ class Canvas: CREATE_FAILED = 0x0004 }; - Canvas(); + /** + * Callback used to disable/enable rendering to the texture if it is not + * visible + */ + class CameraCullCallback: + public osg::NodeCallback + { + public: + CameraCullCallback(); + + /** + * Enable rendering for the next frame + */ + void enableRendering(); + + private: + bool _render; + unsigned int _render_frame; + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); + }; + typedef osg::ref_ptr<CameraCullCallback> CameraCullCallbackPtr; + + /** + * This callback is installed on every placement of the canvas in the + * scene to only render the canvas if at least one placement is visible + */ + class CullCallback: + public osg::NodeCallback + { + public: + CullCallback(CameraCullCallback* camera_cull); + + private: + CameraCullCallback *_camera_cull; + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); + }; + typedef osg::ref_ptr<CullCallback> CullCallbackPtr; + + Canvas(SGPropertyNode* node); virtual ~Canvas(); - void reset(SGPropertyNode* node); void update(double delta_time_sec); void setSizeX(int sx); - int getSizeX() const; - void setSizeY(int sy); - int getSizeY() const; void setViewWidth(int w); - int getViewWidth() const; - void setViewHeight(int h); - int getViewHeight() const; - int getStatus() const; - const char* getStatusMsg() const; + bool handleMouseEvent(const canvas::MouseEvent& event); virtual void childAdded( SGPropertyNode * parent, SGPropertyNode * child ); @@ -71,6 +103,15 @@ class Canvas: SGPropertyNode * child ); virtual void valueChanged (SGPropertyNode * node); + osg::Texture2D* getTexture() const; + GLuint getTexId() const; + + CameraCullCallbackPtr getCameraCullCallback() const; + CullCallbackPtr getCullCallback() const; + + static void addPlacementFactory( const std::string& type, + canvas::PlacementFactory factory ); + private: Canvas(const Canvas&); // = delete; @@ -81,8 +122,16 @@ class Canvas: _view_width, _view_height; - int _status; - std::string _status_msg; + simgear::PropertyObject<int> _status; + simgear::PropertyObject<std::string> _status_msg; + + simgear::PropertyObject<int> _mouse_x, _mouse_y, + _mouse_dx, _mouse_dy, + _mouse_button, + _mouse_state, + _mouse_mod, + _mouse_scroll, + _mouse_event; bool _sampling_dirty, _color_dirty; @@ -90,19 +139,19 @@ class Canvas: FGODGauge _texture; std::auto_ptr<canvas::Group> _root_group; - SGPropertyNode_ptr _node; std::vector<SGPropertyNode_ptr> _color_background; - osg::ref_ptr<osg::NodeCallback> _camera_callback; - osg::ref_ptr<osg::NodeCallback> _cull_callback; + CameraCullCallbackPtr _camera_callback; + CullCallbackPtr _cull_callback; + bool _render_always; //<! Used to disable automatic lazy rendering (culling) + std::vector<SGPropertyNode*> _dirty_placements; - std::vector<Placements> _placements; + std::vector<canvas::Placements> _placements; - void setStatusFlags(unsigned int flags, bool set = true); - void clearPlacements(int index); - void clearPlacements(); + typedef std::map<std::string, canvas::PlacementFactory> PlacementFactoryMap; + static PlacementFactoryMap _placement_factories; - void unbind(); + void setStatusFlags(unsigned int flags, bool set = true); }; #endif /* CANVAS_HXX_ */ diff --git a/src/Canvas/canvas_fwd.hpp b/src/Canvas/canvas_fwd.hpp new file mode 100644 index 0000000..c56612f --- /dev/null +++ b/src/Canvas/canvas_fwd.hpp @@ -0,0 +1,53 @@ +// Canvas forward declarations +// +// Copyright (C) 2012 Thomas Geymayer <to...@gm...> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef CANVAS_FWD_HPP_ +#define CANVAS_FWD_HPP_ + +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <vector> + +class SGPropertyNode; + +class Canvas; +typedef boost::shared_ptr<Canvas> CanvasPtr; +typedef boost::weak_ptr<Canvas> CanvasWeakPtr; + +class PropertyBasedElement; +typedef boost::shared_ptr<PropertyBasedElement> PropertyBasedElementPtr; +typedef boost::weak_ptr<PropertyBasedElement> PropertyBasedElementWeakPtr; + +namespace canvas +{ + class Group; + class MouseEvent; + + class Placement; + typedef boost::shared_ptr<Placement> PlacementPtr; + typedef std::vector<PlacementPtr> Placements; + typedef boost::function<Placements( const SGPropertyNode*, + CanvasPtr )> PlacementFactory; + + class Window; + typedef boost::shared_ptr<Window> WindowPtr; + typedef boost::weak_ptr<Window> WindowWeakPtr; +} + +#endif /* CANVAS_FWD_HPP_ */ diff --git a/src/Canvas/canvas_mgr.cxx b/src/Canvas/canvas_mgr.cxx index 72db55f..21fcccf 100644 --- a/src/Canvas/canvas_mgr.cxx +++ b/src/Canvas/canvas_mgr.cxx @@ -19,123 +19,38 @@ #include "canvas_mgr.hxx" #include "canvas.hxx" -#include <Main/fg_props.hxx> +#include <boost/bind.hpp> -#include <osg/Camera> -#include <osg/Texture2D> - -#include <stdexcept> -#include <string> - -//------------------------------------------------------------------------------ -CanvasMgr::CanvasMgr(): - _props( fgGetNode("/canvas", true) ) -{ - -} - -//------------------------------------------------------------------------------ -CanvasMgr::~CanvasMgr() -{ - -} - -//------------------------------------------------------------------------------ -void CanvasMgr::init() -{ - _props->addChangeListener(this); - triggerChangeRecursive(_props); -} - -//------------------------------------------------------------------------------ -void CanvasMgr::reinit() -{ - -} - -//------------------------------------------------------------------------------ -void CanvasMgr::shutdown() -{ - _props->removeChangeListener(this); -} - -//------------------------------------------------------------------------------ -void CanvasMgr::bind() +typedef boost::shared_ptr<Canvas> CanvasPtr; +CanvasPtr canvasFactory(SGPropertyNode* node) { + return CanvasPtr(new Canvas(node)); } -//------------------------------------------------------------------------------ -void CanvasMgr::unbind() -{ -} //------------------------------------------------------------------------------ -void CanvasMgr::update(double delta_time_sec) -{ - for( size_t i = 0; i < _canvases.size(); ++i ) - if( _canvases[i] ) - _canvases[i]->update(delta_time_sec); -} - -//------------------------------------------------------------------------------ -void CanvasMgr::childAdded( SGPropertyNode * parent, - SGPropertyNode * child ) -{ - if( parent != _props ) - return; - - if( child->getNameString() == "texture" ) - textureAdded(child); -} - -//------------------------------------------------------------------------------ -void CanvasMgr::childRemoved( SGPropertyNode * parent, - SGPropertyNode * child ) -{ - if( parent != _props ) - return; - - if( child->getNameString() == "texture" ) - { - size_t index = child->getIndex(); - - if( index >= _canvases.size() ) - SG_LOG(SG_GL, SG_WARN, "can't removed unknown texture[" << index << "]!"); - else - // remove the canvas... - _canvases[index].reset(); - } -} - -//------------------------------------------------------------------------------ -void CanvasMgr::textureAdded(SGPropertyNode* node) +CanvasMgr::CanvasMgr(): + PropertyBasedMgr("/canvas", "texture", &canvasFactory) { - size_t index = node->getIndex(); - - if( index >= _canvases.size() ) - { - if( index > _canvases.size() ) - SG_LOG(SG_GL, SG_WARN, "Skipping unused texture slot(s)!"); - - _canvases.resize(index + 1); - } - else - { - SG_LOG(SG_GL, SG_WARN, "texture[" << index << "] already exists!"); - } - - _canvases[index].reset( new Canvas() ); - _canvases[index]->reset(node); + Canvas::addPlacementFactory + ( + "object", + boost::bind + ( + &FGODGauge::set_texture, + _1, + boost::bind(&Canvas::getTexture, _2), + boost::bind(&Canvas::getCullCallback, _2) + ) + ); } //------------------------------------------------------------------------------ -void CanvasMgr::triggerChangeRecursive(SGPropertyNode* node) +unsigned int CanvasMgr::getCanvasTexId(size_t index) const { - node->getParent()->fireChildAdded(node); - - if( node->nChildren() == 0 && node->getType() != simgear::props::NONE ) - return node->fireValueChanged(); + if( index >= _elements.size() + || !_elements[index] ) + return 0; - for( int i = 0; i < node->nChildren(); ++i ) - triggerChangeRecursive( node->getChild(i) ); + return static_cast<Canvas*>(_elements[index].get())->getTexId(); } diff --git a/src/Canvas/canvas_mgr.hxx b/src/Canvas/canvas_mgr.hxx index 893b50e..10cbe9a 100644 --- a/src/Canvas/canvas_mgr.hxx +++ b/src/Canvas/canvas_mgr.hxx @@ -19,53 +19,21 @@ #ifndef CANVAS_MGR_H_ #define CANVAS_MGR_H_ -#include <simgear/props/props.hxx> -#include <simgear/structure/subsystem_mgr.hxx> - -#include <boost/shared_ptr.hpp> -#include <vector> - -class Canvas; -typedef boost::shared_ptr<Canvas> CanvasPtr; +#include "property_based_mgr.hxx" class CanvasMgr: - public SGSubsystem, - public SGPropertyChangeListener + public PropertyBasedMgr { public: CanvasMgr(); - virtual ~CanvasMgr(); - - virtual void init(); - virtual void reinit(); - virtual void shutdown(); - - virtual void bind(); - virtual void unbind(); - - virtual void update(double delta_time_sec); - - virtual void childAdded( SGPropertyNode * parent, - SGPropertyNode * child ); - virtual void childRemoved( SGPropertyNode * parent, - SGPropertyNode * child ); - - private: - - /** Root node for everything concerning the canvas system */ - SGPropertyNode_ptr _props; - - /** The actual canvases */ - std::vector<CanvasPtr> _canvases; - - void textureAdded(SGPropertyNode* node); /** - * Trigger a childAdded and valueChanged event for every child of node - * (Unlimited depth) and node itself. + * Get OpenGL texture name for given canvas + * + * @param Index of canvas + * @return OpenGL texture name */ - void triggerChangeRecursive(SGPropertyNode* node); - + unsigned int getCanvasTexId(size_t index) const; }; #endif /* CANVAS_MGR_H_ */ diff --git a/src/Canvas/elements/element.cxx b/src/Canvas/elements/element.cxx index 3c2cdc6..b01b369 100644 --- a/src/Canvas/elements/element.cxx +++ b/src/Canvas/elements/element.cxx @@ -40,6 +40,10 @@ namespace canvas //---------------------------------------------------------------------------- void Element::update(double dt) { + if( !_transform->getNodeMask() ) + // Don't do anything if element is hidden + return; + if( _transform_dirty ) { osg::Matrix m; @@ -159,13 +163,6 @@ namespace canvas type = TT_ROTATE; else if( name == "s" ) type = TT_SCALE; - else - SG_LOG - ( - SG_GL, - SG_WARN, - "Unknown transform element " << child->getPath() - ); _transform_dirty = true; } @@ -208,6 +205,9 @@ namespace canvas { if( child->getNameString() == "update" ) update(0); + else if( child->getNameString() == "visible" ) + // TODO check if we need another nodemask + _transform->setNodeMask( child->getBoolValue() ? 0xffffffff : 0 ); else childChanged(child); } diff --git a/src/Canvas/elements/group.cxx b/src/Canvas/elements/group.cxx index ebbe3e6..96eb49c 100644 --- a/src/Canvas/elements/group.cxx +++ b/src/Canvas/elements/group.cxx @@ -17,6 +17,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "group.hxx" +#include "map.hxx" #include "path.hxx" #include "text.hxx" @@ -56,6 +57,8 @@ namespace canvas element.reset( new Text(child) ); else if( child->getNameString() == "group" ) element.reset( new Group(child) ); + else if( child->getNameString() == "map" ) + element.reset( new Map(child) ); else if( child->getNameString() == "path" ) element.reset( new Path(child) ); @@ -72,7 +75,8 @@ namespace canvas { if( node->getNameString() == "text" || node->getNameString() == "group" - || node->getNameString() == "path") + || node->getNameString() == "map" + || node->getNameString() == "path" ) { ChildMap::iterator child = _children.find(node); diff --git a/src/Canvas/elements/map.cxx b/src/Canvas/elements/map.cxx new file mode 100644 index 0000000..59a7d9b --- /dev/null +++ b/src/Canvas/elements/map.cxx @@ -0,0 +1,232 @@ +// A group of 2D canvas elements which get automatically transformed according +// to the map parameters. +// +// Copyright (C) 2012 Thomas Geymayer <to...@gm...> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "map.hxx" +#include "map/geo_node_pair.hxx" +#include "map/projection.hxx" + +#include <Main/fg_props.hxx> +#include <cmath> + +#define LOG_GEO_RET(msg) \ + {\ + SG_LOG\ + (\ + SG_GENERAL,\ + SG_WARN,\ + msg << " (" << child->getStringValue()\ + << ", " << child->getPath() << ")"\ + );\ + return;\ + } + +namespace canvas +{ + + // TODO make projection configurable + SansonFlamsteedProjection projection; + const std::string GEO = "-geo"; + + //---------------------------------------------------------------------------- + Map::Map(SGPropertyNode_ptr node): + Group(node), + _projection_dirty(true) + { + + } + + //---------------------------------------------------------------------------- + Map::~Map() + { + + } + + //---------------------------------------------------------------------------- + void Map::update(double dt) + { + for( GeoNodes::iterator it = _geo_nodes.begin(); + it != _geo_nodes.end(); + ++it ) + { + GeoNodePair* geo_node = it->second.get(); + if( !geo_node->isComplete() + || (!geo_node->isDirty() && !_projection_dirty) ) + continue; + + GeoCoord lat = parseGeoCoord(geo_node->getLat()); + if( lat.type != GeoCoord::LATITUDE ) + continue; + + GeoCoord lon = parseGeoCoord(geo_node->getLon()); + if( lon.type != GeoCoord::LONGITUDE ) + continue; + + Projection::ScreenPosition pos = + projection.worldToScreen(lat.value, lon.value); + + geo_node->setScreenPos(pos.x, pos.y); + +// geo_node->print(); + geo_node->setDirty(false); + } + _projection_dirty = false; + + Group::update(dt); + } + + //---------------------------------------------------------------------------- + void Map::childAdded(SGPropertyNode* parent, SGPropertyNode* child) + { + if( !hasSuffix(child->getNameString(), GEO) ) + return Element::childAdded(parent, child); + + _geo_nodes[child].reset(new GeoNodePair()); + } + + //---------------------------------------------------------------------------- + void Map::childRemoved(SGPropertyNode* parent, SGPropertyNode* child) + { + if( !hasSuffix(child->getNameString(), GEO) ) + return Element::childRemoved(parent, child); + + // TODO remove from other node + _geo_nodes.erase(child); + } + + //---------------------------------------------------------------------------- + void Map::valueChanged(SGPropertyNode * child) + { + const std::string& name = child->getNameString(); + + if( !hasSuffix(name, GEO) ) + return Group::valueChanged(child); + + GeoNodes::iterator it_geo_node = _geo_nodes.find(child); + if( it_geo_node == _geo_nodes.end() ) + LOG_GEO_RET("geo node not found!") + GeoNodePair* geo_node = it_geo_node->second.get(); + + geo_node->setDirty(); + + if( geo_node->getStatus() & GeoNodePair::INCOMPLETE ) + { + // Detect lat, lon tuples... + GeoCoord coord = parseGeoCoord(child->getStringValue()); + int index_other = -1; + + switch( coord.type ) + { + case GeoCoord::LATITUDE: + index_other = child->getIndex() + 1; + geo_node->setNodeLat(child); + break; + case GeoCoord::LONGITUDE: + index_other = child->getIndex() - 1; + geo_node->setNodeLon(child); + break; + default: + LOG_GEO_RET("Invalid geo coord") + } + + SGPropertyNode *other = child->getParent()->getChild(name, index_other); + if( !other ) + return; + + GeoCoord coord_other = parseGeoCoord(other->getStringValue()); + if( coord_other.type == GeoCoord::INVALID + || coord_other.type == coord.type ) + return; + + GeoNodes::iterator it_geo_node_other = _geo_nodes.find(other); + if( it_geo_node_other == _geo_nodes.end() ) + LOG_GEO_RET("other geo node not found!") + GeoNodePair* geo_node_other = it_geo_node_other->second.get(); + + // Let use both nodes use the same GeoNodePair instance + if( geo_node_other != geo_node ) + it_geo_node_other->second = it_geo_node->second; + + if( coord_other.type == GeoCoord::LATITUDE ) + geo_node->setNodeLat(other); + else + geo_node->setNodeLon(other); + + // Set name for resulting screen coordinate nodes + geo_node->setTargetName( name.substr(0, name.length() - GEO.length()) ); + } + } + + //---------------------------------------------------------------------------- + void Map::childChanged(SGPropertyNode * child) + { + if( child->getNameString() == "ref-lat" + || child->getNameString() == "ref-lon" ) + projection.setWorldPosition( _node->getDoubleValue("ref-lat"), + _node->getDoubleValue("ref-lon") ); + else if( child->getNameString() == "hdg" ) + projection.setOrientation(child->getFloatValue()); + else if( child->getNameString() == "range" ) + projection.setRange(child->getDoubleValue()); + else + return; + + _projection_dirty = true; + } + + //---------------------------------------------------------------------------- + Map::GeoCoord Map::parseGeoCoord(const std::string& val) const + { + GeoCoord coord; + if( val.length() < 2 ) + return coord; + + if( val[0] == 'N' || val[0] == 'S' ) + coord.type = GeoCoord::LATITUDE; + else if( val[0] == 'E' || val[0] == 'W' ) + coord.type = GeoCoord::LONGITUDE; + else + return coord; + + char* end; + coord.value = strtod(&val[1], &end); + + if( end != &val[val.length()] ) + { + coord.type = GeoCoord::INVALID; + return coord; + } + + if( val[0] == 'S' || val[0] == 'W' ) + coord.value *= -1; + + return coord; + } + + //---------------------------------------------------------------------------- + bool Map::hasSuffix(const std::string& str, const std::string& suffix) const + { + if( suffix.length() > str.length() ) + return false; + + return ( str.compare( str.length() - suffix.length(), + suffix.length(), + suffix ) == 0 ); + } + +} // namespace canvas diff --git a/src/Canvas/elements/map.hxx b/src/Canvas/elements/map.hxx new file mode 100644 index 0000000..944f8b6 --- /dev/null +++ b/src/Canvas/elements/map.hxx @@ -0,0 +1,77 @@ +// A group of 2D canvas elements which get automatically transformed according +// to the map parameters. +// +// Copyright (C) 2012 Thomas Geymayer <to...@gm...> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef CANVAS_MAP_HXX_ +#define CANVAS_MAP_HXX_ + +#include "group.hxx" + +#include <boost/shared_ptr.hpp> +#include <boost/unordered_map.hpp> + +namespace canvas +{ + class GeoNodePair; + class Map: + public Group + { + public: + Map(SGPropertyNode_ptr node); + virtual ~Map(); + + virtual void update(double dt); + + virtual void childAdded( SGPropertyNode * parent, + SGPropertyNode * child ); + virtual void childRemoved( SGPropertyNode * parent, + SGPropertyNode * child ); + virtual void valueChanged(SGPropertyNode * child); + + protected: + + virtual void childChanged(SGPropertyNode * child); + + typedef boost::unordered_map< SGPropertyNode*, + boost::shared_ptr<GeoNodePair> + > GeoNodes; + GeoNodes _geo_nodes; + bool _projection_dirty; + + struct GeoCoord + { + GeoCoord(): + type(INVALID) + {} + enum + { + INVALID, + LATITUDE, + LONGITUDE + } type; + double value; + }; + + GeoCoord parseGeoCoord(const std::string& val) const; + + bool hasSuffix(const std::string& str, const std::string& suffix) const; + }; + +} // namespace canvas + +#endif /* CANVAS_MAP_HXX_ */ diff --git a/src/Canvas/elements/map/geo_node_pair.hxx b/src/Canvas/elements/map/geo_node_pair.hxx new file mode 100644 index 0000000..14a04c9 --- /dev/null +++ b/src/Canvas/elements/map/geo_node_pair.hxx @@ -0,0 +1,120 @@ +/* + * geo_node_pair.hxx + * + * Created on: 11.07.2012 + * Author: tom + */ + +#ifndef CANVAS_GEO_NODE_PAIR_HXX_ +#define CANVAS_GEO_NODE_PAIR_HXX_ + +namespace canvas +{ + class GeoNodePair + { + public: + enum StatusFlags + { + LAT_MISSING = 1, + LON_MISSING = LAT_MISSING << 1, + INCOMPLETE = LAT_MISSING | LON_MISSING, + DIRTY = LON_MISSING << 1 + }; + + GeoNodePair(): + _status(INCOMPLETE), + _node_lat(0), + _node_lon(0) + {} + + uint8_t getStatus() const + { + return _status; + } + + void setDirty(bool flag = true) + { + if( flag ) + _status |= DIRTY; + else + _status &= ~DIRTY; + } + + bool isDirty() const + { + return _status & DIRTY; + } + + bool isComplete() const + { + return !(_status & INCOMPLETE); + } + + void setNodeLat(SGPropertyNode* node) + { + _node_lat = node; + _status &= ~LAT_MISSING; + + if( node == _node_lon ) + { + _node_lon = 0; + _status |= LON_MISSING; + } + } + + void setNodeLon(SGPropertyNode* node) + { + _node_lon = node; + _status &= ~LON_MISSING; + + if( node == _node_lat ) + { + _node_lat = 0; + _status |= LAT_MISSING; + } + } + + const char* getLat() const + { + return _node_lat ? _node_lat->getStringValue() : ""; + } + + const char* getLon() const + { + return _node_lon ? _node_lon->getStringValue() : ""; + } + + void setTargetName(const std::string& name) + { + _target_name = name; + } + + void setScreenPos(float x, float y) + { + assert( isComplete() ); + SGPropertyNode *parent = _node_lat->getParent(); + parent->getChild(_target_name, _node_lat->getIndex(), true) + ->setDoubleValue(x); + parent->getChild(_target_name, _node_lon->getIndex(), true) + ->setDoubleValue(y); + } + + void print() + { + std::cout << "lat=" << (_node_lat ? _node_lat->getPath() : "") + << ", lon=" << (_node_lon ? _node_lon->getPath() : "") + << std::endl; + } + + private: + + uint8_t _status; + SGPropertyNode *_node_lat, + *_node_lon; + std::string _target_name; + + }; + +} // namespace canvas + +#endif /* CANVAS_GEO_NODE_PAIR_HXX_ */ diff --git a/src/Canvas/elements/map/projection.hxx b/src/Canvas/elements/map/projection.hxx new file mode 100644 index 0000000..446230e --- /dev/null +++ b/src/Canvas/elements/map/projection.hxx @@ -0,0 +1,165 @@ +/* + * projection.hxx + * + * Created on: 12.07.2012 + * Author: tom + */ + +#ifndef CANVAS_MAP_PROJECTION_HXX_ +#define CANVAS_MAP_PROJECTION_HXX_ + +const double DEG2RAD = M_PI / 180.0; + +namespace canvas +{ + + /** + * Base class for all projections + */ + class Projection + { + public: + struct ScreenPosition + { + ScreenPosition() {} + + ScreenPosition(double x, double y): + x(x), + y(y) + {} + + double x, y; + }; + + virtual ~Projection() {} + + void setScreenRange(double range) + { + _screen_range = range; + } + + virtual ScreenPosition worldToScreen(double x, double y) = 0; + + protected: + + double _screen_range; + }; + + /** + * Base class for horizontal projections + */ + class HorizontalProjection: + public Projection + { + public: + + HorizontalProjection(): + _cos_rot(1), + _sin_rot(0), + _range(5) + { + setScreenRange(200); + } + + /** + * Set world position of center point used for the projection + */ + void setWorldPosition(double lat, double lon) + { + _ref_lat = lat * DEG2RAD; + _ref_lon = lon * DEG2RAD; + } + + /** + * Set up heading + */ + void setOrientation(float hdg) + { + hdg *= DEG2RAD; + _sin_rot = sin(hdg); + _cos_rot = cos(hdg); + } + + void setRange(double range) + { + _range = range; + } + + /** + * Transform given world position to screen position + * + * @param lat Latitude in degrees + * @param lon Longitude in degrees + */ + ScreenPosition worldToScreen(double lat, double lon) + { + lat *= DEG2RAD; + lon *= DEG2RAD; + ScreenPosition pos = project(lat, lon); + double scale = _screen_range / _range; + pos.x *= scale; + pos.y *= scale; + return ScreenPosition + ( + _cos_rot * pos.x - _sin_rot * pos.y, + -_sin_rot * pos.x - _cos_rot * pos.y + ); + } + + protected: + + /** + * Project given geographic world position to screen space + * + * @param lat Latitude in radians + * @param lon Longitude in radians + */ + virtual ScreenPosition project(double lat, double lon) const = 0; + + double _ref_lat, + _ref_lon, + _cos_rot, + _sin_rot, + _range; + }; + + /** + * Sanson-Flamsteed projection, relative to the projection center + */ + class SansonFlamsteedProjection: + public HorizontalProjection + { + protected: + + virtual ScreenPosition project(double lat, double lon) const + { + double d_lat = lat - _ref_lat, + d_lon = lon - _ref_lon; + double r = getEarthRadius(lat); + + ScreenPosition pos; + + pos.x = r * cos(lat) * d_lon; + pos.y = r * d_lat; + + return pos; + } + + /** + * Returns Earth radius at a given latitude (Ellipsoide equation with two + * equal axis) + */ + float getEarthRadius(float lat) const + { + const float rec = 6378137.f / 1852; // earth radius, equator (?) + const float rpol = 6356752.314f / 1852; // earth radius, polar (?) + + double a = cos(lat) / rec; + double b = sin(lat) / rpol; + return 1.0f / sqrt( a * a + b * b ); + } + }; + +} // namespace canvas + +#endif /* CANVAS_MAP_PROJECTION_HXX_ */ diff --git a/src/Canvas/elements/path.cxx b/src/Canvas/elements/path.cxx index a002f50..63b34f1 100644 --- a/src/Canvas/elements/path.cxx +++ b/src/Canvas/elements/path.cxx @@ -41,6 +41,7 @@ namespace canvas _paint_fill(VG_INVALID_HANDLE), _attributes_dirty(~0), _stroke_width(1), + _stroke_linecap(VG_CAP_BUTT), _fill(false) { setSupportsDisplayList(false); @@ -115,6 +116,21 @@ namespace canvas } /** + * Set stroke-linecap + * + * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty + */ + void setStrokeLinecap(const std::string& linecap) + { + if( linecap == "round" ) + _stroke_linecap = VG_CAP_ROUND; + else if( linecap == "square" ) + _stroke_linecap = VG_CAP_SQUARE; + else + _stroke_linecap = VG_CAP_BUTT; + } + + /** * Draw callback */ virtual void drawImplementation(osg::RenderInfo& renderInfo) const @@ -175,6 +191,7 @@ namespace canvas vgSetPaint(_paint, VG_STROKE_PATH); vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width); + vgSeti(VG_STROKE_CAP_STYLE, _stroke_linecap); vgSetfv( VG_STROKE_DASH_PATTERN, _stroke_dash.size(), _stroke_dash.empty() ? 0 : &_stroke_dash[0] ); @@ -244,6 +261,7 @@ namespace canvas VGfloat _stroke_color[4]; VGfloat _stroke_width; std::vector<VGfloat> _stroke_dash; + VGCapStyle _stroke_linecap; bool _fill; VGfloat _fill_color[4]; @@ -371,6 +389,8 @@ namespace canvas else if( child->getNameString() == "stroke-width" || child->getNameString() == "stroke-dasharray" ) _attributes_dirty |= STROKE; + else if( child->getNameString() == "stroke-linecap" ) + _path->setStrokeLinecap( child->getStringValue() ); else if( child->getNameString() == "fill" ) _path->enableFill( child->getBoolValue() ); } diff --git a/src/Canvas/elements/text.cxx b/src/Canvas/elements/text.cxx index dd23e92..518e783 100644 --- a/src/Canvas/elements/text.cxx +++ b/src/Canvas/elements/text.cxx @@ -26,11 +26,96 @@ namespace canvas { + class Text::TextOSG: + public osgText::Text + { + public: + osg::Vec2 handleHit(float x, float y); + }; + + //---------------------------------------------------------------------------- + osg::Vec2 Text::TextOSG::handleHit(float x, float y) + { + float line_height = _characterHeight + _lineSpacing; + + // TODO check with align other than TOP + float first_line_y = -0.5 * _lineSpacing;//_offset.y() - _characterHeight; + size_t line = std::max<int>(0, (y - first_line_y) / line_height); + + if( _textureGlyphQuadMap.empty() ) + return osg::Vec2(-1, -1); + + // TODO check when it can be larger + assert( _textureGlyphQuadMap.size() == 1 ); + + const GlyphQuads& glyphquad = _textureGlyphQuadMap.begin()->second; + const GlyphQuads::Glyphs& glyphs = glyphquad._glyphs; + const GlyphQuads::Coords2& coords = glyphquad._coords; + const GlyphQuads::LineNumbers& line_numbers = glyphquad._lineNumbers; + + const float HIT_FRACTION = 0.6; + const float character_width = getCharacterHeight() + * getCharacterAspectRatio(); + + y = (line + 0.5) * line_height; + + bool line_found = false; + for(size_t i = 0; i < line_numbers.size(); ++i) + { + if( line_numbers[i] != line ) + { + if( !line_found ) + { + if( line_numbers[i] < line ) + // Wait for the correct line... + continue; + + // We have already passed the correct line -> It's empty... + return osg::Vec2(0, y); + } + + // Next line and not returned -> not before any character + // -> return position after last character of line + return osg::Vec2(coords[(i - 1) * 4 + 2].x(), y); + } + + line_found = true; + + // Get threshold for mouse x position for setting cursor before or after + // current character + float threshold = coords[i * 4].x() + + HIT_FRACTION * glyphs[i]->getHorizontalAdvance() + * character_width; + + if( x <= threshold ) + { + if( i == 0 || line_numbers[i - 1] != line ) + // first character of line + x = coords[i * 4].x(); + else if( coords[(i - 1) * 4].x() == coords[(i - 1) * 4 + 2].x() ) + // If previous character width is zero set to begin of next character + // (Happens eg. with spaces) + x = coords[i * 4].x(); + else + // position at center between characters + x = 0.5 * (coords[(i - 1) * 4 + 2].x() + coords[i * 4].x()); + + return osg::Vec2(x, y); + } + } + + // Nothing found -> return position after last character + return osg::Vec2 + ( + coords.back().x(), + (_lineCount - 0.5) * line_height + ); + } //---------------------------------------------------------------------------- Text::Text(SGPropertyNode_ptr node): Element(node, COLOR | COLOR_FILL | BOUNDING_BOX), - _text( new osgText::Text ), + _text( new Text::TextOSG() ), _font_size( 0 ), _font_aspect( 0 ) { @@ -41,54 +126,12 @@ namespace canvas _font_size = getChildDefault<float>(_node, "character-size", 32); _font_aspect = getChildDefault<float>(_node, "character-aspect-ratio", 1); - - _node->tie - ( - "alignment", - SGRawValueMethods<Text, const char*> - ( - *this, - &Text::getAlignment, - &Text::setAlignment - ) - ); - _node->tie - ( - "padding", - SGRawValueMethods<osgText::Text, float> - ( - *_text.get(), - &osgText::Text::getBoundingBoxMargin, - &osgText::Text::setBoundingBoxMargin - ) - ); - typedef SGRawValueMethods<osgText::Text, int> TextIntMethods; - _node->tie - ( - "draw-mode", - // TEXT = 1 default - // BOUNDINGBOX = 2 - // FILLEDBOUNDINGBOX = 4 - // ALIGNMENT = 8 - TextIntMethods - ( - *_text.get(), - (TextIntMethods::getter_t)&osgText::Text::getDrawMode, - (TextIntMethods::setter_t)&osgText::Text::setDrawMode - ) - ); } //---------------------------------------------------------------------------- Text::~Text() { - if( _node ) - { - _node->untie("alignment"); - _node->untie("padding"); - _node->untie("draw-mode"); - } - _node = 0; + } //---------------------------------------------------------------------------- @@ -125,10 +168,20 @@ namespace canvas #include "text-alignment.hxx" #undef ENUM_MAPPING else + { + if( !align_string.empty() ) + SG_LOG + ( + SG_GENERAL, + SG_WARN, + "canvas::Text: unknown alignment '" << align_string << "'" + ); _text->setAlignment(osgText::Text::LEFT_BASE_LINE); + } } //---------------------------------------------------------------------------- +#if 0 const char* Text::getAlignment() const { switch( _text->getAlignment() ) @@ -142,16 +195,40 @@ namespace canvas return "unknown"; } } - +#endif //---------------------------------------------------------------------------- void Text::childChanged(SGPropertyNode* child) { - if( _font_size == child || _font_aspect == child ) + const std::string& name = child->getNameString(); + + if( name == "hit-y" ) + handleHit + ( + _node->getFloatValue("hit-x"), + _node->getFloatValue("hit-y") + ); + else if( _font_size == child || _font_aspect == child ) _attributes_dirty |= FONT_SIZE; - else if( child->getNameString() == "text" ) - _text->setText( child->getStringValue() ); - else if( child->getNameString() == "font" ) + else if( name == "text" ) + _text->setText + ( + osgText::String( child->getStringValue(), + osgText::String::ENCODING_UTF8 ) + ); + else if( name == "padding" ) + _text->setBoundingBoxMargin( child->getFloatValue() ); + else if( name == "draw-mode" ) + // TEXT = 1 default + // BOUNDINGBOX = 2 + // FILLEDBOUNDINGBOX = 4 + // ALIGNMENT = 8 + _text->setDrawMode( child->getIntValue() ); + else if( ... [truncated message content] |