]> git.tdb.fi Git - libs/gltk.git/blob - source/entry.cpp
Fix some more inefficient autosizes
[libs/gltk.git] / source / entry.cpp
1 #include <msp/gl/matrix.h>
2 #include <msp/gl/meshbuilder.h>
3 #include <msp/gl/texture.h>
4 #include <msp/input/keys.h>
5 #include "entry.h"
6 #include "graphic.h"
7 #include "part.h"
8 #include "style.h"
9 #include "vslider.h"
10
11 using namespace std;
12
13 namespace Msp {
14 namespace GLtk {
15
16 Entry::Entry(const string &t):
17         text(),
18         multiline(false),
19         edit_width(10),
20         edit_height(1),
21         edit_pos(0),
22         first_row(0),
23         visible_rows(1),
24         text_part(0),
25         slider(0),
26         got_key_press(false)
27 {
28         set_text(t);
29 }
30
31 void Entry::autosize_special(const Part &part, Geometry &ageom) const
32 {
33         if(part.get_name()=="text")
34         {
35                 const Sides &margin = part.get_margin();
36                 const GL::Font &font = style->get_font();
37                 unsigned en_width = static_cast<unsigned>(font.get_string_width("n")*style->get_font_size());
38                 ageom.w = max(ageom.w, edit_width*en_width+margin.left+margin.right);
39
40                 unsigned line_height = static_cast<unsigned>((font.get_ascent()-font.get_descent())*style->get_font_size());
41                 if(multiline)
42                 {
43                         unsigned line_spacing = style->get_font_size()*6/5;
44                         ageom.h = max(ageom.h, line_height+line_spacing*(edit_height-1)+margin.top+margin.bottom);
45                 }
46                 else
47                         ageom.h = max(ageom.h, line_height+margin.top+margin.bottom);
48         }
49         else if(part.get_name()=="slider" && multiline)
50         {
51                 Geometry sgeom = part.get_geometry();
52                 if(!sgeom.w || !sgeom.h)
53                 {
54                         Geometry wgeom;
55                         slider->autosize(wgeom);
56                         if(!sgeom.w)
57                                 sgeom.w = wgeom.w;
58                         if(!sgeom.h)
59                                 sgeom.h = wgeom.h;
60                 }
61
62                 const Sides &margin = part.get_margin();
63                 ageom.w = max(ageom.w, sgeom.w+margin.left+margin.right);
64                 ageom.h = max(ageom.h, sgeom.h+margin.top+margin.bottom);
65         }
66 }
67
68 void Entry::set_text(const string &t)
69 {
70         text = t;
71         edit_pos = text.size();
72
73         if(multiline)
74                 check_view_range();
75
76         rebuild();
77 }
78
79 void Entry::set_edit_size(unsigned w, unsigned h)
80 {
81         edit_width = w;
82         edit_height = h;
83         signal_autosize_changed.emit();
84 }
85
86 void Entry::set_multiline(bool m)
87 {
88         multiline = m;
89         if(multiline)
90         {
91                 if(!slider)
92                 {
93                         slider = new VSlider;
94                         add(*slider);
95                         slider->set_step(1);
96                         slider->signal_value_changed.connect(sigc::mem_fun(this, &Entry::slider_value_changed));
97                         reposition_slider();
98                 }
99                 check_view_range();
100         }
101 }
102
103 void Entry::rebuild_special(const Part &part)
104 {
105         if(part.get_name()=="text")
106                 text.build(part, state, geom, first_row, part_cache);
107         else if(part.get_name()=="cursor")
108         {
109                 const Graphic *graphic = part.get_graphic(state);
110                 if(!text_part || !graphic || !graphic->get_texture())
111                         return;
112
113                 unsigned row, col;
114                 text.offset_to_coords(edit_pos, row, col);
115
116                 if(row<first_row || row>=first_row+visible_rows)
117                         return;
118
119                 Geometry rgeom = text.coords_to_geometry(*text_part, geom, first_row, row, col);
120
121                 GL::MeshBuilder bld(part_cache.create_mesh(part, *graphic->get_texture()));
122                 bld.matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
123                 graphic->build(part.get_geometry().w, part.get_geometry().h, bld);
124         }
125         else
126                 Widget::rebuild_special(part);
127 }
128
129 void Entry::render_special(const Part &part, GL::Renderer &renderer) const
130 {
131         if(part.get_name()=="slider" && multiline)
132                 slider->render(renderer);
133 }
134
135 void Entry::key_press(unsigned key, unsigned)
136 {
137         got_key_press = true;
138         if(key==Input::KEY_LEFT)
139         {
140                 if(edit_pos>0)
141                         set_edit_position(edit_pos-1);
142         }
143         else if(key==Input::KEY_RIGHT)
144         {
145                 if(edit_pos<text.size())
146                         set_edit_position(edit_pos+1);
147         }
148         else if(key==Input::KEY_DOWN && multiline)
149         {
150                 unsigned row, col;
151                 text.offset_to_coords(edit_pos, row, col);
152                 set_edit_position(text.coords_to_offset(row+1, col));
153         }
154         else if(key==Input::KEY_UP && multiline)
155         {
156                 unsigned row, col;
157                 text.offset_to_coords(edit_pos, row, col);
158                 set_edit_position(row>0 ? text.coords_to_offset(row-1, col) : 0);
159         }
160         else if(key==Input::KEY_BACKSPACE)
161         {
162                 if(edit_pos>0)
163                 {
164                         text.erase(--edit_pos, 1);
165                         check_view_range();
166                         rebuild();
167                 }
168         }
169         else if(key==Input::KEY_ENTER)
170         {
171                 if(multiline)
172                 {
173                         text.insert(edit_pos++, "\n");
174                         check_view_range();
175                         rebuild();
176                 }
177                 else
178                         signal_enter.emit();
179         }
180 }
181
182 void Entry::character(wchar_t ch)
183 {
184         if(got_key_press && ch>=' ')
185         {
186                 text.insert(edit_pos, StringCodec::encode<StringCodec::Utf8>(StringCodec::ustring(1, ch)));
187                 ++edit_pos;
188                 rebuild();
189         }
190 }
191
192 void Entry::focus_out()
193 {
194         Widget::focus_out();
195         got_key_press = false;
196 }
197
198 void Entry::on_geometry_change()
199 {
200         reposition_slider();
201
202         if(multiline)
203                 check_view_range();
204 }
205
206 void Entry::on_style_change()
207 {
208         text.set_style(style);
209
210         if(!style)
211         {
212                 text_part = 0;
213                 return;
214         }
215
216         text_part = style->get_part("text");
217
218         reposition_slider();
219
220         if(multiline)
221                 check_view_range();
222 }
223
224 void Entry::set_edit_position(unsigned ep)
225 {
226         edit_pos = ep;
227         check_view_range();
228         rebuild();
229 }
230
231 void Entry::reposition_slider()
232 {
233         if(!style || !slider)
234                 return;
235
236         if(const Part *slider_part = style->get_part("slider"))
237         {
238                 Geometry sgeom = slider_part->get_geometry();
239                 if(!sgeom.w || !sgeom.h)
240                 {
241                         Geometry wgeom;
242                         slider->autosize(wgeom);
243                         if(!sgeom.w)
244                                 sgeom.w = wgeom.w;
245                         if(!sgeom.h)
246                                 sgeom.h = wgeom.h;
247                 }
248
249                 slider_part->get_alignment().apply(sgeom, geom, slider_part->get_margin());
250                 slider->set_geometry(sgeom);
251         }
252 }
253
254 void Entry::check_view_range()
255 {
256         if(!multiline || !text_part)
257                 return;
258
259         float font_size = style->get_font_size();
260         unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
261
262         const Sides &margin = text_part->get_margin();
263         visible_rows = max((geom.h-margin.top-margin.bottom)/line_spacing, 1U);
264
265         unsigned row, col;
266         text.offset_to_coords(edit_pos, row, col);
267
268         if(first_row>row)
269                 first_row = row;
270         else if(row>=first_row+visible_rows)
271                 first_row = row+1-visible_rows;
272
273         unsigned scroll = max(text.get_n_lines(), visible_rows)-visible_rows;
274         slider->set_range(0, scroll);
275         slider->set_value(scroll-first_row);
276 }
277
278 void Entry::slider_value_changed(double value)
279 {
280         if(text.get_n_lines()>visible_rows)
281                 first_row = text.get_n_lines()-visible_rows-static_cast<unsigned>(value);
282 }
283
284
285 Entry::Loader::Loader(Entry &e):
286         DataFile::DerivedObjectLoader<Entry, Widget::Loader>(e)
287 {
288         add("edit_size", &Entry::edit_width, &Entry::edit_height);
289         add("multiline", &Loader::multiline);
290 }
291
292 void Entry::Loader::multiline(bool m)
293 {
294         obj.set_multiline(m);
295 }
296
297 } // namespace GLtk
298 } // namespace Msp