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 : // constrain results to within buffer - s=start, e=end, return true if a selection range
342 : // also ensures that cy is always within lines[] and cx is valid
343 0 : bool Editor::region(int &sx, int &sy, int &ex, int &ey)
344 : {
345 0 : size_t n = lines.size();
346 0 : if(cy < 0)
347 : {
348 0 : cy = 0;
349 : }
350 0 : else if(cy >= static_cast<int>(n))
351 : {
352 0 : cy = n-1;
353 : }
354 0 : int len = lines[cy].len;
355 0 : if(cx < 0)
356 : {
357 0 : cx = 0;
358 : }
359 0 : else if(cx > len)
360 : {
361 0 : cx = len;
362 : }
363 0 : if(mx >= 0)
364 : {
365 0 : if(my < 0)
366 : {
367 0 : my = 0;
368 : }
369 0 : else if(my >= static_cast<int>(n))
370 : {
371 0 : my = n-1;
372 : }
373 0 : len = lines[my].len;
374 0 : if(mx > len)
375 : {
376 0 : mx = len;
377 : }
378 0 : sx = mx; sy = my;
379 : }
380 : else
381 : {
382 0 : sx = cx;
383 0 : sy = cy;
384 : }
385 0 : ex = cx;
386 0 : ey = cy;
387 0 : if(sy > ey)
388 : {
389 0 : std::swap(sy, ey);
390 0 : std::swap(sx, ex);
391 : }
392 0 : else if(sy==ey && sx > ex)
393 : {
394 0 : std::swap(sx, ex);
395 : }
396 0 : if(mx >= 0)
397 : {
398 0 : ex++;
399 : }
400 0 : return (sx != ex) || (sy != ey);
401 : }
402 :
403 0 : bool Editor::region()
404 : {
405 : int sx, sy, ex, ey;
406 0 : return region(sx, sy, ex, ey);
407 : }
408 :
409 : // also ensures that cy is always within lines[] and cx is valid
410 0 : EditLine &Editor::currentline()
411 : {
412 0 : size_t n = lines.size();
413 0 : if(cy < 0)
414 : {
415 0 : cy = 0;
416 : }
417 0 : else if(cy >= static_cast<int>(n))
418 : {
419 0 : cy = n-1;
420 : }
421 0 : if(cx < 0)
422 : {
423 0 : cx = 0;
424 : }
425 0 : else if(cx > lines[cy].len)
426 : {
427 0 : cx = lines[cy].len;
428 : }
429 0 : return lines[cy];
430 : }
431 :
432 0 : void Editor::copyselectionto(Editor *b)
433 : {
434 0 : if(b==this)
435 : {
436 0 : return;
437 : }
438 0 : b->clear(nullptr);
439 : int sx, sy, ex, ey;
440 0 : region(sx, sy, ex, ey);
441 0 : for(int i = 0; i < 1+ey-sy; ++i)
442 : {
443 0 : if(b->maxy != -1 && static_cast<int>(b->lines.size()) >= b->maxy)
444 : {
445 0 : break;
446 : }
447 0 : int y = sy+i;
448 0 : const char *line = lines[y].text;
449 0 : int len = lines[y].len;
450 0 : if(y == sy && y == ey)
451 : {
452 0 : line += sx;
453 0 : len = ex - sx;
454 : }
455 0 : else if(y == sy)
456 : {
457 0 : line += sx;
458 : }
459 0 : else if(y == ey)
460 : {
461 0 : len = ex;
462 : }
463 0 : b->lines.emplace_back();
464 0 : b->lines.back().set(line, len);
465 : }
466 0 : if(b->lines.empty())
467 : {
468 0 : b->lines.emplace_back();
469 0 : b->lines.back().set("");
470 : }
471 : }
472 :
473 0 : char *Editor::tostring() const
474 : {
475 0 : int len = 0;
476 0 : for(const EditLine &l : lines)
477 : {
478 0 : len += l.len + 1;
479 : }
480 0 : char *str = newstring(len);
481 0 : int offset = 0;
482 0 : for(const EditLine &l : lines)
483 : {
484 0 : std::memcpy(&str[offset], l.text, l.len);
485 0 : offset += l.len;
486 0 : str[offset++] = '\n';
487 : }
488 0 : str[offset] = '\0';
489 0 : return str;
490 : }
491 :
492 0 : char *Editor::selectiontostring()
493 : {
494 0 : std::vector<char> buf;
495 : int sx, sy, ex, ey;
496 0 : region(sx, sy, ex, ey);
497 0 : for(int i = 0; i < 1+ey-sy; ++i)
498 : {
499 0 : int y = sy+i;
500 0 : char *line = lines[y].text;
501 0 : int len = lines[y].len;
502 0 : if(y == sy && y == ey)
503 : {
504 0 : line += sx;
505 0 : len = ex - sx;
506 : }
507 0 : else if(y == sy)
508 : {
509 0 : line += sx;
510 : }
511 0 : else if(y == ey)
512 : {
513 0 : len = ex;
514 : }
515 0 : for(int i = 0; i < len; ++i)
516 : {
517 0 : buf.push_back(line[i]);
518 : }
519 0 : buf.push_back('\n');
520 : }
521 0 : buf.push_back('\0');
522 0 : return newstring(buf.data(), buf.size()-1);
523 0 : }
524 :
525 0 : void Editor::removelines(int start, int count)
526 : {
527 0 : for(int i = 0; i < count; ++i)
528 : {
529 0 : lines[start+i].clear();
530 : }
531 0 : lines.erase(lines.begin() + start, lines.begin() + start + count);
532 0 : }
533 :
534 0 : bool Editor::del() // removes the current selection (if any)
535 : {
536 : int sx, sy, ex, ey;
537 0 : if(!region(sx, sy, ex, ey))
538 : {
539 0 : mark(false);
540 0 : return false;
541 : }
542 0 : if(sy == ey)
543 : {
544 0 : if(sx == 0 && ex == lines[ey].len)
545 : {
546 0 : removelines(sy, 1);
547 : }
548 0 : else lines[sy].del(sx, ex - sx);
549 : }
550 : else
551 : {
552 0 : if(ey > sy+1)
553 : {
554 0 : removelines(sy+1, ey-(sy+1));
555 0 : ey = sy+1;
556 : }
557 0 : if(ex == lines[ey].len)
558 : {
559 0 : removelines(ey, 1);
560 : }
561 : else
562 : {
563 0 : lines[ey].del(0, ex);
564 : }
565 0 : if(sx == 0)
566 : {
567 0 : removelines(sy, 1);
568 : }
569 : else
570 : {
571 0 : lines[sy].del(sx, lines[sy].len - sx);
572 : }
573 : }
574 0 : if(lines.empty())
575 : {
576 0 : lines.emplace_back();
577 0 : lines.back().set("");
578 : }
579 0 : mark(false);
580 0 : cx = sx;
581 0 : cy = sy;
582 0 : EditLine ¤t = currentline();
583 0 : if(cx >= current.len && cy < static_cast<int>(lines.size()) - 1)
584 : {
585 0 : current.append(lines[cy+1].text);
586 0 : removelines(cy + 1, 1);
587 : }
588 0 : return true;
589 : }
590 :
591 0 : void Editor::insert(char ch)
592 : {
593 0 : del();
594 0 : EditLine ¤t = currentline();
595 0 : if(ch == '\n')
596 : {
597 0 : if(maxy == -1 || cy < maxy-1)
598 : {
599 0 : EditLine newline(¤t.text[cx]);
600 0 : current.chop(cx);
601 0 : cy = std::min(static_cast<int>(lines.size()), cy+1);
602 0 : lines.insert(lines.begin() + cy, newline);
603 0 : }
604 : else
605 : {
606 0 : current.chop(cx);
607 : }
608 0 : cx = 0;
609 : }
610 : else
611 : {
612 0 : int len = current.len;
613 0 : if(maxx >= 0 && len > maxx-1)
614 : {
615 0 : len = maxx-1;
616 : }
617 0 : if(cx <= len)
618 : {
619 0 : current.insert(&ch, cx++, 1);
620 : }
621 : }
622 0 : }
623 :
624 0 : void Editor::insert(const char *s)
625 : {
626 0 : while(*s)
627 : {
628 0 : insert(*s++);
629 : }
630 0 : }
631 :
632 0 : void Editor::insertallfrom(const Editor * const b)
633 : {
634 0 : if(b==this)
635 : {
636 0 : return;
637 : }
638 :
639 0 : del();
640 :
641 0 : if(b->lines.size() == 1 || maxy == 1)
642 : {
643 0 : EditLine ¤t = currentline();
644 0 : char *str = b->lines[0].text;
645 0 : int slen = b->lines[0].len;
646 0 : if(maxx >= 0 && b->lines[0].len + cx > maxx)
647 : {
648 0 : slen = maxx-cx;
649 : }
650 0 : if(slen > 0)
651 : {
652 0 : int len = current.len;
653 0 : if(maxx >= 0 && slen + cx + len > maxx)
654 : {
655 0 : len = std::max(0, maxx-(cx+slen));
656 : }
657 0 : current.insert(str, cx, slen);
658 0 : cx += slen;
659 : }
660 : }
661 : else
662 : {
663 0 : for(size_t i = 0; i < b->lines.size(); i++)
664 : {
665 0 : if(!i)
666 : {
667 0 : lines[cy++].append(b->lines[i].text);
668 : }
669 0 : else if(i >= b->lines.size())
670 : {
671 0 : cx = b->lines[i].len;
672 0 : lines[cy].prepend(b->lines[i].text);
673 : }
674 0 : else if(maxy < 0 || static_cast<int>(lines.size()) < maxy)
675 : {
676 0 : lines.insert(lines.begin() + cy++, EditLine(b->lines[i].text));
677 : }
678 : }
679 : }
680 : }
681 :
682 0 : void Editor::scrollup()
683 : {
684 0 : cy--;
685 0 : }
686 :
687 0 : void Editor::scrolldown()
688 : {
689 0 : cy++;
690 0 : }
691 :
692 0 : void Editor::key(int code)
693 : {
694 0 : switch(code)
695 : {
696 0 : case SDLK_UP:
697 : {
698 0 : if(linewrap)
699 : {
700 : int x, y;
701 0 : const char *str = currentline().text;
702 0 : text_pos(str, cx+1, x, y, pixelwidth);
703 0 : if(y > 0)
704 : {
705 0 : cx = text_visible(str, x, y-FONTH, pixelwidth);
706 0 : break;
707 : }
708 : }
709 0 : cy--;
710 0 : break;
711 : }
712 0 : case SDLK_DOWN:
713 : {
714 0 : if(linewrap)
715 : {
716 : int x, y, width, height;
717 0 : const char *str = currentline().text;
718 0 : text_pos(str, cx, x, y, pixelwidth);
719 0 : text_bounds(str, width, height, pixelwidth);
720 0 : y += FONTH;
721 0 : if(y < height)
722 : {
723 0 : cx = text_visible(str, x, y, pixelwidth);
724 0 : break;
725 : }
726 : }
727 0 : cy++;
728 0 : break;
729 : }
730 0 : case SDLK_PAGEUP:
731 : {
732 0 : cy-=pixelheight/FONTH;
733 0 : break;
734 : }
735 0 : case SDLK_PAGEDOWN:
736 : {
737 0 : cy+=pixelheight/FONTH;
738 0 : break;
739 : }
740 0 : case SDLK_HOME:
741 : {
742 0 : cx = cy = 0;
743 0 : break;
744 : }
745 0 : case SDLK_END:
746 : {
747 0 : cx = cy = INT_MAX;
748 0 : break;
749 : }
750 0 : case SDLK_LEFT:
751 : {
752 0 : cx--;
753 0 : break;
754 : }
755 0 : case SDLK_RIGHT:
756 : {
757 0 : cx++;
758 0 : break;
759 : }
760 0 : case SDLK_DELETE:
761 : {
762 0 : if(!del())
763 : {
764 0 : EditLine ¤t = currentline();
765 0 : if(cx < current.len)
766 : {
767 0 : current.del(cx, 1);
768 : }
769 0 : else if(cy < static_cast<int>(lines.size())-1)
770 : { //combine with next line
771 0 : current.append(lines[cy+1].text);
772 0 : removelines(cy+1, 1);
773 : }
774 : }
775 0 : break;
776 : }
777 0 : case SDLK_BACKSPACE:
778 : {
779 0 : if(!del())
780 : {
781 0 : EditLine ¤t = currentline();
782 0 : if(cx > 0)
783 : {
784 0 : current.del(--cx, 1);
785 : }
786 0 : else if(cy > 0)
787 : { //combine with previous line
788 0 : cx = lines[cy-1].len;
789 0 : lines[cy-1].append(current.text);
790 0 : removelines(cy--, 1);
791 : }
792 : }
793 0 : break;
794 : }
795 0 : case SDLK_LSHIFT:
796 : case SDLK_RSHIFT:
797 : {
798 0 : break;
799 : }
800 0 : case SDLK_RETURN:
801 : {
802 0 : insert('\n');
803 0 : break;
804 : }
805 0 : case SDLK_TAB:
806 : {
807 0 : insert('\t');
808 0 : break;
809 : }
810 : }
811 0 : }
812 :
813 0 : void Editor::input(const char *str, int len)
814 : {
815 0 : for(int i = 0; i < len; ++i)
816 : {
817 0 : insert(str[i]);
818 : }
819 0 : }
820 :
821 0 : void Editor::hit(int hitx, int hity, bool dragged)
822 : {
823 0 : int maxwidth = linewrap?pixelwidth:-1,
824 0 : h = 0;
825 0 : for(size_t i = scrolly; i < lines.size(); i++)
826 : {
827 : int width, height;
828 0 : text_bounds(lines[i].text, width, height, maxwidth);
829 0 : if(h + height > pixelheight)
830 : {
831 0 : break;
832 : }
833 0 : if(hity >= h && hity <= h+height)
834 : {
835 0 : int x = text_visible(lines[i].text, hitx, hity-h, maxwidth);
836 0 : if(dragged)
837 : {
838 0 : mx = x;
839 0 : my = i;
840 : }
841 : else
842 : {
843 0 : cx = x;
844 0 : cy = i;
845 : };
846 0 : break;
847 : }
848 0 : h+=height;
849 : }
850 0 : }
851 :
852 0 : void Editor::draw(int x, int y, int color)
853 : {
854 0 : int maxwidth = linewrap?pixelwidth:-1,
855 : sx, sy, ex, ey;
856 0 : bool selection = region(sx, sy, ex, ey);
857 : // fix scrolly so that <cx, cy> is always on screen
858 0 : if(cy < scrolly)
859 : {
860 0 : scrolly = cy;
861 : }
862 : else
863 : {
864 0 : if(scrolly < 0)
865 : {
866 0 : scrolly = 0;
867 : }
868 0 : int h = 0;
869 0 : for(int i = cy; i >= scrolly; i--)
870 : {
871 : int width, height;
872 0 : text_bounds(lines[i].text, width, height, maxwidth);
873 0 : if(h + height > pixelheight)
874 : {
875 0 : scrolly = i+1;
876 0 : break;
877 : }
878 0 : h += height;
879 : }
880 : }
881 :
882 0 : if(selection)
883 : {
884 : // convert from cursor coords into pixel coords
885 : int psx, psy, pex, pey;
886 0 : text_pos(lines[sy].text, sx, psx, psy, maxwidth);
887 0 : text_pos(lines[ey].text, ex, pex, pey, maxwidth);
888 0 : int maxy = static_cast<int>(lines.size()),
889 0 : h = 0;
890 0 : for(int i = scrolly; i < maxy; i++)
891 : {
892 : int width, height;
893 0 : text_bounds(lines[i].text, width, height, maxwidth);
894 0 : if(h + height > pixelheight)
895 : {
896 0 : maxy = i;
897 0 : break;
898 : }
899 0 : if(i == sy)
900 : {
901 0 : psy += h;
902 : }
903 0 : if(i == ey)
904 : {
905 0 : pey += h;
906 0 : break;
907 : }
908 0 : h += height;
909 : }
910 0 : maxy--;
911 0 : if(ey >= scrolly && sy <= maxy)
912 : {
913 : // crop top/bottom within window
914 0 : if(sy < scrolly)
915 : {
916 0 : sy = scrolly;
917 0 : psy = 0;
918 0 : psx = 0;
919 : }
920 0 : if(ey > maxy)
921 : {
922 0 : ey = maxy;
923 0 : pey = pixelheight - FONTH;
924 0 : pex = pixelwidth;
925 : }
926 0 : hudnotextureshader->set();
927 0 : gle::colorub(0xA0, 0x80, 0x80);
928 0 : gle::defvertex(2);
929 0 : gle::begin(GL_TRIANGLE_FAN);
930 0 : if(psy == pey)
931 : {
932 0 : gle::attribf(x+psx, y+psy);
933 0 : gle::attribf(x+pex, y+psy);
934 0 : gle::attribf(x+pex, y+pey+FONTH);
935 0 : gle::attribf(x+psx, y+pey+FONTH);
936 : }
937 : else
938 0 : { gle::attribf(x+psx, y+psy);
939 0 : gle::attribf(x+psx, y+psy+FONTH);
940 0 : gle::attribf(x+pixelwidth, y+psy+FONTH);
941 0 : gle::attribf(x+pixelwidth, y+psy);
942 0 : if(pey-psy > FONTH)
943 : {
944 0 : gle::attribf(x, y+psy+FONTH);
945 0 : gle::attribf(x+pixelwidth, y+psy+FONTH);
946 0 : gle::attribf(x+pixelwidth, y+pey);
947 0 : gle::attribf(x, y+pey);
948 : }
949 0 : gle::attribf(x, y+pey);
950 0 : gle::attribf(x, y+pey+FONTH);
951 0 : gle::attribf(x+pex, y+pey+FONTH);
952 0 : gle::attribf(x+pex, y+pey);
953 : }
954 0 : gle::end();
955 : }
956 : }
957 :
958 0 : int h = 0;
959 0 : for(size_t i = scrolly; i < lines.size(); i++)
960 : {
961 : int width, height;
962 0 : text_bounds(lines[i].text, width, height, maxwidth);
963 0 : if(h + height > pixelheight)
964 : {
965 0 : break;
966 : }
967 : //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);
968 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);
969 0 : if(linewrap && height > FONTH) // line wrap indicator
970 : {
971 0 : hudnotextureshader->set();
972 0 : gle::colorub(0x80, 0xA0, 0x80);
973 0 : gle::defvertex(2);
974 0 : gle::begin(GL_TRIANGLE_STRIP);
975 0 : gle::attribf(x, y+h+FONTH);
976 0 : gle::attribf(x, y+h+height);
977 0 : gle::attribf(x-fontwidth()/2, y+h+FONTH);
978 0 : gle::attribf(x-fontwidth()/2, y+h+height);
979 0 : gle::end();
980 : }
981 0 : h+=height;
982 : }
983 0 : }
984 :
985 : // global
986 :
987 : static std::vector<Editor *> editors;
988 : static Editor *textfocus = nullptr;
989 :
990 0 : void readyeditors()
991 : {
992 0 : for(Editor * i : editors)
993 : {
994 0 : i->active = (i->mode==Editor_Forever);
995 : }
996 0 : }
997 :
998 0 : void flusheditors()
999 : {
1000 0 : for(int i = editors.size(); --i >=0;) //note reverse iteration
1001 : {
1002 0 : if(!editors[i]->active)
1003 : {
1004 0 : Editor *e = editors.at(i);
1005 0 : editors.erase(editors.begin() + i);
1006 0 : if(e == textfocus)
1007 : {
1008 0 : textfocus = nullptr;
1009 : }
1010 0 : delete e;
1011 : }
1012 : }
1013 0 : }
1014 :
1015 0 : Editor *useeditor(std::string name, int mode, bool focus, const char *initval)
1016 : {
1017 0 : for(Editor *i : editors)
1018 : {
1019 0 : if(i->name == name)
1020 : {
1021 0 : if(focus)
1022 : {
1023 0 : textfocus = i;
1024 : }
1025 0 : i->active = true;
1026 0 : return i;
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(size_t 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_view name, const char *file, const 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 : static 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 2 : addcommand("textshow", reinterpret_cast<identfun>(+[] ()
1138 : {
1139 1 : if(!textfocus || identflags&Idf_Overridden)
1140 : {
1141 1 : return; /* @DEBUG return the start of the buffer*/
1142 : }
1143 0 : EditLine line;
1144 0 : line.combinelines(textfocus->lines);
1145 0 : result(line.text); line.clear();
1146 2 : }), "", Id_Command);
1147 2 : addcommand("textfocus", reinterpret_cast<identfun>(textfocuscmd), "si", Id_Command);
1148 2 : addcommand("textprev", reinterpret_cast<identfun>(+[] ()
1149 : {
1150 1 : if(!textfocus || identflags&Idf_Overridden) return;
1151 0 : editors.insert(editors.begin(), textfocus);
1152 0 : editors.pop_back();
1153 2 : }), "", Id_Command); // return to the previous editor
1154 2 : addcommand("textmode", reinterpret_cast<identfun>(+[] (const int *m)
1155 : {
1156 1 : if(!textfocus || identflags&Idf_Overridden)
1157 : {
1158 1 : return;
1159 : }
1160 : /* (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*/
1161 0 : if(*m)
1162 : {
1163 0 : textfocus->mode = *m;
1164 : }
1165 : else
1166 : {
1167 0 : intret(textfocus->mode);
1168 : };
1169 2 : }), "i", Id_Command);
1170 2 : addcommand("textsave", reinterpret_cast<identfun>(textsave), "s", Id_Command);
1171 2 : addcommand("textload", reinterpret_cast<identfun>(textload), "s", Id_Command);
1172 2 : addcommand("textcopy", reinterpret_cast<identfun>(+[] ()
1173 : {
1174 1 : if(!textfocus || identflags&Idf_Overridden)
1175 : {
1176 1 : return;
1177 : }
1178 0 : Editor *b = useeditor(pastebuffer, Editor_Forever, false);
1179 0 : textfocus->copyselectionto(b);
1180 2 : }), "", Id_Command);
1181 2 : addcommand("textpaste", reinterpret_cast<identfun>(+[] ()
1182 : {
1183 1 : if(!textfocus || identflags&Idf_Overridden)
1184 : {
1185 1 : return;
1186 : }
1187 0 : const Editor *b = useeditor(pastebuffer, Editor_Forever, false);
1188 0 : textfocus->insertallfrom(b);
1189 2 : }), "", Id_Command);
1190 2 : addcommand("textmark", reinterpret_cast<identfun>(+[] (const int *m)
1191 : {
1192 1 : if(!textfocus || identflags&Idf_Overridden)
1193 : {
1194 1 : return;
1195 : }
1196 : /* (1=mark, 2=unmark), return current mark setting if no args*/
1197 0 : if(*m)
1198 : {
1199 0 : textfocus->mark(*m==1);
1200 : }
1201 : else
1202 : {
1203 0 : intret(textfocus->region() ? 1 : 2);
1204 : };
1205 2 : }), "i", Id_Command);
1206 2 : addcommand("textselectall", reinterpret_cast<identfun>(+[] ()
1207 : {
1208 1 : if(!textfocus || identflags&Idf_Overridden)
1209 : {
1210 1 : return;
1211 : }
1212 0 : textfocus->selectall();
1213 2 : }), "", Id_Command);
1214 2 : addcommand("textclear", reinterpret_cast<identfun>(+[] ()
1215 : {
1216 1 : if(!textfocus || identflags&Idf_Overridden)
1217 : {
1218 1 : return;
1219 : }
1220 0 : textfocus->clear();
1221 2 : }), "", Id_Command);
1222 2 : addcommand("textcurrentline", reinterpret_cast<identfun>(+[] ()
1223 : {
1224 1 : if(!textfocus || identflags&Idf_Overridden)
1225 : {
1226 1 : return;
1227 : }
1228 0 : result(textfocus->currentline().text);
1229 2 : }), "", Id_Command);
1230 2 : addcommand("textexec", reinterpret_cast<identfun>(+[] (const int *selected)
1231 : {
1232 1 : if(!textfocus || identflags&Idf_Overridden)
1233 : {
1234 1 : return;
1235 : }
1236 : /* execute script commands from the buffer (0=all, 1=selected region only)*/
1237 0 : const char *script = *selected ? textfocus->selectiontostring() : textfocus->tostring();
1238 0 : execute(script);
1239 0 : delete[] script;
1240 2 : }), "i", Id_Command);
1241 2 : }
|