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