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