...

Source file src/codeberg.org/tslocum/etk/messeji/textfield.go

Documentation: codeberg.org/tslocum/etk/messeji

     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  // Alignment specifies how text is aligned within the field.
    18  type Alignment int
    19  
    20  const (
    21  	// AlignStart aligns text at the start of the field.
    22  	AlignStart Alignment = 0
    23  
    24  	// AlignCenter aligns text at the center of the field.
    25  	AlignCenter Alignment = 1
    26  
    27  	// AlignEnd aligns text at the end of the field.
    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  // TextField is a text display field. Call Update and Draw when your Game's
    45  // Update and Draw methods are called.
    46  //
    47  // Note: A position and size must be set via SetRect before the field will appear.
    48  // Keyboard events are not handled by default, and may be enabled via SetHandleKeyboard.
    49  type TextField struct {
    50  	// r specifies the position and size of the field.
    51  	r image.Rectangle
    52  
    53  	// buffer is the text buffer split by newline characters.
    54  	buffer [][]byte
    55  
    56  	// incoming is text to be written to the buffer that has not yet been wrapped.
    57  	incoming []byte
    58  
    59  	// prefix is the text shown before the content of the field.
    60  	prefix string
    61  
    62  	// suffix is the text shown after the content of the field.
    63  	suffix string
    64  
    65  	// wordWrap determines whether content is wrapped at word boundaries.
    66  	wordWrap bool
    67  
    68  	// bufferWrapped is the content of the field after applying wrapping.
    69  	bufferWrapped []string
    70  
    71  	// wrapStart is the first line number in bufferWrapped which corresponds
    72  	// to the last line number in the actual text buffer.
    73  	wrapStart int
    74  
    75  	// needWrap is the first line number in the actual text buffer that needs to be wrapped.
    76  	needWrap int
    77  
    78  	// wrapScrollBar is whether the scroll bar was visible the last time the field was redrawn.
    79  	wrapScrollBar bool
    80  
    81  	// bufferSize is the size (in pixels) of the entire text buffer. When single
    82  	// line mode is enabled,
    83  	bufferSize int
    84  
    85  	// lineWidths is the size (in pixels) of each line as it appears on the screen.
    86  	lineWidths []int
    87  
    88  	// singleLine is whether the field displays all text on a single line.
    89  	singleLine bool
    90  
    91  	// horizontal is the horizontal alignment of the text within field.
    92  	horizontal Alignment
    93  
    94  	// vertical is the vertical alignment of the text within field.
    95  	vertical Alignment
    96  
    97  	autoResize bool
    98  
    99  	// fontSource is the font face source of the text within the field.
   100  	fontSource *text.GoTextFaceSource
   101  
   102  	// fontFace is the font face of the text within the field.
   103  	fontFace *text.GoTextFace
   104  
   105  	// fontSize is the maximum font size of the text within the field.
   106  	fontSize int
   107  
   108  	// overrideFontSize is the actual font size of the text within the field.
   109  	overrideFontSize int
   110  
   111  	// fontMutex is the lock which is held whenever utilizing the font.
   112  	fontMutex *sync.Mutex
   113  
   114  	// lineHeight is the height of a single line of text.
   115  	lineHeight int
   116  
   117  	// overrideLineHeight is the custom height for a line of text, or 0 to disable.
   118  	overrideLineHeight int
   119  
   120  	// lineOffset is the offset of the baseline current font.
   121  	lineOffset int
   122  
   123  	// textColor is the color of the text within the field.
   124  	textColor color.RGBA
   125  
   126  	// backgroundColor is the color of the background of the field.
   127  	backgroundColor color.RGBA
   128  
   129  	// padding is the amount of padding around the text within the field.
   130  	padding int
   131  
   132  	// follow determines whether the field should automatically scroll to the
   133  	// end when content is added to the buffer.
   134  	follow bool
   135  
   136  	// overflow is whether the content of the field is currently larger than the field.
   137  	overflow bool
   138  
   139  	// offset is the current view offset of the text within the field, relative to the top.
   140  	offset int
   141  
   142  	// handleKeyboard is a flag which, when enabled, causes keyboard input to be handled.
   143  	handleKeyboard bool
   144  
   145  	// modified is a flag which, when enabled, causes bufferModified to be called
   146  	// during the next Draw call.
   147  	modified bool
   148  
   149  	// scrollRect specifies the position and size of the scrolling area.
   150  	scrollRect image.Rectangle
   151  
   152  	// scrollWidth is the width of the scroll bar.
   153  	scrollWidth int
   154  
   155  	// scrollAreaColor is the color of the scroll area.
   156  	scrollAreaColor color.RGBA
   157  
   158  	// scrollHandleColor is the color of the scroll handle.
   159  	scrollHandleColor color.RGBA
   160  
   161  	// scrollBorderSize is the size of the border around the scroll bar handle.
   162  	scrollBorderSize int
   163  
   164  	// Scroll bar handle border colors.
   165  	scrollBorderTop    color.RGBA
   166  	scrollBorderRight  color.RGBA
   167  	scrollBorderBottom color.RGBA
   168  	scrollBorderLeft   color.RGBA
   169  
   170  	// scrollVisible is whether the scroll bar is visible on the screen.
   171  	scrollVisible bool
   172  
   173  	// scrollAutoHide is whether the scroll bar should be automatically hidden
   174  	// when the entire text buffer fits within the screen.
   175  	scrollAutoHide bool
   176  
   177  	// scrollDrag is whether the scroll bar is currently being dragged.
   178  	scrollDrag bool
   179  
   180  	// scrollDragPoint is the point where the field is being dragged directly.
   181  	scrollDragPoint image.Point
   182  
   183  	// scrollDragOffset is the original offset when the field is being dragged directly.
   184  	scrollDragOffset int
   185  
   186  	// maskRune is the rune shown instead of the actual buffer contents.
   187  	maskRune rune
   188  
   189  	// img is the image of the field.
   190  	img *ebiten.Image
   191  
   192  	// visible is whether the field is visible on the screen.
   193  	visible bool
   194  
   195  	// redraw is whether the field needs to be redrawn.
   196  	redraw bool
   197  
   198  	// keyBuffer is a buffer of key press events.
   199  	keyBuffer []ebiten.Key
   200  
   201  	// keyBuffer is a buffer of runes from key presses.
   202  	runeBuffer []rune
   203  
   204  	sync.Mutex
   205  }
   206  
   207  // NewTextField returns a new TextField. See type documentation for more info.
   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  // Rect returns the position and size of the field.
   239  func (f *TextField) Rect() image.Rectangle {
   240  	f.Lock()
   241  	defer f.Unlock()
   242  
   243  	return f.r
   244  }
   245  
   246  // SetRect sets the position and size of the field.
   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  // Text returns the text in the field.
   274  func (f *TextField) Text() string {
   275  	f.Lock()
   276  	defer f.Unlock()
   277  
   278  	return f.text()
   279  }
   280  
   281  // SetText sets the text in the field.
   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  // SetLast sets the text of the last line of the field. Newline characters are
   298  // replaced with spaces.
   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  // SetPrefix sets the text shown before the content of the field.
   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  // SetSuffix sets the text shown after the content of the field.
   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  // SetFollow sets whether the field should automatically scroll to the end when
   347  // content is added to the buffer. When following is enabled, the buffer is
   348  // scrolled to the end. When following is disabled, the buffer is scrolled to
   349  // the top.
   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  // SetSingleLine sets whether the field displays all text on a single line.
   364  // When enabled, the field scrolls horizontally. Otherwise, it scrolls vertically.
   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  // SetHorizontal sets the horizontal alignment of the text within the field.
   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  // SetVertical sets the veritcal alignment of the text within the field.
   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  // LineHeight returns the line height for the field.
   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  // SetLineHeight sets a custom line height for the field. Setting a line
   422  // height of 0 restores the automatic line height detection based on the font.
   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  // ForegroundColor returns the color of the text within the field.
   435  func (f *TextField) ForegroundColor() color.RGBA {
   436  	f.Lock()
   437  	defer f.Unlock()
   438  
   439  	return f.textColor
   440  }
   441  
   442  // SetForegroundColor sets the color of the text within the field.
   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  // SetBackgroundColor sets the color of the background of the field.
   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  // SetFont sets the font face of the text within the field.
   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  // SetAutoResize sets whether the font is automatically scaled down when it is
   484  // too large to fit the entire text buffer on one line.
   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  // Padding returns the amount of padding around the text within the field.
   494  func (f *TextField) Padding() int {
   495  	f.Lock()
   496  	defer f.Unlock()
   497  
   498  	return f.padding
   499  }
   500  
   501  // SetPadding sets the amount of padding around the text within the field.
   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  // Visible returns whether the field is currently visible on the screen.
   514  func (f *TextField) Visible() bool {
   515  	return f.visible
   516  }
   517  
   518  // SetVisible sets whether the field is visible on the screen.
   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  // SetScrollBarWidth sets the width of the scroll bar.
   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  // SetScrollBarColors sets the color of the scroll bar area and handle.
   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  // SetScrollBorderSize sets the size of the border around the scroll bar handle.
   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  // SetScrollBorderColor sets the color of the top, right, bottom and left border
   569  // of the scroll bar handle.
   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  // SetScrollBarVisible sets whether the scroll bar is visible on the screen.
   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  // SetAutoHideScrollBar sets whether the scroll bar is automatically hidden
   598  // when the entire text buffer is visible.
   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  // WordWrap returns the current text wrap mode.
   615  func (f *TextField) WordWrap() bool {
   616  	f.Lock()
   617  	defer f.Unlock()
   618  
   619  	return f.wordWrap
   620  }
   621  
   622  // SetWordWrap sets a flag which, when enabled, causes text to wrap without breaking words.
   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  // SetHandleKeyboard sets a flag controlling whether keyboard input should be handled
   696  // by the field. This can be used to facilitate focus changes between multiple inputs.
   697  func (f *TextField) SetHandleKeyboard(handle bool) {
   698  	f.Lock()
   699  	defer f.Unlock()
   700  
   701  	f.handleKeyboard = handle
   702  }
   703  
   704  // SetMask sets the rune used to mask the text buffer contents. Set to 0 to disable.
   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  // Write writes to the field's buffer.
   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  // HandleKeyboardEvent passes the provided key or rune to the TextField.
   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  		// Handle keyboard PageUp/PageDown.
   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  	// Handle mouse wheel.
   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  	// Handle scroll bar click (and drag).
   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  			// Handle dragging the text field directly.
   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 { // Handle dragging the scroll bar handle.
   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  // Update updates the field. This function should be called when
   846  // Game.Update is called.
   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  // Draw draws the field on the screen. This function should be called
   889  // when Game.Draw is called.
   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  // wrapContent wraps the field content to fit within the available space.
   931  // When word wrap is enabled, text will break at any whitespace character.
   932  // When word wrap is disabled, text will break at any character.
   933  //
   934  // The text wrapping algorithm works as follows:
   935  //
   936  //	Process the content of the text buffer one line at a time.
   937  //	Maintain three cursors into the line of text being processed: line, word and character.
   938  //	Starting from the line cursor, find the next whitespace character.
   939  //	    If a whitespace character is found, measure the text from the line cursor to the whitespace character.
   940  //	    If a whitespace character is not found, measure the text from the line cursor to the end of the line.
   941  //	If the measured text fits, add it to the wrapped text buffer and repeat the above process.
   942  //	If the measured text doesn't fit, measure again from the line cursor to the character cursor.
   943  //	    If the measured text fits, advance the character cursor until it no longer fits, or the end of the line is reached.
   944  //	    If the measured text doesn't fit, add the text up to the character which caused the text to no longer fit.
   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 // Marks the beginning of the remaining line.
   978  	var wordCursor int // Marks the beginning of the word being measured.
   979  	var charCursor int // Marks the position of the character being measured.
   980  	var lineWidth int  // Width of the wrapped line segment so far.
   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  		// BoundString returns 0 for strings containing only whitespace.
  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  					// Break at last word.
  1054  					if f.wordWrap && lineWidth > 0 {
  1055  						saveWrappedLine(line[lineCursor:wordCursor], lineWidth)
  1056  						lineCursor = wordCursor
  1057  						continue WRAPLINE
  1058  					}
  1059  
  1060  					// Break at character of current word.
  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  // drawContent draws the text buffer to img.
  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  	// Calculate buffer size (width for single-line fields or height for multi-line fields).
  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  		// Calculate whether the line overflows the visible area.
  1159  		lineOverflows := lineY < 0 || lineY >= h-f.padding
  1160  		if lineOverflows {
  1161  			overflow = true
  1162  		}
  1163  
  1164  		// Skip drawing off-screen lines.
  1165  		if lineY < -lineHeight {
  1166  			continue
  1167  		}
  1168  
  1169  		// Apply scrolling transformation.
  1170  		if f.singleLine {
  1171  			lineX += f.offset
  1172  		} else {
  1173  			lineY += f.offset
  1174  		}
  1175  
  1176  		// Align horizontally.
  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  		// Align vertically.
  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  		// Draw line.
  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  // drawImage draws the field to img (caching it for future draws).
  1248  func (f *TextField) drawImage() {
  1249  	if rectIsZero(f.r) {
  1250  		f.img = nil
  1251  		return
  1252  	}
  1253  
  1254  	f.wrap()
  1255  
  1256  	// Draw scrollbar.
  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  		// Draw scroll area.
  1279  		f.img.SubImage(f.scrollRect).(*ebiten.Image).Fill(f.scrollAreaColor)
  1280  
  1281  		// Draw scroll handle.
  1282  		f.img.SubImage(scrollBarRect).(*ebiten.Image).Fill(f.scrollHandleColor)
  1283  
  1284  		// Draw scroll handle border.
  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