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