...

Source file src/code.rocket9labs.com/tslocum/etk/messeji/textfield.go

Documentation: code.rocket9labs.com/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  		follow:            true,
   224  		wordWrap:          true,
   225  		scrollVisible:     true,
   226  		scrollAutoHide:    true,
   227  		scrollDragPoint:   image.Point{-1, -1},
   228  		visible:           true,
   229  		redraw:            true,
   230  	}
   231  
   232  	f.fontMutex.Lock()
   233  	defer f.fontMutex.Unlock()
   234  
   235  	f.resizeFont()
   236  	return f
   237  }
   238  
   239  // Rect returns the position and size of the field.
   240  func (f *TextField) Rect() image.Rectangle {
   241  	f.Lock()
   242  	defer f.Unlock()
   243  
   244  	return f.r
   245  }
   246  
   247  // SetRect sets the position and size of the field.
   248  func (f *TextField) SetRect(r image.Rectangle) {
   249  	f.Lock()
   250  	defer f.Unlock()
   251  
   252  	if f.r.Eq(r) {
   253  		return
   254  	}
   255  
   256  	if f.r.Dx() != r.Dx() || f.r.Dy() != r.Dy() {
   257  		f.bufferWrapped = f.bufferWrapped[:0]
   258  		f.lineWidths = f.lineWidths[:0]
   259  		f.needWrap = 0
   260  		f.wrapStart = 0
   261  		f.modified = true
   262  	}
   263  
   264  	f.r = r
   265  	f.resizeFont()
   266  }
   267  
   268  func (f *TextField) text() string {
   269  	f.processIncoming()
   270  	f.resizeFont()
   271  	return string(bytes.Join(f.buffer, []byte("\n")))
   272  }
   273  
   274  // Text returns the text in the field.
   275  func (f *TextField) Text() string {
   276  	f.Lock()
   277  	defer f.Unlock()
   278  
   279  	return f.text()
   280  }
   281  
   282  // SetText sets the text in the field.
   283  func (f *TextField) SetText(text string) {
   284  	f.Lock()
   285  	defer f.Unlock()
   286  
   287  	f.buffer = f.buffer[:0]
   288  	f.bufferWrapped = f.bufferWrapped[:0]
   289  	f.lineWidths = f.lineWidths[:0]
   290  	f.needWrap = 0
   291  	f.wrapStart = 0
   292  	f.incoming = append(f.incoming[:0], []byte(text)...)
   293  	f.modified = true
   294  	f.redraw = true
   295  	f.resizeFont()
   296  }
   297  
   298  // SetLast sets the text of the last line of the field. Newline characters are
   299  // replaced with spaces.
   300  func (f *TextField) SetLast(text string) {
   301  	f.Lock()
   302  	defer f.Unlock()
   303  
   304  	t := bytes.ReplaceAll([]byte(text), []byte("\n"), []byte(" "))
   305  
   306  	f.processIncoming()
   307  
   308  	bufferLen := len(f.buffer)
   309  	if bufferLen == 0 {
   310  		f.incoming = append(f.incoming[:0], t...)
   311  	} else {
   312  		f.buffer[bufferLen-1] = t
   313  		if f.needWrap == -1 || f.needWrap > bufferLen-1 {
   314  			f.needWrap = bufferLen - 1
   315  		}
   316  	}
   317  
   318  	f.modified = true
   319  	f.redraw = true
   320  	f.resizeFont()
   321  }
   322  
   323  // SetPrefix sets the text shown before the content of the field.
   324  func (f *TextField) SetPrefix(text string) {
   325  	f.Lock()
   326  	defer f.Unlock()
   327  
   328  	f.prefix = text
   329  	f.needWrap = 0
   330  	f.wrapStart = 0
   331  	f.modified = true
   332  	f.resizeFont()
   333  }
   334  
   335  // SetSuffix sets the text shown after the content of the field.
   336  func (f *TextField) SetSuffix(text string) {
   337  	f.Lock()
   338  	defer f.Unlock()
   339  
   340  	f.suffix = text
   341  	f.needWrap = 0
   342  	f.wrapStart = 0
   343  	f.modified = true
   344  	f.resizeFont()
   345  }
   346  
   347  // SetFollow sets whether the field should automatically scroll to the end when
   348  // content is added to the buffer.
   349  func (f *TextField) SetFollow(follow bool) {
   350  	f.Lock()
   351  	defer f.Unlock()
   352  
   353  	f.follow = follow
   354  }
   355  
   356  // SetSingleLine sets whether the field displays all text on a single line.
   357  // When enabled, the field scrolls horizontally. Otherwise, it scrolls vertically.
   358  func (f *TextField) SetSingleLine(single bool) {
   359  	f.Lock()
   360  	defer f.Unlock()
   361  
   362  	if f.singleLine == single {
   363  		return
   364  	}
   365  
   366  	f.singleLine = single
   367  	f.needWrap = 0
   368  	f.wrapStart = 0
   369  	f.modified = true
   370  	f.resizeFont()
   371  }
   372  
   373  // SetHorizontal sets the horizontal alignment of the text within the field.
   374  func (f *TextField) SetHorizontal(h Alignment) {
   375  	f.Lock()
   376  	defer f.Unlock()
   377  
   378  	if f.horizontal == h {
   379  		return
   380  	}
   381  
   382  	f.horizontal = h
   383  	f.needWrap = 0
   384  	f.wrapStart = 0
   385  	f.modified = true
   386  }
   387  
   388  // SetVertical sets the veritcal alignment of the text within the field.
   389  func (f *TextField) SetVertical(v Alignment) {
   390  	f.Lock()
   391  	defer f.Unlock()
   392  
   393  	if f.vertical == v {
   394  		return
   395  	}
   396  
   397  	f.vertical = v
   398  	f.needWrap = 0
   399  	f.wrapStart = 0
   400  	f.modified = true
   401  }
   402  
   403  // LineHeight returns the line height for the field.
   404  func (f *TextField) LineHeight() int {
   405  	f.Lock()
   406  	defer f.Unlock()
   407  
   408  	if f.overrideLineHeight != 0 {
   409  		return f.overrideLineHeight
   410  	}
   411  	return f.lineHeight
   412  }
   413  
   414  // SetLineHeight sets a custom line height for the field. Setting a line
   415  // height of 0 restores the automatic line height detection based on the font.
   416  func (f *TextField) SetLineHeight(height int) {
   417  	f.Lock()
   418  	defer f.Unlock()
   419  
   420  	f.overrideLineHeight = height
   421  	f.needWrap = 0
   422  	f.wrapStart = 0
   423  	f.modified = true
   424  	f.resizeFont()
   425  }
   426  
   427  // ForegroundColor returns the color of the text within the field.
   428  func (f *TextField) ForegroundColor() color.RGBA {
   429  	f.Lock()
   430  	defer f.Unlock()
   431  
   432  	return f.textColor
   433  }
   434  
   435  // SetForegroundColor sets the color of the text within the field.
   436  func (f *TextField) SetForegroundColor(c color.RGBA) {
   437  	f.Lock()
   438  	defer f.Unlock()
   439  
   440  	f.textColor = c
   441  	f.modified = true
   442  }
   443  
   444  // SetBackgroundColor sets the color of the background of the field.
   445  func (f *TextField) SetBackgroundColor(c color.RGBA) {
   446  	f.Lock()
   447  	defer f.Unlock()
   448  
   449  	f.backgroundColor = c
   450  	f.modified = true
   451  }
   452  
   453  // SetFont sets the font face of the text within the field.
   454  func (f *TextField) SetFont(faceSource *text.GoTextFaceSource, size int, mutex *sync.Mutex) {
   455  	if mutex == nil {
   456  		mutex = &sync.Mutex{}
   457  	}
   458  
   459  	f.Lock()
   460  	defer f.Unlock()
   461  
   462  	mutex.Lock()
   463  	defer mutex.Unlock()
   464  
   465  	f.fontSource = faceSource
   466  	f.fontSize = size
   467  	f.fontMutex = mutex
   468  	f.overrideFontSize = 0
   469  
   470  	f.needWrap = 0
   471  	f.wrapStart = 0
   472  	f.modified = true
   473  	f.resizeFont()
   474  }
   475  
   476  // SetAutoResize sets whether the font is automatically scaled down when it is
   477  // too large to fit the entire text buffer on one line.
   478  func (f *TextField) SetAutoResize(resize bool) {
   479  	f.Lock()
   480  	defer f.Unlock()
   481  
   482  	f.autoResize = resize
   483  	f.resizeFont()
   484  }
   485  
   486  // Padding returns the amount of padding around the text within the field.
   487  func (f *TextField) Padding() int {
   488  	f.Lock()
   489  	defer f.Unlock()
   490  
   491  	return f.padding
   492  }
   493  
   494  // SetPadding sets the amount of padding around the text within the field.
   495  func (f *TextField) SetPadding(padding int) {
   496  	f.Lock()
   497  	defer f.Unlock()
   498  
   499  	f.padding = padding
   500  	f.needWrap = 0
   501  	f.wrapStart = 0
   502  	f.modified = true
   503  	f.resizeFont()
   504  }
   505  
   506  // Visible returns whether the field is currently visible on the screen.
   507  func (f *TextField) Visible() bool {
   508  	return f.visible
   509  }
   510  
   511  // SetVisible sets whether the field is visible on the screen.
   512  func (f *TextField) SetVisible(visible bool) {
   513  	f.Lock()
   514  	defer f.Unlock()
   515  
   516  	if f.visible == visible {
   517  		return
   518  	}
   519  
   520  	f.visible = visible
   521  	if visible {
   522  		f.redraw = true
   523  	}
   524  }
   525  
   526  // SetScrollBarWidth sets the width of the scroll bar.
   527  func (f *TextField) SetScrollBarWidth(width int) {
   528  	f.Lock()
   529  	defer f.Unlock()
   530  
   531  	if f.scrollWidth == width {
   532  		return
   533  	}
   534  
   535  	f.scrollWidth = width
   536  	f.needWrap = 0
   537  	f.wrapStart = 0
   538  	f.modified = true
   539  	f.resizeFont()
   540  }
   541  
   542  // SetScrollBarColors sets the color of the scroll bar area and handle.
   543  func (f *TextField) SetScrollBarColors(area color.RGBA, handle color.RGBA) {
   544  	f.Lock()
   545  	defer f.Unlock()
   546  
   547  	f.scrollAreaColor, f.scrollHandleColor = area, handle
   548  	f.redraw = true
   549  }
   550  
   551  // SetScrollBorderSize sets the size of the border around the scroll bar handle.
   552  func (f *TextField) SetScrollBorderSize(size int) {
   553  	f.Lock()
   554  	defer f.Unlock()
   555  
   556  	f.scrollBorderSize = size
   557  	f.redraw = true
   558  	f.resizeFont()
   559  }
   560  
   561  // SetScrollBorderColor sets the color of the top, right, bottom and left border
   562  // of the scroll bar handle.
   563  func (f *TextField) SetScrollBorderColors(top color.RGBA, right color.RGBA, bottom color.RGBA, left color.RGBA) {
   564  	f.Lock()
   565  	defer f.Unlock()
   566  
   567  	f.scrollBorderTop = top
   568  	f.scrollBorderRight = right
   569  	f.scrollBorderBottom = bottom
   570  	f.scrollBorderLeft = left
   571  	f.redraw = true
   572  }
   573  
   574  // SetScrollBarVisible sets whether the scroll bar is visible on the screen.
   575  func (f *TextField) SetScrollBarVisible(scrollVisible bool) {
   576  	f.Lock()
   577  	defer f.Unlock()
   578  
   579  	if f.scrollVisible == scrollVisible {
   580  		return
   581  	}
   582  
   583  	f.scrollVisible = scrollVisible
   584  	f.needWrap = 0
   585  	f.wrapStart = 0
   586  	f.modified = true
   587  	f.resizeFont()
   588  }
   589  
   590  // SetAutoHideScrollBar sets whether the scroll bar is automatically hidden
   591  // when the entire text buffer is visible.
   592  func (f *TextField) SetAutoHideScrollBar(autoHide bool) {
   593  	f.Lock()
   594  	defer f.Unlock()
   595  
   596  	if f.scrollAutoHide == autoHide {
   597  		return
   598  	}
   599  
   600  	f.scrollAutoHide = autoHide
   601  	f.needWrap = 0
   602  	f.wrapStart = 0
   603  	f.modified = true
   604  	f.resizeFont()
   605  }
   606  
   607  // WordWrap returns the current text wrap mode.
   608  func (f *TextField) WordWrap() bool {
   609  	f.Lock()
   610  	defer f.Unlock()
   611  
   612  	return f.wordWrap
   613  }
   614  
   615  // SetWordWrap sets a flag which, when enabled, causes text to wrap without breaking words.
   616  func (f *TextField) SetWordWrap(wrap bool) {
   617  	f.Lock()
   618  	defer f.Unlock()
   619  
   620  	if f.wordWrap == wrap {
   621  		return
   622  	}
   623  
   624  	f.wordWrap = wrap
   625  	f.needWrap = 0
   626  	f.wrapStart = 0
   627  	f.modified = true
   628  }
   629  
   630  func (f *TextField) resizeFont() {
   631  	if !f.autoResize {
   632  		if f.overrideFontSize == f.fontSize {
   633  			return
   634  		}
   635  		f.overrideFontSize = f.fontSize
   636  		f.fontFace = fontFace(f.fontSource, f.overrideFontSize)
   637  		f.fontUpdated()
   638  		f.bufferModified()
   639  		return
   640  	}
   641  
   642  	w, h := f.r.Dx()-f.padding*2, f.r.Dy()-f.padding*2
   643  	if w <= 0 || h <= 0 {
   644  		if f.overrideFontSize == f.fontSize {
   645  			return
   646  		}
   647  		f.overrideFontSize = f.fontSize
   648  		f.fontFace = fontFace(f.fontSource, f.overrideFontSize)
   649  		f.fontUpdated()
   650  		f.bufferModified()
   651  		return
   652  	}
   653  
   654  	f.processIncoming()
   655  
   656  	for size := f.fontSize; size > 0; size-- {
   657  		f.fontFace = fontFace(f.fontSource, size)
   658  		f.fontUpdated()
   659  		if f.lineHeight > h {
   660  			continue
   661  		}
   662  		f.needWrap = 0
   663  		f.wrapStart = 0
   664  		f.wrap()
   665  		if len(f.bufferWrapped) <= 1 {
   666  			break
   667  		}
   668  	}
   669  }
   670  
   671  // SetHandleKeyboard sets a flag controlling whether keyboard input should be handled
   672  // by the field. This can be used to facilitate focus changes between multiple inputs.
   673  func (f *TextField) SetHandleKeyboard(handle bool) {
   674  	f.Lock()
   675  	defer f.Unlock()
   676  
   677  	f.handleKeyboard = handle
   678  }
   679  
   680  // SetMask sets the rune used to mask the text buffer contents. Set to 0 to disable.
   681  func (f *TextField) SetMask(r rune) {
   682  	f.Lock()
   683  	defer f.Unlock()
   684  
   685  	if f.maskRune == r {
   686  		return
   687  	}
   688  
   689  	f.maskRune = r
   690  	f.modified = true
   691  	f.resizeFont()
   692  }
   693  
   694  // Write writes to the field's buffer.
   695  func (f *TextField) Write(p []byte) (n int, err error) {
   696  	f.Lock()
   697  	defer f.Unlock()
   698  
   699  	return f._write(p)
   700  }
   701  
   702  func (f *TextField) _write(p []byte) (n int, err error) {
   703  	f.incoming = append(f.incoming, p...)
   704  	f.modified = true
   705  	f.redraw = true
   706  	return len(p), nil
   707  }
   708  
   709  // HandleKeyboardEvent passes the provided key or rune to the TextField.
   710  func (f *TextField) HandleKeyboardEvent(key ebiten.Key, r rune) (handled bool, err error) {
   711  	f.Lock()
   712  	defer f.Unlock()
   713  
   714  	if !f.visible || rectIsZero(f.r) || !f.handleKeyboard {
   715  		return false, nil
   716  	}
   717  
   718  	return f._handleKeyboardEvent(key, r)
   719  }
   720  
   721  func (f *TextField) _handleKeyboardEvent(key ebiten.Key, r rune) (handled bool, err error) {
   722  	if key != -1 {
   723  		// Handle keyboard PageUp/PageDown.
   724  		offsetAmount := 0
   725  		switch key {
   726  		case ebiten.KeyPageUp:
   727  			offsetAmount = 100
   728  		case ebiten.KeyPageDown:
   729  			offsetAmount = -100
   730  		}
   731  		if offsetAmount != 0 {
   732  			f.offset += offsetAmount
   733  			f.clampOffset()
   734  			f.redraw = true
   735  			return true, nil
   736  		}
   737  		return true, err
   738  	}
   739  	return true, nil
   740  }
   741  
   742  func (f *TextField) HandleMouseEvent(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
   743  	f.Lock()
   744  	defer f.Unlock()
   745  
   746  	if !f.visible || rectIsZero(f.r) {
   747  		return false, nil
   748  	}
   749  
   750  	return f._handleMouseEvent(cursor, pressed, clicked)
   751  }
   752  
   753  func (f *TextField) _handleMouseEvent(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
   754  	if !f.scrollDrag && !cursor.In(f.r) {
   755  		return false, nil
   756  	}
   757  
   758  	// Handle mouse wheel.
   759  	_, scroll := ebiten.Wheel()
   760  	if scroll != 0 {
   761  		if scroll < -maxScroll {
   762  			scroll = -maxScroll
   763  		} else if scroll > maxScroll {
   764  			scroll = maxScroll
   765  		}
   766  		const offsetAmount = 25
   767  		f.offset += int(scroll * offsetAmount)
   768  		f.clampOffset()
   769  		f.redraw = true
   770  	}
   771  
   772  	// Handle scroll bar click (and drag).
   773  	if !f.showScrollBar() {
   774  		return true, nil
   775  	} else if pressed || f.scrollDrag {
   776  		p := image.Point{cursor.X - f.r.Min.X, cursor.Y - f.r.Min.Y}
   777  		if pressed {
   778  			// Handle dragging the text field directly.
   779  			if !f.scrollDrag && !p.In(f.scrollRect) && f.scrollDragPoint.X == -1 && f.scrollDragPoint.Y == -1 {
   780  				f.scrollDragPoint = p
   781  				f.scrollDragOffset = f.offset
   782  			}
   783  			if f.scrollDragPoint.X != -1 {
   784  				delta := f.scrollDragPoint.Y - p.Y
   785  				f.offset = f.scrollDragOffset - delta
   786  			} else { // Handle dragging the scroll bar handle.
   787  				dragY := cursor.Y - f.r.Min.Y - f.scrollWidth/4
   788  				if dragY < 0 {
   789  					dragY = 0
   790  				} else if dragY > f.scrollRect.Dy() {
   791  					dragY = f.scrollRect.Dy()
   792  				}
   793  
   794  				pct := float64(dragY) / float64(f.scrollRect.Dy()-f.scrollWidth/2)
   795  				if pct < 0 {
   796  					pct = 0
   797  				} else if pct > 1 {
   798  					pct = 1
   799  				}
   800  
   801  				h := f.r.Dy()
   802  				f.offset = -int(float64(f.bufferSize-h-f.lineOffset+f.padding*2) * pct)
   803  			}
   804  			f.clampOffset()
   805  
   806  			f.redraw = true
   807  			f.scrollDrag = true
   808  		} else if !pressed {
   809  			f.scrollDrag = false
   810  			f.scrollDragPoint = image.Point{-1, -1}
   811  			f.scrollDragOffset = 0
   812  		}
   813  	}
   814  	return true, nil
   815  }
   816  
   817  // Update updates the field. This function should be called when
   818  // Game.Update is called.
   819  func (f *TextField) Update() error {
   820  	f.Lock()
   821  	defer f.Unlock()
   822  
   823  	if !f.visible || rectIsZero(f.r) {
   824  		return nil
   825  	}
   826  
   827  	f.keyBuffer = inpututil.AppendJustPressedKeys(f.keyBuffer[:0])
   828  	for _, key := range f.keyBuffer {
   829  		handled, err := f._handleKeyboardEvent(key, 0)
   830  		if err != nil {
   831  			return err
   832  		} else if handled {
   833  			f.redraw = true
   834  		}
   835  	}
   836  
   837  	f.runeBuffer = ebiten.AppendInputChars(f.runeBuffer[:0])
   838  	for _, r := range f.runeBuffer {
   839  		handled, err := f._handleKeyboardEvent(-1, r)
   840  		if err != nil {
   841  			return err
   842  		} else if handled {
   843  			f.redraw = true
   844  		}
   845  	}
   846  
   847  	cx, cy := ebiten.CursorPosition()
   848  	if cx != 0 || cy != 0 {
   849  		handled, err := f._handleMouseEvent(image.Point{X: cx, Y: cy}, ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft), inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft))
   850  		if err != nil {
   851  			return err
   852  		} else if handled {
   853  			f.redraw = true
   854  		}
   855  	}
   856  
   857  	return nil
   858  }
   859  
   860  // Draw draws the field on the screen. This function should be called
   861  // when Game.Draw is called.
   862  func (f *TextField) Draw(screen *ebiten.Image) {
   863  	f.Lock()
   864  	defer f.Unlock()
   865  
   866  	if f.modified {
   867  		f.fontMutex.Lock()
   868  
   869  		f.bufferModified()
   870  		f.modified = false
   871  
   872  		f.fontMutex.Unlock()
   873  	}
   874  
   875  	if !f.visible || rectIsZero(f.r) {
   876  		return
   877  	}
   878  
   879  	if f.redraw {
   880  		f.fontMutex.Lock()
   881  
   882  		f.drawImage()
   883  		f.redraw = false
   884  
   885  		f.fontMutex.Unlock()
   886  	}
   887  
   888  	op := &ebiten.DrawImageOptions{}
   889  	op.GeoM.Translate(float64(f.r.Min.X), float64(f.r.Min.Y))
   890  	screen.DrawImage(f.img, op)
   891  }
   892  
   893  func (f *TextField) fontUpdated() {
   894  	m := f.fontFace.Metrics()
   895  	f.lineHeight = int(m.HAscent + m.HDescent)
   896  	f.lineOffset = int(m.HLineGap)
   897  	if f.lineOffset < 0 {
   898  		f.lineOffset *= -1
   899  	}
   900  }
   901  
   902  func (f *TextField) wrapContent(withScrollBar bool) {
   903  	if withScrollBar != f.wrapScrollBar {
   904  		f.needWrap = 0
   905  		f.wrapStart = 0
   906  	} else if f.needWrap == -1 {
   907  		return
   908  	}
   909  	f.wrapScrollBar = withScrollBar
   910  
   911  	if len(f.buffer) == 0 || (f.singleLine && !f.autoResize) {
   912  		buffer := f.prefix + string(bytes.Join(f.buffer, nil)) + f.suffix
   913  		w, _ := text.Measure(buffer, f.fontFace, float64(f.lineHeight))
   914  
   915  		f.bufferWrapped = []string{buffer}
   916  		f.wrapStart = 0
   917  		f.lineWidths = append(f.lineWidths[:0], int(w))
   918  
   919  		f.needWrap = -1
   920  		return
   921  	}
   922  
   923  	w := f.r.Dx()
   924  	if withScrollBar {
   925  		w -= f.scrollWidth
   926  	}
   927  	bufferLen := len(f.buffer)
   928  	j := f.wrapStart
   929  	for i := f.needWrap; i < bufferLen; i++ {
   930  		var line string
   931  		if i == 0 {
   932  			line = f.prefix + string(f.buffer[i])
   933  		} else {
   934  			line = string(f.buffer[i])
   935  		}
   936  		if i == bufferLen-1 {
   937  			line += f.suffix
   938  		}
   939  		l := len(line)
   940  		availableWidth := w - (f.padding * 2)
   941  
   942  		f.wrapStart = j
   943  
   944  		// BoundString returns 0 for strings containing only whitespace.
   945  		if strings.TrimSpace(line) == "" {
   946  			if len(f.bufferWrapped) <= j {
   947  				f.bufferWrapped = append(f.bufferWrapped, "")
   948  			} else {
   949  				f.bufferWrapped[j] = ""
   950  			}
   951  			if len(f.lineWidths) <= j {
   952  				f.lineWidths = append(f.lineWidths, 0)
   953  			} else {
   954  				f.lineWidths[j] = 0
   955  			}
   956  			j++
   957  			continue
   958  		}
   959  
   960  		// Add characters one at a time until the line doesn't fit. When word
   961  		// wrapping is enabled, break the line at the last whitespace character.
   962  		var start int
   963  		var lastSpace int
   964  		var lastSpaceSize int
   965  		var boundsWidth int
   966  		var lastBoundsWidth int
   967  	WRAPTEXT:
   968  		for start < l {
   969  			lastSpace = -1
   970  			lastBoundsWidth = -1
   971  			var e int
   972  			for _, r := range line[start:] {
   973  				runeSize := len(string(r))
   974  				if e > l-start-runeSize {
   975  					e = l - start - runeSize
   976  				}
   977  				if unicode.IsSpace(r) {
   978  					lastSpace = e
   979  					lastSpaceSize = runeSize
   980  				}
   981  				w, _ := text.Measure(line[start:start+e+runeSize], f.fontFace, float64(f.lineHeight))
   982  				boundsWidth = int(w)
   983  				if boundsWidth > availableWidth {
   984  					var addSpace bool
   985  					if e > 0 {
   986  						if e > 0 {
   987  							e -= runeSize
   988  						}
   989  						if f.wordWrap && lastSpace != -1 {
   990  							e = lastSpace
   991  							addSpace = true
   992  						}
   993  					}
   994  					if lastBoundsWidth == -1 {
   995  						w, _ := text.Measure(line[start:start+e], f.fontFace, float64(f.lineHeight))
   996  						boundsWidth = int(w)
   997  					} else {
   998  						boundsWidth = lastBoundsWidth
   999  					}
  1000  
  1001  					if len(f.bufferWrapped) <= j {
  1002  						f.bufferWrapped = append(f.bufferWrapped, line[start:start+e])
  1003  					} else {
  1004  						f.bufferWrapped[j] = line[start : start+e]
  1005  					}
  1006  					if len(f.lineWidths) <= j {
  1007  						f.lineWidths = append(f.lineWidths, boundsWidth)
  1008  					} else {
  1009  						f.lineWidths[j] = boundsWidth
  1010  					}
  1011  					j++
  1012  
  1013  					if addSpace {
  1014  						e += lastSpaceSize
  1015  					}
  1016  
  1017  					start += e
  1018  					if e == 0 {
  1019  						start += runeSize
  1020  					}
  1021  					continue WRAPTEXT
  1022  				}
  1023  				lastBoundsWidth = boundsWidth
  1024  				e += runeSize
  1025  			}
  1026  
  1027  			if len(f.bufferWrapped) <= j {
  1028  				f.bufferWrapped = append(f.bufferWrapped, line[start:])
  1029  			} else {
  1030  				f.bufferWrapped[j] = line[start:]
  1031  			}
  1032  			if len(f.lineWidths) <= j {
  1033  				f.lineWidths = append(f.lineWidths, boundsWidth)
  1034  			} else {
  1035  				f.lineWidths[j] = boundsWidth
  1036  			}
  1037  			j++
  1038  			break
  1039  		}
  1040  	}
  1041  
  1042  	if len(f.bufferWrapped) >= j {
  1043  		f.bufferWrapped = f.bufferWrapped[:j]
  1044  	}
  1045  
  1046  	f.needWrap = -1
  1047  }
  1048  
  1049  // drawContent draws the text buffer to img.
  1050  func (f *TextField) drawContent() (overflow bool) {
  1051  	if f.backgroundColor.A != 0 {
  1052  		f.img.Fill(f.backgroundColor)
  1053  	} else {
  1054  		f.img.Clear()
  1055  	}
  1056  	fieldWidth := f.r.Dx()
  1057  	fieldHeight := f.r.Dy()
  1058  	if f.showScrollBar() {
  1059  		fieldWidth -= f.scrollWidth
  1060  	}
  1061  	lines := len(f.bufferWrapped)
  1062  
  1063  	h := f.r.Dy()
  1064  	lineHeight := f.overrideLineHeight
  1065  	if lineHeight == 0 {
  1066  		lineHeight = f.lineHeight
  1067  	}
  1068  	var firstVisible, lastVisible int
  1069  	firstVisible = 0
  1070  	lastVisible = len(f.bufferWrapped) - 1
  1071  	if !f.singleLine {
  1072  		firstVisible = (f.offset * -1) / f.lineHeight
  1073  		lastVisible = firstVisible + (f.r.Dy() / f.lineHeight) + 1
  1074  		if lastVisible > len(f.bufferWrapped)-1 {
  1075  			lastVisible = len(f.bufferWrapped) - 1
  1076  		}
  1077  	}
  1078  	numVisible := lastVisible - firstVisible
  1079  	// Calculate buffer size (width for single-line fields or height for multi-line fields).
  1080  	if f.singleLine {
  1081  		w, _ := text.Measure(f.bufferWrapped[firstVisible], f.fontFace, float64(f.lineHeight))
  1082  		f.bufferSize = int(w)
  1083  		if f.bufferSize > fieldWidth-f.padding*2 {
  1084  			overflow = true
  1085  		}
  1086  	} else {
  1087  		f.bufferSize = (len(f.bufferWrapped)) * lineHeight
  1088  		if f.bufferSize > fieldHeight-f.padding*2 {
  1089  			overflow = true
  1090  		}
  1091  	}
  1092  	for i := firstVisible; i <= lastVisible; i++ {
  1093  		line := f.bufferWrapped[i]
  1094  		if f.maskRune != 0 {
  1095  			line = strings.Repeat(string(f.maskRune), len(line))
  1096  			if i == lastVisible && len(line) > 0 && len(line) >= len(f.suffix) {
  1097  				line = line[:len(line)-len(f.suffix)] + f.suffix
  1098  			}
  1099  		}
  1100  		lineX := f.padding
  1101  		lineY := 1 + f.padding + -f.lineOffset + lineHeight*i
  1102  
  1103  		// Calculate whether the line overflows the visible area.
  1104  		lineOverflows := lineY < 0 || lineY >= h-f.padding
  1105  		if lineOverflows {
  1106  			overflow = true
  1107  		}
  1108  
  1109  		// Skip drawing off-screen lines.
  1110  		if lineY < 0 {
  1111  			continue
  1112  		}
  1113  
  1114  		// Apply scrolling transformation.
  1115  		if f.singleLine {
  1116  			lineX += f.offset
  1117  		} else {
  1118  			lineY += f.offset
  1119  		}
  1120  
  1121  		// Align horizontally.
  1122  		if f.horizontal == AlignCenter {
  1123  			lineX = (fieldWidth - f.lineWidths[i]) / 2
  1124  		} else if f.horizontal == AlignEnd {
  1125  			lineX = (fieldWidth - f.lineWidths[i]) - f.padding - 1
  1126  		}
  1127  
  1128  		// Align vertically.
  1129  		totalHeight := f.lineOffset + lineHeight*(lines)
  1130  		if f.vertical == AlignCenter && (f.autoResize || totalHeight <= h) {
  1131  			lineY = fieldHeight/2 - totalHeight/2 + f.lineOffset + (lineHeight * (i)) - 2
  1132  		} else if f.vertical == AlignEnd && (f.autoResize || totalHeight <= h) {
  1133  			lineY = fieldHeight - lineHeight*(numVisible+1-i) - f.padding
  1134  		}
  1135  
  1136  		// Draw line.
  1137  		op := &text.DrawOptions{}
  1138  		op.GeoM.Translate(float64(lineX), float64(lineY))
  1139  		op.ColorScale.ScaleWithColor(f.textColor)
  1140  		text.Draw(f.img, line, f.fontFace, op)
  1141  	}
  1142  
  1143  	return overflow
  1144  }
  1145  
  1146  func (f *TextField) clampOffset() {
  1147  	fieldSize := f.r.Dy()
  1148  	if f.singleLine {
  1149  		fieldSize = f.r.Dx()
  1150  	}
  1151  	minSize := -(f.bufferSize - fieldSize + f.padding*2)
  1152  	if !f.singleLine {
  1153  		minSize += f.lineOffset
  1154  	}
  1155  	if minSize > 0 {
  1156  		minSize = 0
  1157  	}
  1158  	maxSize := 0
  1159  	if f.offset < minSize {
  1160  		f.offset = minSize
  1161  	} else if f.offset > maxSize {
  1162  		f.offset = maxSize
  1163  	}
  1164  }
  1165  
  1166  func (f *TextField) showScrollBar() bool {
  1167  	return !f.autoResize && !f.singleLine && f.scrollVisible && (f.overflow || !f.scrollAutoHide)
  1168  }
  1169  
  1170  func (f *TextField) wrap() {
  1171  	w, h := f.r.Dx(), f.r.Dy()
  1172  
  1173  	var newImage bool
  1174  	if f.img == nil {
  1175  		newImage = true
  1176  	} else {
  1177  		imgRect := f.img.Bounds()
  1178  		imgW, imgH := imgRect.Dx(), imgRect.Dy()
  1179  		newImage = imgW != w || imgH != h
  1180  	}
  1181  	if newImage {
  1182  		f.img = ebiten.NewImage(w, h)
  1183  	}
  1184  
  1185  	showScrollBar := f.showScrollBar()
  1186  	f.wrapContent(showScrollBar)
  1187  	f.overflow = f.drawContent()
  1188  	if f.showScrollBar() != showScrollBar {
  1189  		f.wrapContent(!showScrollBar)
  1190  		f.drawContent()
  1191  	}
  1192  }
  1193  
  1194  // drawImage draws the field to img (caching it for future draws).
  1195  func (f *TextField) drawImage() {
  1196  	if rectIsZero(f.r) {
  1197  		f.img = nil
  1198  		return
  1199  	}
  1200  
  1201  	f.wrap()
  1202  
  1203  	// Draw scrollbar.
  1204  	if f.showScrollBar() {
  1205  		w, h := f.r.Dx(), f.r.Dy()
  1206  
  1207  		scrollAreaX, scrollAreaY := w-f.scrollWidth, 0
  1208  		f.scrollRect = image.Rect(scrollAreaX, scrollAreaY, scrollAreaX+f.scrollWidth, h)
  1209  
  1210  		scrollBarH := f.scrollWidth / 2
  1211  		if scrollBarH < 4 {
  1212  			scrollBarH = 4
  1213  		}
  1214  
  1215  		scrollX, scrollY := w-f.scrollWidth, 0
  1216  		pct := float64(-f.offset) / float64(f.bufferSize-h-f.lineOffset+f.padding*2)
  1217  		scrollY += int(float64(h-scrollBarH) * pct)
  1218  		scrollBarRect := image.Rect(scrollX, scrollY, scrollX+f.scrollWidth, scrollY+scrollBarH)
  1219  
  1220  		// Draw scroll area.
  1221  		f.img.SubImage(f.scrollRect).(*ebiten.Image).Fill(f.scrollAreaColor)
  1222  
  1223  		// Draw scroll handle.
  1224  		f.img.SubImage(scrollBarRect).(*ebiten.Image).Fill(f.scrollHandleColor)
  1225  
  1226  		// Draw scroll handle border.
  1227  		if f.scrollBorderSize != 0 {
  1228  			r := scrollBarRect
  1229  			f.img.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+f.scrollBorderSize, r.Max.Y)).(*ebiten.Image).Fill(f.scrollBorderLeft)
  1230  			f.img.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+f.scrollBorderSize)).(*ebiten.Image).Fill(f.scrollBorderTop)
  1231  			f.img.SubImage(image.Rect(r.Max.X-f.scrollBorderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(f.scrollBorderRight)
  1232  			f.img.SubImage(image.Rect(r.Min.X, r.Max.Y-f.scrollBorderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(f.scrollBorderBottom)
  1233  		}
  1234  	}
  1235  }
  1236  
  1237  func (f *TextField) processIncoming() {
  1238  	if len(f.incoming) == 0 {
  1239  		return
  1240  	}
  1241  
  1242  	line := len(f.buffer) - 1
  1243  	if line < 0 {
  1244  		line = 0
  1245  		f.buffer = append(f.buffer, nil)
  1246  	}
  1247  	if f.needWrap == -1 {
  1248  		f.needWrap = line
  1249  	}
  1250  	for _, b := range f.incoming {
  1251  		if b == '\n' {
  1252  			line++
  1253  			f.buffer = append(f.buffer, nil)
  1254  			continue
  1255  		}
  1256  		f.buffer[line] = append(f.buffer[line], b)
  1257  	}
  1258  	f.incoming = f.incoming[:0]
  1259  }
  1260  
  1261  func (f *TextField) bufferModified() {
  1262  	f.processIncoming()
  1263  	f.resizeFont()
  1264  
  1265  	f.drawImage()
  1266  
  1267  	lastOffset := f.offset
  1268  	if f.follow {
  1269  		f.offset = -math.MaxInt
  1270  	}
  1271  	f.clampOffset()
  1272  	if f.offset != lastOffset {
  1273  		f.drawImage()
  1274  	}
  1275  
  1276  	f.redraw = false
  1277  }
  1278  
  1279  func rectIsZero(r image.Rectangle) bool {
  1280  	return r.Dx() == 0 || r.Dy() == 0
  1281  }
  1282  
  1283  func fontFace(source *text.GoTextFaceSource, size int) *text.GoTextFace {
  1284  	return &text.GoTextFace{
  1285  		Source: source,
  1286  		Size:   float64(size),
  1287  	}
  1288  }
  1289  

View as plain text