]> git.tdb.fi Git - libs/gltk.git/blob - source/text.cpp
Add a VSlider to multiline Entries
[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         float font_size = style->get_font()->get_default_size();
46         for(vector<Line>::iterator i=lines.begin(); i!=lines.end(); ++i)
47                 i->width = static_cast<unsigned>(style->get_font()->get_string_width(text.substr(i->start, i->length))*font_size);
48 }
49
50 unsigned Text::get_width() const
51 {
52         unsigned width = 0;
53         for(vector<Line>::const_iterator i=lines.begin(); i!=lines.end(); ++i)
54                 width = max(width, i->width);
55         return width;
56 }
57
58 unsigned Text::get_height() const
59 {
60         const GL::Font *font = style->get_font();
61         float font_size = font->get_default_size();
62         unsigned line_height = static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
63         unsigned line_spacing = line_height*6/5;
64         return line_height+(lines.size()-1)*line_spacing;
65 }
66
67 void Text::set(const string &t)
68 {
69         text = t;
70         find_lines();
71 }
72
73 void Text::erase(unsigned pos, unsigned len)
74 {
75         text.erase(pos, len);
76
77         vector<Line>::iterator i;
78         for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
79
80         if(pos+len>i->start+i->length)
81                 find_lines();
82         else
83         {
84                 i->length -= len;
85
86                 for(++i; i!=lines.end(); ++i)
87                         i->start -= len;
88         }
89 }
90
91 void Text::insert(unsigned pos, const string &s)
92 {
93         text.insert(pos, s);
94
95         if(s.find('\n')!=string::npos)
96                 find_lines();
97         else
98         {
99                 vector<Line>::iterator i;
100                 for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
101
102                 i->length += s.size();
103
104                 for(++i; i!=lines.end(); ++i)
105                         i->start += s.size();
106         }
107 }
108
109 unsigned Text::get_line_length(unsigned i) const
110 {
111         if(i>=lines.size())
112                 throw InvalidParameterValue("Invalid line number");
113         return lines[i].length;
114 }
115
116 void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
117 {
118         if(lines.empty())
119         {
120                 row = 0;
121                 col = 0;
122                 return;
123         }
124
125         for(unsigned i=0; i<lines.size(); ++i)
126                 if(offs>=lines[i].start && offs<=lines[i].start+lines[i].length)
127                 {
128                         row = i;
129                         col = offs-lines[i].start;
130                         return;
131                 }
132 }
133
134 unsigned Text::coords_to_offset(unsigned row, unsigned col) const
135 {
136         if(row>=lines.size())
137                 return text.size();
138
139         return lines[row].start+min(col, lines[row].length);
140 }
141
142 Geometry Text::coords_to_geometry(const Part &part, const Geometry &parent, unsigned first_row, unsigned row, unsigned col) const
143 {
144         if(row>=lines.size())
145                 row = lines.size()-1;
146         const Line &line = lines[row];
147         if(col>line.length)
148                 col = line.length;
149
150         CoordsToGeomData data;
151         data.row = row;
152         data.col = col;
153
154         process_lines<CoordsToGeomData, &Text::coords_to_geom_line>(part, parent, first_row, data);
155
156         return data.result;
157 }
158
159 void Text::render(const Part &part, const Geometry &parent, unsigned first_row) const
160 {
161         if(lines.empty())
162                 return;
163
164         const GL::Color &color = style->get_font_color();
165         GL::Immediate imm((GL::COLOR4_UBYTE, GL::TEXCOORD2, GL::VERTEX2));
166         imm.color(color.r, color.g, color.b);
167
168         RenderData data;
169         data.bld = &imm;
170
171         process_lines<RenderData, &Text::render_line>(part, parent, first_row, data);
172 }
173
174 Text &Text::operator=(const string &t)
175 {
176         set(t);
177         return *this;
178 }
179
180 void Text::find_lines()
181 {
182         lines.clear();
183         float font_size = style->get_font()->get_default_size();
184         string::size_type start = 0;
185         while(1)
186         {
187                 string::size_type newline = text.find('\n', start);
188
189                 Line line;
190                 line.start = start;
191                 line.length = (newline==string::npos ? text.size() : newline)-start;
192                 line.width = static_cast<unsigned>(style->get_font()->get_string_width(text.substr(line.start, line.length))*font_size);
193                 lines.push_back(line);
194
195                 if(newline==string::npos)
196                         break;
197                 start = newline+1;
198         }
199 }
200
201 template<typename T, void (Text::*func)(unsigned, const Geometry &, T &) const>
202 void Text::process_lines(const Part &part, const Geometry &parent, unsigned first_row, T &data) const
203 {
204         const GL::Font *font = style->get_font();
205         float font_size = font->get_default_size();
206         unsigned line_height = static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
207         unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
208         unsigned height = line_height+(lines.size()-1)*line_spacing;
209         int y_offset = static_cast<int>(-font->get_descent()*font_size);
210
211         const Sides &margin = part.get_margin();
212         unsigned n_lines = min(lines.size(), (parent.h-margin.top-margin.bottom)/line_spacing);
213         first_row = min(first_row, lines.size()-n_lines);
214
215         for(unsigned i=0; i<n_lines; ++i)
216         {
217                 const Line &line = lines[first_row+i];
218
219                 Geometry rgeom;
220                 rgeom.w = line.width;
221                 rgeom.h = height;
222                 rgeom.y = (n_lines-1-i)*line_spacing+y_offset;
223                 part.get_alignment().apply(rgeom, parent, part.get_margin());
224
225                 (this->*func)(first_row+i, rgeom, data);
226         }
227 }
228
229 void Text::render_line(unsigned i, const Geometry &rgeom, RenderData &data) const
230 {
231         const Line &line = lines[i];
232         const GL::Font *font = style->get_font();
233
234         GL::PushMatrix _pushm;
235         GL::translate(rgeom.x, rgeom.y, 0);
236         GL::scale_uniform(font->get_default_size());
237
238         font->draw_string(text.substr(line.start, line.length), *data.bld);
239 }
240
241 void Text::coords_to_geom_line(unsigned i, const Geometry &rgeom, CoordsToGeomData &data) const
242 {
243         if(i==data.row)
244         {
245                 const Line &line = lines[i];
246                 const GL::Font *font = style->get_font();
247
248                 data.result = rgeom;
249                 data.result.x += static_cast<unsigned>(font->get_string_width(text.substr(line.start, data.col))*font->get_default_size());
250         }
251 }
252
253 } // namespace GLtk
254 } // namespace Msp