]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasText.cxx
CanvasGroup: Fix handling z-index while moving child to front.
[simgear.git] / simgear / canvas / elements / CanvasText.cxx
1 // A text 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 "CanvasText.hxx"
20 #include <simgear/canvas/Canvas.hxx>
21 #include <simgear/canvas/CanvasSystemAdapter.hxx>
22 #include <simgear/scene/util/parse_color.hxx>
23 #include <simgear/structure/OSGVersion.hxx>
24 #include <osgText/Text>
25
26 namespace simgear
27 {
28 namespace canvas
29 {
30   class Text::TextOSG:
31     public osgText::Text
32   {
33     public:
34
35       TextOSG(canvas::Text* text);
36
37       void setCharacterAspect(float aspect);
38       void setFill(const std::string& fill);
39       void setBackgroundColor(const std::string& fill);
40
41       osg::Vec2 handleHit(const osg::Vec2f& pos);
42
43       virtual osg::BoundingBox computeBound() const;
44
45     protected:
46
47       canvas::Text *_text_element;
48
49       virtual void computePositions(unsigned int contextID) const;
50   };
51
52   //----------------------------------------------------------------------------
53   Text::TextOSG::TextOSG(canvas::Text* text):
54     _text_element(text)
55   {
56
57   }
58
59   //----------------------------------------------------------------------------
60   void Text::TextOSG::setCharacterAspect(float aspect)
61   {
62     setCharacterSize(getCharacterHeight(), aspect);
63   }
64
65   //----------------------------------------------------------------------------
66   void Text::TextOSG::setFill(const std::string& fill)
67   {
68 //    if( fill == "none" )
69 //      TODO No text
70 //    else
71     osg::Vec4 color;
72     if( parseColor(fill, color) )
73       setColor( color );
74   }
75
76   //----------------------------------------------------------------------------
77   void Text::TextOSG::setBackgroundColor(const std::string& fill)
78   {
79     osg::Vec4 color;
80     if( parseColor(fill, color) )
81       setBoundingBoxColor( color );
82   }
83
84   //----------------------------------------------------------------------------
85   osg::Vec2 Text::TextOSG::handleHit(const osg::Vec2f& pos)
86   {
87     float line_height = _characterHeight + _lineSpacing;
88
89     // TODO check with align other than TOP
90     float first_line_y = -0.5 * _lineSpacing;//_offset.y() - _characterHeight;
91     size_t line = std::max<int>(0, (pos.y() - first_line_y) / line_height);
92
93     if( _textureGlyphQuadMap.empty() )
94       return osg::Vec2(-1, -1);
95
96     // TODO check when it can be larger
97     assert( _textureGlyphQuadMap.size() == 1 );
98
99     const GlyphQuads& glyphquad = _textureGlyphQuadMap.begin()->second;
100     const GlyphQuads::Glyphs& glyphs = glyphquad._glyphs;
101     const GlyphQuads::Coords2& coords = glyphquad._coords;
102     const GlyphQuads::LineNumbers& line_numbers = glyphquad._lineNumbers;
103
104     const float HIT_FRACTION = 0.6;
105     const float character_width = getCharacterHeight()
106                                 * getCharacterAspectRatio();
107
108     float y = (line + 0.5) * line_height;
109
110     bool line_found = false;
111     for(size_t i = 0; i < line_numbers.size(); ++i)
112     {
113       if( line_numbers[i] != line )
114       {
115         if( !line_found )
116         {
117           if( line_numbers[i] < line )
118             // Wait for the correct line...
119             continue;
120
121           // We have already passed the correct line -> It's empty...
122           return osg::Vec2(0, y);
123         }
124
125         // Next line and not returned -> not before any character
126         // -> return position after last character of line
127         return osg::Vec2(coords[(i - 1) * 4 + 2].x(), y);
128       }
129
130       line_found = true;
131
132       // Get threshold for mouse x position for setting cursor before or after
133       // current character
134       float threshold = coords[i * 4].x()
135                       + HIT_FRACTION * glyphs[i]->getHorizontalAdvance()
136                                      * character_width;
137
138       if( pos.x() <= threshold )
139       {
140         osg::Vec2 hit(0, y);
141         if( i == 0 || line_numbers[i - 1] != line )
142           // first character of line
143           hit.x() = coords[i * 4].x();
144         else if( coords[(i - 1) * 4].x() == coords[(i - 1) * 4 + 2].x() )
145           // If previous character width is zero set to begin of next character
146           // (Happens eg. with spaces)
147           hit.x() = coords[i * 4].x();
148         else
149           // position at center between characters
150           hit.x() = 0.5 * (coords[(i - 1) * 4 + 2].x() + coords[i * 4].x());
151
152         return hit;
153       }
154     }
155
156     // Nothing found -> return position after last character
157     return osg::Vec2
158     (
159       coords.back().x(),
160       (_lineCount - 0.5) * line_height
161     );
162   }
163
164   //----------------------------------------------------------------------------
165   osg::BoundingBox Text::TextOSG::computeBound() const
166   {
167     osg::BoundingBox bb = osgText::Text::computeBound();
168     if( !bb.valid() )
169       return bb;
170
171 #if SG_OSG_VERSION_LESS_THAN(3,1,0)
172     // TODO bounding box still doesn't seem always right (eg. with center
173     //      horizontal alignment not completely accurate)
174     bb._min.y() += _offset.y();
175     bb._max.y() += _offset.y();
176 #endif
177
178     _text_element->setBoundingBox(bb);
179
180     return bb;
181   }
182
183   //----------------------------------------------------------------------------
184   void Text::TextOSG::computePositions(unsigned int contextID) const
185   {
186     if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
187       return osgText::Text::computePositions(contextID);
188
189     // TODO check when it can be larger
190     assert( _textureGlyphQuadMap.size() == 1 );
191
192     const GlyphQuads& quads = _textureGlyphQuadMap.begin()->second;
193     const GlyphQuads::Glyphs& glyphs = quads._glyphs;
194     const GlyphQuads::Coords2& coords = quads._coords;
195     const GlyphQuads::LineNumbers& line_numbers = quads._lineNumbers;
196
197     float wr = _characterHeight / getCharacterAspectRatio();
198
199     size_t cur_line = static_cast<size_t>(-1);
200     for(size_t i = 0; i < glyphs.size(); ++i)
201     {
202       // Check horizontal offsets
203
204       bool first_char = cur_line != line_numbers[i];
205       cur_line = line_numbers[i];
206
207       bool last_char = (i + 1 == glyphs.size())
208                     || (cur_line != line_numbers[i + 1]);
209
210       if( first_char || last_char )
211       {
212         // From osg/src/osgText/Text.cpp:
213         //
214         // osg::Vec2 upLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
215         // osg::Vec2 lowLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
216         // osg::Vec2 lowRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
217         // osg::Vec2 upRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
218
219         float left = coords[i * 4].x(),
220               right = coords[i * 4 + 2].x(),
221               width = glyphs[i]->getWidth() * wr;
222
223         // (local + width + fHoriz) - (local - fHoriz) = width + 2*fHoriz | -width
224         float margin = 0.5f * (right - left - width),
225               cursor_x = left + margin
226                        - glyphs[i]->getHorizontalBearing().x() * wr;
227
228         if( first_char )
229         {
230           if( cur_line == 0 || cursor_x < _textBB._min.x() )
231             _textBB._min.x() = cursor_x;
232         }
233
234         if( last_char )
235         {
236           float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
237
238           if( cur_line == 0 || cursor_w > _textBB._max.x() )
239             _textBB._max.x() = cursor_w;
240         }
241       }
242     }
243
244     return osgText::Text::computePositions(contextID);
245   }
246
247   //----------------------------------------------------------------------------
248   Text::Text( const CanvasWeakPtr& canvas,
249               const SGPropertyNode_ptr& node,
250               const Style& parent_style,
251               Element* parent ):
252     Element(canvas, node, parent_style, parent),
253     _text( new Text::TextOSG(this) )
254   {
255     setDrawable(_text);
256     _text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
257     _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
258     _text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
259
260     if( !isInit<Text>() )
261     {
262       osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
263
264       addStyle("fill", "color", &TextOSG::setFill, text);
265       addStyle("background", "color", &TextOSG::setBackgroundColor, text);
266       addStyle("character-size",
267                "numeric",
268                static_cast<
269                  void (TextOSG::*)(float)
270                > (&TextOSG::setCharacterSize),
271                text);
272       addStyle("character-aspect-ratio",
273                "numeric",
274                &TextOSG::setCharacterAspect, text);
275       addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
276       //  TEXT              = 1 default
277       //  BOUNDINGBOX       = 2
278       //  FILLEDBOUNDINGBOX = 4
279       //  ALIGNMENT         = 8
280       addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
281       addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
282       addStyle("font", "", &Text::setFont);
283       addStyle("alignment", "", &Text::setAlignment);
284       addStyle("text", "", &Text::setText);
285     }
286
287     setupStyle();
288   }
289
290   //----------------------------------------------------------------------------
291   Text::~Text()
292   {
293
294   }
295
296   //----------------------------------------------------------------------------
297   void Text::setText(const char* text)
298   {
299     _text->setText(text, osgText::String::ENCODING_UTF8);
300   }
301
302   //----------------------------------------------------------------------------
303   void Text::setFont(const char* name)
304   {
305     _text->setFont( _canvas.lock()->getSystemAdapter()->getFont(name) );
306   }
307
308   //----------------------------------------------------------------------------
309   void Text::setAlignment(const char* align)
310   {
311     const std::string align_string(align);
312     if( 0 ) return;
313 #define ENUM_MAPPING(enum_val, string_val) \
314     else if( align_string == string_val )\
315       _text->setAlignment( osgText::Text::enum_val );
316 #include "text-alignment.hxx"
317 #undef ENUM_MAPPING
318     else
319     {
320       if( !align_string.empty() )
321         SG_LOG
322         (
323           SG_GENERAL,
324           SG_WARN,
325           "canvas::Text: unknown alignment '" << align_string << "'"
326         );
327       _text->setAlignment(osgText::Text::LEFT_BASE_LINE);
328     }
329   }
330
331   //----------------------------------------------------------------------------
332 #if 0
333   const char* Text::getAlignment() const
334   {
335     switch( _text->getAlignment() )
336     {
337 #define ENUM_MAPPING(enum_val, string_val) \
338       case osgText::Text::enum_val:\
339         return string_val;
340 #include "text-alignment.hxx"
341 #undef ENUM_MAPPING
342       default:
343         return "unknown";
344     }
345   }
346 #endif
347
348   //----------------------------------------------------------------------------
349   osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const
350   {
351     return _text->handleHit(pos);
352   }
353
354 } // namespace canvas
355 } // namespace simgear