]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasElement.cxx
Canvas: fix transformation of clip rect.
[simgear.git] / simgear / canvas / elements / CanvasElement.cxx
1 // Interface for 2D Canvas element
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
18
19 #include "CanvasElement.hxx"
20 #include <simgear/canvas/Canvas.hxx>
21 #include <simgear/canvas/CanvasEventVisitor.hxx>
22 #include <simgear/canvas/events/MouseEvent.hxx>
23 #include <simgear/math/SGMisc.hxx>
24 #include <simgear/misc/strutils.hxx>
25 #include <simgear/scene/material/parseBlendFunc.hxx>
26
27 #include <osg/Drawable>
28 #include <osg/Geode>
29 #include <osg/Scissor>
30
31 #include <boost/algorithm/string/predicate.hpp>
32 #include <boost/foreach.hpp>
33 #include <boost/make_shared.hpp>
34
35 #include <cassert>
36 #include <cmath>
37 #include <cstring>
38
39 namespace simgear
40 {
41 namespace canvas
42 {
43   const std::string NAME_TRANSFORM = "tf";
44
45   /**
46    * glScissor with coordinates relative to different reference frames.
47    */
48   class Element::RelativeScissor:
49     public osg::Scissor
50   {
51     public:
52
53       ReferenceFrame                _coord_reference;
54       osg::observer_ptr<osg::Node>  _node;
55
56       RelativeScissor(osg::Node* node = NULL):
57         _coord_reference(GLOBAL),
58         _node(node)
59       {
60         _width = 0;
61         _height = 0;
62       }
63
64       /** Copy constructor using CopyOp to manage deep vs shallow copy. */
65       RelativeScissor( const RelativeScissor& vp,
66                        const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY ):
67         Scissor(vp, copyop),
68         _coord_reference(vp._coord_reference),
69         _node(vp._node)
70       {}
71
72       META_StateAttribute(simgear, RelativeScissor, SCISSOR);
73
74       /** Return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs. */
75       virtual int compare(const StateAttribute& sa) const
76       {
77         // check the types are equal and then create the rhs variable
78         // used by the COMPARE_StateAttribute_Parameter macros below.
79         COMPARE_StateAttribute_Types(RelativeScissor,sa)
80
81         // compare each parameter in turn against the rhs.
82         COMPARE_StateAttribute_Parameter(_x)
83         COMPARE_StateAttribute_Parameter(_y)
84         COMPARE_StateAttribute_Parameter(_width)
85         COMPARE_StateAttribute_Parameter(_height)
86         COMPARE_StateAttribute_Parameter(_coord_reference)
87         COMPARE_StateAttribute_Parameter(_node)
88
89         return 0; // passed all the above comparison macros, must be equal.
90       }
91
92       virtual void apply(osg::State& state) const
93       {
94         if( _width <= 0 || _height <= 0 )
95           return;
96
97         const osg::Viewport* vp = state.getCurrentViewport();
98         float w2 = 0.5 * vp->width(),
99               h2 = 0.5 * vp->height();
100
101         osg::Matrix model_view
102         (
103           w2, 0,  0, 0,
104           0,  h2, 0, 0,
105           0,  0,  1, 0,
106           w2, h2, 0, 1
107         );
108         model_view.preMult(state.getProjectionMatrix());
109
110         if( _coord_reference != GLOBAL )
111         {
112           osg::Node* ref_obj = _node.get();
113
114           if( _coord_reference == PARENT )
115           {
116             if( _node->getNumParents() < 1 )
117             {
118               SG_LOG(SG_GL, SG_WARN, "RelativeScissor: missing parent.");
119               return;
120             }
121
122             ref_obj = _node->getParent(0);
123           }
124
125           osg::MatrixList const& parent_matrices = ref_obj->getWorldMatrices();
126           assert( !parent_matrices.empty() );
127           model_view.preMult(parent_matrices.front());
128         }
129
130         const osg::Vec2 scale( model_view(0,0), model_view(1,1)),
131                         offset(model_view(3,0), model_view(3,1));
132
133         // TODO check/warn for rotation?
134         GLint x = SGMiscf::roundToInt(scale.x() * _x + offset.x()),
135               y = SGMiscf::roundToInt(scale.y() * _y + offset.y()),
136               w = SGMiscf::roundToInt(std::fabs(scale.x()) * _width),
137               h = SGMiscf::roundToInt(std::fabs(scale.y()) * _height);
138
139         if( scale.x() < 0 )
140           x -= w;
141         if( scale.y() < 0 )
142           y -= h;
143
144         glScissor(x, y, w, h);
145       }
146
147       bool contains(const osg::Vec2f& pos) const
148       {
149         return _x <= pos.x() && pos.x() <= _x + _width
150             && _y <= pos.y() && pos.y() <= _y + _height;
151       }
152
153       bool contains( const osg::Vec2f& global_pos,
154                      const osg::Vec2f& parent_pos,
155                      const osg::Vec2f& local_pos ) const
156       {
157         switch( _coord_reference )
158         {
159           case GLOBAL: return contains(global_pos);
160           case PARENT: return contains(parent_pos);
161           case LOCAL:  return contains(local_pos);
162         }
163
164         return false;
165       }
166   };
167
168   //----------------------------------------------------------------------------
169   Element::OSGUserData::OSGUserData(ElementPtr element):
170     element(element)
171   {
172
173   }
174
175   //----------------------------------------------------------------------------
176   Element::~Element()
177   {
178     if( !_transform.valid() )
179       return;
180
181     for(unsigned int i = 0; i < _transform->getNumChildren(); ++i)
182     {
183       OSGUserData* ud =
184         static_cast<OSGUserData*>(_transform->getChild(i)->getUserData());
185
186       if( ud )
187         // Ensure parent is cleared to prevent accessing released memory if an
188         // element somehow survives longer than his parent.
189         ud->element->_parent = 0;
190     }
191   }
192
193   //----------------------------------------------------------------------------
194   void Element::onDestroy()
195   {
196     if( !_transform.valid() )
197       return;
198
199     // The transform node keeps a reference on this element, so ensure it is
200     // deleted.
201     BOOST_FOREACH(osg::Group* parent, _transform->getParents())
202     {
203       parent->removeChild(_transform.get());
204     }
205   }
206
207   //----------------------------------------------------------------------------
208   ElementPtr Element::getParent() const
209   {
210     return _parent;
211   }
212
213   //----------------------------------------------------------------------------
214   CanvasWeakPtr Element::getCanvas() const
215   {
216     return _canvas;
217   }
218
219   //----------------------------------------------------------------------------
220   void Element::update(double dt)
221   {
222     if( !isVisible() )
223       return;
224
225     // Trigger matrix update
226     getMatrix();
227
228     // Update bounding box on manual update (manual updates pass zero dt)
229     if( dt == 0 && _drawable )
230       _drawable->getBound();
231
232     if( _attributes_dirty & BLEND_FUNC )
233     {
234       parseBlendFunc(
235         _transform->getOrCreateStateSet(),
236         _node->getChild("blend-source"),
237         _node->getChild("blend-destination"),
238         _node->getChild("blend-source-rgb"),
239         _node->getChild("blend-destination-rgb"),
240         _node->getChild("blend-source-alpha"),
241         _node->getChild("blend-destination-alpha")
242       );
243       _attributes_dirty &= ~BLEND_FUNC;
244     }
245   }
246
247   //----------------------------------------------------------------------------
248   bool Element::addEventListener( const std::string& type_str,
249                                   const EventListener& cb )
250   {
251     SG_LOG
252     (
253       SG_GENERAL,
254       SG_INFO,
255       "addEventListener(" << _node->getPath() << ", " << type_str << ")"
256     );
257
258     _listener[ Event::getOrRegisterType(type_str) ].push_back(cb);
259     return true;
260   }
261
262   //----------------------------------------------------------------------------
263   void Element::clearEventListener()
264   {
265     _listener.clear();
266   }
267
268   //----------------------------------------------------------------------------
269   bool Element::accept(EventVisitor& visitor)
270   {
271     if( !isVisible() )
272       return false;
273
274     return visitor.apply(*this);
275   }
276
277   //----------------------------------------------------------------------------
278   bool Element::ascend(EventVisitor& visitor)
279   {
280     if( _parent )
281       return _parent->accept(visitor);
282     return true;
283   }
284
285   //----------------------------------------------------------------------------
286   bool Element::traverse(EventVisitor& visitor)
287   {
288     return true;
289   }
290
291   //----------------------------------------------------------------------------
292   bool Element::handleEvent(const EventPtr& event)
293   {
294     ListenerMap::iterator listeners = _listener.find(event->getType());
295     if( listeners == _listener.end() )
296       return false;
297
298     BOOST_FOREACH(EventListener const& listener, listeners->second)
299       try
300       {
301         listener(event);
302       }
303       catch( std::exception const& ex )
304       {
305         SG_LOG(
306           SG_GENERAL,
307           SG_WARN,
308           "canvas::Element: event handler error: '" << ex.what() << "'"
309         );
310       }
311
312     return true;
313   }
314
315   //----------------------------------------------------------------------------
316   bool Element::dispatchEvent(const EventPtr& event)
317   {
318     EventPropagationPath path;
319     path.push_back( EventTarget(this) );
320
321     for( Element* parent = _parent;
322                   parent != NULL;
323                   parent = parent->_parent )
324       path.push_front( EventTarget(parent) );
325
326     CanvasPtr canvas = _canvas.lock();
327     if( !canvas )
328       return false;
329
330     return canvas->propagateEvent(event, path);
331   }
332
333   //----------------------------------------------------------------------------
334   bool Element::hitBound( const osg::Vec2f& global_pos,
335                           const osg::Vec2f& parent_pos,
336                           const osg::Vec2f& local_pos ) const
337   {
338     if( _scissor && !_scissor->contains(global_pos, parent_pos, local_pos) )
339       return false;
340
341     const osg::Vec3f pos3(parent_pos, 0);
342
343     // Drawables have a bounding box...
344     if( _drawable )
345       return _drawable->getBound().contains(osg::Vec3f(local_pos, 0));
346     else if( _transform.valid() )
347       // ... for other elements, i.e. groups only a bounding sphere is available
348       return _transform->getBound().contains(osg::Vec3f(parent_pos, 0));
349     else
350       return false;
351   }
352
353
354   //----------------------------------------------------------------------------
355   void Element::setVisible(bool visible)
356   {
357     if( _transform.valid() )
358       // TODO check if we need another nodemask
359       _transform->setNodeMask(visible ? 0xffffffff : 0);
360   }
361
362   //----------------------------------------------------------------------------
363   bool Element::isVisible() const
364   {
365     return _transform.valid() && _transform->getNodeMask() != 0;
366   }
367
368   //----------------------------------------------------------------------------
369   osg::MatrixTransform* Element::getMatrixTransform()
370   {
371     return _transform.get();
372   }
373
374   //----------------------------------------------------------------------------
375   osg::MatrixTransform const* Element::getMatrixTransform() const
376   {
377     return _transform.get();
378   }
379
380   //----------------------------------------------------------------------------
381   osg::Vec2f Element::posToLocal(const osg::Vec2f& pos) const
382   {
383     getMatrix();
384     const osg::Matrix& m = _transform->getInverseMatrix();
385     return osg::Vec2f
386     (
387       m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
388       m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
389     );
390   }
391
392   //----------------------------------------------------------------------------
393   void Element::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
394   {
395     if(    parent == _node
396         && child->getNameString() == NAME_TRANSFORM )
397     {
398       if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
399         _transform_types.resize( child->getIndex() + 1 );
400
401       _transform_types[ child->getIndex() ] = TT_NONE;
402       _attributes_dirty |= TRANSFORM;
403       return;
404     }
405     else if(    parent->getParent() == _node
406              && parent->getNameString() == NAME_TRANSFORM )
407     {
408       assert(parent->getIndex() < static_cast<int>(_transform_types.size()));
409
410       const std::string& name = child->getNameString();
411
412       TransformType& type = _transform_types[parent->getIndex()];
413
414       if(      name == "m" )
415         type = TT_MATRIX;
416       else if( name == "t" )
417         type = TT_TRANSLATE;
418       else if( name == "rot" )
419         type = TT_ROTATE;
420       else if( name == "s" )
421         type = TT_SCALE;
422
423       _attributes_dirty |= TRANSFORM;
424       return;
425     }
426
427     childAdded(child);
428   }
429
430   //----------------------------------------------------------------------------
431   void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
432   {
433     if( parent == _node )
434     {
435       if( child->getNameString() == NAME_TRANSFORM )
436       {
437         if( !_transform.valid() )
438           return;
439
440         if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
441         {
442           SG_LOG
443           (
444             SG_GENERAL,
445             SG_WARN,
446             "Element::childRemoved: unknown transform: " << child->getPath()
447           );
448           return;
449         }
450
451         _transform_types[ child->getIndex() ] = TT_NONE;
452
453         while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
454           _transform_types.pop_back();
455
456         _attributes_dirty |= TRANSFORM;
457         return;
458       }
459       else if( StyleInfo const* style = getStyleInfo(child->getNameString()) )
460       {
461         if( setStyle(getParentStyle(child), style) )
462           return;
463       }
464     }
465
466     childRemoved(child);
467   }
468
469   //----------------------------------------------------------------------------
470   void Element::valueChanged(SGPropertyNode* child)
471   {
472     SGPropertyNode *parent = child->getParent();
473     if( parent == _node )
474     {
475       const std::string& name = child->getNameString();
476       if( boost::starts_with(name, "data-") )
477         return;
478       else if( StyleInfo const* style_info = getStyleInfo(name) )
479       {
480         SGPropertyNode const* style = child;
481         if( isStyleEmpty(child) )
482         {
483           child->clearValue();
484           style = getParentStyle(child);
485         }
486         setStyle(style, style_info);
487         return;
488       }
489       else if( name == "update" )
490         return update(0);
491       else if( boost::starts_with(name, "blend-") )
492         return (void)(_attributes_dirty |= BLEND_FUNC);
493     }
494     else if(   parent
495             && parent->getParent() == _node
496             && parent->getNameString() == NAME_TRANSFORM )
497     {
498       _attributes_dirty |= TRANSFORM;
499       return;
500     }
501
502     childChanged(child);
503   }
504
505   //----------------------------------------------------------------------------
506   bool Element::setStyle( const SGPropertyNode* child,
507                           const StyleInfo* style_info )
508   {
509     return canApplyStyle(child) && setStyleImpl(child, style_info);
510   }
511
512   //----------------------------------------------------------------------------
513   void Element::setClip(const std::string& clip)
514   {
515     if( clip.empty() || clip == "auto" )
516     {
517       getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
518       _scissor = 0;
519       return;
520     }
521
522     // TODO generalize CSS property parsing
523     const std::string RECT("rect(");
524     if(    !boost::ends_with(clip, ")")
525         || !boost::starts_with(clip, RECT) )
526     {
527       SG_LOG(SG_GENERAL, SG_WARN, "Canvas: invalid clip: " << clip);
528       return;
529     }
530
531     const std::string sep(", \t\npx");
532     int comp = 0;
533     float values[4];
534
535     for(size_t pos = RECT.size(); comp < 4; ++comp)
536     {
537       pos = clip.find_first_not_of(sep, pos);
538       if( pos == std::string::npos || pos == clip.size() - 1 )
539         break;
540
541       char *end = 0;
542       values[comp] = strtod(&clip[pos], &end);
543       if( end == &clip[pos] || !end )
544         break;
545
546       pos = end - &clip[0];
547     }
548
549     if( comp < 4 )
550     {
551       SG_LOG(SG_GENERAL, SG_WARN, "Canvas: invalid clip: " << clip);
552       return;
553     }
554
555     float width = values[1] - values[3],
556           height = values[2] - values[0];
557
558     if( width < 0 || height < 0 )
559     {
560       SG_LOG(SG_GENERAL, SG_WARN, "Canvas: negative clip size: " << clip);
561       return;
562     }
563
564     if( !_scissor )
565       _scissor = new RelativeScissor(_transform.get());
566
567     // <top>, <right>, <bottom>, <left>
568     _scissor->x() = SGMiscf::roundToInt(values[3]);
569     _scissor->y() = SGMiscf::roundToInt(values[0]);
570     _scissor->width() = SGMiscf::roundToInt(width);
571     _scissor->height() = SGMiscf::roundToInt(height);
572
573     SGPropertyNode* clip_frame = _node->getChild("clip-frame", 0);
574     if( clip_frame )
575       valueChanged(clip_frame);
576     else
577       _scissor->_coord_reference = GLOBAL;
578
579     getOrCreateStateSet()->setAttributeAndModes(_scissor);
580   }
581
582   //----------------------------------------------------------------------------
583   void Element::setClipFrame(ReferenceFrame rf)
584   {
585     if( _scissor )
586       _scissor->_coord_reference = rf;
587   }
588
589   //----------------------------------------------------------------------------
590   osg::BoundingBox Element::getBoundingBox() const
591   {
592     if( _drawable )
593       return _drawable->getBound();
594
595     osg::BoundingBox bb;
596
597     if( _transform.valid() )
598       bb.expandBy(_transform->getBound());
599
600     return bb;
601   }
602
603   //----------------------------------------------------------------------------
604   osg::BoundingBox Element::getTightBoundingBox() const
605   {
606     return getTransformedBounds(getMatrix());
607   }
608
609   //----------------------------------------------------------------------------
610   osg::BoundingBox Element::getTransformedBounds(const osg::Matrix& m) const
611   {
612     if( !_drawable )
613       return osg::BoundingBox();
614
615     osg::BoundingBox transformed;
616     const osg::BoundingBox& bb = _drawable->getBound();
617     for(int i = 0; i < 4; ++i)
618       transformed.expandBy( bb.corner(i) * m );
619
620     return transformed;
621   }
622
623   //----------------------------------------------------------------------------
624   osg::Matrix Element::getMatrix() const
625   {
626     if( !_transform )
627       return osg::Matrix::identity();
628
629     if( !(_attributes_dirty & TRANSFORM) )
630       return _transform->getMatrix();
631
632     osg::Matrix m;
633     for( size_t i = 0; i < _transform_types.size(); ++i )
634     {
635       // Skip unused indizes...
636       if( _transform_types[i] == TT_NONE )
637         continue;
638
639       SGPropertyNode* tf_node = _node->getChild("tf", i, true);
640
641       // Build up the matrix representation of the current transform node
642       osg::Matrix tf;
643       switch( _transform_types[i] )
644       {
645         case TT_MATRIX:
646           tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
647                             tf_node->getDoubleValue("m[1]", 0),
648                             0,
649                             tf_node->getDoubleValue("m[6]", 0),
650
651                             tf_node->getDoubleValue("m[2]", 0),
652                             tf_node->getDoubleValue("m[3]", 1),
653                             0,
654                             tf_node->getDoubleValue("m[7]", 0),
655
656                             0,
657                             0,
658                             1,
659                             0,
660
661                             tf_node->getDoubleValue("m[4]", 0),
662                             tf_node->getDoubleValue("m[5]", 0),
663                             0,
664                             tf_node->getDoubleValue("m[8]", 1) );
665           break;
666         case TT_TRANSLATE:
667           tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
668                                         tf_node->getDoubleValue("t[1]", 0),
669                                         0 ) );
670           break;
671         case TT_ROTATE:
672           tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
673           break;
674         case TT_SCALE:
675         {
676           float sx = tf_node->getDoubleValue("s[0]", 1);
677           // sy defaults to sx...
678           tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
679           break;
680         }
681         default:
682           break;
683       }
684       m.postMult( tf );
685     }
686     _transform->setMatrix(m);
687     _attributes_dirty &= ~TRANSFORM;
688
689     return m;
690   }
691
692   //----------------------------------------------------------------------------
693   Element::StyleSetters Element::_style_setters;
694
695   //----------------------------------------------------------------------------
696   Element::Element( const CanvasWeakPtr& canvas,
697                     const SGPropertyNode_ptr& node,
698                     const Style& parent_style,
699                     Element* parent ):
700     PropertyBasedElement(node),
701     _canvas( canvas ),
702     _parent( parent ),
703     _attributes_dirty( 0 ),
704     _transform( new osg::MatrixTransform ),
705     _style( parent_style ),
706     _scissor( 0 ),
707     _drawable( 0 )
708   {
709     staticInit();
710
711     SG_LOG
712     (
713       SG_GL,
714       SG_DEBUG,
715       "New canvas element " << node->getPath()
716     );
717
718     // Ensure elements are drawn in order they appear in the element tree
719     _transform->getOrCreateStateSet()
720               ->setRenderBinDetails
721               (
722                 0,
723                 "PreOrderBin",
724                 osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
725               );
726
727     _transform->setUserData( new OSGUserData(this) );
728   }
729
730   //----------------------------------------------------------------------------
731   void Element::staticInit()
732   {
733     if( isInit<Element>() )
734       return;
735
736     addStyle("clip", "", &Element::setClip, false);
737     addStyle("clip-frame", "", &Element::setClipFrame, false);
738     addStyle("visible", "", &Element::setVisible, false);
739   }
740
741   //----------------------------------------------------------------------------
742   bool Element::isStyleEmpty(const SGPropertyNode* child) const
743   {
744     return !child
745         || simgear::strutils::strip(child->getStringValue()).empty();
746   }
747
748   //----------------------------------------------------------------------------
749   bool Element::canApplyStyle(const SGPropertyNode* child) const
750   {
751     if( _node == child->getParent() )
752       return true;
753
754     // Parent values do not override if element has own value
755     return isStyleEmpty( _node->getChild(child->getName()) );
756   }
757
758   //----------------------------------------------------------------------------
759   bool Element::setStyleImpl( const SGPropertyNode* child,
760                               const StyleInfo* style_info )
761   {
762     const StyleSetter* style_setter = style_info
763                                     ? &style_info->setter
764                                     : getStyleSetter(child->getNameString());
765     while( style_setter )
766     {
767       if( style_setter->func(*this, child) )
768         return true;
769       style_setter = style_setter->next;
770     }
771     return false;
772   }
773
774   //----------------------------------------------------------------------------
775   const Element::StyleInfo*
776   Element::getStyleInfo(const std::string& name) const
777   {
778     StyleSetters::const_iterator setter = _style_setters.find(name);
779     if( setter == _style_setters.end() )
780       return 0;
781
782     return &setter->second;
783   }
784
785   //----------------------------------------------------------------------------
786   const Element::StyleSetter*
787   Element::getStyleSetter(const std::string& name) const
788   {
789     const StyleInfo* info = getStyleInfo(name);
790     return info ? &info->setter : 0;
791   }
792
793   //----------------------------------------------------------------------------
794   const SGPropertyNode*
795   Element::getParentStyle(const SGPropertyNode* child) const
796   {
797     // Try to get value from parent...
798     if( _parent )
799     {
800       Style::const_iterator style =
801         _parent->_style.find(child->getNameString());
802       if( style != _parent->_style.end() )
803         return style->second;
804     }
805
806     // ...or reset to default if none is available
807     return child; // TODO somehow get default value for each style?
808   }
809
810   //----------------------------------------------------------------------------
811   void Element::setDrawable( osg::Drawable* drawable )
812   {
813     _drawable = drawable;
814     assert( _drawable );
815
816     osg::ref_ptr<osg::Geode> geode = new osg::Geode;
817     geode->addDrawable(_drawable);
818     _transform->addChild(geode);
819   }
820
821   //----------------------------------------------------------------------------
822   osg::StateSet* Element::getOrCreateStateSet()
823   {
824     return _drawable ? _drawable->getOrCreateStateSet()
825                      : _transform->getOrCreateStateSet();
826   }
827
828   //----------------------------------------------------------------------------
829   void Element::setupStyle()
830   {
831     BOOST_FOREACH( Style::value_type style, _style )
832       setStyle(style.second);
833   }
834
835 } // namespace canvas
836 } // namespace simgear