gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/widget/editor.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package widget
     4  
     5  import (
     6  	"bufio"
     7  	"image"
     8  	"io"
     9  	"math"
    10  	"strings"
    11  	"time"
    12  	"unicode"
    13  	"unicode/utf8"
    14  
    15  	"gioui.org/f32"
    16  	"gioui.org/font"
    17  	"gioui.org/gesture"
    18  	"gioui.org/io/clipboard"
    19  	"gioui.org/io/event"
    20  	"gioui.org/io/key"
    21  	"gioui.org/io/pointer"
    22  	"gioui.org/io/semantic"
    23  	"gioui.org/io/system"
    24  	"gioui.org/io/transfer"
    25  	"gioui.org/layout"
    26  	"gioui.org/op"
    27  	"gioui.org/op/clip"
    28  	"gioui.org/text"
    29  	"gioui.org/unit"
    30  )
    31  
    32  // Editor implements an editable and scrollable text area.
    33  type Editor struct {
    34  	// text manages the text buffer and provides shaping and cursor positioning
    35  	// services.
    36  	text textView
    37  	// Alignment controls the alignment of text within the editor.
    38  	Alignment text.Alignment
    39  	// LineHeight determines the gap between baselines of text. If zero, a sensible
    40  	// default will be used.
    41  	LineHeight unit.Sp
    42  	// LineHeightScale is multiplied by LineHeight to determine the final gap
    43  	// between baselines. If zero, a sensible default will be used.
    44  	LineHeightScale float32
    45  	// SingleLine force the text to stay on a single line.
    46  	// SingleLine also sets the scrolling direction to
    47  	// horizontal.
    48  	SingleLine bool
    49  	// ReadOnly controls whether the contents of the editor can be altered by
    50  	// user interaction. If set to true, the editor will allow selecting text
    51  	// and copying it interactively, but not modifying it.
    52  	ReadOnly bool
    53  	// Submit enabled translation of carriage return keys to SubmitEvents.
    54  	// If not enabled, carriage returns are inserted as newlines in the text.
    55  	Submit bool
    56  	// Mask replaces the visual display of each rune in the contents with the given rune.
    57  	// Newline characters are not masked. When non-zero, the unmasked contents
    58  	// are accessed by Len, Text, and SetText.
    59  	Mask rune
    60  	// InputHint specifies the type of on-screen keyboard to be displayed.
    61  	InputHint key.InputHint
    62  	// MaxLen limits the editor content to a maximum length. Zero means no limit.
    63  	MaxLen int
    64  	// Filter is the list of characters allowed in the Editor. If Filter is empty,
    65  	// all characters are allowed.
    66  	Filter string
    67  	// WrapPolicy configures how displayed text will be broken into lines.
    68  	WrapPolicy text.WrapPolicy
    69  
    70  	buffer *editBuffer
    71  	// scratch is a byte buffer that is reused to efficiently read portions of text
    72  	// from the textView.
    73  	scratch    []byte
    74  	blinkStart time.Time
    75  
    76  	// ime tracks the state relevant to input methods.
    77  	ime struct {
    78  		imeState
    79  		scratch []byte
    80  	}
    81  
    82  	dragging    bool
    83  	dragger     gesture.Drag
    84  	scroller    gesture.Scroll
    85  	scrollCaret bool
    86  	showCaret   bool
    87  
    88  	clicker gesture.Click
    89  
    90  	// history contains undo history.
    91  	history []modification
    92  	// nextHistoryIdx is the index within the history of the next modification. This
    93  	// is only not len(history) immediately after undo operations occur. It is framed as the "next" value
    94  	// to make the zero value consistent.
    95  	nextHistoryIdx int
    96  
    97  	pending []EditorEvent
    98  }
    99  
   100  type offEntry struct {
   101  	runes int
   102  	bytes int
   103  }
   104  
   105  type imeState struct {
   106  	selection struct {
   107  		rng   key.Range
   108  		caret key.Caret
   109  	}
   110  	snippet    key.Snippet
   111  	start, end int
   112  }
   113  
   114  type maskReader struct {
   115  	// rr is the underlying reader.
   116  	rr      io.RuneReader
   117  	maskBuf [utf8.UTFMax]byte
   118  	// mask is the utf-8 encoded mask rune.
   119  	mask []byte
   120  	// overflow contains excess mask bytes left over after the last Read call.
   121  	overflow []byte
   122  }
   123  
   124  type selectionAction int
   125  
   126  const (
   127  	selectionExtend selectionAction = iota
   128  	selectionClear
   129  )
   130  
   131  func (m *maskReader) Reset(r io.Reader, mr rune) {
   132  	m.rr = bufio.NewReader(r)
   133  	n := utf8.EncodeRune(m.maskBuf[:], mr)
   134  	m.mask = m.maskBuf[:n]
   135  }
   136  
   137  // Read reads from the underlying reader and replaces every
   138  // rune with the mask rune.
   139  func (m *maskReader) Read(b []byte) (n int, err error) {
   140  	for len(b) > 0 {
   141  		var replacement []byte
   142  		if len(m.overflow) > 0 {
   143  			replacement = m.overflow
   144  		} else {
   145  			var r rune
   146  			r, _, err = m.rr.ReadRune()
   147  			if err != nil {
   148  				break
   149  			}
   150  			if r == '\n' {
   151  				replacement = []byte{'\n'}
   152  			} else {
   153  				replacement = m.mask
   154  			}
   155  		}
   156  		nn := copy(b, replacement)
   157  		m.overflow = replacement[nn:]
   158  		n += nn
   159  		b = b[nn:]
   160  	}
   161  	return n, err
   162  }
   163  
   164  type EditorEvent interface {
   165  	isEditorEvent()
   166  }
   167  
   168  // A ChangeEvent is generated for every user change to the text.
   169  type ChangeEvent struct{}
   170  
   171  // A SubmitEvent is generated when Submit is set
   172  // and a carriage return key is pressed.
   173  type SubmitEvent struct {
   174  	Text string
   175  }
   176  
   177  // A SelectEvent is generated when the user selects some text, or changes the
   178  // selection (e.g. with a shift-click), including if they remove the
   179  // selection. The selected text is not part of the event, on the theory that
   180  // it could be a relatively expensive operation (for a large editor), most
   181  // applications won't actually care about it, and those that do can call
   182  // Editor.SelectedText() (which can be empty).
   183  type SelectEvent struct{}
   184  
   185  const (
   186  	blinksPerSecond  = 1
   187  	maxBlinkDuration = 10 * time.Second
   188  )
   189  
   190  func (e *Editor) processEvents(gtx layout.Context) (ev EditorEvent, ok bool) {
   191  	if len(e.pending) > 0 {
   192  		out := e.pending[0]
   193  		e.pending = e.pending[:copy(e.pending, e.pending[1:])]
   194  		return out, true
   195  	}
   196  	selStart, selEnd := e.Selection()
   197  	defer func() {
   198  		afterSelStart, afterSelEnd := e.Selection()
   199  		if selStart != afterSelStart || selEnd != afterSelEnd {
   200  			if ok {
   201  				e.pending = append(e.pending, SelectEvent{})
   202  			} else {
   203  				ev = SelectEvent{}
   204  				ok = true
   205  			}
   206  		}
   207  	}()
   208  
   209  	ev, ok = e.processPointer(gtx)
   210  	if ok {
   211  		return ev, ok
   212  	}
   213  	ev, ok = e.processKey(gtx)
   214  	if ok {
   215  		return ev, ok
   216  	}
   217  	return nil, false
   218  }
   219  
   220  func (e *Editor) processPointer(gtx layout.Context) (EditorEvent, bool) {
   221  	sbounds := e.text.ScrollBounds()
   222  	var smin, smax int
   223  	var axis gesture.Axis
   224  	if e.SingleLine {
   225  		axis = gesture.Horizontal
   226  		smin, smax = sbounds.Min.X, sbounds.Max.X
   227  	} else {
   228  		axis = gesture.Vertical
   229  		smin, smax = sbounds.Min.Y, sbounds.Max.Y
   230  	}
   231  	var scrollX, scrollY pointer.ScrollRange
   232  	textDims := e.text.FullDimensions()
   233  	visibleDims := e.text.Dimensions()
   234  	if e.SingleLine {
   235  		scrollOffX := e.text.ScrollOff().X
   236  		scrollX.Min = min(-scrollOffX, 0)
   237  		scrollX.Max = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
   238  	} else {
   239  		scrollOffY := e.text.ScrollOff().Y
   240  		scrollY.Min = -scrollOffY
   241  		scrollY.Max = max(0, textDims.Size.Y-(scrollOffY+visibleDims.Size.Y))
   242  	}
   243  	sdist := e.scroller.Update(gtx.Metric, gtx.Source, gtx.Now, axis, scrollX, scrollY)
   244  	var soff int
   245  	if e.SingleLine {
   246  		e.text.ScrollRel(sdist, 0)
   247  		soff = e.text.ScrollOff().X
   248  	} else {
   249  		e.text.ScrollRel(0, sdist)
   250  		soff = e.text.ScrollOff().Y
   251  	}
   252  	for {
   253  		evt, ok := e.clicker.Update(gtx.Source)
   254  		if !ok {
   255  			break
   256  		}
   257  		ev, ok := e.processPointerEvent(gtx, evt)
   258  		if ok {
   259  			return ev, ok
   260  		}
   261  	}
   262  	for {
   263  		evt, ok := e.dragger.Update(gtx.Metric, gtx.Source, gesture.Both)
   264  		if !ok {
   265  			break
   266  		}
   267  		ev, ok := e.processPointerEvent(gtx, evt)
   268  		if ok {
   269  			return ev, ok
   270  		}
   271  	}
   272  
   273  	if (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin) {
   274  		e.scroller.Stop()
   275  	}
   276  	return nil, false
   277  }
   278  
   279  func (e *Editor) processPointerEvent(gtx layout.Context, ev event.Event) (EditorEvent, bool) {
   280  	switch evt := ev.(type) {
   281  	case gesture.ClickEvent:
   282  		switch {
   283  		case evt.Kind == gesture.KindPress && evt.Source == pointer.Mouse,
   284  			evt.Kind == gesture.KindClick && evt.Source != pointer.Mouse:
   285  			prevCaretPos, _ := e.text.Selection()
   286  			e.blinkStart = gtx.Now
   287  			e.text.MoveCoord(image.Point{
   288  				X: int(math.Round(float64(evt.Position.X))),
   289  				Y: int(math.Round(float64(evt.Position.Y))),
   290  			})
   291  			gtx.Execute(key.FocusCmd{Tag: e})
   292  			if e.scroller.State() != gesture.StateFlinging {
   293  				e.scrollCaret = true
   294  			}
   295  
   296  			if evt.Modifiers == key.ModShift {
   297  				start, end := e.text.Selection()
   298  				// If they clicked closer to the end, then change the end to
   299  				// where the caret used to be (effectively swapping start & end).
   300  				if abs(end-start) < abs(start-prevCaretPos) {
   301  					e.text.SetCaret(start, prevCaretPos)
   302  				}
   303  			} else {
   304  				e.text.ClearSelection()
   305  			}
   306  			e.dragging = true
   307  
   308  			// Process multi-clicks.
   309  			switch {
   310  			case evt.NumClicks == 2:
   311  				e.text.MoveWord(-1, selectionClear)
   312  				e.text.MoveWord(1, selectionExtend)
   313  				e.dragging = false
   314  			case evt.NumClicks >= 3:
   315  				e.text.MoveLineStart(selectionClear)
   316  				e.text.MoveLineEnd(selectionExtend)
   317  				e.dragging = false
   318  			}
   319  		}
   320  	case pointer.Event:
   321  		release := false
   322  		switch {
   323  		case evt.Kind == pointer.Release && evt.Source == pointer.Mouse:
   324  			release = true
   325  			fallthrough
   326  		case evt.Kind == pointer.Drag && evt.Source == pointer.Mouse:
   327  			if e.dragging {
   328  				e.blinkStart = gtx.Now
   329  				e.text.MoveCoord(image.Point{
   330  					X: int(math.Round(float64(evt.Position.X))),
   331  					Y: int(math.Round(float64(evt.Position.Y))),
   332  				})
   333  				e.scrollCaret = true
   334  
   335  				if release {
   336  					e.dragging = false
   337  				}
   338  			}
   339  		}
   340  	}
   341  	return nil, false
   342  }
   343  
   344  func condFilter(pred bool, f key.Filter) event.Filter {
   345  	if pred {
   346  		return f
   347  	} else {
   348  		return nil
   349  	}
   350  }
   351  
   352  func (e *Editor) processKey(gtx layout.Context) (EditorEvent, bool) {
   353  	if e.text.Changed() {
   354  		return ChangeEvent{}, true
   355  	}
   356  	caret, _ := e.text.Selection()
   357  	atBeginning := caret == 0
   358  	atEnd := caret == e.text.Len()
   359  	if gtx.Locale.Direction.Progression() != system.FromOrigin {
   360  		atEnd, atBeginning = atBeginning, atEnd
   361  	}
   362  	filters := []event.Filter{
   363  		key.FocusFilter{Target: e},
   364  		transfer.TargetFilter{Target: e, Type: "application/text"},
   365  		key.Filter{Focus: e, Name: key.NameEnter, Optional: key.ModShift},
   366  		key.Filter{Focus: e, Name: key.NameReturn, Optional: key.ModShift},
   367  
   368  		key.Filter{Focus: e, Name: "Z", Required: key.ModShortcut, Optional: key.ModShift},
   369  		key.Filter{Focus: e, Name: "C", Required: key.ModShortcut},
   370  		key.Filter{Focus: e, Name: "V", Required: key.ModShortcut},
   371  		key.Filter{Focus: e, Name: "X", Required: key.ModShortcut},
   372  		key.Filter{Focus: e, Name: "A", Required: key.ModShortcut},
   373  
   374  		key.Filter{Focus: e, Name: key.NameDeleteBackward, Optional: key.ModShortcutAlt | key.ModShift},
   375  		key.Filter{Focus: e, Name: key.NameDeleteForward, Optional: key.ModShortcutAlt | key.ModShift},
   376  
   377  		key.Filter{Focus: e, Name: key.NameHome, Optional: key.ModShortcut | key.ModShift},
   378  		key.Filter{Focus: e, Name: key.NameEnd, Optional: key.ModShortcut | key.ModShift},
   379  		key.Filter{Focus: e, Name: key.NamePageDown, Optional: key.ModShift},
   380  		key.Filter{Focus: e, Name: key.NamePageUp, Optional: key.ModShift},
   381  		condFilter(!atBeginning, key.Filter{Focus: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift}),
   382  		condFilter(!atBeginning, key.Filter{Focus: e, Name: key.NameUpArrow, Optional: key.ModShortcutAlt | key.ModShift}),
   383  		condFilter(!atEnd, key.Filter{Focus: e, Name: key.NameRightArrow, Optional: key.ModShortcutAlt | key.ModShift}),
   384  		condFilter(!atEnd, key.Filter{Focus: e, Name: key.NameDownArrow, Optional: key.ModShortcutAlt | key.ModShift}),
   385  	}
   386  	// adjust keeps track of runes dropped because of MaxLen.
   387  	var adjust int
   388  	for {
   389  		ke, ok := gtx.Event(filters...)
   390  		if !ok {
   391  			break
   392  		}
   393  		e.blinkStart = gtx.Now
   394  		switch ke := ke.(type) {
   395  		case key.FocusEvent:
   396  			// Reset IME state.
   397  			e.ime.imeState = imeState{}
   398  			if ke.Focus {
   399  				gtx.Execute(key.SoftKeyboardCmd{Show: true})
   400  			}
   401  		case key.Event:
   402  			if !gtx.Focused(e) || ke.State != key.Press {
   403  				break
   404  			}
   405  			if !e.ReadOnly && e.Submit && (ke.Name == key.NameReturn || ke.Name == key.NameEnter) {
   406  				if !ke.Modifiers.Contain(key.ModShift) {
   407  					e.scratch = e.text.Text(e.scratch)
   408  					return SubmitEvent{
   409  						Text: string(e.scratch),
   410  					}, true
   411  				}
   412  			}
   413  			e.scrollCaret = true
   414  			e.scroller.Stop()
   415  			ev, ok := e.command(gtx, ke)
   416  			if ok {
   417  				return ev, ok
   418  			}
   419  		case key.SnippetEvent:
   420  			e.updateSnippet(gtx, ke.Start, ke.End)
   421  		case key.EditEvent:
   422  			if e.ReadOnly {
   423  				break
   424  			}
   425  			e.scrollCaret = true
   426  			e.scroller.Stop()
   427  			s := ke.Text
   428  			moves := 0
   429  			submit := false
   430  			switch {
   431  			case e.Submit:
   432  				if i := strings.IndexByte(s, '\n'); i != -1 {
   433  					submit = true
   434  					moves += len(s) - i
   435  					s = s[:i]
   436  				}
   437  			case e.SingleLine:
   438  				s = strings.ReplaceAll(s, "\n", " ")
   439  			}
   440  			moves += e.replace(ke.Range.Start, ke.Range.End, s, true)
   441  			adjust += utf8.RuneCountInString(ke.Text) - moves
   442  			// Reset caret xoff.
   443  			e.text.MoveCaret(0, 0)
   444  			if submit {
   445  				e.scratch = e.text.Text(e.scratch)
   446  				submitEvent := SubmitEvent{
   447  					Text: string(e.scratch),
   448  				}
   449  				if e.text.Changed() {
   450  					e.pending = append(e.pending, submitEvent)
   451  					return ChangeEvent{}, true
   452  				}
   453  				return submitEvent, true
   454  			}
   455  		// Complete a paste event, initiated by Shortcut-V in Editor.command().
   456  		case transfer.DataEvent:
   457  			e.scrollCaret = true
   458  			e.scroller.Stop()
   459  			content, err := io.ReadAll(ke.Open())
   460  			if err == nil {
   461  				if e.Insert(string(content)) != 0 {
   462  					return ChangeEvent{}, true
   463  				}
   464  			}
   465  		case key.SelectionEvent:
   466  			e.scrollCaret = true
   467  			e.scroller.Stop()
   468  			ke.Start -= adjust
   469  			ke.End -= adjust
   470  			adjust = 0
   471  			e.text.SetCaret(ke.Start, ke.End)
   472  		}
   473  	}
   474  	if e.text.Changed() {
   475  		return ChangeEvent{}, true
   476  	}
   477  	return nil, false
   478  }
   479  
   480  func (e *Editor) command(gtx layout.Context, k key.Event) (EditorEvent, bool) {
   481  	direction := 1
   482  	if gtx.Locale.Direction.Progression() == system.TowardOrigin {
   483  		direction = -1
   484  	}
   485  	moveByWord := k.Modifiers.Contain(key.ModShortcutAlt)
   486  	selAct := selectionClear
   487  	if k.Modifiers.Contain(key.ModShift) {
   488  		selAct = selectionExtend
   489  	}
   490  	if k.Modifiers.Contain(key.ModShortcut) {
   491  		switch k.Name {
   492  		// Initiate a paste operation, by requesting the clipboard contents; other
   493  		// half is in Editor.processKey() under clipboard.Event.
   494  		case "V":
   495  			if !e.ReadOnly {
   496  				gtx.Execute(clipboard.ReadCmd{Tag: e})
   497  			}
   498  		// Copy or Cut selection -- ignored if nothing selected.
   499  		case "C", "X":
   500  			e.scratch = e.text.SelectedText(e.scratch)
   501  			if text := string(e.scratch); text != "" {
   502  				gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(strings.NewReader(text))})
   503  				if k.Name == "X" && !e.ReadOnly {
   504  					if e.Delete(1) != 0 {
   505  						return ChangeEvent{}, true
   506  					}
   507  				}
   508  			}
   509  		// Select all
   510  		case "A":
   511  			e.text.SetCaret(0, e.text.Len())
   512  		case "Z":
   513  			if !e.ReadOnly {
   514  				if k.Modifiers.Contain(key.ModShift) {
   515  					if ev, ok := e.redo(); ok {
   516  						return ev, ok
   517  					}
   518  				} else {
   519  					if ev, ok := e.undo(); ok {
   520  						return ev, ok
   521  					}
   522  				}
   523  			}
   524  		case key.NameHome:
   525  			e.text.MoveTextStart(selAct)
   526  		case key.NameEnd:
   527  			e.text.MoveTextEnd(selAct)
   528  		}
   529  		return nil, false
   530  	}
   531  	switch k.Name {
   532  	case key.NameReturn, key.NameEnter:
   533  		if !e.ReadOnly {
   534  			if e.Insert("\n") != 0 {
   535  				return ChangeEvent{}, true
   536  			}
   537  		}
   538  	case key.NameDeleteBackward:
   539  		if !e.ReadOnly {
   540  			if moveByWord {
   541  				if e.deleteWord(-1) != 0 {
   542  					return ChangeEvent{}, true
   543  				}
   544  			} else {
   545  				if e.Delete(-1) != 0 {
   546  					return ChangeEvent{}, true
   547  				}
   548  			}
   549  		}
   550  	case key.NameDeleteForward:
   551  		if !e.ReadOnly {
   552  			if moveByWord {
   553  				if e.deleteWord(1) != 0 {
   554  					return ChangeEvent{}, true
   555  				}
   556  			} else {
   557  				if e.Delete(1) != 0 {
   558  					return ChangeEvent{}, true
   559  				}
   560  			}
   561  		}
   562  	case key.NameUpArrow:
   563  		e.text.MoveLines(-1, selAct)
   564  	case key.NameDownArrow:
   565  		e.text.MoveLines(+1, selAct)
   566  	case key.NameLeftArrow:
   567  		if moveByWord {
   568  			e.text.MoveWord(-1*direction, selAct)
   569  		} else {
   570  			if selAct == selectionClear {
   571  				e.text.ClearSelection()
   572  			}
   573  			e.text.MoveCaret(-1*direction, -1*direction*int(selAct))
   574  		}
   575  	case key.NameRightArrow:
   576  		if moveByWord {
   577  			e.text.MoveWord(1*direction, selAct)
   578  		} else {
   579  			if selAct == selectionClear {
   580  				e.text.ClearSelection()
   581  			}
   582  			e.text.MoveCaret(1*direction, int(selAct)*direction)
   583  		}
   584  	case key.NamePageUp:
   585  		e.text.MovePages(-1, selAct)
   586  	case key.NamePageDown:
   587  		e.text.MovePages(+1, selAct)
   588  	case key.NameHome:
   589  		e.text.MoveLineStart(selAct)
   590  	case key.NameEnd:
   591  		e.text.MoveLineEnd(selAct)
   592  	}
   593  	return nil, false
   594  }
   595  
   596  // initBuffer should be invoked first in every exported function that accesses
   597  // text state. It ensures that the underlying text widget is both ready to use
   598  // and has its fields synced with the editor.
   599  func (e *Editor) initBuffer() {
   600  	if e.buffer == nil {
   601  		e.buffer = new(editBuffer)
   602  		e.text.SetSource(e.buffer)
   603  	}
   604  	e.text.Alignment = e.Alignment
   605  	e.text.LineHeight = e.LineHeight
   606  	e.text.LineHeightScale = e.LineHeightScale
   607  	e.text.SingleLine = e.SingleLine
   608  	e.text.Mask = e.Mask
   609  	e.text.WrapPolicy = e.WrapPolicy
   610  }
   611  
   612  // Update the state of the editor in response to input events. Update consumes editor
   613  // input events until there are no remaining events or an editor event is generated.
   614  // To fully update the state of the editor, callers should call Update until it returns
   615  // false.
   616  func (e *Editor) Update(gtx layout.Context) (EditorEvent, bool) {
   617  	e.initBuffer()
   618  	event, ok := e.processEvents(gtx)
   619  	// Notify IME of selection if it changed.
   620  	newSel := e.ime.selection
   621  	start, end := e.text.Selection()
   622  	newSel.rng = key.Range{
   623  		Start: start,
   624  		End:   end,
   625  	}
   626  	caretPos, carAsc, carDesc := e.text.CaretInfo()
   627  	newSel.caret = key.Caret{
   628  		Pos:     layout.FPt(caretPos),
   629  		Ascent:  float32(carAsc),
   630  		Descent: float32(carDesc),
   631  	}
   632  	if newSel != e.ime.selection {
   633  		e.ime.selection = newSel
   634  		gtx.Execute(key.SelectionCmd{Tag: e, Range: newSel.rng, Caret: newSel.caret})
   635  	}
   636  
   637  	e.updateSnippet(gtx, e.ime.start, e.ime.end)
   638  	return event, ok
   639  }
   640  
   641  // Layout lays out the editor using the provided textMaterial as the paint material
   642  // for the text glyphs+caret and the selectMaterial as the paint material for the
   643  // selection rectangle.
   644  func (e *Editor) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp, textMaterial, selectMaterial op.CallOp) layout.Dimensions {
   645  	for {
   646  		_, ok := e.Update(gtx)
   647  		if !ok {
   648  			break
   649  		}
   650  	}
   651  
   652  	e.text.Layout(gtx, lt, font, size)
   653  	return e.layout(gtx, textMaterial, selectMaterial)
   654  }
   655  
   656  // updateSnippet queues a key.SnippetCmd if the snippet content or position
   657  // have changed. off and len are in runes.
   658  func (e *Editor) updateSnippet(gtx layout.Context, start, end int) {
   659  	if start > end {
   660  		start, end = end, start
   661  	}
   662  	length := e.text.Len()
   663  	if start > length {
   664  		start = length
   665  	}
   666  	if end > length {
   667  		end = length
   668  	}
   669  	e.ime.start = start
   670  	e.ime.end = end
   671  	startOff := e.text.ByteOffset(start)
   672  	endOff := e.text.ByteOffset(end)
   673  	n := endOff - startOff
   674  	if n > int64(len(e.ime.scratch)) {
   675  		e.ime.scratch = make([]byte, n)
   676  	}
   677  	scratch := e.ime.scratch[:n]
   678  	read, _ := e.text.ReadAt(scratch, startOff)
   679  	if read != len(scratch) {
   680  		panic("e.rr.Read truncated data")
   681  	}
   682  	newSnip := key.Snippet{
   683  		Range: key.Range{
   684  			Start: e.ime.start,
   685  			End:   e.ime.end,
   686  		},
   687  		Text: e.ime.snippet.Text,
   688  	}
   689  	if string(scratch) != newSnip.Text {
   690  		newSnip.Text = string(scratch)
   691  	}
   692  	if newSnip == e.ime.snippet {
   693  		return
   694  	}
   695  	e.ime.snippet = newSnip
   696  	gtx.Execute(key.SnippetCmd{Tag: e, Snippet: newSnip})
   697  }
   698  
   699  func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.CallOp) layout.Dimensions {
   700  	// Adjust scrolling for new viewport and layout.
   701  	e.text.ScrollRel(0, 0)
   702  
   703  	if e.scrollCaret {
   704  		e.scrollCaret = false
   705  		e.text.ScrollToCaret()
   706  	}
   707  	visibleDims := e.text.Dimensions()
   708  
   709  	defer clip.Rect(image.Rectangle{Max: visibleDims.Size}).Push(gtx.Ops).Pop()
   710  	pointer.CursorText.Add(gtx.Ops)
   711  	event.Op(gtx.Ops, e)
   712  	key.InputHintOp{Tag: e, Hint: e.InputHint}.Add(gtx.Ops)
   713  
   714  	e.scroller.Add(gtx.Ops)
   715  
   716  	e.clicker.Add(gtx.Ops)
   717  	e.dragger.Add(gtx.Ops)
   718  	e.showCaret = false
   719  	if gtx.Focused(e) {
   720  		now := gtx.Now
   721  		dt := now.Sub(e.blinkStart)
   722  		blinking := dt < maxBlinkDuration
   723  		const timePerBlink = time.Second / blinksPerSecond
   724  		nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
   725  		if blinking {
   726  			gtx.Execute(op.InvalidateCmd{At: nextBlink})
   727  		}
   728  		e.showCaret = !blinking || dt%timePerBlink < timePerBlink/2
   729  	}
   730  	semantic.Editor.Add(gtx.Ops)
   731  	if e.Len() > 0 {
   732  		e.paintSelection(gtx, selectMaterial)
   733  		e.paintText(gtx, textMaterial)
   734  	}
   735  	if gtx.Enabled() {
   736  		e.paintCaret(gtx, textMaterial)
   737  	}
   738  	return visibleDims
   739  }
   740  
   741  // paintSelection paints the contrasting background for selected text using the provided
   742  // material to set the painting material for the selection.
   743  func (e *Editor) paintSelection(gtx layout.Context, material op.CallOp) {
   744  	e.initBuffer()
   745  	if !gtx.Focused(e) {
   746  		return
   747  	}
   748  	e.text.PaintSelection(gtx, material)
   749  }
   750  
   751  // paintText paints the text glyphs using the provided material to set the fill of the
   752  // glyphs.
   753  func (e *Editor) paintText(gtx layout.Context, material op.CallOp) {
   754  	e.initBuffer()
   755  	e.text.PaintText(gtx, material)
   756  }
   757  
   758  // paintCaret paints the text glyphs using the provided material to set the fill material
   759  // of the caret rectangle.
   760  func (e *Editor) paintCaret(gtx layout.Context, material op.CallOp) {
   761  	e.initBuffer()
   762  	if !e.showCaret || e.ReadOnly {
   763  		return
   764  	}
   765  	e.text.PaintCaret(gtx, material)
   766  }
   767  
   768  // Len is the length of the editor contents, in runes.
   769  func (e *Editor) Len() int {
   770  	e.initBuffer()
   771  	return e.text.Len()
   772  }
   773  
   774  // Text returns the contents of the editor.
   775  func (e *Editor) Text() string {
   776  	e.initBuffer()
   777  	e.scratch = e.text.Text(e.scratch)
   778  	return string(e.scratch)
   779  }
   780  
   781  func (e *Editor) SetText(s string) {
   782  	e.initBuffer()
   783  	if e.SingleLine {
   784  		s = strings.ReplaceAll(s, "\n", " ")
   785  	}
   786  	e.replace(0, e.text.Len(), s, true)
   787  	// Reset xoff and move the caret to the beginning.
   788  	e.SetCaret(0, 0)
   789  }
   790  
   791  // CaretPos returns the line & column numbers of the caret.
   792  func (e *Editor) CaretPos() (line, col int) {
   793  	e.initBuffer()
   794  	return e.text.CaretPos()
   795  }
   796  
   797  // CaretCoords returns the coordinates of the caret, relative to the
   798  // editor itself.
   799  func (e *Editor) CaretCoords() f32.Point {
   800  	e.initBuffer()
   801  	return e.text.CaretCoords()
   802  }
   803  
   804  // Delete runes from the caret position. The sign of the argument specifies the
   805  // direction to delete: positive is forward, negative is backward.
   806  //
   807  // If there is a selection, it is deleted and counts as a single grapheme
   808  // cluster.
   809  func (e *Editor) Delete(graphemeClusters int) (deletedRunes int) {
   810  	e.initBuffer()
   811  	if graphemeClusters == 0 {
   812  		return 0
   813  	}
   814  
   815  	start, end := e.text.Selection()
   816  	if start != end {
   817  		graphemeClusters -= sign(graphemeClusters)
   818  	}
   819  
   820  	// Move caret by the target quantity of clusters.
   821  	e.text.MoveCaret(0, graphemeClusters)
   822  	// Get the new rune offsets of the selection.
   823  	start, end = e.text.Selection()
   824  	e.replace(start, end, "", true)
   825  	// Reset xoff.
   826  	e.text.MoveCaret(0, 0)
   827  	e.ClearSelection()
   828  	return end - start
   829  }
   830  
   831  func (e *Editor) Insert(s string) (insertedRunes int) {
   832  	e.initBuffer()
   833  	if e.SingleLine {
   834  		s = strings.ReplaceAll(s, "\n", " ")
   835  	}
   836  	start, end := e.text.Selection()
   837  	moves := e.replace(start, end, s, true)
   838  	if end < start {
   839  		start = end
   840  	}
   841  	// Reset xoff.
   842  	e.text.MoveCaret(0, 0)
   843  	e.SetCaret(start+moves, start+moves)
   844  	e.scrollCaret = true
   845  	return moves
   846  }
   847  
   848  // modification represents a change to the contents of the editor buffer.
   849  // It contains the necessary information to both apply the change and
   850  // reverse it, and is useful for implementing undo/redo.
   851  type modification struct {
   852  	// StartRune is the inclusive index of the first rune
   853  	// modified.
   854  	StartRune int
   855  	// ApplyContent is the data inserted at StartRune to
   856  	// apply this operation. It overwrites len([]rune(ReverseContent)) runes.
   857  	ApplyContent string
   858  	// ReverseContent is the data inserted at StartRune to
   859  	// apply this operation. It overwrites len([]rune(ApplyContent)) runes.
   860  	ReverseContent string
   861  }
   862  
   863  // undo applies the modification at e.history[e.historyIdx] and decrements
   864  // e.historyIdx.
   865  func (e *Editor) undo() (EditorEvent, bool) {
   866  	e.initBuffer()
   867  	if len(e.history) < 1 || e.nextHistoryIdx == 0 {
   868  		return nil, false
   869  	}
   870  	mod := e.history[e.nextHistoryIdx-1]
   871  	replaceEnd := mod.StartRune + utf8.RuneCountInString(mod.ApplyContent)
   872  	e.replace(mod.StartRune, replaceEnd, mod.ReverseContent, false)
   873  	caretEnd := mod.StartRune + utf8.RuneCountInString(mod.ReverseContent)
   874  	e.SetCaret(caretEnd, mod.StartRune)
   875  	e.nextHistoryIdx--
   876  	return ChangeEvent{}, true
   877  }
   878  
   879  // redo applies the modification at e.history[e.historyIdx] and increments
   880  // e.historyIdx.
   881  func (e *Editor) redo() (EditorEvent, bool) {
   882  	e.initBuffer()
   883  	if len(e.history) < 1 || e.nextHistoryIdx == len(e.history) {
   884  		return nil, false
   885  	}
   886  	mod := e.history[e.nextHistoryIdx]
   887  	end := mod.StartRune + utf8.RuneCountInString(mod.ReverseContent)
   888  	e.replace(mod.StartRune, end, mod.ApplyContent, false)
   889  	caretEnd := mod.StartRune + utf8.RuneCountInString(mod.ApplyContent)
   890  	e.SetCaret(caretEnd, mod.StartRune)
   891  	e.nextHistoryIdx++
   892  	return ChangeEvent{}, true
   893  }
   894  
   895  // replace the text between start and end with s. Indices are in runes.
   896  // It returns the number of runes inserted.
   897  // addHistory controls whether this modification is recorded in the undo
   898  // history. replace can modify text in positions unrelated to the cursor
   899  // position.
   900  func (e *Editor) replace(start, end int, s string, addHistory bool) int {
   901  	length := e.text.Len()
   902  	if start > end {
   903  		start, end = end, start
   904  	}
   905  	start = min(start, length)
   906  	end = min(end, length)
   907  	replaceSize := end - start
   908  	el := e.Len()
   909  	var sc int
   910  	idx := 0
   911  	for idx < len(s) {
   912  		if e.MaxLen > 0 && el-replaceSize+sc >= e.MaxLen {
   913  			s = s[:idx]
   914  			break
   915  		}
   916  		_, n := utf8.DecodeRuneInString(s[idx:])
   917  		if e.Filter != "" && !strings.Contains(e.Filter, s[idx:idx+n]) {
   918  			s = s[:idx] + s[idx+n:]
   919  			continue
   920  		}
   921  		idx += n
   922  		sc++
   923  	}
   924  
   925  	if addHistory {
   926  		deleted := make([]rune, 0, replaceSize)
   927  		readPos := e.text.ByteOffset(start)
   928  		for i := 0; i < replaceSize; i++ {
   929  			ru, s, _ := e.text.ReadRuneAt(int64(readPos))
   930  			readPos += int64(s)
   931  			deleted = append(deleted, ru)
   932  		}
   933  		if e.nextHistoryIdx < len(e.history) {
   934  			e.history = e.history[:e.nextHistoryIdx]
   935  		}
   936  		e.history = append(e.history, modification{
   937  			StartRune:      start,
   938  			ApplyContent:   s,
   939  			ReverseContent: string(deleted),
   940  		})
   941  		e.nextHistoryIdx++
   942  	}
   943  
   944  	sc = e.text.Replace(start, end, s)
   945  	newEnd := start + sc
   946  	adjust := func(pos int) int {
   947  		switch {
   948  		case newEnd < pos && pos <= end:
   949  			pos = newEnd
   950  		case end < pos:
   951  			diff := newEnd - end
   952  			pos = pos + diff
   953  		}
   954  		return pos
   955  	}
   956  	e.ime.start = adjust(e.ime.start)
   957  	e.ime.end = adjust(e.ime.end)
   958  	return sc
   959  }
   960  
   961  // MoveCaret moves the caret (aka selection start) and the selection end
   962  // relative to their current positions. Positive distances moves forward,
   963  // negative distances moves backward. Distances are in grapheme clusters,
   964  // which closely match what users perceive as "characters" even when the
   965  // characters are multiple code points long.
   966  func (e *Editor) MoveCaret(startDelta, endDelta int) {
   967  	e.initBuffer()
   968  	e.text.MoveCaret(startDelta, endDelta)
   969  }
   970  
   971  // deleteWord deletes the next word(s) in the specified direction.
   972  // Unlike moveWord, deleteWord treats whitespace as a word itself.
   973  // Positive is forward, negative is backward.
   974  // Absolute values greater than one will delete that many words.
   975  // The selection counts as a single word.
   976  func (e *Editor) deleteWord(distance int) (deletedRunes int) {
   977  	if distance == 0 {
   978  		return
   979  	}
   980  
   981  	start, end := e.text.Selection()
   982  	if start != end {
   983  		deletedRunes = e.Delete(1)
   984  		distance -= sign(distance)
   985  	}
   986  	if distance == 0 {
   987  		return deletedRunes
   988  	}
   989  
   990  	// split the distance information into constituent parts to be
   991  	// used independently.
   992  	words, direction := distance, 1
   993  	if distance < 0 {
   994  		words, direction = distance*-1, -1
   995  	}
   996  	caret, _ := e.text.Selection()
   997  	// atEnd if offset is at or beyond either side of the buffer.
   998  	atEnd := func(runes int) bool {
   999  		idx := caret + runes*direction
  1000  		return idx <= 0 || idx >= e.Len()
  1001  	}
  1002  	// next returns the appropriate rune given the direction and offset in runes).
  1003  	next := func(runes int) rune {
  1004  		idx := caret + runes*direction
  1005  		if idx < 0 {
  1006  			idx = 0
  1007  		} else if idx > e.Len() {
  1008  			idx = e.Len()
  1009  		}
  1010  		off := e.text.ByteOffset(idx)
  1011  		var r rune
  1012  		if direction < 0 {
  1013  			r, _, _ = e.text.ReadRuneBefore(int64(off))
  1014  		} else {
  1015  			r, _, _ = e.text.ReadRuneAt(int64(off))
  1016  		}
  1017  		return r
  1018  	}
  1019  	runes := 1
  1020  	for ii := 0; ii < words; ii++ {
  1021  		r := next(runes)
  1022  		wantSpace := unicode.IsSpace(r)
  1023  		for r := next(runes); unicode.IsSpace(r) == wantSpace && !atEnd(runes); r = next(runes) {
  1024  			runes += 1
  1025  		}
  1026  	}
  1027  	deletedRunes += e.Delete(runes * direction)
  1028  	return deletedRunes
  1029  }
  1030  
  1031  // SelectionLen returns the length of the selection, in runes; it is
  1032  // equivalent to utf8.RuneCountInString(e.SelectedText()).
  1033  func (e *Editor) SelectionLen() int {
  1034  	e.initBuffer()
  1035  	return e.text.SelectionLen()
  1036  }
  1037  
  1038  // Selection returns the start and end of the selection, as rune offsets.
  1039  // start can be > end.
  1040  func (e *Editor) Selection() (start, end int) {
  1041  	e.initBuffer()
  1042  	return e.text.Selection()
  1043  }
  1044  
  1045  // SetCaret moves the caret to start, and sets the selection end to end. start
  1046  // and end are in runes, and represent offsets into the editor text.
  1047  func (e *Editor) SetCaret(start, end int) {
  1048  	e.initBuffer()
  1049  	e.text.SetCaret(start, end)
  1050  	e.scrollCaret = true
  1051  	e.scroller.Stop()
  1052  }
  1053  
  1054  // SelectedText returns the currently selected text (if any) from the editor.
  1055  func (e *Editor) SelectedText() string {
  1056  	e.initBuffer()
  1057  	e.scratch = e.text.SelectedText(e.scratch)
  1058  	return string(e.scratch)
  1059  }
  1060  
  1061  // ClearSelection clears the selection, by setting the selection end equal to
  1062  // the selection start.
  1063  func (e *Editor) ClearSelection() {
  1064  	e.initBuffer()
  1065  	e.text.ClearSelection()
  1066  }
  1067  
  1068  // WriteTo implements io.WriterTo.
  1069  func (e *Editor) WriteTo(w io.Writer) (int64, error) {
  1070  	e.initBuffer()
  1071  	return e.text.WriteTo(w)
  1072  }
  1073  
  1074  // Seek implements io.Seeker.
  1075  func (e *Editor) Seek(offset int64, whence int) (int64, error) {
  1076  	e.initBuffer()
  1077  	return e.text.Seek(offset, whence)
  1078  }
  1079  
  1080  // Read implements io.Reader.
  1081  func (e *Editor) Read(p []byte) (int, error) {
  1082  	e.initBuffer()
  1083  	return e.text.Read(p)
  1084  }
  1085  
  1086  // Regions returns visible regions covering the rune range [start,end).
  1087  func (e *Editor) Regions(start, end int, regions []Region) []Region {
  1088  	e.initBuffer()
  1089  	return e.text.Regions(start, end, regions)
  1090  }
  1091  
  1092  func max(a, b int) int {
  1093  	if a > b {
  1094  		return a
  1095  	}
  1096  	return b
  1097  }
  1098  
  1099  func min(a, b int) int {
  1100  	if a < b {
  1101  		return a
  1102  	}
  1103  	return b
  1104  }
  1105  
  1106  func abs(n int) int {
  1107  	if n < 0 {
  1108  		return -n
  1109  	}
  1110  	return n
  1111  }
  1112  
  1113  func sign(n int) int {
  1114  	switch {
  1115  	case n < 0:
  1116  		return -1
  1117  	case n > 0:
  1118  		return 1
  1119  	default:
  1120  		return 0
  1121  	}
  1122  }
  1123  
  1124  func (s ChangeEvent) isEditorEvent() {}
  1125  func (s SubmitEvent) isEditorEvent() {}
  1126  func (s SelectEvent) isEditorEvent() {}