1
0
Fork 0

Canvas: CSS like property value inheritance.

- Groups store property nodes for different styles and pass them
   to child elements on creation (No dynamic updates yet)
 - Use StyleSetter map instead of loads of if/else if statements
   for setting element styles.
 - Unify element style properties (Only use one property, instead
   of multiple like it has been with colors)
 - Fix: Create only one element per property node (Was two)
This commit is contained in:
Thomas Geymayer 2012-08-23 19:52:36 +02:00
parent 29e267f18a
commit ced478cf95
16 changed files with 437 additions and 290 deletions

View file

@ -102,6 +102,10 @@ Canvas::Canvas(SGPropertyNode* node):
_root_group( new canvas::Group(node) ),
_render_always(false)
{
// Remove automatically created property listener as we forward them on our
// own
_root_group->removeListener();
_status = 0;
setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y);
@ -317,6 +321,7 @@ void Canvas::valueChanged(SGPropertyNode* node)
if( boost::starts_with(node->getNameString(), "status") )
return;
bool handled = true;
if( node->getParent()->getParent() == _node )
{
if( !_color_background.empty()
@ -335,6 +340,8 @@ void Canvas::valueChanged(SGPropertyNode* node)
_dirty_placements.push_back(node->getParent());
}
else
handled = false;
}
else if( node->getParent() == _node )
{
@ -358,8 +365,13 @@ void Canvas::valueChanged(SGPropertyNode* node)
else if( node->getIndex() == 1 )
setViewHeight( node->getIntValue() );
}
else
handled = false;
}
else
handled = false;
if( !handled )
_root_group->valueChanged(node);
}

View file

@ -71,8 +71,8 @@ bool CullCallback::cull( osg::NodeVisitor* nv,
namespace canvas
{
//----------------------------------------------------------------------------
Image::Image(SGPropertyNode_ptr node):
Element(node, COLOR_FILL | BOUNDING_BOX),
Image::Image(SGPropertyNode_ptr node, const Style& parent_style):
Element(node, parent_style, BOUNDING_BOX),
_texture(new osg::Texture2D),
_node_src_rect( node->getNode("source", 0, true) )
{
@ -103,6 +103,11 @@ namespace canvas
_geom->addPrimitiveSet(prim);
setDrawable(_geom);
addStyle("fill", &Image::setFill, this);
setFill("#ffffff"); // TODO how should we handle default values?
setupStyle();
}
//----------------------------------------------------------------------------
@ -189,37 +194,44 @@ namespace canvas
setupDefaultDimensions();
}
//----------------------------------------------------------------------------
void Image::setFill(const std::string& fill)
{
osg::Vec4 color = parseColor(fill);
for( int i = 0; i < 4; ++i )
(*_colors)[i] = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
const Rect<float>& Image::getRegion() const
{
return _region;
}
//----------------------------------------------------------------------------
void Image::valueChanged(SGPropertyNode *node)
{
if( node->getParent() == _node_src_rect )
{
_attributes_dirty |= SRC_RECT;
if( node->getNameString() == "left" )
_src_rect.setLeft( node->getFloatValue() );
else if( node->getNameString() == "right" )
_src_rect.setRight( node->getFloatValue() );
else if( node->getNameString() == "top" )
_src_rect.setTop( node->getFloatValue() );
else if( node->getNameString() == "bottom" )
_src_rect.setBottom( node->getFloatValue() );
}
else
Element::valueChanged(node);
}
//----------------------------------------------------------------------------
void Image::childChanged(SGPropertyNode* child)
{
const std::string& name = child->getNameString();
if( child->getParent() == _node_src_rect )
{
_attributes_dirty |= SRC_RECT;
if( name == "left" )
_src_rect.setLeft( child->getFloatValue() );
else if( name == "right" )
_src_rect.setRight( child->getFloatValue() );
else if( name == "top" )
_src_rect.setTop( child->getFloatValue() );
else if( name == "bottom" )
_src_rect.setBottom( child->getFloatValue() );
return;
}
else if( child->getParent() != _node )
return;
if( name == "x" )
{
_region.setX( child->getFloatValue() );
@ -289,14 +301,6 @@ namespace canvas
}
}
//----------------------------------------------------------------------------
void Image::colorFillChanged(const osg::Vec4& color)
{
for( int i = 0; i < 4; ++i )
(*_colors)[i] = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
void Image::setupDefaultDimensions()
{

View file

@ -39,8 +39,8 @@ namespace canvas
* size[0-1] Dimensions of rectangle
* [x,y] Position of rectangle
*/
Image(SGPropertyNode_ptr node);
~Image();
Image(SGPropertyNode_ptr node, const Style& parent_style);
virtual ~Image();
virtual void update(double dt);
@ -48,14 +48,10 @@ namespace canvas
CanvasWeakPtr getCanvas() const;
void setImage(osg::Image *img);
void setFill(const std::string& fill);
const Rect<float>& getRegion() const;
/**
* Callback for every changed child node
*/
virtual void valueChanged(SGPropertyNode *node);
protected:
enum ImageAttributes
@ -64,13 +60,7 @@ namespace canvas
DEST_SIZE = SRC_RECT << 1 // Element size
};
/**
* Callback for changed direct child nodes
*/
virtual void childChanged(SGPropertyNode * child);
virtual void colorFillChanged(const osg::Vec4& color);
void handleHit(float x, float y);
void setupDefaultDimensions();
Rect<int> getTextureDimensions() const;
@ -79,10 +69,10 @@ namespace canvas
// TODO optionally forward events to canvas
CanvasWeakPtr _canvas;
osg::Geometry *_geom;
osg::Vec3Array *_vertices;
osg::Vec2Array *_texCoords;
osg::Vec4Array* _colors;
osg::ref_ptr<osg::Geometry> _geom;
osg::ref_ptr<osg::Vec3Array> _vertices;
osg::ref_ptr<osg::Vec2Array> _texCoords;
osg::ref_ptr<osg::Vec4Array> _colors;
SGPropertyNode *_node_src_rect;
Rect<float> _src_rect,

View file

@ -23,19 +23,30 @@
#include <osg/Drawable>
#include <osg/Geode>
#include <boost/foreach.hpp>
#include <cassert>
#include <cstring>
namespace canvas
{
const std::string NAME_TRANSFORM = "tf";
const std::string NAME_COLOR = "color";
const std::string NAME_COLOR_FILL = "color-fill";
//----------------------------------------------------------------------------
void Element::removeListener()
{
_node->removeChangeListener(this);
}
//----------------------------------------------------------------------------
Element::~Element()
{
removeListener();
BOOST_FOREACH(osg::Group* parent, _transform->getParents())
{
parent->removeChild(_transform);
}
}
//----------------------------------------------------------------------------
@ -95,24 +106,6 @@ namespace canvas
_transform_dirty = false;
}
if( _attributes_dirty & COLOR )
{
colorChanged( osg::Vec4( _color[0]->getFloatValue(),
_color[1]->getFloatValue(),
_color[2]->getFloatValue(),
_color[3]->getFloatValue() ) );
_attributes_dirty &= ~COLOR;
}
if( _attributes_dirty & COLOR_FILL )
{
colorFillChanged( osg::Vec4( _color_fill[0]->getFloatValue(),
_color_fill[1]->getFloatValue(),
_color_fill[2]->getFloatValue(),
_color_fill[3]->getFloatValue() ) );
_attributes_dirty &= ~COLOR_FILL;
}
if( !_bounding_box.empty() )
{
assert( _drawable );
@ -159,18 +152,15 @@ namespace canvas
//----------------------------------------------------------------------------
void Element::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
{
if( parent == _node )
{
if( child->getNameString() == NAME_TRANSFORM )
if( parent == _node
&& child->getNameString() == NAME_TRANSFORM )
{
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
_transform_types.resize( child->getIndex() + 1 );
_transform_types[ child->getIndex() ] = TT_NONE;
_transform_dirty = true;
}
else
childAdded(child);
return;
}
else if( parent->getParent() == _node
&& parent->getNameString() == NAME_TRANSFORM )
@ -191,16 +181,16 @@ namespace canvas
type = TT_SCALE;
_transform_dirty = true;
return;
}
childAdded(child);
}
//----------------------------------------------------------------------------
void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
{
if( parent != _node )
return;
if( child->getNameString() == NAME_TRANSFORM )
if( parent == _node && child->getNameString() == NAME_TRANSFORM )
{
assert(child->getIndex() < static_cast<int>(_transform_types.size()));
_transform_types[ child->getIndex() ] = TT_NONE;
@ -209,8 +199,9 @@ namespace canvas
_transform_types.pop_back();
_transform_dirty = true;
return;
}
else
childRemoved(child);
}
@ -218,45 +209,41 @@ namespace canvas
void Element::valueChanged(SGPropertyNode* child)
{
SGPropertyNode *parent = child->getParent();
if( parent->getParent() == _node )
if( parent == _node )
{
if( parent->getNameString() == NAME_TRANSFORM )
_transform_dirty = true;
else if( !_color.empty() && _color[0]->getParent() == parent )
_attributes_dirty |= COLOR;
else if( !_color_fill.empty() && _color_fill[0]->getParent() == parent )
_attributes_dirty |= COLOR_FILL;
}
else if( parent == _node )
{
if( child->getNameString() == "update" )
update(0);
if( setStyle(child) )
return;
else if( child->getNameString() == "update" )
return update(0);
else if( child->getNameString() == "visible" )
// TODO check if we need another nodemask
_transform->setNodeMask( child->getBoolValue() ? 0xffffffff : 0 );
else
childChanged(child);
return _transform->setNodeMask( child->getBoolValue() ? 0xffffffff : 0 );
}
else if( parent->getParent() == _node
&& parent->getNameString() == NAME_TRANSFORM )
{
_transform_dirty = true;
return;
}
childChanged(child);
}
//----------------------------------------------------------------------------
Element::Element(SGPropertyNode_ptr node, uint32_t attributes_used):
Element::Element( SGPropertyNode_ptr node,
const Style& parent_style,
uint32_t attributes_used ):
_attributes_used( attributes_used ),
_attributes_dirty( attributes_used ),
_transform_dirty( false ),
_transform( new osg::MatrixTransform ),
_node( node ),
_style( parent_style ),
_drawable( 0 )
{
assert( _node );
_node->addChangeListener(this);
if( _attributes_used & COLOR )
linkColorNodes("color", _node, _color, osg::Vec4f(0,0,0,1));
if( _attributes_used & COLOR_FILL )
linkColorNodes("color-fill", _node, _color_fill, osg::Vec4f(1,1,1,1));
SG_LOG
(
SG_GL,
@ -296,4 +283,23 @@ namespace canvas
}
}
//----------------------------------------------------------------------------
void Element::setupStyle()
{
BOOST_FOREACH( Style::value_type style, _style )
setStyle(style.second);
}
//----------------------------------------------------------------------------
bool Element::setStyle(const SGPropertyNode* child)
{
StyleSetters::const_iterator setter =
_style_setters.find(child->getNameString());
if( setter == _style_setters.end() )
return false;
setter->second(child);
return true;
}
} // namespace canvas

View file

@ -20,8 +20,11 @@
#define CANVAS_ELEMENT_HXX_
#include <simgear/props/props.hxx>
#include <osg/MatrixTransform>
#include <simgear/misc/stdint.hxx> // for uint32_t
#include <osg/MatrixTransform>
#include <boost/bind.hpp>
#include <boost/function.hpp>
namespace osg
{
@ -36,6 +39,11 @@ namespace canvas
public SGPropertyChangeListener
{
public:
typedef std::map<std::string, const SGPropertyNode*> Style;
typedef boost::function<void(const SGPropertyNode*)> StyleSetter;
typedef std::map<std::string, StyleSetter> StyleSetters;
void removeListener();
virtual ~Element() = 0;
/**
@ -59,9 +67,7 @@ namespace canvas
enum Attributes
{
COLOR = 0x0001,
COLOR_FILL = 0x0002,
BOUNDING_BOX = 0x0004,
BOUNDING_BOX = 0x0001,
LAST_ATTRIBUTE = BOUNDING_BOX
};
@ -82,11 +88,48 @@ namespace canvas
std::vector<TransformType> _transform_types;
SGPropertyNode_ptr _node;
std::vector<SGPropertyNode_ptr> _color,
_color_fill;
Style _style;
StyleSetters _style_setters;
std::vector<SGPropertyNode_ptr> _bounding_box;
Element(SGPropertyNode_ptr node, uint32_t attributes_used = 0);
Element( SGPropertyNode_ptr node,
const Style& parent_style,
uint32_t attributes_used = 0 );
template<typename T, class C1, class C2>
Element::StyleSetter
addStyle(const std::string& name, void (C1::*setter)(T), C2 instance)
{
return _style_setters[ name ] =
bindStyleSetter<T>(name, setter, instance);
}
template<typename T1, typename T2, class C1, class C2>
Element::StyleSetter
addStyle(const std::string& name, void (C1::*setter)(T2), C2 instance)
{
return _style_setters[ name ] =
bindStyleSetter<T1>(name, setter, instance);
}
template<class C1, class C2>
Element::StyleSetter
addStyle( const std::string& name,
void (C1::*setter)(const std::string&),
C2 instance )
{
return _style_setters[ name ] =
bindStyleSetter<const char*>(name, setter, instance);
}
template<typename T1, typename T2, class C1, class C2>
Element::StyleSetter
bindStyleSetter( const std::string& name,
void (C1::*setter)(T2),
C2 instance )
{
return boost::bind(setter, instance, boost::bind(&getValue<T1>, _1));
}
virtual bool handleLocalMouseEvent(const canvas::MouseEvent& event);
@ -94,14 +137,14 @@ namespace canvas
virtual void childRemoved(SGPropertyNode * child){}
virtual void childChanged(SGPropertyNode * child){}
virtual void colorChanged(const osg::Vec4& color) {}
virtual void colorFillChanged(const osg::Vec4& color){}
void setDrawable( osg::Drawable* drawable );
void setupStyle();
bool setStyle(const SGPropertyNode* child);
private:
osg::Drawable *_drawable;
osg::ref_ptr<osg::Drawable> _drawable;
Element(const Element&);// = delete
};

View file

@ -28,8 +28,8 @@ namespace canvas
{
//----------------------------------------------------------------------------
Group::Group(SGPropertyNode_ptr node):
Element(node)
Group::Group(SGPropertyNode_ptr node, const Style& parent_style):
Element(node, parent_style)
{
}
@ -64,27 +64,33 @@ namespace canvas
//----------------------------------------------------------------------------
void Group::childAdded(SGPropertyNode* child)
{
if( child->getParent() != _node )
return;
boost::shared_ptr<Element> element;
// TODO create map of child factories and use also to check for element
// on deletion in ::childRemoved
if( child->getNameString() == "text" )
element.reset( new Text(child) );
element.reset( new Text(child, _style) );
else if( child->getNameString() == "group" )
element.reset( new Group(child) );
element.reset( new Group(child, _style) );
else if( child->getNameString() == "map" )
element.reset( new Map(child) );
element.reset( new Map(child, _style) );
else if( child->getNameString() == "path" )
element.reset( new Path(child) );
element.reset( new Path(child, _style) );
else if( child->getNameString() == "image" )
element.reset( new Image(child) );
if( !element )
return;
element.reset( new Image(child, _style) );
if( element )
{
// Add to osg scene graph...
_transform->addChild( element->getMatrixTransform() );
_children.push_back( ChildList::value_type(child, element) );
return;
}
_style[ child->getNameString() ] = child;
}
//----------------------------------------------------------------------------
@ -107,6 +113,9 @@ namespace canvas
//----------------------------------------------------------------------------
void Group::childRemoved(SGPropertyNode* node)
{
if( node->getParent() != _node )
return;
if( node->getNameString() == "text"
|| node->getNameString() == "group"
|| node->getNameString() == "map"
@ -130,6 +139,12 @@ namespace canvas
_children.erase(child);
}
}
else
{
Style::iterator style = _style.find(node->getNameString());
if( style != _style.end() )
_style.erase(style);
}
}
} // namespace canvas

View file

@ -22,6 +22,7 @@
#include "element.hxx"
#include <boost/shared_ptr.hpp>
#include <list>
#include <map>
namespace canvas
{
@ -37,7 +38,7 @@ namespace canvas
>
> ChildList;
Group(SGPropertyNode_ptr node);
Group(SGPropertyNode_ptr node, const Style& parent_style = Style());
virtual ~Group();
virtual void update(double dt);

View file

@ -46,8 +46,8 @@ namespace canvas
const std::string GEO = "-geo";
//----------------------------------------------------------------------------
Map::Map(SGPropertyNode_ptr node):
Group(node),
Map::Map(SGPropertyNode_ptr node, const Style& parent_style):
Group(node, parent_style),
_projection_dirty(true)
{
@ -177,6 +177,9 @@ namespace canvas
//----------------------------------------------------------------------------
void Map::childChanged(SGPropertyNode * child)
{
if( child->getParent() != _node )
return;
if( child->getNameString() == "ref-lat"
|| child->getNameString() == "ref-lon" )
projection.setWorldPosition( _node->getDoubleValue("ref-lat"),

View file

@ -32,7 +32,7 @@ namespace canvas
public Group
{
public:
Map(SGPropertyNode_ptr node);
Map(SGPropertyNode_ptr node, const Style& parent_style);
virtual ~Map();
virtual void update(double dt);

View file

@ -40,9 +40,9 @@ namespace canvas
_paint(VG_INVALID_HANDLE),
_paint_fill(VG_INVALID_HANDLE),
_attributes_dirty(~0),
_mode(0),
_stroke_width(1),
_stroke_linecap(VG_CAP_BUTT),
_fill(false)
_stroke_linecap(VG_CAP_BUTT)
{
setSupportsDisplayList(false);
setDataVariance(Object::DYNAMIC);
@ -76,43 +76,54 @@ namespace canvas
}
/**
* Set stroke width and dash (line stipple)
* Set path fill paint ("none" if not filled)
*/
void setStroke( float width,
const std::vector<float> dash = std::vector<float>() )
void setFill(const std::string& fill)
{
if( fill == "none" )
{
_mode &= ~VG_FILL_PATH;
}
else
{
_fill_color = parseColor(fill);
_mode |= VG_FILL_PATH;
_attributes_dirty |= FILL_COLOR;
}
}
/**
* Set path stroke paint ("none" if no stroke)
*/
void setStroke(const std::string& stroke)
{
if( stroke == "none" )
{
_mode &= ~VG_STROKE_PATH;
}
else
{
_stroke_color = parseColor(stroke);
_mode |= VG_STROKE_PATH;
_attributes_dirty |= STROKE_COLOR;
}
}
/**
* Set stroke width
*/
void setStrokeWidth(float width)
{
_stroke_width = width;
_stroke_dash = dash;
_attributes_dirty |= BOUNDING_BOX;
}
/**
* Set the line color
* Set stroke dash (line stipple)
*/
void setColor(const osg::Vec4& color)
void setStrokeDashArray(const std::string& dash)
{
for( size_t i = 0; i < 4; ++i )
_stroke_color[i] = color[i];
_attributes_dirty |= STROKE_COLOR;
}
/**
* Enable/Disable filling of the path
*/
void enableFill(bool enable)
{
_fill = enable;
}
/**
* Set the line color
*/
void setColorFill(const osg::Vec4& color)
{
for( size_t i = 0; i < 4; ++i )
_fill_color[i] = color[i];
_attributes_dirty |= FILL_COLOR;
_stroke_dash = splitAndConvert(",\t\n ", dash);
}
/**
@ -166,28 +177,25 @@ namespace canvas
if( _paint == VG_INVALID_HANDLE )
_paint = vgCreatePaint();
vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color);
vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color._v);
_attributes_dirty &= ~STROKE_COLOR;
}
// Initialize/update fill paint
if( _attributes_dirty & (FILL_COLOR | FILL) )
if( _attributes_dirty & FILL_COLOR )
{
if( _paint_fill == VG_INVALID_HANDLE )
_paint_fill = vgCreatePaint();
if( _attributes_dirty & FILL_COLOR )
vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color);
vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color._v);
_attributes_dirty &= ~(FILL_COLOR | FILL);
_attributes_dirty &= ~FILL_COLOR;
}
// Detect draw mode
VGbitfield mode = 0;
if( _stroke_width > 0 )
// Setup paint
if( _mode & VG_STROKE_PATH )
{
mode |= VG_STROKE_PATH;
vgSetPaint(_paint, VG_STROKE_PATH);
vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
@ -196,15 +204,14 @@ namespace canvas
_stroke_dash.size(),
_stroke_dash.empty() ? 0 : &_stroke_dash[0] );
}
if( _fill )
if( _mode & VG_FILL_PATH )
{
mode |= VG_FILL_PATH;
vgSetPaint(_paint_fill, VG_FILL_PATH);
}
// And finally draw the path
if( mode )
vgDrawPath(_path, mode);
if( _mode )
vgDrawPath(_path, _mode);
VGErrorCode err = vgGetError();
if( err != VG_NO_ERROR )
@ -246,8 +253,7 @@ namespace canvas
PATH = 0x0001,
STROKE_COLOR = PATH << 1,
FILL_COLOR = STROKE_COLOR << 1,
FILL = FILL_COLOR << 1,
BOUNDING_BOX = FILL << 1
BOUNDING_BOX = FILL_COLOR << 1
};
mutable VGPath _path;
@ -258,14 +264,13 @@ namespace canvas
CmdList _cmds;
CoordList _coords;
VGfloat _stroke_color[4];
VGbitfield _mode;
osg::Vec4f _fill_color;
osg::Vec4f _stroke_color;
VGfloat _stroke_width;
std::vector<VGfloat> _stroke_dash;
VGCapStyle _stroke_linecap;
bool _fill;
VGfloat _fill_color[4];
/**
* Initialize/Update the OpenVG path
*/
@ -339,11 +344,20 @@ namespace canvas
bool PathDrawable::_vg_initialized = false;
//----------------------------------------------------------------------------
Path::Path(SGPropertyNode_ptr node):
Element(node, COLOR | COLOR_FILL | BOUNDING_BOX),
Path::Path(SGPropertyNode_ptr node, const Style& parent_style):
Element(node, parent_style, BOUNDING_BOX),
_path( new PathDrawable() )
{
setDrawable(_path);
PathDrawable *path = _path.get();
addStyle("fill", &PathDrawable::setFill, path);
addStyle("stroke", &PathDrawable::setStroke, path);
addStyle("stroke-width", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", &PathDrawable::setStrokeLinecap, path);
setupStyle();
}
//----------------------------------------------------------------------------
@ -365,16 +379,6 @@ namespace canvas
_attributes_dirty &= ~(CMDS | COORDS);
}
if( _attributes_dirty & STROKE )
{
_path->setStroke
(
_node->getFloatValue("stroke-width", 1),
getVectorFromChildren<VGfloat, float>(_node, "stroke-dasharray")
);
_attributes_dirty &= ~STROKE;
}
Element::update(dt);
}
@ -382,29 +386,13 @@ namespace canvas
//----------------------------------------------------------------------------
void Path::childChanged(SGPropertyNode* child)
{
if( child->getParent() != _node )
return;
if( child->getNameString() == "cmd" )
_attributes_dirty |= CMDS;
else if( child->getNameString() == "coord" )
_attributes_dirty |= COORDS;
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() );
}
//----------------------------------------------------------------------------
void Path::colorChanged(const osg::Vec4& color)
{
_path->setColor(color);
}
//----------------------------------------------------------------------------
void Path::colorFillChanged(const osg::Vec4& color)
{
_path->setColorFill(color);
}
} // namespace canvas

View file

@ -28,7 +28,7 @@ namespace canvas
public Element
{
public:
Path(SGPropertyNode_ptr node);
Path(SGPropertyNode_ptr node, const Style& parent_style);
virtual ~Path();
virtual void update(double dt);
@ -38,15 +38,12 @@ namespace canvas
enum PathAttributes
{
CMDS = LAST_ATTRIBUTE << 1,
COORDS = CMDS << 1,
STROKE = COORDS << 1
COORDS = CMDS << 1
};
osg::ref_ptr<PathDrawable> _path;
virtual void childChanged(SGPropertyNode * child);
virtual void colorChanged(const osg::Vec4& color);
virtual void colorFillChanged(const osg::Vec4& color);
};
} // namespace canvas

View file

@ -30,11 +30,37 @@ namespace canvas
public osgText::Text
{
public:
void setCharacterAspect(float aspect);
void setFill(const std::string& fill);
void setBackgroundColor(const std::string& fill);
osg::Vec2 handleHit(float x, float y);
virtual osg::BoundingBox computeBound() const;
};
//----------------------------------------------------------------------------
void Text::TextOSG::setCharacterAspect(float aspect)
{
setCharacterSize(getCharacterHeight(), aspect);
}
//----------------------------------------------------------------------------
void Text::TextOSG::setFill(const std::string& fill)
{
// if( fill == "none" )
// TODO No text
// else
setColor( parseColor(fill) );
}
//----------------------------------------------------------------------------
void Text::TextOSG::setBackgroundColor(const std::string& fill)
{
setBoundingBoxColor( parseColor(fill) );
}
//----------------------------------------------------------------------------
osg::Vec2 Text::TextOSG::handleHit(float x, float y)
{
@ -130,19 +156,33 @@ namespace canvas
}
//----------------------------------------------------------------------------
Text::Text(SGPropertyNode_ptr node):
Element(node, COLOR | COLOR_FILL | BOUNDING_BOX),
_text( new Text::TextOSG() ),
_font_size( 0 ),
_font_aspect( 0 )
Text::Text(SGPropertyNode_ptr node, const Style& parent_style):
Element(node, parent_style, BOUNDING_BOX),
_text( new Text::TextOSG() )
{
setDrawable(_text);
_text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
_text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
_text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
_font_size = getChildDefault<float>(_node, "character-size", 32);
_font_aspect = getChildDefault<float>(_node, "character-aspect-ratio", 1);
addStyle("fill", &TextOSG::setFill, _text);
addStyle("background", &TextOSG::setBackgroundColor, _text);
addStyle("character-size",
static_cast<void (TextOSG::*)(float)>(&TextOSG::setCharacterSize),
_text);
addStyle("character-aspect-ratio", &TextOSG::setCharacterAspect, _text);
addStyle("padding", &TextOSG::setBoundingBoxMargin, _text);
// TEXT = 1 default
// BOUNDINGBOX = 2
// FILLEDBOUNDINGBOX = 4
// ALIGNMENT = 8
addStyle<int>("draw-mode", &TextOSG::setDrawMode, _text);
addStyle("max-width", &TextOSG::setMaximumWidth, _text);
addStyle("font", &Text::setFont, this);
addStyle("alignment", &Text::setAlignment, this);
addStyle("text", &Text::setText, this);
setupStyle();
}
//----------------------------------------------------------------------------
@ -152,20 +192,9 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Text::update(double dt)
void Text::setText(const char* text)
{
Element::update(dt);
if( _attributes_dirty & FONT_SIZE )
{
_text->setCharacterSize
(
_font_size->getFloatValue(),
_font_aspect->getFloatValue()
);
_attributes_dirty &= ~FONT_SIZE;
}
_text->setText(text, osgText::String::ENCODING_UTF8);
}
//----------------------------------------------------------------------------
@ -213,51 +242,20 @@ namespace canvas
}
}
#endif
//----------------------------------------------------------------------------
void Text::childChanged(SGPropertyNode* child)
{
const std::string& name = child->getNameString();
if( child->getParent() != _node )
return;
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( 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( name == "max-width" )
_text->setMaximumWidth( child->getFloatValue() );
else if( name == "font" )
setFont( child->getStringValue() );
else if( name == "alignment" )
setAlignment( child->getStringValue() );
}
//----------------------------------------------------------------------------
void Text::colorChanged(const osg::Vec4& color)
{
_text->setColor(color);
}
//----------------------------------------------------------------------------
void Text::colorFillChanged(const osg::Vec4& color)
{
_text->setBoundingBoxColor(color);
}
//----------------------------------------------------------------------------

View file

@ -32,30 +32,19 @@ namespace canvas
public Element
{
public:
Text(SGPropertyNode_ptr node);
Text(SGPropertyNode_ptr node, const Style& parent_style);
~Text();
virtual void update(double dt);
void setText(const char* text);
void setFont(const char* name);
void setAlignment(const char* align);
protected:
enum TextAttributes
{
FONT_SIZE = LAST_ATTRIBUTE << 1, // Font size and aspect ration
};
class TextOSG;
osg::ref_ptr<TextOSG> _text;
SGPropertyNode_ptr _font_size,
_font_aspect;
virtual void childChanged(SGPropertyNode * child);
virtual void colorChanged(const osg::Vec4& color);
virtual void colorFillChanged(const osg::Vec4& color);
void handleHit(float x, float y);

View file

@ -17,10 +17,98 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "property_helper.hxx"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/tokenizer.hpp>
#include <cassert>
namespace canvas
{
//----------------------------------------------------------------------------
std::vector<float> splitAndConvert(const char del[], const std::string& str)
{
std::vector<float> values;
size_t pos = 0;
for(;;)
{
pos = str.find_first_not_of(del, pos);
if( pos == std::string::npos )
break;
char *end = 0;
float val = strtod(&str[pos], &end);
if( end == &str[pos] || !end )
break;
values.push_back(val);
pos = end - &str[0];
}
return values;
}
//----------------------------------------------------------------------------
osg::Vec4 parseColor(std::string str)
{
boost::trim(str);
osg::Vec4 color(0,0,0,1);
if( str.empty() )
return color;
// #rrggbb
if( str[0] == '#' )
{
const int offsets[] = {2,2,2};
const boost::offset_separator hex_separator( boost::begin(offsets),
boost::end(offsets) );
typedef boost::tokenizer<boost::offset_separator> offset_tokenizer;
offset_tokenizer tokens(str.begin() + 1, str.end(), hex_separator);
int comp = 0;
for( offset_tokenizer::const_iterator tok = tokens.begin();
tok != tokens.end() && comp < 4;
++tok, ++comp )
{
color._v[comp] = strtol(std::string(*tok).c_str(), 0, 16) / 255.f;
}
}
// rgb(r,g,b)
// rgba(r,g,b,a)
else if( boost::ends_with(str, ")") )
{
const std::string RGB = "rgb(",
RGBA = "rgba(";
size_t pos;
if( boost::starts_with(str, RGB) )
pos = RGB.length();
else if( boost::starts_with(str, RGBA) )
pos = RGBA.length();
else
return color;
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
const boost::char_separator<char> del(", \t\n");
tokenizer tokens(str.begin() + pos, str.end() - 1, del);
int comp = 0;
for( tokenizer::const_iterator tok = tokens.begin();
tok != tokens.end() && comp < 4;
++tok, ++comp )
{
color._v[comp] = boost::lexical_cast<float>(*tok)
// rgb = [0,255], a = [0,1]
/ (comp < 3 ? 255 : 1);
}
}
else
SG_LOG(SG_GENERAL, SG_WARN, "Unknown color: " << str);
return color;
}
//----------------------------------------------------------------------------
void linkColorNodes( const char* name,
SGPropertyNode* parent,

View file

@ -60,12 +60,26 @@ namespace canvas
return values;
}
/**
* Split a string by a delimter and convert the values to float
*
* TODO do we need other types than float?
*/
std::vector<float> splitAndConvert(const char del[], const std::string& str);
/**
* Parse a (CSS) color
*/
osg::Vec4 parseColor(std::string str);
/**
* @param name Name of color node
* @param parent Parent for color channel nodes
* @param nodes Vector to push color nodes into
* @param def Default color
*
* @deprecated Use only a single property instead and parse with #parseColor
*
* <name>
* <red type="float">def[0] or existing value</red>
* <green type="float">def[1] or existing value</green>

View file

@ -28,8 +28,10 @@ namespace canvas
//----------------------------------------------------------------------------
Window::Window(SGPropertyNode* node):
PropertyBasedElement(node),
_image(node)
_image(node, Element::Style())
{
_image.removeListener();
// TODO probably better remove default position and size
node->setFloatValue("x", 50);
node->setFloatValue("y", 100);
@ -44,10 +46,7 @@ namespace canvas
//----------------------------------------------------------------------------
Window::~Window()
{
BOOST_FOREACH(osg::Group* parent, getGroup()->getParents())
{
parent->removeChild(getGroup());
}
}
//----------------------------------------------------------------------------