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() const
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(size_t 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() const
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(size_t 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 : 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 : // also ensures that cy is always within lines[] and cx is valid
408 0 : EditLine &Editor::currentline()
409 : {
410 0 : size_t 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 : const 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() const
472 : {
473 0 : int len = 0;
474 0 : for(const EditLine &l : lines)
475 : {
476 0 : len += l.len + 1;
477 : }
478 0 : char *str = newstring(len);
479 0 : int offset = 0;
480 0 : for(const EditLine &l : lines)
481 : {
482 0 : std::memcpy(&str[offset], l.text, l.len);
483 0 : offset += l.len;
484 0 : str[offset++] = '\n';
485 : }
486 0 : str[offset] = '\0';
487 0 : return str;
488 : }
489 :
490 0 : char *Editor::selectiontostring()
491 : {
492 0 : std::vector<char> buf;
493 : int sx, sy, ex, ey;
494 0 : region(sx, sy, ex, ey);
495 0 : for(int i = 0; i < 1+ey-sy; ++i)
496 : {
497 0 : int y = sy+i;
498 0 : char *line = lines[y].text;
499 0 : int len = lines[y].len;
500 0 : if(y == sy && y == ey)
501 : {
502 0 : line += sx;
503 0 : len = ex - sx;
504 : }
505 0 : else if(y == sy)
506 : {
507 0 : line += sx;
508 : }
509 0 : else if(y == ey)
510 : {
511 0 : len = ex;
512 : }
513 0 : for(int i = 0; i < len; ++i)
514 : {
515 0 : buf.push_back(line[i]);
516 : }
517 0 : buf.push_back('\n');
518 : }
519 0 : buf.push_back('\0');
520 0 : return newstring(buf.data(), buf.size()-1);
521 0 : }
522 :
523 0 : void Editor::removelines(int start, int count)
524 : {
525 0 : for(int i = 0; i < count; ++i)
526 : {
527 0 : lines[start+i].clear();
528 : }
529 0 : lines.erase(lines.begin() + start, lines.begin() + start + count);
530 0 : }
531 :
532 0 : bool Editor::del() // removes the current selection (if any)
533 : {
534 : int sx, sy, ex, ey;
535 0 : if(!region(sx, sy, ex, ey))
536 : {
537 0 : mark(false);
538 0 : return false;
539 : }
540 0 : if(sy == ey)
541 : {
542 0 : if(sx == 0 && ex == lines[ey].len)
543 : {
544 0 : removelines(sy, 1);
545 : }
546 0 : else lines[sy].del(sx, ex - sx);
547 : }
548 : else
549 : {
550 0 : if(ey > sy+1)
551 : {
552 0 : removelines(sy+1, ey-(sy+1));
553 0 : ey = sy+1;
554 : }
555 0 : if(ex == lines[ey].len)
556 : {
557 0 : removelines(ey, 1);
558 : }
559 : else
560 : {
561 0 : lines[ey].del(0, ex);
562 : }
563 0 : if(sx == 0)
564 : {
565 0 : removelines(sy, 1);
566 : }
567 : else
568 : {
569 0 : lines[sy].del(sx, lines[sy].len - sx);
570 : }
571 : }
572 0 : if(lines.empty())
573 : {
574 0 : lines.emplace_back();
575 0 : lines.back().set("");
576 : }
577 0 : mark(false);
578 0 : cx = sx;
579 0 : cy = sy;
580 0 : EditLine ¤t = currentline();
581 0 : if(cx >= current.len && cy < static_cast<int>(lines.size()) - 1)
582 : {
583 0 : current.append(lines[cy+1].text);
584 0 : removelines(cy + 1, 1);
585 : }
586 0 : return true;
587 : }
588 :
589 0 : void Editor::insert(char ch)
590 : {
591 0 : del();
592 0 : EditLine ¤t = currentline();
593 0 : if(ch == '\n')
594 : {
595 0 : if(maxy == -1 || cy < maxy-1)
596 : {
597 0 : EditLine newline(¤t.text[cx]);
598 0 : current.chop(cx);
599 0 : cy = std::min(static_cast<int>(lines.size()), cy+1);
600 0 : lines.insert(lines.begin() + cy, newline);
601 0 : }
602 : else
603 : {
604 0 : current.chop(cx);
605 : }
606 0 : cx = 0;
607 : }
608 : else
609 : {
610 0 : int len = current.len;
611 0 : if(maxx >= 0 && len > maxx-1)
612 : {
613 0 : len = maxx-1;
614 : }
615 0 : if(cx <= len)
616 : {
617 0 : current.insert(&ch, cx++, 1);
618 : }
619 : }
620 0 : }
621 :
622 0 : void Editor::insert(const char *s)
623 : {
624 0 : while(*s)
625 : {
626 0 : insert(*s++);
627 : }
628 0 : }
629 :
630 0 : void Editor::insertallfrom(const Editor * const b)
631 : {
632 0 : if(b==this)
633 : {
634 0 : return;
635 : }
636 :
637 0 : del();
638 :
639 0 : if(b->lines.size() == 1 || maxy == 1)
640 : {
641 0 : EditLine ¤t = currentline();
642 0 : char *str = b->lines[0].text;
643 0 : int slen = b->lines[0].len;
644 0 : if(maxx >= 0 && b->lines[0].len + cx > maxx)
645 : {
646 0 : slen = maxx-cx;
647 : }
648 0 : if(slen > 0)
649 : {
650 0 : int len = current.len;
651 0 : if(maxx >= 0 && slen + cx + len > maxx)
652 : {
653 0 : len = std::max(0, maxx-(cx+slen));
654 : }
655 0 : current.insert(str, cx, slen);
656 0 : cx += slen;
657 : }
658 : }
659 : else
660 : {
661 0 : for(size_t i = 0; i < b->lines.size(); i++)
662 : {
663 0 : if(!i)
664 : {
665 0 : lines[cy++].append(b->lines[i].text);
666 : }
667 0 : else if(i >= b->lines.size())
668 : {
669 0 : cx = b->lines[i].len;
670 0 : lines[cy].prepend(b->lines[i].text);
671 : }
672 0 : else if(maxy < 0 || static_cast<int>(lines.size()) < maxy)
673 : {
674 0 : lines.insert(lines.begin() + cy++, EditLine(b->lines[i].text));
675 : }
676 : }
677 : }
678 : }
679 :
680 0 : void Editor::scrollup()
681 : {
682 0 : cy--;
683 0 : }
684 :
685 0 : void Editor::scrolldown()
686 : {
687 0 : cy++;
688 0 : }
689 :
690 0 : void Editor::key(int code)
691 : {
692 0 : switch(code)
693 : {
694 0 : case SDLK_UP:
695 : {
696 0 : if(linewrap)
697 : {
698 : int x, y;
699 0 : const char *str = currentline().text;
700 0 : text_pos(str, cx+1, x, y, pixelwidth);
701 0 : if(y > 0)
702 : {
703 0 : cx = text_visible(str, x, y-FONTH, pixelwidth);
704 0 : break;
705 : }
706 : }
707 0 : cy--;
708 0 : break;
709 : }
710 0 : case SDLK_DOWN:
711 : {
712 0 : if(linewrap)
713 : {
714 : int x, y, width, height;
715 0 : const char *str = currentline().text;
716 0 : text_pos(str, cx, x, y, pixelwidth);
717 0 : text_bounds(str, width, height, pixelwidth);
718 0 : y += FONTH;
719 0 : if(y < height)
720 : {
721 0 : cx = text_visible(str, x, y, pixelwidth);
722 0 : break;
723 : }
724 : }
725 0 : cy++;
726 0 : break;
727 : }
728 0 : case SDLK_PAGEUP:
729 : {
730 0 : cy-=pixelheight/FONTH;
731 0 : break;
732 : }
733 0 : case SDLK_PAGEDOWN:
734 : {
735 0 : cy+=pixelheight/FONTH;
736 0 : break;
737 : }
738 0 : case SDLK_HOME:
739 : {
740 0 : cx = cy = 0;
741 0 : break;
742 : }
743 0 : case SDLK_END:
744 : {
745 0 : cx = cy = INT_MAX;
746 0 : break;
747 : }
748 0 : case SDLK_LEFT:
749 : {
750 0 : cx--;
751 0 : break;
752 : }
753 0 : case SDLK_RIGHT:
754 : {
755 0 : cx++;
756 0 : break;
757 : }
758 0 : case SDLK_DELETE:
759 : {
760 0 : if(!del())
761 : {
762 0 : EditLine ¤t = currentline();
763 0 : if(cx < current.len)
764 : {
765 0 : current.del(cx, 1);
766 : }
767 0 : else if(cy < static_cast<int>(lines.size())-1)
768 : { //combine with next line
769 0 : current.append(lines[cy+1].text);
770 0 : removelines(cy+1, 1);
771 : }
772 : }
773 0 : break;
774 : }
775 0 : case SDLK_BACKSPACE:
776 : {
777 0 : if(!del())
778 : {
779 0 : EditLine ¤t = currentline();
780 0 : if(cx > 0)
781 : {
782 0 : current.del(--cx, 1);
783 : }
784 0 : else if(cy > 0)
785 : { //combine with previous line
786 0 : cx = lines[cy-1].len;
787 0 : lines[cy-1].append(current.text);
788 0 : removelines(cy--, 1);
789 : }
790 : }
791 0 : break;
792 : }
793 0 : case SDLK_LSHIFT:
794 : case SDLK_RSHIFT:
795 : {
796 0 : break;
797 : }
798 0 : case SDLK_RETURN:
799 : {
800 0 : insert('\n');
801 0 : break;
802 : }
803 0 : case SDLK_TAB:
804 : {
805 0 : insert('\t');
806 0 : break;
807 : }
808 : }
809 0 : }
810 :
811 0 : void Editor::input(const char *str, int len)
812 : {
813 0 : for(int i = 0; i < len; ++i)
814 : {
815 0 : insert(str[i]);
816 : }
817 0 : }
818 :
819 0 : void Editor::hit(int hitx, int hity, bool dragged)
820 : {
821 0 : int maxwidth = linewrap?pixelwidth:-1,
822 0 : h = 0;
823 0 : for(size_t i = scrolly; i < lines.size(); i++)
824 : {
825 : int width, height;
826 0 : text_bounds(lines[i].text, width, height, maxwidth);
827 0 : if(h + height > pixelheight)
828 : {
829 0 : break;
830 : }
831 0 : if(hity >= h && hity <= h+height)
832 : {
833 0 : int x = text_visible(lines[i].text, hitx, hity-h, maxwidth);
834 0 : if(dragged)
835 : {
836 0 : mx = x;
837 0 : my = i;
838 : }
839 : else
840 : {
841 0 : cx = x;
842 0 : cy = i;
843 : };
844 0 : break;
845 : }
846 0 : h+=height;
847 : }
848 0 : }
849 :
850 0 : void Editor::draw(int x, int y, int color)
851 : {
852 0 : int maxwidth = linewrap?pixelwidth:-1,
853 : sx, sy, ex, ey;
854 0 : bool selection = region(sx, sy, ex, ey);
855 : // fix scrolly so that <cx, cy> is always on screen
856 0 : if(cy < scrolly)
857 : {
858 0 : scrolly = cy;
859 : }
860 : else
861 : {
862 0 : if(scrolly < 0)
863 : {
864 0 : scrolly = 0;
865 : }
866 0 : int h = 0;
867 0 : for(int i = cy; i >= scrolly; i--)
868 : {
869 : int width, height;
870 0 : text_bounds(lines[i].text, width, height, maxwidth);
871 0 : if(h + height > pixelheight)
872 : {
873 0 : scrolly = i+1;
874 0 : break;
875 : }
876 0 : h += height;
877 : }
878 : }
879 :
880 0 : if(selection)
881 : {
882 : // convert from cursor coords into pixel coords
883 : int psx, psy, pex, pey;
884 0 : text_pos(lines[sy].text, sx, psx, psy, maxwidth);
885 0 : text_pos(lines[ey].text, ex, pex, pey, maxwidth);
886 0 : int maxy = static_cast<int>(lines.size()),
887 0 : h = 0;
888 0 : for(int i = scrolly; i < maxy; i++)
889 : {
890 : int width, height;
891 0 : text_bounds(lines[i].text, width, height, maxwidth);
892 0 : if(h + height > pixelheight)
893 : {
894 0 : maxy = i;
895 0 : break;
896 : }
897 0 : if(i == sy)
898 : {
899 0 : psy += h;
900 : }
901 0 : if(i == ey)
902 : {
903 0 : pey += h;
904 0 : break;
905 : }
906 0 : h += height;
907 : }
908 0 : maxy--;
909 0 : if(ey >= scrolly && sy <= maxy)
910 : {
911 : // crop top/bottom within window
912 0 : if(sy < scrolly)
913 : {
914 0 : sy = scrolly;
915 0 : psy = 0;
916 0 : psx = 0;
917 : }
918 0 : if(ey > maxy)
919 : {
920 0 : ey = maxy;
921 0 : pey = pixelheight - FONTH;
922 0 : pex = pixelwidth;
923 : }
924 0 : hudnotextureshader->set();
925 0 : gle::colorub(0xA0, 0x80, 0x80);
926 0 : gle::defvertex(2);
927 0 : gle::begin(GL_TRIANGLE_FAN);
928 0 : if(psy == pey)
929 : {
930 0 : gle::attribf(x+psx, y+psy);
931 0 : gle::attribf(x+pex, y+psy);
932 0 : gle::attribf(x+pex, y+pey+FONTH);
933 0 : gle::attribf(x+psx, y+pey+FONTH);
934 : }
935 : else
936 0 : { gle::attribf(x+psx, y+psy);
937 0 : gle::attribf(x+psx, y+psy+FONTH);
938 0 : gle::attribf(x+pixelwidth, y+psy+FONTH);
939 0 : gle::attribf(x+pixelwidth, y+psy);
940 0 : if(pey-psy > FONTH)
941 : {
942 0 : gle::attribf(x, y+psy+FONTH);
943 0 : gle::attribf(x+pixelwidth, y+psy+FONTH);
944 0 : gle::attribf(x+pixelwidth, y+pey);
945 0 : gle::attribf(x, y+pey);
946 : }
947 0 : gle::attribf(x, y+pey);
948 0 : gle::attribf(x, y+pey+FONTH);
949 0 : gle::attribf(x+pex, y+pey+FONTH);
950 0 : gle::attribf(x+pex, y+pey);
951 : }
952 0 : gle::end();
953 : }
954 : }
955 :
956 0 : int h = 0;
957 0 : for(size_t i = scrolly; i < lines.size(); i++)
958 : {
959 : int width, height;
960 0 : text_bounds(lines[i].text, width, height, maxwidth);
961 0 : if(h + height > pixelheight)
962 : {
963 0 : break;
964 : }
965 : //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);
966 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);
967 0 : if(linewrap && height > FONTH) // line wrap indicator
968 : {
969 0 : hudnotextureshader->set();
970 0 : gle::colorub(0x80, 0xA0, 0x80);
971 0 : gle::defvertex(2);
972 0 : gle::begin(GL_TRIANGLE_STRIP);
973 0 : gle::attribf(x, y+h+FONTH);
974 0 : gle::attribf(x, y+h+height);
975 0 : gle::attribf(x-fontwidth()/2, y+h+FONTH);
976 0 : gle::attribf(x-fontwidth()/2, y+h+height);
977 0 : gle::end();
978 : }
979 0 : h+=height;
980 : }
981 0 : }
982 :
983 : // global
984 :
985 : static std::vector<Editor *> editors;
986 : static Editor *textfocus = nullptr;
987 :
988 0 : void readyeditors()
989 : {
990 0 : for(Editor * i : editors)
991 : {
992 0 : i->active = (i->mode==Editor_Forever);
993 : }
994 0 : }
995 :
996 0 : void flusheditors()
997 : {
998 0 : for(int i = editors.size(); --i >=0;) //note reverse iteration
999 : {
1000 0 : if(!editors[i]->active)
1001 : {
1002 0 : Editor *e = editors.at(i);
1003 0 : editors.erase(editors.begin() + i);
1004 0 : if(e == textfocus)
1005 : {
1006 0 : textfocus = nullptr;
1007 : }
1008 0 : delete e;
1009 : }
1010 : }
1011 0 : }
1012 :
1013 0 : Editor *useeditor(std::string name, int mode, bool focus, const char *initval)
1014 : {
1015 0 : for(Editor *i : editors)
1016 : {
1017 0 : if(i->name == name)
1018 : {
1019 0 : if(focus)
1020 : {
1021 0 : textfocus = i;
1022 : }
1023 0 : i->active = true;
1024 0 : return i;
1025 : }
1026 : }
1027 0 : if(mode < 0)
1028 : {
1029 0 : return nullptr;
1030 : }
1031 0 : Editor *e = new Editor(name, mode, initval);
1032 0 : editors.push_back(e);
1033 0 : if(focus)
1034 : {
1035 0 : textfocus = e;
1036 : }
1037 0 : return e;
1038 : }
1039 :
1040 1 : void textlist()
1041 : {
1042 1 : if(!textfocus)
1043 : {
1044 1 : return;
1045 : }
1046 0 : std::string s;
1047 0 : for(size_t i = 0; i < editors.size(); i++)
1048 : {
1049 0 : if(i > 0)
1050 : {
1051 0 : s.push_back(',');
1052 0 : s.push_back(' ');
1053 : }
1054 0 : s.append(editors[i]->name);
1055 : }
1056 0 : result(s.c_str());
1057 0 : }
1058 :
1059 1 : void textfocuscmd(const char *name, const int *mode)
1060 : {
1061 1 : if(identflags&Idf_Overridden)
1062 : {
1063 0 : return;
1064 : }
1065 1 : if(*name)
1066 : {
1067 0 : useeditor(name, *mode<=0 ? Editor_Forever : *mode, true);
1068 : }
1069 1 : else if(editors.size() > 0)
1070 : {
1071 0 : result(editors.back()->name.c_str());
1072 : }
1073 : }
1074 :
1075 1 : void textsave(const char *file)
1076 : {
1077 1 : if(!textfocus)
1078 : {
1079 1 : return;
1080 : }
1081 0 : if(*file)
1082 : {
1083 0 : textfocus->setfile(copypath(file));
1084 : }
1085 0 : textfocus->save();
1086 : }
1087 :
1088 :
1089 1 : void textload(const char *file)
1090 : {
1091 1 : if(!textfocus)
1092 : {
1093 1 : return;
1094 : }
1095 0 : if(*file)
1096 : {
1097 0 : textfocus->setfile(copypath(file));
1098 0 : textfocus->load();
1099 : }
1100 0 : else if(textfocus->filename)
1101 : {
1102 0 : result(textfocus->filename);
1103 : }
1104 : }
1105 :
1106 :
1107 1 : void textinit(std::string_view name, const char *file, const char *initval)
1108 : {
1109 1 : if(identflags&Idf_Overridden)
1110 : {
1111 0 : return;
1112 : }
1113 1 : Editor *e = nullptr;
1114 1 : for(Editor *i : editors)
1115 : {
1116 0 : if(i->name == name)
1117 : {
1118 0 : e = i;
1119 0 : break;
1120 : }
1121 : }
1122 1 : if(e && e->rendered && !e->filename && *file && (e->lines.empty() || (e->lines.size() == 1 && !std::strcmp(e->lines[0].text, initval))))
1123 : {
1124 0 : e->setfile(copypath(file));
1125 0 : e->load();
1126 : }
1127 : }
1128 :
1129 : static const std::string pastebuffer = "#pastebuffer";
1130 :
1131 2 : void inittextcmds()
1132 : {
1133 2 : addcommand("textinit", reinterpret_cast<identfun>(textinit), "sss", Id_Command); // loads into named editor if no file assigned and editor has been rendered
1134 2 : addcommand("textlist", reinterpret_cast<identfun>(textlist), "", Id_Command);
1135 2 : addcommand("textshow", reinterpret_cast<identfun>(+[] ()
1136 : {
1137 1 : if(!textfocus || identflags&Idf_Overridden)
1138 : {
1139 1 : return; /* @DEBUG return the start of the buffer*/
1140 : }
1141 0 : EditLine line;
1142 0 : line.combinelines(textfocus->lines);
1143 0 : result(line.text); line.clear();
1144 2 : }), "", Id_Command);
1145 2 : addcommand("textfocus", reinterpret_cast<identfun>(textfocuscmd), "si", Id_Command);
1146 2 : addcommand("textprev", reinterpret_cast<identfun>(+[] ()
1147 : {
1148 1 : if(!textfocus || identflags&Idf_Overridden) return;
1149 0 : editors.insert(editors.begin(), textfocus);
1150 0 : editors.pop_back();
1151 2 : }), "", Id_Command); // return to the previous editor
1152 2 : addcommand("textmode", reinterpret_cast<identfun>(+[] (const int *m)
1153 : {
1154 1 : if(!textfocus || identflags&Idf_Overridden)
1155 : {
1156 1 : return;
1157 : }
1158 : /* (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*/
1159 0 : if(*m)
1160 : {
1161 0 : textfocus->mode = *m;
1162 : }
1163 : else
1164 : {
1165 0 : intret(textfocus->mode);
1166 : };
1167 2 : }), "i", Id_Command);
1168 2 : addcommand("textsave", reinterpret_cast<identfun>(textsave), "s", Id_Command);
1169 2 : addcommand("textload", reinterpret_cast<identfun>(textload), "s", Id_Command);
1170 2 : addcommand("textcopy", reinterpret_cast<identfun>(+[] ()
1171 : {
1172 1 : if(!textfocus || identflags&Idf_Overridden)
1173 : {
1174 1 : return;
1175 : }
1176 0 : Editor *b = useeditor(pastebuffer, Editor_Forever, false);
1177 0 : textfocus->copyselectionto(b);
1178 2 : }), "", Id_Command);
1179 2 : addcommand("textpaste", reinterpret_cast<identfun>(+[] ()
1180 : {
1181 1 : if(!textfocus || identflags&Idf_Overridden)
1182 : {
1183 1 : return;
1184 : }
1185 0 : const Editor *b = useeditor(pastebuffer, Editor_Forever, false);
1186 0 : textfocus->insertallfrom(b);
1187 2 : }), "", Id_Command);
1188 2 : addcommand("textmark", reinterpret_cast<identfun>(+[] (const int *m)
1189 : {
1190 1 : if(!textfocus || identflags&Idf_Overridden)
1191 : {
1192 1 : return;
1193 : }
1194 : /* (1=mark, 2=unmark), return current mark setting if no args*/
1195 0 : if(*m)
1196 : {
1197 0 : textfocus->mark(*m==1);
1198 : }
1199 : else
1200 : {
1201 0 : intret(textfocus->region() ? 1 : 2);
1202 : };
1203 2 : }), "i", Id_Command);
1204 2 : addcommand("textselectall", reinterpret_cast<identfun>(+[] ()
1205 : {
1206 1 : if(!textfocus || identflags&Idf_Overridden)
1207 : {
1208 1 : return;
1209 : }
1210 0 : textfocus->selectall();
1211 2 : }), "", Id_Command);
1212 2 : addcommand("textclear", reinterpret_cast<identfun>(+[] ()
1213 : {
1214 1 : if(!textfocus || identflags&Idf_Overridden)
1215 : {
1216 1 : return;
1217 : }
1218 0 : textfocus->clear();
1219 2 : }), "", Id_Command);
1220 2 : addcommand("textcurrentline", reinterpret_cast<identfun>(+[] ()
1221 : {
1222 1 : if(!textfocus || identflags&Idf_Overridden)
1223 : {
1224 1 : return;
1225 : }
1226 0 : result(textfocus->currentline().text);
1227 2 : }), "", Id_Command);
1228 2 : addcommand("textexec", reinterpret_cast<identfun>(+[] (const int *selected)
1229 : {
1230 1 : if(!textfocus || identflags&Idf_Overridden)
1231 : {
1232 1 : return;
1233 : }
1234 : /* execute script commands from the buffer (0=all, 1=selected region only)*/
1235 0 : const char *script = *selected ? textfocus->selectiontostring() : textfocus->tostring();
1236 0 : execute(script);
1237 0 : delete[] script;
1238 2 : }), "i", Id_Command);
1239 2 : }
|