1 package messeji
2
3 import (
4 "bytes"
5 "image"
6 "image/color"
7 "math"
8 "runtime/debug"
9 "strings"
10 "sync"
11 "time"
12 "unicode"
13
14 "github.com/hajimehoshi/ebiten/v2"
15 "github.com/hajimehoshi/ebiten/v2/inpututil"
16 "github.com/hajimehoshi/ebiten/v2/text"
17 "golang.org/x/image/font"
18 "golang.org/x/image/math/fixed"
19 )
20
21
22 type Alignment int
23
24 const (
25
26 AlignStart Alignment = 0
27
28
29 AlignCenter Alignment = 1
30
31
32 AlignEnd Alignment = 2
33 )
34
35 const (
36 initialPadding = 5
37 initialScrollWidth = 32
38 )
39
40 var (
41 initialForeground = color.RGBA{0, 0, 0, 255}
42 initialBackground = color.RGBA{255, 255, 255, 255}
43 initialScrollArea = color.RGBA{200, 200, 200, 255}
44 initialScrollHandle = color.RGBA{108, 108, 108, 255}
45 )
46
47
48
49
50
51
52 type TextField struct {
53
54 r image.Rectangle
55
56
57 buffer [][]byte
58
59
60 incoming []byte
61
62
63 prefix string
64
65
66 suffix string
67
68
69 wordWrap bool
70
71
72 bufferWrapped []string
73
74
75
76 wrapStart int
77
78
79 needWrap int
80
81
82 wrapScrollBar bool
83
84
85
86 bufferSize int
87
88
89 lineWidths []int
90
91
92 singleLine bool
93
94
95 horizontal Alignment
96
97
98 vertical Alignment
99
100
101 face font.Face
102
103
104 faceMutex *sync.Mutex
105
106
107 lineHeight int
108
109
110 overrideLineHeight int
111
112
113 lineOffset int
114
115
116 textColor color.RGBA
117
118
119 backgroundColor color.RGBA
120
121
122 padding int
123
124
125
126 follow bool
127
128
129 overflow bool
130
131
132 offset int
133
134
135 handleKeyboard bool
136
137
138
139 modified bool
140
141
142 scrollRect image.Rectangle
143
144
145 scrollWidth int
146
147
148 scrollAreaColor color.RGBA
149
150
151 scrollHandleColor color.RGBA
152
153
154 scrollBorderSize int
155
156
157 scrollBorderTop color.RGBA
158 scrollBorderRight color.RGBA
159 scrollBorderBottom color.RGBA
160 scrollBorderLeft color.RGBA
161
162
163 scrollVisible bool
164
165
166
167 scrollAutoHide bool
168
169
170 scrollDrag bool
171
172
173 maskRune rune
174
175
176 img *ebiten.Image
177
178
179 visible bool
180
181
182 redraw bool
183
184
185 keyBuffer []ebiten.Key
186
187
188 runeBuffer []rune
189
190 sync.Mutex
191 }
192
193
194 func NewTextField(face font.Face, faceMutex *sync.Mutex) *TextField {
195 if faceMutex == nil {
196 faceMutex = &sync.Mutex{}
197 }
198
199 f := &TextField{
200 face: face,
201 faceMutex: faceMutex,
202 textColor: initialForeground,
203 backgroundColor: initialBackground,
204 padding: initialPadding,
205 scrollWidth: initialScrollWidth,
206 scrollAreaColor: initialScrollArea,
207 scrollHandleColor: initialScrollHandle,
208 follow: true,
209 wordWrap: true,
210 scrollVisible: true,
211 scrollAutoHide: true,
212 visible: true,
213 redraw: true,
214 }
215
216 f.faceMutex.Lock()
217 defer f.faceMutex.Unlock()
218
219 f.fontUpdated()
220 return f
221 }
222
223
224 func (f *TextField) Rect() image.Rectangle {
225 f.Lock()
226 defer f.Unlock()
227
228 return f.r
229 }
230
231
232 func (f *TextField) SetRect(r image.Rectangle) {
233 f.Lock()
234 defer f.Unlock()
235
236 if f.r.Eq(r) {
237 return
238 }
239
240 if f.r.Dx() != r.Dx() || f.r.Dy() != r.Dy() {
241 f.bufferWrapped = f.bufferWrapped[:0]
242 f.lineWidths = f.lineWidths[:0]
243 f.needWrap = 0
244 f.wrapStart = 0
245 f.modified = true
246 }
247
248 f.r = r
249 }
250
251
252 func (f *TextField) Text() string {
253 f.Lock()
254 defer f.Unlock()
255
256 f.processIncoming()
257
258 return string(bytes.Join(f.buffer, []byte("\n")))
259 }
260
261
262 func (f *TextField) SetText(text string) {
263 f.Lock()
264 defer f.Unlock()
265
266 f.buffer = f.buffer[:0]
267 f.bufferWrapped = f.bufferWrapped[:0]
268 f.lineWidths = f.lineWidths[:0]
269 f.needWrap = 0
270 f.wrapStart = 0
271 f.incoming = append(f.incoming[:0], []byte(text)...)
272 f.modified = true
273 f.redraw = true
274 }
275
276
277 func (f *TextField) SetPrefix(text string) {
278 f.Lock()
279 defer f.Unlock()
280
281 f.prefix = text
282 f.needWrap = 0
283 f.wrapStart = 0
284 f.modified = true
285 }
286
287
288 func (f *TextField) SetSuffix(text string) {
289 f.Lock()
290 defer f.Unlock()
291
292 f.suffix = text
293 f.needWrap = 0
294 f.wrapStart = 0
295 f.modified = true
296 }
297
298
299
300 func (f *TextField) SetFollow(follow bool) {
301 f.Lock()
302 defer f.Unlock()
303
304 f.follow = follow
305 }
306
307
308
309 func (f *TextField) SetSingleLine(single bool) {
310 f.Lock()
311 defer f.Unlock()
312
313 if f.singleLine == single {
314 return
315 }
316
317 f.singleLine = single
318 f.needWrap = 0
319 f.wrapStart = 0
320 f.modified = true
321 }
322
323
324 func (f *TextField) SetHorizontal(h Alignment) {
325 f.Lock()
326 defer f.Unlock()
327
328 if f.horizontal == h {
329 return
330 }
331
332 f.horizontal = h
333 f.needWrap = 0
334 f.wrapStart = 0
335 f.modified = true
336 }
337
338
339 func (f *TextField) SetVertical(v Alignment) {
340 f.Lock()
341 defer f.Unlock()
342
343 if f.vertical == v {
344 return
345 }
346
347 f.vertical = v
348 f.needWrap = 0
349 f.wrapStart = 0
350 f.modified = true
351 }
352
353
354 func (f *TextField) LineHeight() int {
355 f.Lock()
356 defer f.Unlock()
357
358 if f.overrideLineHeight != 0 {
359 return f.overrideLineHeight
360 }
361 return f.lineHeight
362 }
363
364
365
366 func (f *TextField) SetLineHeight(height int) {
367 f.Lock()
368 defer f.Unlock()
369
370 f.overrideLineHeight = height
371 f.needWrap = 0
372 f.wrapStart = 0
373 f.modified = true
374 }
375
376
377 func (f *TextField) ForegroundColor() color.RGBA {
378 f.Lock()
379 defer f.Unlock()
380
381 return f.textColor
382 }
383
384
385 func (f *TextField) SetForegroundColor(c color.RGBA) {
386 f.Lock()
387 defer f.Unlock()
388
389 f.textColor = c
390 f.modified = true
391 }
392
393
394 func (f *TextField) SetBackgroundColor(c color.RGBA) {
395 f.Lock()
396 defer f.Unlock()
397
398 f.backgroundColor = c
399 f.modified = true
400 }
401
402
403 func (f *TextField) SetFont(face font.Face, mutex *sync.Mutex) {
404 if mutex == nil {
405 mutex = &sync.Mutex{}
406 }
407
408 f.Lock()
409 defer f.Unlock()
410
411 mutex.Lock()
412 defer mutex.Unlock()
413
414 f.face = face
415 f.faceMutex = mutex
416 f.fontUpdated()
417
418 f.needWrap = 0
419 f.wrapStart = 0
420 f.modified = true
421 }
422
423
424 func (f *TextField) Padding() int {
425 f.Lock()
426 defer f.Unlock()
427
428 return f.padding
429 }
430
431
432 func (f *TextField) SetPadding(padding int) {
433 f.Lock()
434 defer f.Unlock()
435
436 f.padding = padding
437 f.needWrap = 0
438 f.wrapStart = 0
439 f.modified = true
440 }
441
442
443 func (f *TextField) Visible() bool {
444 return f.visible
445 }
446
447
448 func (f *TextField) SetVisible(visible bool) {
449 f.Lock()
450 defer f.Unlock()
451
452 if f.visible == visible {
453 return
454 }
455
456 f.visible = visible
457 if visible {
458 f.redraw = true
459 }
460 }
461
462
463 func (f *TextField) SetScrollBarWidth(width int) {
464 f.Lock()
465 defer f.Unlock()
466
467 if f.scrollWidth == width {
468 return
469 }
470
471 f.scrollWidth = width
472 f.needWrap = 0
473 f.wrapStart = 0
474 f.modified = true
475 }
476
477
478 func (f *TextField) SetScrollBarColors(area color.RGBA, handle color.RGBA) {
479 f.Lock()
480 defer f.Unlock()
481
482 f.scrollAreaColor, f.scrollHandleColor = area, handle
483 f.redraw = true
484 }
485
486
487 func (f *TextField) SetScrollBorderSize(size int) {
488 f.Lock()
489 defer f.Unlock()
490
491 f.scrollBorderSize = size
492 f.redraw = true
493 }
494
495
496
497 func (f *TextField) SetScrollBorderColors(top color.RGBA, right color.RGBA, bottom color.RGBA, left color.RGBA) {
498 f.Lock()
499 defer f.Unlock()
500
501 f.scrollBorderTop = top
502 f.scrollBorderRight = right
503 f.scrollBorderBottom = bottom
504 f.scrollBorderLeft = left
505 f.redraw = true
506 }
507
508
509 func (f *TextField) SetScrollBarVisible(scrollVisible bool) {
510 f.Lock()
511 defer f.Unlock()
512
513 if f.scrollVisible == scrollVisible {
514 return
515 }
516
517 f.scrollVisible = scrollVisible
518 f.needWrap = 0
519 f.wrapStart = 0
520 f.modified = true
521 }
522
523
524
525 func (f *TextField) SetAutoHideScrollBar(autoHide bool) {
526 f.Lock()
527 defer f.Unlock()
528
529 if f.scrollAutoHide == autoHide {
530 return
531 }
532
533 f.scrollAutoHide = autoHide
534 f.needWrap = 0
535 f.wrapStart = 0
536 f.modified = true
537 }
538
539
540 func (f *TextField) WordWrap() bool {
541 f.Lock()
542 defer f.Unlock()
543
544 return f.wordWrap
545 }
546
547
548 func (f *TextField) SetWordWrap(wrap bool) {
549 f.Lock()
550 defer f.Unlock()
551
552 if f.wordWrap == wrap {
553 return
554 }
555
556 f.wordWrap = wrap
557 f.needWrap = 0
558 f.wrapStart = 0
559 f.modified = true
560 }
561
562
563
564 func (f *TextField) SetHandleKeyboard(handle bool) {
565 f.Lock()
566 defer f.Unlock()
567
568 f.handleKeyboard = handle
569 }
570
571
572 func (f *TextField) SetMask(r rune) {
573 f.Lock()
574 defer f.Unlock()
575
576 if f.maskRune == r {
577 return
578 }
579
580 f.maskRune = r
581 f.modified = true
582 }
583
584
585 func (f *TextField) Write(p []byte) (n int, err error) {
586 f.Lock()
587 defer f.Unlock()
588
589 return f._write(p)
590 }
591
592 func (f *TextField) _write(p []byte) (n int, err error) {
593 f.incoming = append(f.incoming, p...)
594 f.modified = true
595 f.redraw = true
596 return len(p), nil
597 }
598
599
600 func (f *TextField) HandleKeyboardEvent(key ebiten.Key, r rune) (handled bool, err error) {
601 f.Lock()
602 defer f.Unlock()
603
604 if !f.visible || rectIsZero(f.r) || !f.handleKeyboard {
605 return false, nil
606 }
607
608 return f._handleKeyboardEvent(key, r)
609 }
610
611 func (f *TextField) _handleKeyboardEvent(key ebiten.Key, r rune) (handled bool, err error) {
612 if key != -1 {
613
614 offsetAmount := 0
615 switch key {
616 case ebiten.KeyPageUp:
617 offsetAmount = 100
618 case ebiten.KeyPageDown:
619 offsetAmount = -100
620 }
621 if offsetAmount != 0 {
622 f.offset += offsetAmount
623 f.clampOffset()
624 f.redraw = true
625 return true, nil
626 }
627 return true, err
628 }
629 return true, nil
630 }
631
632 func (f *TextField) HandleMouseEvent(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
633 f.Lock()
634 defer f.Unlock()
635
636 if !f.visible || rectIsZero(f.r) {
637 return false, nil
638 }
639
640 return f._handleMouseEvent(cursor, pressed, clicked)
641 }
642
643 func (f *TextField) _handleMouseEvent(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
644 if !cursor.In(f.r) {
645 return false, nil
646 }
647
648
649 _, scrollY := ebiten.Wheel()
650 if scrollY != 0 {
651 const offsetAmount = 25
652 f.offset += int(scrollY * offsetAmount)
653 f.clampOffset()
654 f.redraw = true
655 }
656
657
658 if !f.showScrollBar() {
659 return true, nil
660 } else if pressed || f.scrollDrag {
661 p := image.Point{cursor.X - f.r.Min.X, cursor.Y - f.r.Min.Y}
662 if pressed && p.In(f.scrollRect) {
663 dragY := cursor.Y - f.r.Min.Y - f.scrollWidth/4
664 if dragY < 0 {
665 dragY = 0
666 } else if dragY > f.scrollRect.Dy() {
667 dragY = f.scrollRect.Dy()
668 }
669
670 pct := float64(dragY) / float64(f.scrollRect.Dy()-f.scrollWidth/2)
671 if pct < 0 {
672 pct = 0
673 } else if pct > 1 {
674 pct = 1
675 }
676
677 h := f.r.Dy()
678 f.offset = -int(float64(f.bufferSize-h-f.lineOffset+f.padding*2) * pct)
679 f.clampOffset()
680
681 f.redraw = true
682 f.scrollDrag = true
683 } else if !pressed {
684 f.scrollDrag = false
685 }
686 }
687 return true, nil
688 }
689
690
691
692 func (f *TextField) Update() error {
693 f.Lock()
694 defer f.Unlock()
695
696 if !f.visible || rectIsZero(f.r) {
697 return nil
698 }
699
700 f.keyBuffer = inpututil.AppendJustPressedKeys(f.keyBuffer[:0])
701 for _, key := range f.keyBuffer {
702 handled, err := f._handleKeyboardEvent(key, 0)
703 if err != nil {
704 return err
705 } else if handled {
706 f.redraw = true
707 }
708 }
709
710 f.runeBuffer = ebiten.AppendInputChars(f.runeBuffer[:0])
711 for _, r := range f.runeBuffer {
712 handled, err := f._handleKeyboardEvent(-1, r)
713 if err != nil {
714 return err
715 } else if handled {
716 f.redraw = true
717 }
718 }
719
720 cx, cy := ebiten.CursorPosition()
721 if cx != 0 || cy != 0 {
722 handled, err := f._handleMouseEvent(image.Point{X: cx, Y: cy}, ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft), inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft))
723 if err != nil {
724 return err
725 } else if handled {
726 f.redraw = true
727 }
728 }
729
730 return nil
731 }
732
733
734
735 func (f *TextField) Draw(screen *ebiten.Image) {
736 f.Lock()
737 defer f.Unlock()
738
739 if f.modified {
740 f.faceMutex.Lock()
741
742 f.bufferModified()
743 f.modified = false
744
745 f.faceMutex.Unlock()
746 }
747
748 if !f.visible || rectIsZero(f.r) {
749 return
750 }
751
752 if f.redraw {
753 f.faceMutex.Lock()
754
755 f.drawImage()
756 f.redraw = false
757
758 f.faceMutex.Unlock()
759 }
760
761 op := &ebiten.DrawImageOptions{}
762 op.GeoM.Translate(float64(f.r.Min.X), float64(f.r.Min.Y))
763 screen.DrawImage(f.img, op)
764 }
765
766 func (f *TextField) fontUpdated() {
767 m := f.face.Metrics()
768 f.lineHeight = m.Height.Ceil()
769 f.lineOffset = m.CapHeight.Ceil()
770 if f.lineOffset < 0 {
771 f.lineOffset *= -1
772 }
773 }
774
775 func (f *TextField) wrapContent(withScrollBar bool) {
776 if withScrollBar != f.wrapScrollBar {
777 f.needWrap = 0
778 f.wrapStart = 0
779 } else if f.needWrap == -1 {
780 return
781 }
782 f.wrapScrollBar = withScrollBar
783
784 if f.singleLine || len(f.buffer) == 0 {
785 buffer := f.prefix + string(bytes.Join(f.buffer, nil)) + f.suffix
786 bounds, _ := boundString(f.face, buffer)
787
788 f.bufferWrapped = []string{buffer}
789 f.wrapStart = 0
790 f.lineWidths = append(f.lineWidths[:0], (bounds.Max.X - bounds.Min.X).Floor())
791
792 f.needWrap = -1
793 return
794 }
795
796 w := f.r.Dx()
797 if withScrollBar {
798 w -= f.scrollWidth
799 }
800 bufferLen := len(f.buffer)
801 j := f.wrapStart
802 for i := f.needWrap; i < bufferLen; i++ {
803 var line string
804 if i == 0 {
805 line = f.prefix + string(f.buffer[i])
806 } else {
807 line = string(f.buffer[i])
808 }
809 if i == bufferLen-1 {
810 line += f.suffix
811 }
812 l := len(line)
813 availableWidth := w - (f.padding * 2)
814
815 f.wrapStart = j
816
817
818 if strings.TrimSpace(line) == "" {
819 if len(f.bufferWrapped) <= j {
820 f.bufferWrapped = append(f.bufferWrapped, "")
821 } else {
822 f.bufferWrapped[j] = ""
823 }
824 if len(f.lineWidths) <= j {
825 f.lineWidths = append(f.lineWidths, 0)
826 } else {
827 f.lineWidths[j] = 0
828 }
829 j++
830 continue
831 }
832
833 var start int
834 var end int
835 for start < l {
836 end = l
837 var initialEnd int
838 var bounds fixed.Rectangle26_6
839 var boundsWidth int
840
841
842 for end > start {
843 initialEnd = end
844
845 bounds, _ := boundString(f.face, line[start:end])
846 boundsWidth := (bounds.Max.X - bounds.Min.X).Floor()
847 if boundsWidth > availableWidth && end > start+1 {
848 delta := (end - start) / 2
849 if delta < 1 {
850 delta = 1
851 }
852 end -= delta
853 } else {
854 break
855 }
856 }
857
858
859 lineEnd := end
860 var lastSpace = -1
861 for end < l {
862 initialEnd = end
863
864 bounds, _ := boundString(f.face, line[start:end])
865 boundsWidth := (bounds.Max.X - bounds.Min.X).Floor()
866 if boundsWidth > availableWidth && end > start+1 {
867 break
868 }
869
870 lineEnd = end
871 end++
872 if unicode.IsSpace(rune(line[lineEnd])) {
873 lastSpace = lineEnd
874 }
875 }
876
877
878 if f.wordWrap && lineEnd < l {
879 if lastSpace == -1 {
880
881 end = lineEnd
882 for offset := 1; offset < end-start-2; offset++ {
883 if unicode.IsSpace(rune(line[end-offset])) {
884 lastSpace = end - offset
885 break
886 }
887 }
888 }
889 if lastSpace != -1 {
890 end = lastSpace + 1
891 } else {
892 end = lineEnd
893 }
894 } else {
895 end = lineEnd
896 }
897
898 if boundsWidth == 0 || end != initialEnd {
899 bounds, _ = boundString(f.face, line[start:end])
900 boundsWidth = (bounds.Max.X - bounds.Min.X).Floor()
901 }
902
903 if len(f.bufferWrapped) <= j {
904 f.bufferWrapped = append(f.bufferWrapped, line[start:end])
905 } else {
906 f.bufferWrapped[j] = line[start:end]
907 }
908 if len(f.lineWidths) <= j {
909 f.lineWidths = append(f.lineWidths, boundsWidth)
910 } else {
911 f.lineWidths[j] = boundsWidth
912 }
913 j++
914
915 start = end
916 }
917 }
918
919 if len(f.bufferWrapped) >= j {
920 f.bufferWrapped = f.bufferWrapped[:j]
921 }
922
923 f.needWrap = -1
924 }
925
926
927 func (f *TextField) drawContent() (overflow bool) {
928 if f.backgroundColor.A != 0 {
929 f.img.Fill(f.backgroundColor)
930 } else {
931 f.img.Clear()
932 }
933 fieldWidth := f.r.Dx()
934 fieldHeight := f.r.Dy()
935 if f.showScrollBar() {
936 fieldWidth -= f.scrollWidth
937 }
938 lines := len(f.bufferWrapped)
939
940 h := f.r.Dy()
941 lineHeight := f.overrideLineHeight
942 if lineHeight == 0 {
943 lineHeight = f.lineHeight
944 }
945 var firstVisible, lastVisible int
946 firstVisible = 0
947 lastVisible = len(f.bufferWrapped) - 1
948 if !f.singleLine {
949 firstVisible = (f.offset * -1) / f.lineHeight
950 lastVisible = firstVisible + (f.r.Dy() / f.lineHeight) + 1
951 if lastVisible > len(f.bufferWrapped)-1 {
952 lastVisible = len(f.bufferWrapped) - 1
953 }
954 }
955
956 if f.singleLine {
957 bounds, _ := boundString(f.face, f.bufferWrapped[firstVisible])
958 f.bufferSize = (bounds.Max.X - bounds.Min.X).Floor()
959 } else {
960 f.bufferSize = (len(f.bufferWrapped)) * lineHeight
961 }
962 for i := firstVisible; i <= lastVisible; i++ {
963 line := f.bufferWrapped[i]
964 if f.maskRune != 0 {
965 line = strings.Repeat(string(f.maskRune), len(line))
966 if i == lastVisible && len(line) > 0 && len(line) >= len(f.suffix) {
967 line = line[:len(line)-len(f.suffix)] + f.suffix
968 }
969 }
970 lineX := f.padding
971 lineY := 1 + f.padding + f.lineOffset + lineHeight*i
972
973
974 lineOverflows := lineY < 0 || lineY >= h-(f.padding*2)
975 if lineOverflows {
976 overflow = true
977 }
978
979
980 if lineY < 0 {
981 continue
982 }
983
984
985 if f.singleLine {
986 lineX += f.offset
987 } else {
988 lineY += f.offset
989 }
990
991
992 if f.horizontal == AlignCenter {
993 lineX = (fieldWidth - f.lineWidths[i]) / 2
994 } else if f.horizontal == AlignEnd {
995 lineX = (fieldWidth - f.lineWidths[i]) - f.padding - 1
996 }
997
998
999 totalHeight := f.lineOffset + lineHeight*(lines-1)
1000 if f.vertical == AlignCenter && totalHeight <= h {
1001 lineY = fieldHeight/2 - totalHeight/2 + f.lineOffset + (lineHeight * (i))
1002 } else if f.vertical == AlignEnd && totalHeight <= h {
1003 lineY = (fieldHeight - lineHeight*i) - f.padding
1004 }
1005
1006
1007 text.Draw(f.img, line, f.face, lineX, lineY, f.textColor)
1008 }
1009
1010 return overflow
1011 }
1012
1013 func (f *TextField) clampOffset() {
1014 fieldSize := f.r.Dy()
1015 if f.singleLine {
1016 fieldSize = f.r.Dx()
1017 }
1018 minSize := -(f.bufferSize - fieldSize + f.padding*2)
1019 if !f.singleLine {
1020 minSize += f.lineOffset
1021 }
1022 if minSize > 0 {
1023 minSize = 0
1024 }
1025 maxSize := 0
1026 if f.offset < minSize {
1027 f.offset = minSize
1028 } else if f.offset > maxSize {
1029 f.offset = maxSize
1030 }
1031 }
1032
1033 func (f *TextField) showScrollBar() bool {
1034 return !f.singleLine && f.scrollVisible && (f.overflow || !f.scrollAutoHide)
1035 }
1036
1037 func (f *TextField) wrap() {
1038 showScrollBar := f.showScrollBar()
1039 f.wrapContent(showScrollBar)
1040 f.overflow = f.drawContent()
1041 if f.showScrollBar() != showScrollBar {
1042 f.wrapContent(!showScrollBar)
1043 f.drawContent()
1044 }
1045 }
1046
1047
1048 func (f *TextField) drawImage() {
1049 if rectIsZero(f.r) {
1050 f.img = nil
1051 return
1052 }
1053
1054 w, h := f.r.Dx(), f.r.Dy()
1055
1056 var newImage bool
1057 if f.img == nil {
1058 newImage = true
1059 } else {
1060 imgRect := f.img.Bounds()
1061 imgW, imgH := imgRect.Dx(), imgRect.Dy()
1062 newImage = imgW != w || imgH != h
1063 }
1064 if newImage {
1065 f.img = ebiten.NewImage(w, h)
1066 }
1067
1068 f.wrap()
1069
1070
1071 if f.showScrollBar() {
1072 scrollAreaX, scrollAreaY := w-f.scrollWidth, 0
1073 f.scrollRect = image.Rect(scrollAreaX, scrollAreaY, scrollAreaX+f.scrollWidth, h)
1074
1075 scrollBarH := f.scrollWidth / 2
1076 if scrollBarH < 4 {
1077 scrollBarH = 4
1078 }
1079
1080 scrollX, scrollY := w-f.scrollWidth, 0
1081 pct := float64(-f.offset) / float64(f.bufferSize-h-f.lineOffset+f.padding*2)
1082 scrollY += int(float64(h-scrollBarH) * pct)
1083 scrollBarRect := image.Rect(scrollX, scrollY, scrollX+f.scrollWidth, scrollY+scrollBarH)
1084
1085
1086 f.img.SubImage(f.scrollRect).(*ebiten.Image).Fill(f.scrollAreaColor)
1087
1088
1089 f.img.SubImage(scrollBarRect).(*ebiten.Image).Fill(f.scrollHandleColor)
1090
1091
1092 if f.scrollBorderSize != 0 {
1093 r := scrollBarRect
1094 f.img.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+f.scrollBorderSize, r.Max.Y)).(*ebiten.Image).Fill(f.scrollBorderLeft)
1095 f.img.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+f.scrollBorderSize)).(*ebiten.Image).Fill(f.scrollBorderTop)
1096 f.img.SubImage(image.Rect(r.Max.X-f.scrollBorderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(f.scrollBorderRight)
1097 f.img.SubImage(image.Rect(r.Min.X, r.Max.Y-f.scrollBorderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(f.scrollBorderBottom)
1098 }
1099 }
1100 }
1101
1102 func (f *TextField) processIncoming() {
1103 if len(f.incoming) == 0 {
1104 return
1105 }
1106
1107 line := len(f.buffer) - 1
1108 if line < 0 {
1109 line = 0
1110 f.buffer = append(f.buffer, nil)
1111 }
1112 if f.needWrap == -1 {
1113 f.needWrap = line
1114 }
1115 for _, b := range f.incoming {
1116 if b == '\n' {
1117 line++
1118 f.buffer = append(f.buffer, nil)
1119 continue
1120 }
1121 f.buffer[line] = append(f.buffer[line], b)
1122 }
1123 f.incoming = f.incoming[:0]
1124 }
1125
1126 func (f *TextField) bufferModified() {
1127 f.processIncoming()
1128
1129 f.drawImage()
1130
1131 lastOffset := f.offset
1132 if f.follow {
1133 f.offset = -math.MaxInt
1134 f.clampOffset()
1135 if f.offset != lastOffset {
1136 f.drawImage()
1137 }
1138 }
1139
1140 f.redraw = false
1141 }
1142
1143 func rectIsZero(r image.Rectangle) bool {
1144 return r.Dx() == 0 || r.Dy() == 0
1145 }
1146
1147 func boundString(f font.Face, s string) (bounds fixed.Rectangle26_6, advance fixed.Int26_6) {
1148 if strings.TrimSpace(s) == "" {
1149 return fixed.Rectangle26_6{}, 0
1150 }
1151 for i := 0; i < 100; i++ {
1152 bounds, advance = func() (fixed.Rectangle26_6, fixed.Int26_6) {
1153 defer func() {
1154 err := recover()
1155 if err != nil && i == 99 {
1156 debug.PrintStack()
1157 panic("failed to calculate bounds of string '" + s + "'")
1158 }
1159 }()
1160 bounds, advance = font.BoundString(f, s)
1161 return bounds, advance
1162 }()
1163 if !bounds.Empty() {
1164 return bounds, advance
1165 }
1166 time.Sleep(10 * time.Millisecond)
1167 }
1168 return fixed.Rectangle26_6{}, 0
1169 }
1170
View as plain text