...

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

View as plain text