...

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

View as plain text