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