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