...

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

View as plain text