1 | #include "gl-app.h" |
2 | |
3 | #include <string.h> |
4 | #include <stdio.h> |
5 | |
6 | static char *find_string_location(char *s, char *e, float w, float x) |
7 | { |
8 | int c; |
9 | while (s < e) |
10 | { |
11 | int n = fz_chartorune(&c, s); |
12 | float cw = ui_measure_character(c); |
13 | if (w + (cw / 2) >= x) |
14 | return s; |
15 | w += cw; |
16 | s += n; |
17 | } |
18 | return e; |
19 | } |
20 | |
21 | static char *find_input_location(struct line *lines, int n, float left, float top, float x, float y) |
22 | { |
23 | int i = 0; |
24 | if (y > top) i = (y - top) / ui.lineheight; |
25 | if (i >= n) i = n - 1; |
26 | return find_string_location(lines[i].a, lines[i].b, left, x); |
27 | } |
28 | |
29 | static inline int myisalnum(char *s) |
30 | { |
31 | int cat, c; |
32 | fz_chartorune(&c, s); |
33 | cat = ucdn_get_general_category(c); |
34 | if (cat >= UCDN_GENERAL_CATEGORY_LL && cat <= UCDN_GENERAL_CATEGORY_LU) |
35 | return 1; |
36 | if (cat >= UCDN_GENERAL_CATEGORY_ND && cat <= UCDN_GENERAL_CATEGORY_NO) |
37 | return 1; |
38 | return 0; |
39 | } |
40 | |
41 | static char *home_line(char *p, char *start) |
42 | { |
43 | while (p > start) |
44 | { |
45 | if (p[-1] == '\n' || p[-1] == '\r') |
46 | return p; |
47 | --p; |
48 | } |
49 | return p; |
50 | } |
51 | |
52 | static char *end_line(char *p, char *end) |
53 | { |
54 | while (p < end) |
55 | { |
56 | if (p[0] == '\n' || p[0] == '\r') |
57 | return p; |
58 | ++p; |
59 | } |
60 | return p; |
61 | } |
62 | |
63 | static char *up_line(char *p, char *start) |
64 | { |
65 | while (p > start) |
66 | { |
67 | --p; |
68 | if (*p == '\n' || *p == '\r') |
69 | return p; |
70 | } |
71 | return p; |
72 | } |
73 | |
74 | static char *down_line(char *p, char *end) |
75 | { |
76 | while (p < end) |
77 | { |
78 | if (*p == '\n' || *p == '\r') |
79 | return p+1; |
80 | ++p; |
81 | } |
82 | return p; |
83 | } |
84 | |
85 | static char *prev_char(char *p, char *start) |
86 | { |
87 | --p; |
88 | while ((*p & 0xC0) == 0x80 && p > start) /* skip middle and final multibytes */ |
89 | --p; |
90 | return p; |
91 | } |
92 | |
93 | static char *next_char(char *p) |
94 | { |
95 | ++p; |
96 | while ((*p & 0xC0) == 0x80) /* skip middle and final multibytes */ |
97 | ++p; |
98 | return p; |
99 | } |
100 | |
101 | static char *prev_word(char *p, char *start) |
102 | { |
103 | while (p > start && !myisalnum(prev_char(p, start))) p = prev_char(p, start); |
104 | while (p > start && myisalnum(prev_char(p, start))) p = prev_char(p, start); |
105 | return p; |
106 | } |
107 | |
108 | static char *next_word(char *p, char *end) |
109 | { |
110 | while (p < end && !myisalnum(p)) p = next_char(p); |
111 | while (p < end && myisalnum(p)) p = next_char(p); |
112 | return p; |
113 | } |
114 | |
115 | static void ui_input_delete_selection(struct input *input) |
116 | { |
117 | char *p = input->p < input->q ? input->p : input->q; |
118 | char *q = input->p > input->q ? input->p : input->q; |
119 | memmove(p, q, input->end - q); |
120 | input->end -= q - p; |
121 | *input->end = 0; |
122 | input->p = input->q = p; |
123 | } |
124 | |
125 | static void ui_input_paste(struct input *input, const char *buf, int n) |
126 | { |
127 | if (input->p != input->q) |
128 | ui_input_delete_selection(input); |
129 | if (input->end + n + 1 < input->text + sizeof(input->text)) |
130 | { |
131 | memmove(input->p + n, input->p, input->end - input->p); |
132 | memmove(input->p, buf, n); |
133 | input->p += n; |
134 | input->end += n; |
135 | *input->end = 0; |
136 | } |
137 | input->q = input->p; |
138 | } |
139 | |
140 | static int ui_input_key(struct input *input, int multiline) |
141 | { |
142 | switch (ui.key) |
143 | { |
144 | case 0: |
145 | return UI_INPUT_NONE; |
146 | case KEY_LEFT: |
147 | if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT) |
148 | { |
149 | input->q = prev_word(input->q, input->text); |
150 | } |
151 | else if (ui.mod == GLUT_ACTIVE_CTRL) |
152 | { |
153 | if (input->p != input->q) |
154 | input->p = input->q = input->p < input->q ? input->p : input->q; |
155 | else |
156 | input->p = input->q = prev_word(input->q, input->text); |
157 | } |
158 | else if (ui.mod == GLUT_ACTIVE_SHIFT) |
159 | { |
160 | if (input->q > input->text) |
161 | input->q = prev_char(input->q, input->text); |
162 | } |
163 | else if (ui.mod == 0) |
164 | { |
165 | if (input->p != input->q) |
166 | input->p = input->q = input->p < input->q ? input->p : input->q; |
167 | else if (input->q > input->text) |
168 | input->p = input->q = prev_char(input->q, input->text); |
169 | } |
170 | break; |
171 | case KEY_RIGHT: |
172 | if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT) |
173 | { |
174 | input->q = next_word(input->q, input->end); |
175 | } |
176 | else if (ui.mod == GLUT_ACTIVE_CTRL) |
177 | { |
178 | if (input->p != input->q) |
179 | input->p = input->q = input->p > input->q ? input->p : input->q; |
180 | else |
181 | input->p = input->q = next_word(input->q, input->end); |
182 | } |
183 | else if (ui.mod == GLUT_ACTIVE_SHIFT) |
184 | { |
185 | if (input->q < input->end) |
186 | input->q = next_char(input->q); |
187 | } |
188 | else if (ui.mod == 0) |
189 | { |
190 | if (input->p != input->q) |
191 | input->p = input->q = input->p > input->q ? input->p : input->q; |
192 | else if (input->q < input->end) |
193 | input->p = input->q = next_char(input->q); |
194 | } |
195 | break; |
196 | case KEY_UP: |
197 | if (ui.mod & GLUT_ACTIVE_SHIFT) |
198 | input->q = up_line(input->q, input->text); |
199 | else |
200 | input->p = input->q = up_line(input->p, input->text); |
201 | break; |
202 | case KEY_DOWN: |
203 | if (ui.mod & GLUT_ACTIVE_SHIFT) |
204 | input->q = down_line(input->q, input->end); |
205 | else |
206 | input->p = input->q = down_line(input->q, input->end); |
207 | break; |
208 | case KEY_HOME: |
209 | if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT) |
210 | input->q = input->text; |
211 | else if (ui.mod == GLUT_ACTIVE_SHIFT) |
212 | input->q = home_line(input->q, input->text); |
213 | else if (ui.mod == GLUT_ACTIVE_CTRL) |
214 | input->p = input->q = input->text; |
215 | else if (ui.mod == 0) |
216 | input->p = input->q = home_line(input->p, input->text); |
217 | break; |
218 | case KEY_END: |
219 | if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT) |
220 | input->q = input->end; |
221 | else if (ui.mod == GLUT_ACTIVE_SHIFT) |
222 | input->q = end_line(input->q, input->end); |
223 | else if (ui.mod == GLUT_ACTIVE_CTRL) |
224 | input->p = input->q = input->end; |
225 | else if (ui.mod == 0) |
226 | input->p = input->q = end_line(input->p, input->end); |
227 | break; |
228 | case KEY_DELETE: |
229 | if (input->p != input->q) |
230 | ui_input_delete_selection(input); |
231 | else if (input->p < input->end) |
232 | { |
233 | char *np = next_char(input->p); |
234 | memmove(input->p, np, input->end - np); |
235 | input->end -= np - input->p; |
236 | *input->end = 0; |
237 | input->q = input->p; |
238 | } |
239 | break; |
240 | case KEY_ESCAPE: |
241 | ui.focus = NULL; |
242 | return UI_INPUT_NONE; |
243 | case KEY_ENTER: |
244 | if (!multiline) |
245 | { |
246 | ui.focus = NULL; |
247 | return UI_INPUT_ACCEPT; |
248 | } |
249 | ui_input_paste(input, "\n" , 1); |
250 | break; |
251 | case KEY_BACKSPACE: |
252 | if (input->p != input->q) |
253 | ui_input_delete_selection(input); |
254 | else if (input->p > input->text) |
255 | { |
256 | char *pp = prev_char(input->p, input->text); |
257 | memmove(pp, input->p, input->end - input->p); |
258 | input->end -= input->p - pp; |
259 | *input->end = 0; |
260 | input->q = input->p = pp; |
261 | } |
262 | break; |
263 | case KEY_CTL_A: |
264 | input->p = input->q = input->text; |
265 | break; |
266 | case KEY_CTL_E: |
267 | input->p = input->q = input->end; |
268 | break; |
269 | case KEY_CTL_W: |
270 | if (input->p != input->q) |
271 | ui_input_delete_selection(input); |
272 | else |
273 | { |
274 | input->p = prev_word(input->p, input->text); |
275 | ui_input_delete_selection(input); |
276 | } |
277 | break; |
278 | case KEY_CTL_U: |
279 | input->p = input->q = input->end = input->text; |
280 | *input->end = 0; |
281 | break; |
282 | case KEY_CTL_C: |
283 | case KEY_CTL_X: |
284 | if (input->p != input->q) |
285 | { |
286 | char buf[sizeof input->text]; |
287 | char *p = input->p < input->q ? input->p : input->q; |
288 | char *q = input->p > input->q ? input->p : input->q; |
289 | memmove(buf, p, q - p); |
290 | buf[q-p] = 0; |
291 | ui_set_clipboard(buf); |
292 | if (ui.key == KEY_CTL_X) |
293 | ui_input_delete_selection(input); |
294 | } |
295 | break; |
296 | case KEY_CTL_V: |
297 | { |
298 | const char *buf = ui_get_clipboard(); |
299 | if (buf) |
300 | ui_input_paste(input, buf, (int)strlen(buf)); |
301 | } |
302 | break; |
303 | default: |
304 | if (ui.key >= 32 && ui.plain) |
305 | { |
306 | int cat = ucdn_get_general_category(ui.key); |
307 | if (ui.key == ' ' || (cat >= UCDN_GENERAL_CATEGORY_LL && cat < UCDN_GENERAL_CATEGORY_ZL)) |
308 | { |
309 | char buf[8]; |
310 | int n = fz_runetochar(buf, ui.key); |
311 | ui_input_paste(input, buf, n); |
312 | } |
313 | } |
314 | break; |
315 | } |
316 | return UI_INPUT_EDIT; |
317 | } |
318 | |
319 | void ui_input_init(struct input *input, const char *text) |
320 | { |
321 | fz_strlcpy(input->text, text, sizeof input->text); |
322 | input->end = input->text + strlen(input->text); |
323 | input->p = input->text; |
324 | input->q = input->end; |
325 | input->scroll = 0; |
326 | } |
327 | |
328 | int ui_input(struct input *input, int width, int height) |
329 | { |
330 | struct line lines[500]; |
331 | fz_irect area; |
332 | float ax, bx; |
333 | int ay, sy; |
334 | char *p, *q; |
335 | int state; |
336 | int i, n; |
337 | |
338 | if (ui.focus == input) |
339 | state = ui_input_key(input, height > 1); |
340 | else |
341 | state = UI_INPUT_NONE; |
342 | |
343 | area = ui_pack(width, ui.lineheight * height + 6); |
344 | ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1); |
345 | area = fz_expand_irect(area, -2); |
346 | |
347 | if (height > 1) |
348 | area.x1 -= ui.lineheight; |
349 | |
350 | n = ui_break_lines(input->text, lines, nelem(lines), area.x1-area.x0-2, NULL); |
351 | |
352 | if (height > 1) |
353 | ui_scrollbar(area.x1, area.y0, area.x1+ui.lineheight, area.y1, &input->scroll, 1, fz_maxi(0, n-height)+1); |
354 | else |
355 | input->scroll = 0; |
356 | |
357 | ax = area.x0 + 2; |
358 | bx = area.x1 - 2; |
359 | ay = area.y0 + 1; |
360 | sy = input->scroll * ui.lineheight; |
361 | |
362 | if (ui_mouse_inside(area)) |
363 | { |
364 | ui.hot = input; |
365 | if (!ui.active || ui.active == input) |
366 | ui.cursor = GLUT_CURSOR_TEXT; |
367 | if (!ui.active && ui.down) |
368 | { |
369 | input->p = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y); |
370 | ui.active = input; |
371 | } |
372 | } |
373 | |
374 | if (ui.active == input) |
375 | { |
376 | input->q = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y); |
377 | ui.focus = input; |
378 | } |
379 | |
380 | p = input->p < input->q ? input->p : input->q; |
381 | q = input->p > input->q ? input->p : input->q; |
382 | |
383 | for (i = input->scroll; i < n && i < input->scroll+height; ++i) |
384 | { |
385 | char *a = lines[i].a, *b = lines[i].b; |
386 | if (ui.focus == input) |
387 | { |
388 | if (p >= a && p <= b && q >= a && q <= b) |
389 | { |
390 | float px = ax + ui_measure_string_part(a, p); |
391 | float qx = px + ui_measure_string_part(p, q); |
392 | glColorHex(UI_COLOR_TEXT_SEL_BG); |
393 | glRectf(px, ay, qx+1, ay + ui.lineheight); |
394 | glColorHex(UI_COLOR_TEXT_FG); |
395 | ui_draw_string_part(ax, ay, a, p); |
396 | glColorHex(UI_COLOR_TEXT_SEL_FG); |
397 | ui_draw_string_part(px, ay, p, q); |
398 | glColorHex(UI_COLOR_TEXT_FG); |
399 | ui_draw_string_part(qx, ay, q, b); |
400 | } |
401 | else if (p < a && q >= a && q <= b) |
402 | { |
403 | float qx = ax + ui_measure_string_part(a, q); |
404 | glColorHex(UI_COLOR_TEXT_SEL_BG); |
405 | glRectf(ax, ay, qx+1, ay + ui.lineheight); |
406 | glColorHex(UI_COLOR_TEXT_SEL_FG); |
407 | ui_draw_string_part(ax, ay, a, q); |
408 | glColorHex(UI_COLOR_TEXT_FG); |
409 | ui_draw_string_part(qx, ay, q, b); |
410 | } |
411 | else if (p >= a && p <= b && q > b) |
412 | { |
413 | float px = ax + ui_measure_string_part(a, p); |
414 | glColorHex(UI_COLOR_TEXT_SEL_BG); |
415 | glRectf(px, ay, bx, ay + ui.lineheight); |
416 | glColorHex(UI_COLOR_TEXT_FG); |
417 | ui_draw_string_part(ax, ay, a, p); |
418 | glColorHex(UI_COLOR_TEXT_SEL_FG); |
419 | ui_draw_string_part(px, ay, p, b); |
420 | } |
421 | else if (p < a && q > b) |
422 | { |
423 | glColorHex(UI_COLOR_TEXT_SEL_BG); |
424 | glRectf(ax, ay, bx, ay + ui.lineheight); |
425 | glColorHex(UI_COLOR_TEXT_SEL_FG); |
426 | ui_draw_string_part(ax, ay, a, b); |
427 | } |
428 | else |
429 | { |
430 | glColorHex(UI_COLOR_TEXT_FG); |
431 | ui_draw_string_part(ax, ay, a, b); |
432 | } |
433 | } |
434 | else |
435 | { |
436 | glColorHex(UI_COLOR_TEXT_FG); |
437 | ui_draw_string_part(ax, ay, a, b); |
438 | } |
439 | ay += ui.lineheight; |
440 | } |
441 | |
442 | return state; |
443 | } |
444 | |