]> git.tdb.fi Git - libs/gltk.git/blob - source/text.cpp
Store the Resources reference only in Root widget
[libs/gltk.git] / source / text.cpp
1 /* $Id$
2
3 This file is part of libmspgltk
4 Copyright © 2009-2010  Mikko Rasa, Mikkosoft Productions
5 Distributed under the LGPL
6 */
7
8 #include <msp/gl/immediate.h>
9 #include <msp/gl/matrix.h>
10 #include "style.h"
11 #include "text.h"
12
13 using namespace std;
14
15 namespace Msp {
16 namespace GLtk {
17
18 struct Text::RenderData
19 {
20         GL::PrimitiveBuilder *bld;
21 };
22
23 struct Text::CoordsToGeomData
24 {
25         unsigned row;
26         unsigned col;
27         Geometry result;
28 };
29
30
31 Text::Text():
32         style(0)
33 { }
34
35 Text::Text(const Style &s, const string &t):
36         style(&s)
37 {
38         set(t);
39 }
40
41 void Text::set_style(const Style *s)
42 {
43         style = s;
44
45         if(style)
46         {
47                 float font_size = style->get_font()->get_default_size();
48                 for(vector<Line>::iterator i=lines.begin(); i!=lines.end(); ++i)
49                         i->width = static_cast<unsigned>(style->get_font()->get_string_width(text.substr(i->start, i->length))*font_size);
50         }
51 }
52
53 unsigned Text::get_width() const
54 {
55         unsigned width = 0;
56         for(vector<Line>::const_iterator i=lines.begin(); i!=lines.end(); ++i)
57                 width = max(width, i->width);
58         return width;
59 }
60
61 unsigned Text::get_height() const
62 {
63         if(!style)
64                 return lines.size();
65
66         const GL::Font *font = style->get_font();
67         float font_size = font->get_default_size();
68         unsigned line_height = static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
69         unsigned line_spacing = line_height*6/5;
70         return line_height+(lines.size()-1)*line_spacing;
71 }
72
73 void Text::set(const string &t)
74 {
75         text = t;
76         find_lines();
77 }
78
79 void Text::erase(unsigned pos, unsigned len)
80 {
81         text.erase(pos, len);
82
83         vector<Line>::iterator i;
84         for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
85
86         if(pos+len>i->start+i->length)
87                 find_lines();
88         else
89         {
90                 i->length -= len;
91
92                 for(++i; i!=lines.end(); ++i)
93                         i->start -= len;
94         }
95 }
96
97 void Text::insert(unsigned pos, const string &s)
98 {
99         text.insert(pos, s);
100
101         if(s.find('\n')!=string::npos)
102                 find_lines();
103         else
104         {
105                 vector<Line>::iterator i;
106                 for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
107
108                 i->length += s.size();
109
110                 for(++i; i!=lines.end(); ++i)
111                         i->start += s.size();
112         }
113 }
114
115 unsigned Text::get_line_length(unsigned i) const
116 {
117         if(i>=lines.size())
118                 throw InvalidParameterValue("Invalid line number");
119         return lines[i].length;
120 }
121
122 void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
123 {
124         if(lines.empty())
125         {
126                 row = 0;
127                 col = 0;
128                 return;
129         }
130
131         for(unsigned i=0; i<lines.size(); ++i)
132                 if(offs>=lines[i].start && offs<=lines[i].start+lines[i].length)
133                 {
134                         row = i;
135                         col = offs-lines[i].start;
136                         return;
137                 }
138 }
139
140 unsigned Text::coords_to_offset(unsigned row, unsigned col) const
141 {
142         if(row>=lines.size())
143                 return text.size();
144
145         return lines[row].start+min(col, lines[row].length);
146 }
147
148 Geometry Text::coords_to_geometry(const Part &part, const Geometry &parent, unsigned first_row, unsigned row, unsigned col) const
149 {
150         if(row>=lines.size())
151                 row = lines.size()-1;
152         const Line &line = lines[row];
153         if(col>line.length)
154                 col = line.length;
155
156         CoordsToGeomData data;
157         data.row = row;
158         data.col = col;
159
160         process_lines<CoordsToGeomData, &Text::coords_to_geom_line>(part, parent, first_row, data);
161
162         return data.result;
163 }
164
165 void Text::render(const Part &part, const Geometry &parent, unsigned first_row) const
166 {
167         if(!style || lines.empty())
168                 return;
169
170         const GL::Color &color = style->get_font_color();
171         GL::Immediate imm((GL::COLOR4_UBYTE, GL::TEXCOORD2, GL::VERTEX2));
172         imm.color(color.r, color.g, color.b);
173
174         RenderData data;
175         data.bld = &imm;
176
177         process_lines<RenderData, &Text::render_line>(part, parent, first_row, data);
178 }
179
180 Text &Text::operator=(const string &t)
181 {
182         set(t);
183         return *this;
184 }
185
186 void Text::find_lines()
187 {
188         lines.clear();
189         float font_size = (style ? style->get_font()->get_default_size() : 1);
190         string::size_type start = 0;
191         while(1)
192         {
193                 string::size_type newline = text.find('\n', start);
194
195                 Line line;
196                 line.start = start;
197                 line.length = (newline==string::npos ? text.size() : newline)-start;
198                 line.width = line.length;
199                 if(style)
200                 {
201                         string str = text.substr(line.start, line.length);
202                         line.width = static_cast<unsigned>(style->get_font()->get_string_width(str)*font_size);
203                 }
204                 lines.push_back(line);
205
206                 if(newline==string::npos)
207                         break;
208                 start = newline+1;
209         }
210 }
211
212 template<typename T, void (Text::*func)(unsigned, const Geometry &, T &) const>
213 void Text::process_lines(const Part &part, const Geometry &parent, unsigned first_row, T &data) const
214 {
215         if(!style)
216                 return;
217
218         const GL::Font *font = style->get_font();
219         float font_size = font->get_default_size();
220         unsigned line_height = static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
221         unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
222         unsigned height = line_height+(lines.size()-1)*line_spacing;
223         int y_offset = static_cast<int>(-font->get_descent()*font_size);
224
225         const Sides &margin = part.get_margin();
226         unsigned n_lines = min(lines.size(), max((parent.h-margin.top-margin.bottom)/line_spacing, 1U));
227         first_row = min(first_row, lines.size()-n_lines);
228
229         for(unsigned i=0; i<n_lines; ++i)
230         {
231                 const Line &line = lines[first_row+i];
232
233                 Geometry rgeom;
234                 rgeom.w = line.width;
235                 rgeom.h = height;
236                 rgeom.y = (n_lines-1-i)*line_spacing+y_offset;
237                 part.get_alignment().apply(rgeom, parent, part.get_margin());
238
239                 (this->*func)(first_row+i, rgeom, data);
240         }
241 }
242
243 void Text::render_line(unsigned i, const Geometry &rgeom, RenderData &data) const
244 {
245         const Line &line = lines[i];
246         const GL::Font *font = style->get_font();
247
248         GL::PushMatrix _pushm;
249         GL::translate(rgeom.x, rgeom.y, 0);
250         GL::scale_uniform(font->get_default_size());
251
252         font->draw_string(text.substr(line.start, line.length), *data.bld);
253 }
254
255 void Text::coords_to_geom_line(unsigned i, const Geometry &rgeom, CoordsToGeomData &data) const
256 {
257         if(i==data.row)
258         {
259                 const Line &line = lines[i];
260                 const GL::Font *font = style->get_font();
261
262                 data.result = rgeom;
263                 data.result.x += static_cast<unsigned>(font->get_string_width(text.substr(line.start, data.col))*font->get_default_size());
264         }
265 }
266
267 } // namespace GLtk
268 } // namespace Msp