Line data Source code
1 : /* textedit.cpp: ui text editing functionality
2 : *
3 : * libprimis supports text entry and large text editor blocks, for creating user
4 : * interfaces that require input (files, string fields)
5 : *
6 : * For the objects which are actually called by ui.cpp, see textedit.h
7 : *
8 : */
9 :
10 : #include "../libprimis-headers/cube.h"
11 : #include "../../shared/geomexts.h"
12 : #include "../../shared/glemu.h"
13 : #include "../../shared/glexts.h"
14 :
15 : #include "textedit.h"
16 : #include "render/rendertext.h"
17 : #include "render/renderttf.h"
18 : #include "render/shader.h"
19 : #include "render/shaderparam.h"
20 : #include "render/texture.h"
21 :
22 : // editline
23 :
24 0 : inline void text_bounds(const char *str, int &width, int &height, int maxwidth = -1)
25 : {
26 : float widthf, heightf;
27 0 : text_boundsf(str, widthf, heightf, maxwidth);
28 0 : width = static_cast<int>(std::ceil(widthf));
29 0 : height = static_cast<int>(std::ceil(heightf));
30 0 : }
31 :
32 0 : inline void text_pos(const char *str, int cursor, int &cx, int &cy, int maxwidth)
33 : {
34 : float cxf, cyf;
35 0 : text_posf(str, cursor, cxf, cyf, maxwidth);
36 0 : cx = static_cast<int>(cxf);
37 0 : cy = static_cast<int>(cyf);
38 0 : }
39 :
40 0 : bool EditLine::empty()
41 : {
42 0 : return len <= 0;
43 : }
44 :
45 0 : void EditLine::clear()
46 : {
47 0 : delete[] text;
48 0 : text = nullptr;
49 0 : len = maxlen = 0;
50 0 : }
51 :
52 0 : bool EditLine::grow(int total, const char *fmt, ...)
53 : {
54 0 : if(total + 1 <= maxlen)
55 : {
56 0 : return false;
57 : }
58 0 : maxlen = (total + Chunk_Size) - total%Chunk_Size;
59 0 : char *newtext = new char[maxlen];
60 0 : if(fmt)
61 : {
62 : va_list args;
63 0 : va_start(args, fmt);
64 0 : vformatstring(newtext, fmt, args, maxlen);
65 0 : va_end(args);
66 : }
67 : else
68 : {
69 0 : newtext[0] = '\0';
70 : }
71 0 : delete[] text;
72 0 : text = newtext;
73 0 : return true;
74 : }
75 :
76 0 : void EditLine::set(const char *str, int slen)
77 : {
78 0 : if(slen < 0)
79 : {
80 0 : slen = std::strlen(str);
81 0 : if(!grow(slen, "%s", str))
82 : {
83 0 : std::memcpy(text, str, slen + 1);
84 : }
85 : }
86 : else
87 : {
88 0 : grow(slen);
89 0 : std::memcpy(text, str, slen);
90 0 : text[slen] = '\0';
91 : }
92 0 : len = slen;
93 0 : }
94 :
95 0 : void EditLine::prepend(const char *str)
96 : {
97 0 : int slen = std::strlen(str);
98 0 : if(!grow(slen + len, "%s%s", str, text ? text : ""))
99 : {
100 0 : std::memmove(&text[slen], text, len + 1);
101 0 : std::memcpy(text, str, slen + 1);
102 : }
103 0 : len += slen;
104 0 : }
105 :
106 0 : void EditLine::append(const char *str)
107 : {
108 0 : int slen = std::strlen(str);
109 0 : if(!grow(len + slen, "%s%s", text ? text : "", str))
110 : {
111 0 : std::memcpy(&text[len], str, slen + 1);
112 : }
113 0 : len += slen;
114 0 : }
115 :
116 0 : bool EditLine::read(std::fstream& f, int chop)
117 : {
118 0 : if(chop < 0)
119 : {
120 0 : chop = INT_MAX;
121 : }
122 : else
123 : {
124 0 : chop++;
125 : }
126 0 : set("");
127 0 : while(len + 1 < chop && f.getline(&text[len], std::min(maxlen, chop) - len))
128 : {
129 0 : len += std::strlen(&text[len]);
130 0 : if(len > 0 && text[len-1] == '\n')
131 : {
132 0 : text[--len] = '\0';
133 0 : return true;
134 : }
135 0 : if(len + 1 >= maxlen && len + 1 < chop)
136 : {
137 0 : grow(len + Chunk_Size, "%s", text);
138 : }
139 : }
140 0 : if(len + 1 >= chop)
141 : {
142 : char buf[Chunk_Size];
143 0 : while(f.getline(buf, sizeof(buf)))
144 : {
145 0 : int blen = std::strlen(buf);
146 0 : if(blen > 0 && buf[blen-1] == '\n')
147 : {
148 0 : return true;
149 : }
150 : }
151 : }
152 0 : return len > 0;
153 : }
154 :
155 0 : void EditLine::del(int start, int count)
156 : {
157 0 : if(!text)
158 : {
159 0 : return;
160 : }
161 0 : if(start < 0)
162 : {
163 0 : count += start;
164 0 : start = 0;
165 : }
166 0 : if(count <= 0 || start >= len)
167 : {
168 0 : return;
169 : }
170 0 : if(start + count > len)
171 : {
172 0 : count = len - start - 1;
173 : }
174 0 : std::memmove(&text[start], &text[start+count], len + 1 - (start + count));
175 0 : len -= count;
176 : }
177 :
178 0 : void EditLine::chop(int newlen)
179 : {
180 0 : if(!text)
181 : {
182 0 : return;
183 : }
184 0 : len = std::clamp(newlen, 0, len);
185 0 : text[len] = '\0';
186 : }
187 :
188 0 : void EditLine::insert(char *str, int start, int count)
189 : {
190 0 : if(count <= 0)
191 : {
192 0 : count = std::strlen(str);
193 : }
194 0 : start = std::clamp(start, 0, len);
195 0 : grow(len + count, "%s", text ? text : "");
196 0 : std::memmove(&text[start + count], &text[start], len - start + 1);
197 0 : std::memcpy(&text[start], str, count);
198 0 : len += count;
199 0 : }
200 :
201 0 : void EditLine::combinelines(std::vector<EditLine> &src)
202 : {
203 0 : if(src.empty())
204 : {
205 0 : set("");
206 : }
207 : else
208 : {
209 0 : for(uint i = 0; i < src.size(); i++)
210 : {
211 0 : if(i)
212 : {
213 0 : append("\n");
214 : }
215 0 : if(!i)
216 : {
217 0 : set(src[i].text, src[i].len);
218 : }
219 : else
220 : {
221 0 : insert(src[i].text, len, src[i].len);
222 : }
223 : }
224 : }
225 0 : }
226 :
227 : // editor
228 :
229 0 : bool Editor::empty()
230 : {
231 0 : return lines.size() == 1 && lines[0].empty();
232 : }
233 :
234 0 : void Editor::clear(const char *init)
235 : {
236 0 : cx = cy = 0;
237 0 : mark(false);
238 0 : for(EditLine &i : lines)
239 : {
240 0 : i.clear();
241 : }
242 0 : lines.clear();
243 0 : if(init)
244 : {
245 0 : lines.emplace_back();
246 0 : lines.back().set(init);
247 : }
248 0 : }
249 :
250 0 : void Editor::init(const char *inittext)
251 : {
252 0 : if(std::strcmp(lines[0].text, inittext))
253 : {
254 0 : clear(inittext);
255 : }
256 0 : }
257 :
258 0 : void Editor::updateheight()
259 : {
260 : int width;
261 0 : text_bounds(lines[0].text, width, pixelheight, pixelwidth);
262 0 : }
263 :
264 0 : void Editor::setfile(const char *fname)
265 : {
266 0 : delete[] filename;
267 0 : if(fname)
268 : {
269 0 : filename = newstring(fname);
270 : }
271 : else
272 : {
273 0 : filename = nullptr;
274 : }
275 0 : }
276 :
277 0 : bool Editor::readback(std::fstream& file)
278 : {
279 0 : lines.emplace_back();
280 0 : return lines.back().read(file, maxx) && (maxy < 0 || static_cast<int>(lines.size()) <= maxy);
281 : }
282 :
283 0 : void Editor::load()
284 : {
285 0 : if(!filename)
286 : {
287 0 : return;
288 : }
289 0 : clear(nullptr);
290 0 : std::fstream file;
291 0 : file.open(filename);
292 0 : if(file)
293 : {
294 0 : while(readback(file))
295 : {
296 0 : lines.back().clear();
297 0 : lines.pop_back();
298 : }
299 : }
300 0 : if(lines.empty())
301 : {
302 0 : lines.emplace_back();
303 0 : lines.back().set("");
304 : }
305 0 : file.close();
306 0 : }
307 :
308 0 : void Editor::save()
309 : {
310 0 : if(!filename)
311 : {
312 0 : return;
313 : }
314 0 : std::fstream file;
315 0 : file.open(filename);
316 0 : if(!file)
317 : {
318 0 : return;
319 : }
320 0 : for(uint i = 0; i < lines.size(); i++)
321 : {
322 0 : file << lines[i].text;
323 : }
324 0 : file.close();
325 0 : }
326 :
327 0 : void Editor::mark(bool enable)
328 : {
329 0 : mx = (enable) ? cx : -1;
330 0 : my = cy;
331 0 : }
332 :
333 0 : void Editor::selectall()
334 : {
335 0 : mx = my = INT_MAX;
336 0 : cx = cy = 0;
337 0 : }
338 :
339 : // constrain results to within buffer - s=start, e=end, return true if a selection range
340 : // also ensures that cy is always within lines[] and cx is valid
341 0 : bool Editor::region(int &sx, int &sy, int &ex, int &ey)
342 : {
343 0 : uint n = lines.size();
344 0 : if(cy < 0)
345 : {
346 0 : cy = 0;
347 : }
348 0 : else if(cy >= static_cast<int>(n))
349 : {
350 0 : cy = n-1;
351 : }
352 0 : int len = lines[cy].len;
353 0 : if(cx < 0)
354 : {
355 0 : cx = 0;
356 : }
357 0 : else if(cx > len)
358 : {
359 0 : cx = len;
360 : }
361 0 : if(mx >= 0)
362 : {
363 0 : if(my < 0)
364 : {
365 0 : my = 0;
366 : }
367 0 : else if(my >= static_cast<int>(n))
368 : {
369 0 : my = n-1;
370 : }
371 0 : len = lines[my].len;
372 0 : if(mx > len)
373 : {
374 0 : mx = len;
375 : }
376 0 : sx = mx; sy = my;
377 : }
378 : else
379 : {
380 0 : sx = cx;
381 0 : sy = cy;
382 : }
383 0 : ex = cx;
384 0 : ey = cy;
385 0 : if(sy > ey)
386 : {
387 0 : std::swap(sy, ey);
388 0 : std::swap(sx, ex);
389 : }
390 0 : else if(sy==ey && sx > ex)
391 : {
392 0 : std::swap(sx, ex);
393 : }
394 0 : if(mx >= 0)
395 : {
396 0 : ex++;
397 : }
398 0 : return (sx != ex) || (sy != ey);
399 : }
400 :
401 0 : bool Editor::region()
402 : {
403 : int sx, sy, ex, ey;
404 0 : return region(sx, sy, ex, ey);
405 : }
406 :
407 : // also ensures that cy is always within lines[] and cx is valid
408 0 : EditLine &Editor::currentline()
409 : {
410 0 : uint n = lines.size();
411 0 : if(cy < 0)
412 : {
413 0 : cy = 0;
414 : }
415 0 : else if(cy >= static_cast<int>(n))
416 : {
417 0 : cy = n-1;
418 : }
419 0 : if(cx < 0)
420 : {
421 0 : cx = 0;
422 : }
423 0 : else if(cx > lines[cy].len)
424 : {
425 0 : cx = lines[cy].len;
426 : }
427 0 : return lines[cy];
428 : }
429 :
430 0 : void Editor::copyselectionto(Editor *b)
431 : {
432 0 : if(b==this)
433 : {
434 0 : return;
435 : }
436 0 : b->clear(nullptr);
437 : int sx, sy, ex, ey;
438 0 : region(sx, sy, ex, ey);
439 0 : for(int i = 0; i < 1+ey-sy; ++i)
440 : {
441 0 : if(b->maxy != -1 && static_cast<int>(b->lines.size()) >= b->maxy)
442 : {
443 0 : break;
444 : }
445 0 : int y = sy+i;
446 0 : char *line = lines[y].text;
447 0 : int len = lines[y].len;
448 0 : if(y == sy && y == ey)
449 : {
450 0 : line += sx;
451 0 : len = ex - sx;
452 : }
453 0 : else if(y == sy)
454 : {
455 0 : line += sx;
456 : }
457 0 : else if(y == ey)
458 : {
459 0 : len = ex;
460 : }
461 0 : b->lines.emplace_back();
462 0 : b->lines.back().set(line, len);
463 : }
464 0 : if(b->lines.empty())
465 : {
466 0 : b->lines.emplace_back();
467 0 : b->lines.back().set("");
468 : }
469 : }
470 :
471 0 : char *Editor::tostring()
472 : {
473 0 : int len = 0;
474 0 : for(uint i = 0; i < lines.size(); i++)
475 : {
476 0 : len += lines[i].len + 1;
477 : }
478 0 : char *str = newstring(len);
479 0 : int offset = 0;
480 0 : for(uint i = 0; i < lines.size(); i++)
481 : {
482 0 : EditLine &l = lines[i];
483 0 : std::memcpy(&str[offset], l.text, l.len);
484 0 : offset += l.len;
485 0 : str[offset++] = '\n';
486 : }
487 0 : str[offset] = '\0';
488 0 : return str;
489 : }
490 :
491 0 : char *Editor::selectiontostring()
492 : {
493 0 : std::vector<char> buf;
494 : int sx, sy, ex, ey;
495 0 : region(sx, sy, ex, ey);
496 0 : for(int i = 0; i < 1+ey-sy; ++i)
497 : {
498 0 : int y = sy+i;
499 0 : char *line = lines[y].text;
500 0 : int len = lines[y].len;
501 0 : if(y == sy && y == ey)
502 : {
503 0 : line += sx;
504 0 : len = ex - sx;
505 : }
506 0 : else if(y == sy)
507 : {
508 0 : line += sx;
509 : }
510 0 : else if(y == ey)
511 : {
512 0 : len = ex;
513 : }
514 0 : for(int i = 0; i < len; ++i)
515 : {
516 0 : buf.push_back(line[i]);
517 : }
518 0 : buf.push_back('\n');
519 : }
520 0 : buf.push_back('\0');
521 0 : return newstring(buf.data(), buf.size()-1);
522 0 : }
523 :
524 0 : void Editor::removelines(int start, int count)
525 : {
526 0 : for(int i = 0; i < count; ++i)
527 : {
528 0 : lines[start+i].clear();
529 : }
530 0 : lines.erase(lines.begin() + start, lines.begin() + start + count);
531 0 : }
532 :
533 0 : bool Editor::del() // removes the current selection (if any)
534 : {
535 : int sx, sy, ex, ey;
536 0 : if(!region(sx, sy, ex, ey))
537 : {
538 0 : mark(false);
539 0 : return false;
540 : }
541 0 : if(sy == ey)
542 : {
543 0 : if(sx == 0 && ex == lines[ey].len)
544 : {
545 0 : removelines(sy, 1);
546 : }
547 0 : else lines[sy].del(sx, ex - sx);
548 : }
549 : else
550 : {
551 0 : if(ey > sy+1)
552 : {
553 0 : removelines(sy+1, ey-(sy+1));
554 0 : ey = sy+1;
555 : }
556 0 : if(ex == lines[ey].len)
557 : {
558 0 : removelines(ey, 1);
559 : }
560 : else
561 : {
562 0 : lines[ey].del(0, ex);
563 : }
564 0 : if(sx == 0)
565 : {
566 0 : removelines(sy, 1);
567 : }
568 : else
569 : {
570 0 : lines[sy].del(sx, lines[sy].len - sx);
571 : }
572 : }
573 0 : if(lines.empty())
574 : {
575 0 : lines.emplace_back();
576 0 : lines.back().set("");
577 : }
578 0 : mark(false);
579 0 : cx = sx;
580 0 : cy = sy;
581 0 : EditLine ¤t = currentline();
582 0 : if(cx >= current.len && cy < static_cast<int>(lines.size()) - 1)
583 : {
584 0 : current.append(lines[cy+1].text);
585 0 : removelines(cy + 1, 1);
586 : }
587 0 : return true;
588 : }
589 :
590 0 : void Editor::insert(char ch)
591 : {
592 0 : del();
593 0 : EditLine ¤t = currentline();
594 0 : if(ch == '\n')
595 : {
596 0 : if(maxy == -1 || cy < maxy-1)
597 : {
598 0 : EditLine newline(¤t.text[cx]);
599 0 : current.chop(cx);
600 0 : cy = std::min(static_cast<int>(lines.size()), cy+1);
601 0 : lines.insert(lines.begin() + cy, newline);
602 0 : }
603 : else
604 : {
605 0 : current.chop(cx);
606 : }
607 0 : cx = 0;
608 : }
609 : else
610 : {
611 0 : int len = current.len;
612 0 : if(maxx >= 0 && len > maxx-1)
613 : {
614 0 : len = maxx-1;
615 : }
616 0 : if(cx <= len)
617 : {
618 0 : current.insert(&ch, cx++, 1);
619 : }
620 : }
621 0 : }
622 :
623 0 : void Editor::insert(const char *s)
624 : {
625 0 : while(*s)
626 : {
627 0 : insert(*s++);
628 : }
629 0 : }
630 :
631 0 : void Editor::insertallfrom(const Editor * const b)
632 : {
633 0 : if(b==this)
634 : {
635 0 : return;
636 : }
637 :
638 0 : del();
639 :
640 0 : if(b->lines.size() == 1 || maxy == 1)
641 : {
642 0 : EditLine ¤t = currentline();
643 0 : char *str = b->lines[0].text;
644 0 : int slen = b->lines[0].len;
645 0 : if(maxx >= 0 && b->lines[0].len + cx > maxx)
646 : {
647 0 : slen = maxx-cx;
648 : }
649 0 : if(slen > 0)
650 : {
651 0 : int len = current.len;
652 0 : if(maxx >= 0 && slen + cx + len > maxx)
653 : {
654 0 : len = std::max(0, maxx-(cx+slen));
655 : }
656 0 : current.insert(str, cx, slen);
657 0 : cx += slen;
658 : }
659 : }
660 : else
661 : {
662 0 : for(uint i = 0; i < b->lines.size(); i++)
663 : {
664 0 : if(!i)
665 : {
666 0 : lines[cy++].append(b->lines[i].text);
667 : }
668 0 : else if(i >= b->lines.size())
669 : {
670 0 : cx = b->lines[i].len;
671 0 : lines[cy].prepend(b->lines[i].text);
672 : }
673 0 : else if(maxy < 0 || static_cast<int>(lines.size()) < maxy)
674 : {
675 0 : lines.insert(lines.begin() + cy++, EditLine(b->lines[i].text));
676 : }
677 : }
678 : }
679 : }
680 :
681 0 : void Editor::scrollup()
682 : {
683 0 : cy--;
684 0 : }
685 :
686 0 : void Editor::scrolldown()
687 : {
688 0 : cy++;
689 0 : }
690 :
691 0 : void Editor::key(int code)
692 : {
693 0 : switch(code)
694 : {
695 0 : case SDLK_UP:
696 : {
697 0 : if(linewrap)
698 : {
699 : int x, y;
700 0 : const char *str = currentline().text;
701 0 : text_pos(str, cx+1, x, y, pixelwidth);
702 0 : if(y > 0)
703 : {
704 0 : cx = text_visible(str, x, y-FONTH, pixelwidth);
705 0 : break;
706 : }
707 : }
708 0 : cy--;
709 0 : break;
710 : }
711 0 : case SDLK_DOWN:
712 : {
713 0 : if(linewrap)
714 : {
715 : int x, y, width, height;
716 0 : const char *str = currentline().text;
717 0 : text_pos(str, cx, x, y, pixelwidth);
718 0 : text_bounds(str, width, height, pixelwidth);
719 0 : y += FONTH;
720 0 : if(y < height)
721 : {
722 0 : cx = text_visible(str, x, y, pixelwidth);
723 0 : break;
724 : }
725 : }
726 0 : cy++;
727 0 : break;
728 : }
729 0 : case SDLK_PAGEUP:
730 : {
731 0 : cy-=pixelheight/FONTH;
732 0 : break;
733 : }
734 0 : case SDLK_PAGEDOWN:
735 : {
736 0 : cy+=pixelheight/FONTH;
737 0 : break;
738 : }
739 0 : case SDLK_HOME:
740 : {
741 0 : cx = cy = 0;
742 0 : break;
743 : }
744 0 : case SDLK_END:
745 : {
746 0 : cx = cy = INT_MAX;
747 0 : break;
748 : }
749 0 : case SDLK_LEFT:
750 : {
751 0 : cx--;
752 0 : break;
753 : }
754 0 : case SDLK_RIGHT:
755 : {
756 0 : cx++;
757 0 : break;
758 : }
759 0 : case SDLK_DELETE:
760 : {
761 0 : if(!del())
762 : {
763 0 : EditLine ¤t = currentline();
764 0 : if(cx < current.len)
765 : {
766 0 : current.del(cx, 1);
767 : }
768 0 : else if(cy < static_cast<int>(lines.size())-1)
769 : { //combine with next line
770 0 : current.append(lines[cy+1].text);
771 0 : removelines(cy+1, 1);
772 : }
773 : }
774 0 : break;
775 : }
776 0 : case SDLK_BACKSPACE:
777 : {
778 0 : if(!del())
779 : {
780 0 : EditLine ¤t = currentline();
781 0 : if(cx > 0)
782 : {
783 0 : current.del(--cx, 1);
784 : }
785 0 : else if(cy > 0)
786 : { //combine with previous line
787 0 : cx = lines[cy-1].len;
788 0 : lines[cy-1].append(current.text);
789 0 : removelines(cy--, 1);
790 : }
791 : }
792 0 : break;
793 : }
794 0 : case SDLK_LSHIFT:
795 : case SDLK_RSHIFT:
796 : {
797 0 : break;
798 : }
799 0 : case SDLK_RETURN:
800 : {
801 0 : insert('\n');
802 0 : break;
803 : }
804 0 : case SDLK_TAB:
805 : {
806 0 : insert('\t');
807 0 : break;
808 : }
809 : }
810 0 : }
811 :
812 0 : void Editor::input(const char *str, int len)
813 : {
814 0 : for(int i = 0; i < len; ++i)
815 : {
816 0 : insert(str[i]);
817 : }
818 0 : }
819 :
820 0 : void Editor::hit(int hitx, int hity, bool dragged)
821 : {
822 0 : int maxwidth = linewrap?pixelwidth:-1,
823 0 : h = 0;
824 0 : for(uint i = scrolly; i < lines.size(); i++)
825 : {
826 : int width, height;
827 0 : text_bounds(lines[i].text, width, height, maxwidth);
828 0 : if(h + height > pixelheight)
829 : {
830 0 : break;
831 : }
832 0 : if(hity >= h && hity <= h+height)
833 : {
834 0 : int x = text_visible(lines[i].text, hitx, hity-h, maxwidth);
835 0 : if(dragged)
836 : {
837 0 : mx = x;
838 0 : my = i;
839 : }
840 : else
841 : {
842 0 : cx = x;
843 0 : cy = i;
844 : };
845 0 : break;
846 : }
847 0 : h+=height;
848 : }
849 0 : }
850 :
851 0 : void Editor::draw(int x, int y, int color, bool hit)
852 : {
853 0 : int maxwidth = linewrap?pixelwidth:-1,
854 : sx, sy, ex, ey;
855 0 : bool selection = region(sx, sy, ex, ey);
856 : // fix scrolly so that <cx, cy> is always on screen
857 0 : if(cy < scrolly)
858 : {
859 0 : scrolly = cy;
860 : }
861 : else
862 : {
863 0 : if(scrolly < 0)
864 : {
865 0 : scrolly = 0;
866 : }
867 0 : int h = 0;
868 0 : for(int i = cy; i >= scrolly; i--)
869 : {
870 : int width, height;
871 0 : text_bounds(lines[i].text, width, height, maxwidth);
872 0 : if(h + height > pixelheight)
873 : {
874 0 : scrolly = i+1;
875 0 : break;
876 : }
877 0 : h += height;
878 : }
879 : }
880 :
881 0 : if(selection)
882 : {
883 : // convert from cursor coords into pixel coords
884 : int psx, psy, pex, pey;
885 0 : text_pos(lines[sy].text, sx, psx, psy, maxwidth);
886 0 : text_pos(lines[ey].text, ex, pex, pey, maxwidth);
887 0 : int maxy = static_cast<int>(lines.size()),
888 0 : h = 0;
889 0 : for(int i = scrolly; i < maxy; i++)
890 : {
891 : int width, height;
892 0 : text_bounds(lines[i].text, width, height, maxwidth);
893 0 : if(h + height > pixelheight)
894 : {
895 0 : maxy = i;
896 0 : break;
897 : }
898 0 : if(i == sy)
899 : {
900 0 : psy += h;
901 : }
902 0 : if(i == ey)
903 : {
904 0 : pey += h;
905 0 : break;
906 : }
907 0 : h += height;
908 : }
909 0 : maxy--;
910 0 : if(ey >= scrolly && sy <= maxy)
911 : {
912 : // crop top/bottom within window
913 0 : if(sy < scrolly)
914 : {
915 0 : sy = scrolly;
916 0 : psy = 0;
917 0 : psx = 0;
918 : }
919 0 : if(ey > maxy)
920 : {
921 0 : ey = maxy;
922 0 : pey = pixelheight - FONTH;
923 0 : pex = pixelwidth;
924 : }
925 0 : hudnotextureshader->set();
926 0 : gle::colorub(0xA0, 0x80, 0x80);
927 0 : gle::defvertex(2);
928 0 : gle::begin(GL_TRIANGLE_FAN);
929 0 : if(psy == pey)
930 : {
931 0 : gle::attribf(x+psx, y+psy);
932 0 : gle::attribf(x+pex, y+psy);
933 0 : gle::attribf(x+pex, y+pey+FONTH);
934 0 : gle::attribf(x+psx, y+pey+FONTH);
935 : }
936 : else
937 0 : { gle::attribf(x+psx, y+psy);
938 0 : gle::attribf(x+psx, y+psy+FONTH);
939 0 : gle::attribf(x+pixelwidth, y+psy+FONTH);
940 0 : gle::attribf(x+pixelwidth, y+psy);
941 0 : if(pey-psy > FONTH)
942 : {
943 0 : gle::attribf(x, y+psy+FONTH);
944 0 : gle::attribf(x+pixelwidth, y+psy+FONTH);
945 0 : gle::attribf(x+pixelwidth, y+pey);
946 0 : gle::attribf(x, y+pey);
947 : }
948 0 : gle::attribf(x, y+pey);
949 0 : gle::attribf(x, y+pey+FONTH);
950 0 : gle::attribf(x+pex, y+pey+FONTH);
951 0 : gle::attribf(x+pex, y+pey);
952 : }
953 0 : gle::end();
954 : }
955 : }
956 :
957 0 : int h = 0;
958 0 : for(uint i = scrolly; i < lines.size(); i++)
959 : {
960 : int width, height;
961 0 : text_bounds(lines[i].text, width, height, maxwidth);
962 0 : if(h + height > pixelheight)
963 : {
964 0 : break;
965 : }
966 : //draw_text(lines[i].text, x, y+h, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, hit&&(static_cast<uint>(cy)==i)?cx:-1, maxwidth);
967 0 : ttr.renderttf(lines[i].text, {static_cast<uchar>(color>>16), static_cast<uchar>((color>>8)&0xFF), static_cast<uchar>(color&0xFF), 0}, x, y+h);
968 0 : if(linewrap && height > FONTH) // line wrap indicator
969 : {
970 0 : hudnotextureshader->set();
971 0 : gle::colorub(0x80, 0xA0, 0x80);
972 0 : gle::defvertex(2);
973 0 : gle::begin(GL_TRIANGLE_STRIP);
974 0 : gle::attribf(x, y+h+FONTH);
975 0 : gle::attribf(x, y+h+height);
976 0 : gle::attribf(x-fontwidth()/2, y+h+FONTH);
977 0 : gle::attribf(x-fontwidth()/2, y+h+height);
978 0 : gle::end();
979 : }
980 0 : h+=height;
981 : }
982 0 : }
983 :
984 : // global
985 :
986 : std::vector<Editor *> editors;
987 : Editor *textfocus = nullptr;
988 :
989 0 : void readyeditors()
990 : {
991 0 : for(Editor * i : editors)
992 : {
993 0 : i->active = (i->mode==Editor_Forever);
994 : }
995 0 : }
996 :
997 0 : void flusheditors()
998 : {
999 0 : for(int i = editors.size(); --i >=0;) //note reverse iteration
1000 : {
1001 0 : if(!editors[i]->active)
1002 : {
1003 0 : Editor *e = editors.at(i);
1004 0 : editors.erase(editors.begin() + i);
1005 0 : if(e == textfocus)
1006 : {
1007 0 : textfocus = nullptr;
1008 : }
1009 0 : delete e;
1010 : }
1011 : }
1012 0 : }
1013 :
1014 0 : Editor *useeditor(std::string name, int mode, bool focus, const char *initval)
1015 : {
1016 0 : for(uint i = 0; i < editors.size(); i++)
1017 : {
1018 0 : if(editors[i]->name == name)
1019 : {
1020 0 : Editor *e = editors[i];
1021 0 : if(focus)
1022 : {
1023 0 : textfocus = e;
1024 : }
1025 0 : e->active = true;
1026 0 : return e;
1027 : }
1028 : }
1029 0 : if(mode < 0)
1030 : {
1031 0 : return nullptr;
1032 : }
1033 0 : Editor *e = new Editor(name, mode, initval);
1034 0 : editors.push_back(e);
1035 0 : if(focus)
1036 : {
1037 0 : textfocus = e;
1038 : }
1039 0 : return e;
1040 : }
1041 :
1042 1 : void textlist()
1043 : {
1044 1 : if(!textfocus)
1045 : {
1046 1 : return;
1047 : }
1048 0 : std::string s;
1049 0 : for(uint i = 0; i < editors.size(); i++)
1050 : {
1051 0 : if(i > 0)
1052 : {
1053 0 : s.push_back(',');
1054 0 : s.push_back(' ');
1055 : }
1056 0 : s.append(editors[i]->name);
1057 : }
1058 0 : result(s.c_str());
1059 0 : }
1060 :
1061 1 : void textfocuscmd(const char *name, const int *mode)
1062 : {
1063 1 : if(identflags&Idf_Overridden)
1064 : {
1065 0 : return;
1066 : }
1067 1 : if(*name)
1068 : {
1069 0 : useeditor(name, *mode<=0 ? Editor_Forever : *mode, true);
1070 : }
1071 1 : else if(editors.size() > 0)
1072 : {
1073 0 : result(editors.back()->name.c_str());
1074 : }
1075 : }
1076 :
1077 1 : void textsave(const char *file)
1078 : {
1079 1 : if(!textfocus)
1080 : {
1081 1 : return;
1082 : }
1083 0 : if(*file)
1084 : {
1085 0 : textfocus->setfile(copypath(file));
1086 : }
1087 0 : textfocus->save();
1088 : }
1089 :
1090 :
1091 1 : void textload(const char *file)
1092 : {
1093 1 : if(!textfocus)
1094 : {
1095 1 : return;
1096 : }
1097 0 : if(*file)
1098 : {
1099 0 : textfocus->setfile(copypath(file));
1100 0 : textfocus->load();
1101 : }
1102 0 : else if(textfocus->filename)
1103 : {
1104 0 : result(textfocus->filename);
1105 : }
1106 : }
1107 :
1108 :
1109 1 : void textinit(std::string name, char *file, char *initval)
1110 : {
1111 1 : if(identflags&Idf_Overridden)
1112 : {
1113 0 : return;
1114 : }
1115 1 : Editor *e = nullptr;
1116 1 : for(Editor *i : editors)
1117 : {
1118 0 : if(i->name == name)
1119 : {
1120 0 : e = i;
1121 0 : break;
1122 : }
1123 : }
1124 1 : if(e && e->rendered && !e->filename && *file && (e->lines.empty() || (e->lines.size() == 1 && !std::strcmp(e->lines[0].text, initval))))
1125 : {
1126 0 : e->setfile(copypath(file));
1127 0 : e->load();
1128 : }
1129 : }
1130 :
1131 : const std::string pastebuffer = "#pastebuffer";
1132 :
1133 2 : void inittextcmds()
1134 : {
1135 2 : addcommand("textinit", reinterpret_cast<identfun>(textinit), "sss", Id_Command); // loads into named editor if no file assigned and editor has been rendered
1136 2 : addcommand("textlist", reinterpret_cast<identfun>(textlist), "", Id_Command);
1137 3 : addcommand("textshow", reinterpret_cast<identfun>(+[] () { if(!textfocus || identflags&Idf_Overridden) return; /* @DEBUG return the start of the buffer*/ EditLine line; line.combinelines(textfocus->lines); result(line.text); line.clear();; }), "", Id_Command);
1138 2 : addcommand("textfocus", reinterpret_cast<identfun>(textfocuscmd), "si", Id_Command);
1139 3 : addcommand("textprev", reinterpret_cast<identfun>(+[] () { if(!textfocus || identflags&Idf_Overridden) return; editors.insert(editors.begin(), textfocus); editors.pop_back();; }), "", Id_Command);; // return to the previous editor
1140 3 : addcommand("textmode", reinterpret_cast<identfun>(+[] (int *m) { if(!textfocus || identflags&Idf_Overridden) return; /* (1= keep while focused, 2= keep while used in gui, 3= keep forever (i.e. until mode changes)) topmost editor, return current setting if no args*/ if(*m) { textfocus->mode = *m; } else { intret(textfocus->mode); }; }), "i", Id_Command);
1141 2 : addcommand("textsave", reinterpret_cast<identfun>(textsave), "s", Id_Command);
1142 2 : addcommand("textload", reinterpret_cast<identfun>(textload), "s", Id_Command);
1143 3 : addcommand("textcopy", reinterpret_cast<identfun>(+[] () { if(!textfocus || identflags&Idf_Overridden) return; Editor *b = useeditor(pastebuffer, Editor_Forever, false); textfocus->copyselectionto(b);; }), "", Id_Command);;
1144 3 : addcommand("textpaste", reinterpret_cast<identfun>(+[] () { if(!textfocus || identflags&Idf_Overridden) return; Editor *b = useeditor(pastebuffer, Editor_Forever, false); textfocus->insertallfrom(b);; }), "", Id_Command);;
1145 3 : addcommand("textmark", reinterpret_cast<identfun>(+[] (int *m) { if(!textfocus || identflags&Idf_Overridden) return; /* (1=mark, 2=unmark), return current mark setting if no args*/ if(*m) { textfocus->mark(*m==1); } else { intret(textfocus->region() ? 1 : 2); }; }), "i", Id_Command);;
1146 3 : addcommand("textselectall", reinterpret_cast<identfun>(+[] () { if(!textfocus || identflags&Idf_Overridden) return; textfocus->selectall();; }), "", Id_Command);;
1147 3 : addcommand("textclear", reinterpret_cast<identfun>(+[] () { if(!textfocus || identflags&Idf_Overridden) return; textfocus->clear();; }), "", Id_Command);;
1148 3 : addcommand("textcurrentline", reinterpret_cast<identfun>(+[] () { if(!textfocus || identflags&Idf_Overridden) return; result(textfocus->currentline().text);; }), "", Id_Command);;
1149 3 : addcommand("textexec", reinterpret_cast<identfun>(+[] (int *selected) { if(!textfocus || identflags&Idf_Overridden) return; /* execute script commands from the buffer (0=all, 1=selected region only)*/ char *script = *selected ? textfocus->selectiontostring() : textfocus->tostring(); execute(script); delete[] script;; }), "i", Id_Command);
1150 2 : }
|