]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/elements/text.cxx
Canvas: Forward mouse events to elements.
[flightgear.git] / src / Canvas / elements / text.cxx
1 // A text on the canvas
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 #include "text.hxx"
20 #include <Canvas/property_helper.hxx>
21
22 #include <Main/globals.hxx>
23 #include <Main/fg_props.hxx>
24
25 #include <osgText/Text>
26
27 namespace canvas
28 {
29   class Text::TextOSG:
30     public osgText::Text
31   {
32     public:
33       osg::Vec2 handleHit(float x, float y);
34
35       virtual osg::BoundingBox computeBound() const;
36   };
37
38   //----------------------------------------------------------------------------
39   osg::Vec2 Text::TextOSG::handleHit(float x, float y)
40   {
41     float line_height = _characterHeight + _lineSpacing;
42
43     // TODO check with align other than TOP
44     float first_line_y = -0.5 * _lineSpacing;//_offset.y() - _characterHeight;
45     size_t line = std::max<int>(0, (y - first_line_y) / line_height);
46
47     if( _textureGlyphQuadMap.empty() )
48       return osg::Vec2(-1, -1);
49
50     // TODO check when it can be larger
51     assert( _textureGlyphQuadMap.size() == 1 );
52
53     const GlyphQuads& glyphquad = _textureGlyphQuadMap.begin()->second;
54     const GlyphQuads::Glyphs& glyphs = glyphquad._glyphs;
55     const GlyphQuads::Coords2& coords = glyphquad._coords;
56     const GlyphQuads::LineNumbers& line_numbers = glyphquad._lineNumbers;
57
58     const float HIT_FRACTION = 0.6;
59     const float character_width = getCharacterHeight()
60                                 * getCharacterAspectRatio();
61
62     y = (line + 0.5) * line_height;
63
64     bool line_found = false;
65     for(size_t i = 0; i < line_numbers.size(); ++i)
66     {
67       if( line_numbers[i] != line )
68       {
69         if( !line_found )
70         {
71           if( line_numbers[i] < line )
72             // Wait for the correct line...
73             continue;
74
75           // We have already passed the correct line -> It's empty...
76           return osg::Vec2(0, y);
77         }
78
79         // Next line and not returned -> not before any character
80         // -> return position after last character of line
81         return osg::Vec2(coords[(i - 1) * 4 + 2].x(), y);
82       }
83
84       line_found = true;
85
86       // Get threshold for mouse x position for setting cursor before or after
87       // current character
88       float threshold = coords[i * 4].x()
89                       + HIT_FRACTION * glyphs[i]->getHorizontalAdvance()
90                                      * character_width;
91
92       if( x <= threshold )
93       {
94         if( i == 0 || line_numbers[i - 1] != line )
95           // first character of line
96           x = coords[i * 4].x();
97         else if( coords[(i - 1) * 4].x() == coords[(i - 1) * 4 + 2].x() )
98           // If previous character width is zero set to begin of next character
99           // (Happens eg. with spaces)
100           x = coords[i * 4].x();
101         else
102           // position at center between characters
103           x = 0.5 * (coords[(i - 1) * 4 + 2].x() + coords[i * 4].x());
104
105         return osg::Vec2(x, y);
106       }
107     }
108
109     // Nothing found -> return position after last character
110     return osg::Vec2
111     (
112       coords.back().x(),
113       (_lineCount - 0.5) * line_height
114     );
115   }
116
117   //----------------------------------------------------------------------------
118   osg::BoundingBox Text::TextOSG::computeBound() const
119   {
120     osg::BoundingBox bb = osgText::Text::computeBound();
121     if( !bb.valid() )
122       return bb;
123
124     // TODO bounding box still doesn't seem always right (eg. with center
125     //      horizontal alignment not completely accurate)
126     bb._min.y() += _offset.y();
127     bb._max.y() += _offset.y();
128
129     return bb;
130   }
131
132   //----------------------------------------------------------------------------
133   Text::Text(SGPropertyNode_ptr node):
134     Element(node, COLOR | COLOR_FILL | BOUNDING_BOX),
135     _text( new Text::TextOSG() ),
136     _font_size( 0 ),
137     _font_aspect( 0 )
138   {
139     setDrawable(_text);
140     _text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
141     _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
142     _text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
143
144     _font_size = getChildDefault<float>(_node, "character-size", 32);
145     _font_aspect = getChildDefault<float>(_node, "character-aspect-ratio", 1);
146   }
147
148   //----------------------------------------------------------------------------
149   Text::~Text()
150   {
151
152   }
153
154   //----------------------------------------------------------------------------
155   void Text::update(double dt)
156   {
157     Element::update(dt);
158
159     if( _attributes_dirty & FONT_SIZE )
160     {
161       _text->setCharacterSize
162       (
163         _font_size->getFloatValue(),
164         _font_aspect->getFloatValue()
165       );
166
167       _attributes_dirty &= ~FONT_SIZE;
168     }
169   }
170
171   //----------------------------------------------------------------------------
172   void Text::setFont(const char* name)
173   {
174     _text->setFont( getFont(name) );
175   }
176
177   //----------------------------------------------------------------------------
178   void Text::setAlignment(const char* align)
179   {
180     const std::string align_string(align);
181     if( 0 ) return;
182 #define ENUM_MAPPING(enum_val, string_val) \
183     else if( align_string == string_val )\
184       _text->setAlignment( osgText::Text::enum_val );
185 #include "text-alignment.hxx"
186 #undef ENUM_MAPPING
187     else
188     {
189       if( !align_string.empty() )
190         SG_LOG
191         (
192           SG_GENERAL,
193           SG_WARN,
194           "canvas::Text: unknown alignment '" << align_string << "'"
195         );
196       _text->setAlignment(osgText::Text::LEFT_BASE_LINE);
197     }
198   }
199
200   //----------------------------------------------------------------------------
201 #if 0
202   const char* Text::getAlignment() const
203   {
204     switch( _text->getAlignment() )
205     {
206 #define ENUM_MAPPING(enum_val, string_val) \
207       case osgText::Text::enum_val:\
208         return string_val;
209 #include "text-alignment.hxx"
210 #undef ENUM_MAPPING
211       default:
212         return "unknown";
213     }
214   }
215 #endif
216   //----------------------------------------------------------------------------
217   void Text::childChanged(SGPropertyNode* child)
218   {
219     const std::string& name = child->getNameString();
220
221     if( name == "hit-y" )
222       handleHit
223       (
224         _node->getFloatValue("hit-x"),
225         _node->getFloatValue("hit-y")
226       );
227     else if( _font_size == child || _font_aspect == child )
228       _attributes_dirty |= FONT_SIZE;
229     else if( name == "text" )
230       _text->setText
231       (
232         osgText::String( child->getStringValue(),
233                          osgText::String::ENCODING_UTF8 )
234       );
235     else if( name == "padding" )
236       _text->setBoundingBoxMargin( child->getFloatValue() );
237     else if( name == "draw-mode" )
238       //  TEXT              = 1 default
239       //  BOUNDINGBOX       = 2
240       //  FILLEDBOUNDINGBOX = 4
241       //  ALIGNMENT         = 8
242       _text->setDrawMode( child->getIntValue() );
243     else if( name == "max-width" )
244       _text->setMaximumWidth( child->getFloatValue() );
245     else if( name == "font" )
246       setFont( child->getStringValue() );
247     else if( name == "alignment" )
248       setAlignment( child->getStringValue() );
249   }
250
251   //----------------------------------------------------------------------------
252   void Text::colorChanged(const osg::Vec4& color)
253   {
254     _text->setColor(color);
255   }
256
257   //----------------------------------------------------------------------------
258   void Text::colorFillChanged(const osg::Vec4& color)
259   {
260     _text->setBoundingBoxColor(color);
261   }
262
263   //----------------------------------------------------------------------------
264   void Text::handleHit(float x, float y)
265   {
266     const osg::Vec2& pos = _text->handleHit(x, y);
267     _node->setFloatValue("cursor-x", pos.x());
268     _node->setFloatValue("cursor-y", pos.y());
269   }
270
271   //----------------------------------------------------------------------------
272   Text::font_ptr Text::getFont(const std::string& name)
273   {
274     SGPath path = globals->resolve_ressource_path("Fonts/" + name);
275     if( path.isNull() )
276     {
277       SG_LOG
278       (
279         SG_GL,
280         SG_ALERT,
281         "canvas::Text: No such font: " << name
282       );
283       return font_ptr();
284     }
285
286     SG_LOG
287     (
288       SG_GL,
289       SG_INFO,
290       "canvas::Text: using font file " << path.str()
291     );
292
293     font_ptr font = osgText::readFontFile(path.c_str());
294     if( !font )
295       SG_LOG
296       (
297         SG_GL,
298         SG_ALERT,
299         "canvas::Text: Failed to open font file " << path.c_str()
300       );
301
302     return font;
303   }
304
305 } // namespace canvas