]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
CanvasGroup: Fix handling z-index while moving child to front.
[simgear.git] / simgear / canvas / elements / CanvasImage.cxx
1 // An image on the Canvas
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 "CanvasImage.hxx"
20
21 #include <simgear/canvas/Canvas.hxx>
22 #include <simgear/canvas/CanvasMgr.hxx>
23 #include <simgear/canvas/CanvasSystemAdapter.hxx>
24 #include <simgear/canvas/MouseEvent.hxx>
25 #include <simgear/scene/util/OsgMath.hxx>
26 #include <simgear/scene/util/parse_color.hxx>
27 #include <simgear/misc/sg_path.hxx>
28
29 #include <osg/Array>
30 #include <osg/Geometry>
31 #include <osg/PrimitiveSet>
32
33 #include <boost/algorithm/string/predicate.hpp>
34
35 namespace simgear
36 {
37 namespace canvas
38 {
39   /**
40    * Callback to enable/disable rendering of canvas displayed inside windows or
41    * other canvases.
42    */
43   class CullCallback:
44     public osg::Drawable::CullCallback
45   {
46     public:
47       CullCallback(const CanvasWeakPtr& canvas);
48       void cullNextFrame();
49
50     private:
51       CanvasWeakPtr _canvas;
52       mutable bool  _cull_next_frame;
53
54       virtual bool cull( osg::NodeVisitor* nv,
55                          osg::Drawable* drawable,
56                          osg::RenderInfo* renderInfo ) const;
57   };
58
59   //----------------------------------------------------------------------------
60   CullCallback::CullCallback(const CanvasWeakPtr& canvas):
61     _canvas( canvas ),
62     _cull_next_frame( false )
63   {
64
65   }
66
67   //----------------------------------------------------------------------------
68   void CullCallback::cullNextFrame()
69   {
70     _cull_next_frame = true;
71   }
72
73   //----------------------------------------------------------------------------
74   bool CullCallback::cull( osg::NodeVisitor* nv,
75                            osg::Drawable* drawable,
76                            osg::RenderInfo* renderInfo ) const
77   {
78     if( !_canvas.expired() )
79       _canvas.lock()->enableRendering();
80
81     if( !_cull_next_frame )
82       // TODO check if window/image should be culled
83       return false;
84
85     _cull_next_frame = false;
86     return true;
87   }
88
89   //----------------------------------------------------------------------------
90   Image::Image( const CanvasWeakPtr& canvas,
91                 const SGPropertyNode_ptr& node,
92                 const Style& parent_style,
93                 Element* parent ):
94     Element(canvas, node, parent_style, parent),
95     _texture(new osg::Texture2D),
96     _node_src_rect( node->getNode("source", 0, true) ),
97     _src_rect(0,0),
98     _region(0,0)
99   {
100     _geom = new osg::Geometry;
101     _geom->setUseDisplayList(false);
102
103     osg::StateSet *stateSet = _geom->getOrCreateStateSet();
104     stateSet->setTextureAttributeAndModes(0, _texture.get());
105     stateSet->setDataVariance(osg::Object::STATIC);
106
107     // allocate arrays for the image
108     _vertices = new osg::Vec3Array(4);
109     _vertices->setDataVariance(osg::Object::DYNAMIC);
110     _geom->setVertexArray(_vertices);
111
112     _texCoords = new osg::Vec2Array(4);
113     _texCoords->setDataVariance(osg::Object::DYNAMIC);
114     _geom->setTexCoordArray(0, _texCoords);
115
116     _colors = new osg::Vec4Array(1);
117     _colors->setDataVariance(osg::Object::DYNAMIC);
118     _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
119     _geom->setColorArray(_colors);
120
121     _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
122     _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
123     _prim->setDataVariance(osg::Object::DYNAMIC);
124     _geom->addPrimitiveSet(_prim);
125
126     setDrawable(_geom);
127
128     if( !isInit<Image>() )
129     {
130       addStyle("fill", "color", &Image::setFill);
131       addStyle("slice", "", &Image::setSlice);
132       addStyle("slice-width", "", &Image::setSliceWidth);
133       addStyle("outset", "", &Image::setOutset);
134     }
135
136     setFill("#ffffff"); // TODO how should we handle default values?
137
138     setupStyle();
139   }
140
141   //----------------------------------------------------------------------------
142   Image::~Image()
143   {
144
145   }
146
147   //----------------------------------------------------------------------------
148   void Image::update(double dt)
149   {
150     Element::update(dt);
151
152     osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
153     (
154       _geom->getOrCreateStateSet()
155            ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
156     );
157     simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
158
159     if(    (_attributes_dirty & SRC_CANVAS)
160            // check if texture has changed (eg. due to resizing)
161         || (canvas && texture != canvas->getTexture()) )
162     {
163       _geom->getOrCreateStateSet()
164            ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
165
166       if( !canvas || canvas->isInit() )
167         _attributes_dirty &= ~SRC_CANVAS;
168     }
169
170     if( !_attributes_dirty )
171       return;
172
173     const SGRect<int>& tex_dim = getTextureDimensions();
174
175     // http://www.w3.org/TR/css3-background/#border-image-slice
176
177     // The ‘fill’ keyword, if present, causes the middle part of the image to be
178     // preserved. (By default it is discarded, i.e., treated as empty.)
179     bool fill = (_slice.getKeyword() == "fill");
180
181     if( _attributes_dirty & DEST_SIZE )
182     {
183       size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
184
185       if( num_vertices != _prim->getNumPrimitives() )
186       {
187         _vertices->resize(num_vertices);
188         _texCoords->resize(num_vertices);
189         _prim->setCount(num_vertices);
190         _prim->dirty();
191
192         _attributes_dirty |= SRC_RECT;
193       }
194
195       // http://www.w3.org/TR/css3-background/#border-image-outset
196       SGRect<float> region = _region;
197       if( _outset.isValid() )
198       {
199         const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
200         region.t() -= outset.t;
201         region.r() += outset.r;
202         region.b() += outset.b;
203         region.l() -= outset.l;
204       }
205
206       if( !_slice.isValid() )
207       {
208         setQuad(0, region.getMin(), region.getMax());
209       }
210       else
211       {
212         /*
213         Image slice, 9-scale, whatever it is called. The four corner images
214         stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
215         fill the remaining space up to the specified size.
216
217         x[0] x[1]     x[2] x[3]
218           |    |        |    |
219           -------------------- - y[0]
220           | tl |   top  | tr |
221           -------------------- - y[1]
222           |    |        |    |
223           | l  |        |  r |
224           | e  | center |  i |
225           | f  |        |  g |
226           | t  |        |  h |
227           |    |        |  t |
228           -------------------- - y[2]
229           | bl | bottom | br |
230           -------------------- - y[3]
231          */
232
233         const CSSBorder::Offsets& slice =
234           (_slice_width.isValid() ? _slice_width : _slice)
235           .getAbsOffsets(tex_dim);
236         float x[4] = {
237           region.l(),
238           region.l() + slice.l,
239           region.r() - slice.r,
240           region.r()
241         };
242         float y[4] = {
243           region.t(),
244           region.t() + slice.t,
245           region.b() - slice.b,
246           region.b()
247         };
248
249         int i = 0;
250         for(int ix = 0; ix < 3; ++ix)
251           for(int iy = 0; iy < 3; ++iy)
252           {
253             if( ix == 1 && iy == 1 && !fill )
254               // The ‘fill’ keyword, if present, causes the middle part of the
255               // image to be filled.
256               continue;
257
258             setQuad( i++,
259                      SGVec2f(x[ix    ], y[iy    ]),
260                      SGVec2f(x[ix + 1], y[iy + 1]) );
261           }
262       }
263
264       _vertices->dirty();
265       _attributes_dirty &= ~DEST_SIZE;
266       _geom->dirtyBound();
267       setBoundingBox(_geom->getBound());
268     }
269
270     if( _attributes_dirty & SRC_RECT )
271     {
272       SGRect<float> src_rect = _src_rect;
273       if( !_node_src_rect->getBoolValue("normalized", true) )
274       {
275         src_rect.t() /= tex_dim.height();
276         src_rect.r() /= tex_dim.width();
277         src_rect.b() /= tex_dim.height();
278         src_rect.l() /= tex_dim.width();
279       }
280
281       // Image coordinate systems y-axis is flipped
282       std::swap(src_rect.t(), src_rect.b());
283
284       if( !_slice.isValid() )
285       {
286         setQuadUV(0, src_rect.getMin(), src_rect.getMax());
287       }
288       else
289       {
290         const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
291         float x[4] = {
292           src_rect.l(),
293           src_rect.l() + slice.l,
294           src_rect.r() - slice.r,
295           src_rect.r()
296         };
297         float y[4] = {
298           src_rect.t(),
299           src_rect.t() - slice.t,
300           src_rect.b() + slice.b,
301           src_rect.b()
302         };
303
304         int i = 0;
305         for(int ix = 0; ix < 3; ++ix)
306           for(int iy = 0; iy < 3; ++iy)
307           {
308             if( ix == 1 && iy == 1 && !fill )
309               // The ‘fill’ keyword, if present, causes the middle part of the
310               // image to be filled.
311               continue;
312
313             setQuadUV( i++,
314                        SGVec2f(x[ix    ], y[iy    ]),
315                        SGVec2f(x[ix + 1], y[iy + 1]) );
316           }
317       }
318
319       _texCoords->dirty();
320       _attributes_dirty &= ~SRC_RECT;
321     }
322   }
323
324   //----------------------------------------------------------------------------
325   void Image::valueChanged(SGPropertyNode* child)
326   {
327     // If the image is switched from invisible to visible, and it shows a
328     // canvas, we need to delay showing it by one frame to ensure the canvas is
329     // updated before the image is displayed.
330     //
331     // As canvas::Element handles and filters changes to the "visible" property
332     // we can not check this in Image::childChanged but instead have to override
333     // Element::valueChanged.
334     if(    !isVisible()
335         && child->getParent() == _node
336         && child->getNameString() == "visible"
337         && child->getBoolValue() )
338     {
339       CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
340       if( cb )
341         cb->cullNextFrame();
342     }
343
344     Element::valueChanged(child);
345   }
346
347   //----------------------------------------------------------------------------
348   void Image::setSrcCanvas(CanvasPtr canvas)
349   {
350     if( !_src_canvas.expired() )
351       _src_canvas.lock()->removeParentCanvas(_canvas);
352     if( !_canvas.expired() )
353       _canvas.lock()->removeChildCanvas(_src_canvas);
354
355     _src_canvas = canvas;
356     _attributes_dirty |= SRC_CANVAS;
357     _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
358
359     if( !_src_canvas.expired() )
360     {
361       setupDefaultDimensions();
362       _src_canvas.lock()->addParentCanvas(_canvas);
363
364       if( !_canvas.expired() )
365         _canvas.lock()->addChildCanvas(_src_canvas);
366     }
367   }
368
369   //----------------------------------------------------------------------------
370   CanvasWeakPtr Image::getSrcCanvas() const
371   {
372     return _src_canvas;
373   }
374
375   //----------------------------------------------------------------------------
376   void Image::setImage(osg::Image *img)
377   {
378     // remove canvas...
379     setSrcCanvas( CanvasPtr() );
380
381     _texture->setResizeNonPowerOfTwoHint(false);
382     _texture->setImage(img);
383     _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
384     _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
385     _geom->getOrCreateStateSet()
386          ->setTextureAttributeAndModes(0, _texture);
387
388     if( img )
389       setupDefaultDimensions();
390   }
391
392   //----------------------------------------------------------------------------
393   void Image::setFill(const std::string& fill)
394   {
395     osg::Vec4 color;
396     if( !parseColor(fill, color) )
397       return;
398
399     _colors->front() = color;
400     _colors->dirty();
401   }
402
403   //----------------------------------------------------------------------------
404   void Image::setSlice(const std::string& slice)
405   {
406     _slice = CSSBorder::parse(slice);
407     _attributes_dirty |= SRC_RECT | DEST_SIZE;
408   }
409
410   //----------------------------------------------------------------------------
411   void Image::setSliceWidth(const std::string& width)
412   {
413     _slice_width = CSSBorder::parse(width);
414     _attributes_dirty |= DEST_SIZE;
415   }
416
417   //----------------------------------------------------------------------------
418   void Image::setOutset(const std::string& outset)
419   {
420     _outset = CSSBorder::parse(outset);
421     _attributes_dirty |= DEST_SIZE;
422   }
423
424   //----------------------------------------------------------------------------
425   const SGRect<float>& Image::getRegion() const
426   {
427     return _region;
428   }
429
430   //----------------------------------------------------------------------------
431   bool Image::handleEvent(EventPtr event)
432   {
433     bool handled = Element::handleEvent(event);
434
435     CanvasPtr src_canvas = _src_canvas.lock();
436     if( !src_canvas )
437       return handled;
438
439     MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
440     if( mouse_event )
441     {
442       mouse_event.reset( new MouseEvent(*mouse_event) );
443       event = mouse_event;
444
445       mouse_event->client_pos = mouse_event->local_pos
446                               - toOsg(_region.getMin());
447
448       osg::Vec2f size(_region.width(), _region.height());
449       if( _outset.isValid() )
450       {
451         CSSBorder::Offsets outset =
452           _outset.getAbsOffsets(getTextureDimensions());
453
454         mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
455         size.x() += outset.l + outset.r;
456         size.y() += outset.t + outset.b;
457       }
458
459       // Scale event pos according to canvas view size vs. displayed/screen size
460       mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
461       mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
462       mouse_event->local_pos = mouse_event->client_pos;
463     }
464
465     return handled || src_canvas->handleMouseEvent(mouse_event);
466   }
467
468   //----------------------------------------------------------------------------
469   void Image::childChanged(SGPropertyNode* child)
470   {
471     const std::string& name = child->getNameString();
472
473     if( child->getParent() == _node_src_rect )
474     {
475       _attributes_dirty |= SRC_RECT;
476
477       if(      name == "left" )
478         _src_rect.setLeft( child->getFloatValue() );
479       else if( name == "right" )
480         _src_rect.setRight( child->getFloatValue() );
481       else if( name == "top" )
482         _src_rect.setTop( child->getFloatValue() );
483       else if( name == "bottom" )
484         _src_rect.setBottom( child->getFloatValue() );
485
486       return;
487     }
488     else if( child->getParent() != _node )
489       return;
490
491     if( name == "x" )
492     {
493       _region.setX( child->getFloatValue() );
494       _attributes_dirty |= DEST_SIZE;
495     }
496     else if( name == "y" )
497     {
498       _region.setY( child->getFloatValue() );
499       _attributes_dirty |= DEST_SIZE;
500     }
501     else if( name == "size" )
502     {
503       if( child->getIndex() == 0 )
504         _region.setWidth( child->getFloatValue() );
505       else
506         _region.setHeight( child->getFloatValue() );
507
508       _attributes_dirty |= DEST_SIZE;
509     }
510     else if( name == "file" )
511     {
512       static const std::string CANVAS_PROTOCOL = "canvas://";
513       const std::string& path = child->getStringValue();
514
515       CanvasPtr canvas = _canvas.lock();
516       if( !canvas )
517       {
518         SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
519         return;
520       }
521
522       if( boost::starts_with(path, CANVAS_PROTOCOL) )
523       {
524         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
525         if( !canvas_mgr )
526         {
527           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
528           return;
529         }
530
531         const SGPropertyNode* canvas_node =
532           canvas_mgr->getPropertyRoot()
533                     ->getParent()
534                     ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
535         if( !canvas_node )
536         {
537           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
538           return;
539         }
540
541         // TODO add support for other means of addressing canvases (eg. by
542         // name)
543         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
544         if( !src_canvas )
545         {
546           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
547           return;
548         }
549
550         setSrcCanvas(src_canvas);
551       }
552       else
553       {
554         setImage( canvas->getSystemAdapter()->getImage(path) );
555       }
556     }
557   }
558
559   //----------------------------------------------------------------------------
560   void Image::setupDefaultDimensions()
561   {
562     if( !_src_rect.width() || !_src_rect.height() )
563     {
564       // Show whole image by default
565       _node_src_rect->setBoolValue("normalized", true);
566       _node_src_rect->setFloatValue("right", 1);
567       _node_src_rect->setFloatValue("bottom", 1);
568     }
569
570     if( !_region.width() || !_region.height() )
571     {
572       // Default to image size.
573       // TODO handle showing only part of image?
574       const SGRect<int>& dim = getTextureDimensions();
575       _node->setFloatValue("size[0]", dim.width());
576       _node->setFloatValue("size[1]", dim.height());
577     }
578   }
579
580   //----------------------------------------------------------------------------
581   SGRect<int> Image::getTextureDimensions() const
582   {
583     CanvasPtr canvas = _src_canvas.lock();
584     SGRect<int> dim(0,0);
585
586     // Use canvas/image dimensions rather than texture dimensions, as they could
587     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
588     if( canvas )
589     {
590       dim.setRight( canvas->getViewWidth() );
591       dim.setBottom( canvas->getViewHeight() );
592     }
593     else if( _texture )
594     {
595       osg::Image* img = _texture->getImage();
596
597       if( img )
598       {
599         dim.setRight( img->s() );
600         dim.setBottom( img->t() );
601       }
602     }
603
604     return dim;
605   }
606
607   //----------------------------------------------------------------------------
608   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
609   {
610     int i = index * 4;
611     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
612     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
613     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
614     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
615   }
616
617   //----------------------------------------------------------------------------
618   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
619   {
620     int i = index * 4;
621     (*_texCoords)[i + 0].set(tl.x(), tl.y());
622     (*_texCoords)[i + 1].set(br.x(), tl.y());
623     (*_texCoords)[i + 2].set(br.x(), br.y());
624     (*_texCoords)[i + 3].set(tl.x(), br.y());
625   }
626
627 } // namespace canvas
628 } // namespace simgear