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