github.com/utopiagio/gio@v0.0.8/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  	//"log"
    10  	"math"
    11  	"strings"
    12  	"time"
    13  	"unicode"
    14  	"unicode/utf8"
    15  
    16  	"github.com/utopiagio/gio/f32"
    17  	"github.com/utopiagio/gio/font"
    18  	"github.com/utopiagio/gio/gesture"
    19  	"github.com/utopiagio/gio/io/clipboard"
    20  	"github.com/utopiagio/gio/io/event"
    21  	"github.com/utopiagio/gio/io/key"
    22  	"github.com/utopiagio/gio/io/pointer"
    23  	"github.com/utopiagio/gio/io/semantic"
    24  	"github.com/utopiagio/gio/io/system"
    25  	"github.com/utopiagio/gio/io/transfer"
    26  	"github.com/utopiagio/gio/layout"
    27  	"github.com/utopiagio/gio/op"
    28  	"github.com/utopiagio/gio/op/clip"
    29  	"github.com/utopiagio/gio/text"
    30  	"github.com/utopiagio/gio/unit"
    31  )
    32  
    33  // Editor implements an editable and scrollable text area.
    34  type Editor struct {
    35  	// text manages the text buffer and provides shaping and cursor positioning
    36  	// services.
    37  	text textView
    38  	// Alignment controls the alignment of text within the editor.
    39  	Alignment text.Alignment
    40  	// LineHeight determines the gap between baselines of text. If zero, a sensible
    41  	// default will be used.
    42  	LineHeight unit.Sp
    43  	// LineHeightScale is multiplied by LineHeight to determine the final gap
    44  	// between baselines. If zero, a sensible default will be used.
    45  	LineHeightScale float32
    46  	// SingleLine force the text to stay on a single line.
    47  	// SingleLine also sets the scrolling direction to
    48  	// horizontal.
    49  	SingleLine bool
    50  	// ReadOnly controls whether the contents of the editor can be altered by
    51  	// user interaction. If set to true, the editor will allow selecting text
    52  	// and copying it interactively, but not modifying it.
    53  	ReadOnly bool
    54  	// Submit enabled translation of carriage return keys to SubmitEvents.
    55  	// If not enabled, carriage returns are inserted as newlines in the text.
    56  	Submit bool
    57  	// Mask replaces the visual display of each rune in the contents with the given rune.
    58  	// Newline characters are not masked. When non-zero, the unmasked contents
    59  	// are accessed by Len, Text, and SetText.
    60  	Mask rune
    61  	// InputHint specifies the type of on-screen keyboard to be displayed.
    62  	InputHint key.InputHint
    63  	// MaxLen limits the editor content to a maximum length. Zero means no limit.
    64  	MaxLen int
    65  	// Filter is the list of characters allowed in the Editor. If Filter is empty,
    66  	// all characters are allowed.
    67  	Filter string
    68  	// WrapPolicy configures how displayed text will be broken into lines.
    69  	WrapPolicy text.WrapPolicy
    70  
    71  	buffer *editBuffer
    72  	// scratch is a byte buffer that is reused to efficiently read portions of text
    73  	// from the textView.
    74  	scratch    []byte
    75  	blinkStart time.Time
    76  
    77  	// ime tracks the state relevant to input methods.
    78  	ime struct {
    79  		imeState
    80  		scratch []byte
    81  	}
    82  
    83  	dragging    bool
    84  	dragger     gesture.Drag
    85  	scroller    gesture.Scroll
    86  	scrollCaret bool
    87  	showCaret   bool
    88  
    89  	clicker gesture.Click
    90  
    91  	// history contains undo history.
    92  	history []modification
    93  	// nextHistoryIdx is the index within the history of the next modification. This
    94  	// is only not len(history) immediately after undo operations occur. It is framed as the "next" value
    95  	// to make the zero value consistent.
    96  	nextHistoryIdx int
    97  
    98  	pending []EditorEvent
    99  }
   100  
   101  type offEntry struct {
   102  	runes int
   103  	bytes int
   104  }
   105  
   106  type imeState struct {
   107  	selection struct {
   108  		rng   key.Range
   109  		caret key.Caret
   110  	}
   111  	snippet    key.Snippet
   112  	start, end int
   113  }
   114  
   115  type maskReader struct {
   116  	// rr is the underlying reader.
   117  	rr      io.RuneReader
   118  	maskBuf [utf8.UTFMax]byte
   119  	// mask is the utf-8 encoded mask rune.
   120  	mask []byte
   121  	// overflow contains excess mask bytes left over after the last Read call.
   122  	overflow []byte
   123  }
   124  
   125  type selectionAction int
   126  
   127  const (
   128  	selectionExtend selectionAction = iota
   129  	selectionClear
   130  )
   131  
   132  func (m *maskReader) Reset(r io.Reader, mr rune) {
   133  	m.rr = bufio.NewReader(r)
   134  	n := utf8.EncodeRune(m.maskBuf[:], mr)
   135  	m.mask = m.maskBuf[:n]
   136  }
   137  
   138  // Read reads from the underlying reader and replaces every
   139  // rune with the mask rune.
   140  func (m *maskReader) Read(b []byte) (n int, err error) {
   141  	for len(b) > 0 {
   142  		var replacement []byte
   143  		if len(m.overflow) > 0 {
   144  			replacement = m.overflow
   145  		} else {
   146  			var r rune
   147  			r, _, err = m.rr.ReadRune()
   148  			if err != nil {
   149  				break
   150  			}
   151  			if r == '\n' {
   152  				replacement = []byte{'\n'}
   153  			} else {
   154  				replacement = m.mask
   155  			}
   156  		}
   157  		nn := copy(b, replacement)
   158  		m.overflow = replacement[nn:]
   159  		n += nn
   160  		b = b[nn:]
   161  	}
   162  	return n, err
   163  }
   164  
   165  type EditorEvent interface {
   166  	isEditorEvent()
   167  }
   168  
   169  // A ChangeEvent is generated for every user change to the text.
   170  type ChangeEvent struct{}
   171  
   172  // A SubmitEvent is generated when Submit is set
   173  // and a carriage return key is pressed.
   174  type SubmitEvent struct {
   175  	Text string
   176  }
   177  
   178  // A SelectEvent is generated when the user selects some text, or changes the
   179  // selection (e.g. with a shift-click), including if they remove the
   180  // selection. The selected text is not part of the event, on the theory that
   181  // it could be a relatively expensive operation (for a large editor), most
   182  // applications won't actually care about it, and those that do can call
   183  // Editor.SelectedText() (which can be empty).
   184  type SelectEvent struct{}
   185  
   186  const (
   187  	blinksPerSecond  = 1
   188  	maxBlinkDuration = 10 * time.Second
   189  )
   190  
   191  func (e *Editor) processEvents(gtx layout.Context) (ev EditorEvent, ok bool) {
   192  	if len(e.pending) > 0 {
   193  		out := e.pending[0]
   194  		e.pending = e.pending[:copy(e.pending, e.pending[1:])]
   195  		return out, true
   196  	}
   197  	selStart, selEnd := e.Selection()
   198  	defer func() {
   199  		afterSelStart, afterSelEnd := e.Selection()
   200  		if selStart != afterSelStart || selEnd != afterSelEnd {
   201  			if ok {
   202  				e.pending = append(e.pending, SelectEvent{})
   203  			} else {
   204  				ev = SelectEvent{}
   205  				ok = true
   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 scrollRange image.Rectangle
   232  	textDims := e.text.FullDimensions()
   233  	visibleDims := e.text.Dimensions()
   234  	if e.SingleLine {
   235  		scrollOffX := e.text.ScrollOff().X
   236  		scrollRange.Min.X = min(-scrollOffX, 0)
   237  		scrollRange.Max.X = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
   238  	} else {
   239  		scrollOffY := e.text.ScrollOff().Y
   240  		scrollRange.Min.Y = -scrollOffY
   241  		scrollRange.Max.Y = max(0, textDims.Size.Y-(scrollOffY+visibleDims.Size.Y))
   242  	}
   243  	sdist := e.scroller.Update(gtx.Metric, gtx.Source, gtx.Now, axis, scrollRange)
   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.MoveStart(selectionClear)
   316  				e.text.MoveEnd(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.ModShift},
   378  		key.Filter{Focus: e, Name: key.NameEnd, Optional: 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  		}
   525  		return nil, false
   526  	}
   527  	switch k.Name {
   528  	case key.NameReturn, key.NameEnter:
   529  		if !e.ReadOnly {
   530  			if e.Insert("\n") != 0 {
   531  				return ChangeEvent{}, true
   532  			}
   533  		}
   534  	case key.NameDeleteBackward:
   535  		if !e.ReadOnly {
   536  			if moveByWord {
   537  				if e.deleteWord(-1) != 0 {
   538  					return ChangeEvent{}, true
   539  				}
   540  			} else {
   541  				if e.Delete(-1) != 0 {
   542  					return ChangeEvent{}, true
   543  				}
   544  			}
   545  		}
   546  	case key.NameDeleteForward:
   547  		if !e.ReadOnly {
   548  			if moveByWord {
   549  				if e.deleteWord(1) != 0 {
   550  					return ChangeEvent{}, true
   551  				}
   552  			} else {
   553  				if e.Delete(1) != 0 {
   554  					return ChangeEvent{}, true
   555  				}
   556  			}
   557  		}
   558  	case key.NameUpArrow:
   559  		e.text.MoveLines(-1, selAct)
   560  	case key.NameDownArrow:
   561  		e.text.MoveLines(+1, selAct)
   562  	case key.NameLeftArrow:
   563  		if moveByWord {
   564  			e.text.MoveWord(-1*direction, selAct)
   565  		} else {
   566  			if selAct == selectionClear {
   567  				e.text.ClearSelection()
   568  			}
   569  			e.text.MoveCaret(-1*direction, -1*direction*int(selAct))
   570  		}
   571  	case key.NameRightArrow:
   572  		if moveByWord {
   573  			e.text.MoveWord(1*direction, selAct)
   574  		} else {
   575  			if selAct == selectionClear {
   576  				e.text.ClearSelection()
   577  			}
   578  			e.text.MoveCaret(1*direction, int(selAct)*direction)
   579  		}
   580  	case key.NamePageUp:
   581  		e.text.MovePages(-1, selAct)
   582  	case key.NamePageDown:
   583  		e.text.MovePages(+1, selAct)
   584  	case key.NameHome:
   585  		e.text.MoveStart(selAct)
   586  	case key.NameEnd:
   587  		e.text.MoveEnd(selAct)
   588  	}
   589  	return nil, false
   590  }
   591  
   592  // initBuffer should be invoked first in every exported function that accesses
   593  // text state. It ensures that the underlying text widget is both ready to use
   594  // and has its fields synced with the editor.
   595  func (e *Editor) initBuffer() {
   596  	if e.buffer == nil {
   597  		e.buffer = new(editBuffer)
   598  		e.text.SetSource(e.buffer)
   599  	}
   600  	e.text.Alignment = e.Alignment
   601  	e.text.LineHeight = e.LineHeight
   602  	e.text.LineHeightScale = e.LineHeightScale
   603  	e.text.SingleLine = e.SingleLine
   604  	e.text.Mask = e.Mask
   605  	e.text.WrapPolicy = e.WrapPolicy
   606  }
   607  
   608  // Update the state of the editor in response to input events. Update consumes editor
   609  // input events until there are no remaining events or an editor event is generated.
   610  // To fully update the state of the editor, callers should call Update until it returns
   611  // false.
   612  func (e *Editor) Update(gtx layout.Context) (EditorEvent, bool) {
   613  	e.initBuffer()
   614  	event, ok := e.processEvents(gtx)
   615  	// Notify IME of selection if it changed.
   616  	newSel := e.ime.selection
   617  	start, end := e.text.Selection()
   618  	newSel.rng = key.Range{
   619  		Start: start,
   620  		End:   end,
   621  	}
   622  	caretPos, carAsc, carDesc := e.text.CaretInfo()
   623  	newSel.caret = key.Caret{
   624  		Pos:     layout.FPt(caretPos),
   625  		Ascent:  float32(carAsc),
   626  		Descent: float32(carDesc),
   627  	}
   628  	if newSel != e.ime.selection {
   629  		e.ime.selection = newSel
   630  		gtx.Execute(key.SelectionCmd{Tag: e, Range: newSel.rng, Caret: newSel.caret})
   631  	}
   632  
   633  	e.updateSnippet(gtx, e.ime.start, e.ime.end)
   634  	return event, ok
   635  }
   636  
   637  // Layout lays out the editor using the provided textMaterial as the paint material
   638  // for the text glyphs+caret and the selectMaterial as the paint material for the
   639  // selection rectangle.
   640  func (e *Editor) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp, textMaterial, selectMaterial op.CallOp) layout.Dimensions {
   641  	for {
   642  		_, ok := e.Update(gtx)
   643  		if !ok {
   644  			break
   645  		}
   646  	}
   647  
   648  	e.text.Layout(gtx, lt, font, size)
   649  	return e.layout(gtx, textMaterial, selectMaterial)
   650  }
   651  
   652  // updateSnippet queues a key.SnippetCmd if the snippet content or position
   653  // have changed. off and len are in runes.
   654  func (e *Editor) updateSnippet(gtx layout.Context, start, end int) {
   655  	if start > end {
   656  		start, end = end, start
   657  	}
   658  	length := e.text.Len()
   659  	if start > length {
   660  		start = length
   661  	}
   662  	if end > length {
   663  		end = length
   664  	}
   665  	e.ime.start = start
   666  	e.ime.end = end
   667  	startOff := e.text.ByteOffset(start)
   668  	endOff := e.text.ByteOffset(end)
   669  	n := endOff - startOff
   670  	if n > int64(len(e.ime.scratch)) {
   671  		e.ime.scratch = make([]byte, n)
   672  	}
   673  	scratch := e.ime.scratch[:n]
   674  	read, _ := e.text.ReadAt(scratch, startOff)
   675  	if read != len(scratch) {
   676  		panic("e.rr.Read truncated data")
   677  	}
   678  	newSnip := key.Snippet{
   679  		Range: key.Range{
   680  			Start: e.ime.start,
   681  			End:   e.ime.end,
   682  		},
   683  		Text: e.ime.snippet.Text,
   684  	}
   685  	if string(scratch) != newSnip.Text {
   686  		newSnip.Text = string(scratch)
   687  	}
   688  	if newSnip == e.ime.snippet {
   689  		return
   690  	}
   691  	e.ime.snippet = newSnip
   692  	gtx.Execute(key.SnippetCmd{Tag: e, Snippet: newSnip})
   693  }
   694  
   695  func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.CallOp) layout.Dimensions {
   696  	// Adjust scrolling for new viewport and layout.
   697  	e.text.ScrollRel(0, 0)
   698  
   699  	if e.scrollCaret {
   700  		e.scrollCaret = false
   701  		e.text.ScrollToCaret()
   702  	}
   703  	visibleDims := e.text.Dimensions()
   704  
   705  	defer clip.Rect(image.Rectangle{Max: visibleDims.Size}).Push(gtx.Ops).Pop()
   706  	pointer.CursorText.Add(gtx.Ops)
   707  	event.Op(gtx.Ops, e)
   708  	key.InputHintOp{Tag: e, Hint: e.InputHint}.Add(gtx.Ops)
   709  
   710  	e.scroller.Add(gtx.Ops)
   711  
   712  	e.clicker.Add(gtx.Ops)
   713  	e.dragger.Add(gtx.Ops)
   714  	e.showCaret = false
   715  	if gtx.Focused(e) {
   716  		now := gtx.Now
   717  		dt := now.Sub(e.blinkStart)
   718  		blinking := dt < maxBlinkDuration
   719  		const timePerBlink = time.Second / blinksPerSecond
   720  		nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
   721  		if blinking {
   722  			gtx.Execute(op.InvalidateCmd{At: nextBlink})
   723  		}
   724  		e.showCaret = !blinking || dt%timePerBlink < timePerBlink/2
   725  	}
   726  	semantic.Editor.Add(gtx.Ops)
   727  	if e.Len() > 0 {
   728  		e.paintSelection(gtx, selectMaterial)
   729  		e.paintText(gtx, textMaterial)
   730  	}
   731  	if gtx.Enabled() {
   732  		e.paintCaret(gtx, textMaterial)
   733  	}
   734  	return visibleDims
   735  }
   736  
   737  // paintSelection paints the contrasting background for selected text using the provided
   738  // material to set the painting material for the selection.
   739  func (e *Editor) paintSelection(gtx layout.Context, material op.CallOp) {
   740  	e.initBuffer()
   741  	if !gtx.Focused(e) {
   742  		return
   743  	}
   744  	e.text.PaintSelection(gtx, material)
   745  }
   746  
   747  // paintText paints the text glyphs using the provided material to set the fill of the
   748  // glyphs.
   749  func (e *Editor) paintText(gtx layout.Context, material op.CallOp) {
   750  	e.initBuffer()
   751  	e.text.PaintText(gtx, material)
   752  }
   753  
   754  // paintCaret paints the text glyphs using the provided material to set the fill material
   755  // of the caret rectangle.
   756  func (e *Editor) paintCaret(gtx layout.Context, material op.CallOp) {
   757  	e.initBuffer()
   758  	if !e.showCaret || e.ReadOnly {
   759  		return
   760  	}
   761  	e.text.PaintCaret(gtx, material)
   762  }
   763  
   764  // Len is the length of the editor contents, in runes.
   765  func (e *Editor) Len() int {
   766  	e.initBuffer()
   767  	return e.text.Len()
   768  }
   769  
   770  // Text returns the contents of the editor.
   771  func (e *Editor) Text() string {
   772  	e.initBuffer()
   773  	e.scratch = e.text.Text(e.scratch)
   774  	return string(e.scratch)
   775  }
   776  
   777  func (e *Editor) SetText(s string) {
   778  	e.initBuffer()
   779  	if e.SingleLine {
   780  		s = strings.ReplaceAll(s, "\n", " ")
   781  	}
   782  	e.replace(0, e.text.Len(), s, true)
   783  	// Reset xoff and move the caret to the beginning.
   784  	e.SetCaret(0, 0)
   785  }
   786  
   787  // CaretPos returns the line & column numbers of the caret.
   788  func (e *Editor) CaretPos() (line, col int) {
   789  	e.initBuffer()
   790  	return e.text.CaretPos()
   791  }
   792  
   793  // CaretCoords returns the coordinates of the caret, relative to the
   794  // editor itself.
   795  func (e *Editor) CaretCoords() f32.Point {
   796  	e.initBuffer()
   797  	return e.text.CaretCoords()
   798  }
   799  
   800  // Delete runes from the caret position. The sign of the argument specifies the
   801  // direction to delete: positive is forward, negative is backward.
   802  //
   803  // If there is a selection, it is deleted and counts as a single grapheme
   804  // cluster.
   805  func (e *Editor) Delete(graphemeClusters int) (deletedRunes int) {
   806  	e.initBuffer()
   807  	if graphemeClusters == 0 {
   808  		return 0
   809  	}
   810  
   811  	start, end := e.text.Selection()
   812  	if start != end {
   813  		graphemeClusters -= sign(graphemeClusters)
   814  	}
   815  
   816  	// Move caret by the target quantity of clusters.
   817  	e.text.MoveCaret(0, graphemeClusters)
   818  	// Get the new rune offsets of the selection.
   819  	start, end = e.text.Selection()
   820  	e.replace(start, end, "", true)
   821  	// Reset xoff.
   822  	e.text.MoveCaret(0, 0)
   823  	e.ClearSelection()
   824  	return end - start
   825  }
   826  
   827  func (e *Editor) Insert(s string) (insertedRunes int) {
   828  	e.initBuffer()
   829  	if e.SingleLine {
   830  		s = strings.ReplaceAll(s, "\n", " ")
   831  	}
   832  	start, end := e.text.Selection()
   833  	moves := e.replace(start, end, s, true)
   834  	if end < start {
   835  		start = end
   836  	}
   837  	// Reset xoff.
   838  	e.text.MoveCaret(0, 0)
   839  	e.SetCaret(start+moves, start+moves)
   840  	e.scrollCaret = true
   841  	return moves
   842  }
   843  
   844  // modification represents a change to the contents of the editor buffer.
   845  // It contains the necessary information to both apply the change and
   846  // reverse it, and is useful for implementing undo/redo.
   847  type modification struct {
   848  	// StartRune is the inclusive index of the first rune
   849  	// modified.
   850  	StartRune int
   851  	// ApplyContent is the data inserted at StartRune to
   852  	// apply this operation. It overwrites len([]rune(ReverseContent)) runes.
   853  	ApplyContent string
   854  	// ReverseContent is the data inserted at StartRune to
   855  	// apply this operation. It overwrites len([]rune(ApplyContent)) runes.
   856  	ReverseContent string
   857  }
   858  
   859  // undo applies the modification at e.history[e.historyIdx] and decrements
   860  // e.historyIdx.
   861  func (e *Editor) undo() (EditorEvent, bool) {
   862  	e.initBuffer()
   863  	if len(e.history) < 1 || e.nextHistoryIdx == 0 {
   864  		return nil, false
   865  	}
   866  	mod := e.history[e.nextHistoryIdx-1]
   867  	replaceEnd := mod.StartRune + utf8.RuneCountInString(mod.ApplyContent)
   868  	e.replace(mod.StartRune, replaceEnd, mod.ReverseContent, false)
   869  	caretEnd := mod.StartRune + utf8.RuneCountInString(mod.ReverseContent)
   870  	e.SetCaret(caretEnd, mod.StartRune)
   871  	e.nextHistoryIdx--
   872  	return ChangeEvent{}, true
   873  }
   874  
   875  // redo applies the modification at e.history[e.historyIdx] and increments
   876  // e.historyIdx.
   877  func (e *Editor) redo() (EditorEvent, bool) {
   878  	e.initBuffer()
   879  	if len(e.history) < 1 || e.nextHistoryIdx == len(e.history) {
   880  		return nil, false
   881  	}
   882  	mod := e.history[e.nextHistoryIdx]
   883  	end := mod.StartRune + utf8.RuneCountInString(mod.ReverseContent)
   884  	e.replace(mod.StartRune, end, mod.ApplyContent, false)
   885  	caretEnd := mod.StartRune + utf8.RuneCountInString(mod.ApplyContent)
   886  	e.SetCaret(caretEnd, mod.StartRune)
   887  	e.nextHistoryIdx++
   888  	return ChangeEvent{}, true
   889  }
   890  
   891  // replace the text between start and end with s. Indices are in runes.
   892  // It returns the number of runes inserted.
   893  // addHistory controls whether this modification is recorded in the undo
   894  // history. replace can modify text in positions unrelated to the cursor
   895  // position.
   896  func (e *Editor) replace(start, end int, s string, addHistory bool) int {
   897  	length := e.text.Len()
   898  	if start > end {
   899  		start, end = end, start
   900  	}
   901  	start = min(start, length)
   902  	end = min(end, length)
   903  	replaceSize := end - start
   904  	el := e.Len()
   905  	var sc int
   906  	idx := 0
   907  	for idx < len(s) {
   908  		if e.MaxLen > 0 && el-replaceSize+sc >= e.MaxLen {
   909  			s = s[:idx]
   910  			break
   911  		}
   912  		_, n := utf8.DecodeRuneInString(s[idx:])
   913  		if e.Filter != "" && !strings.Contains(e.Filter, s[idx:idx+n]) {
   914  			s = s[:idx] + s[idx+n:]
   915  			continue
   916  		}
   917  		idx += n
   918  		sc++
   919  	}
   920  
   921  	if addHistory {
   922  		deleted := make([]rune, 0, replaceSize)
   923  		readPos := e.text.ByteOffset(start)
   924  		for i := 0; i < replaceSize; i++ {
   925  			ru, s, _ := e.text.ReadRuneAt(int64(readPos))
   926  			readPos += int64(s)
   927  			deleted = append(deleted, ru)
   928  		}
   929  		if e.nextHistoryIdx < len(e.history) {
   930  			e.history = e.history[:e.nextHistoryIdx]
   931  		}
   932  		e.history = append(e.history, modification{
   933  			StartRune:      start,
   934  			ApplyContent:   s,
   935  			ReverseContent: string(deleted),
   936  		})
   937  		e.nextHistoryIdx++
   938  	}
   939  
   940  	sc = e.text.Replace(start, end, s)
   941  	newEnd := start + sc
   942  	adjust := func(pos int) int {
   943  		switch {
   944  		case newEnd < pos && pos <= end:
   945  			pos = newEnd
   946  		case end < pos:
   947  			diff := newEnd - end
   948  			pos = pos + diff
   949  		}
   950  		return pos
   951  	}
   952  	e.ime.start = adjust(e.ime.start)
   953  	e.ime.end = adjust(e.ime.end)
   954  	return sc
   955  }
   956  
   957  // MoveCaret moves the caret (aka selection start) and the selection end
   958  // relative to their current positions. Positive distances moves forward,
   959  // negative distances moves backward. Distances are in grapheme clusters,
   960  // which closely match what users perceive as "characters" even when the
   961  // characters are multiple code points long.
   962  func (e *Editor) MoveCaret(startDelta, endDelta int) {
   963  	e.initBuffer()
   964  	e.text.MoveCaret(startDelta, endDelta)
   965  }
   966  
   967  // deleteWord deletes the next word(s) in the specified direction.
   968  // Unlike moveWord, deleteWord treats whitespace as a word itself.
   969  // Positive is forward, negative is backward.
   970  // Absolute values greater than one will delete that many words.
   971  // The selection counts as a single word.
   972  func (e *Editor) deleteWord(distance int) (deletedRunes int) {
   973  	if distance == 0 {
   974  		return
   975  	}
   976  
   977  	start, end := e.text.Selection()
   978  	if start != end {
   979  		deletedRunes = e.Delete(1)
   980  		distance -= sign(distance)
   981  	}
   982  	if distance == 0 {
   983  		return deletedRunes
   984  	}
   985  
   986  	// split the distance information into constituent parts to be
   987  	// used independently.
   988  	words, direction := distance, 1
   989  	if distance < 0 {
   990  		words, direction = distance*-1, -1
   991  	}
   992  	caret, _ := e.text.Selection()
   993  	// atEnd if offset is at or beyond either side of the buffer.
   994  	atEnd := func(runes int) bool {
   995  		idx := caret + runes*direction
   996  		return idx <= 0 || idx >= e.Len()
   997  	}
   998  	// next returns the appropriate rune given the direction and offset in runes).
   999  	next := func(runes int) rune {
  1000  		idx := caret + runes*direction
  1001  		if idx < 0 {
  1002  			idx = 0
  1003  		} else if idx > e.Len() {
  1004  			idx = e.Len()
  1005  		}
  1006  		off := e.text.ByteOffset(idx)
  1007  		var r rune
  1008  		if direction < 0 {
  1009  			r, _, _ = e.text.ReadRuneBefore(int64(off))
  1010  		} else {
  1011  			r, _, _ = e.text.ReadRuneAt(int64(off))
  1012  		}
  1013  		return r
  1014  	}
  1015  	runes := 1
  1016  	for ii := 0; ii < words; ii++ {
  1017  		r := next(runes)
  1018  		wantSpace := unicode.IsSpace(r)
  1019  		for r := next(runes); unicode.IsSpace(r) == wantSpace && !atEnd(runes); r = next(runes) {
  1020  			runes += 1
  1021  		}
  1022  	}
  1023  	deletedRunes += e.Delete(runes * direction)
  1024  	return deletedRunes
  1025  }
  1026  
  1027  // SelectionLen returns the length of the selection, in runes; it is
  1028  // equivalent to utf8.RuneCountInString(e.SelectedText()).
  1029  func (e *Editor) SelectionLen() int {
  1030  	e.initBuffer()
  1031  	return e.text.SelectionLen()
  1032  }
  1033  
  1034  // Selection returns the start and end of the selection, as rune offsets.
  1035  // start can be > end.
  1036  func (e *Editor) Selection() (start, end int) {
  1037  	e.initBuffer()
  1038  	return e.text.Selection()
  1039  }
  1040  
  1041  // SetCaret moves the caret to start, and sets the selection end to end. start
  1042  // and end are in runes, and represent offsets into the editor text.
  1043  func (e *Editor) SetCaret(start, end int) {
  1044  	e.initBuffer()
  1045  	e.text.SetCaret(start, end)
  1046  	e.scrollCaret = true
  1047  	e.scroller.Stop()
  1048  }
  1049  
  1050  // SelectedText returns the currently selected text (if any) from the editor.
  1051  func (e *Editor) SelectedText() string {
  1052  	e.initBuffer()
  1053  	e.scratch = e.text.SelectedText(e.scratch)
  1054  	return string(e.scratch)
  1055  }
  1056  
  1057  // ClearSelection clears the selection, by setting the selection end equal to
  1058  // the selection start.
  1059  func (e *Editor) ClearSelection() {
  1060  	e.initBuffer()
  1061  	e.text.ClearSelection()
  1062  }
  1063  
  1064  // WriteTo implements io.WriterTo.
  1065  func (e *Editor) WriteTo(w io.Writer) (int64, error) {
  1066  	e.initBuffer()
  1067  	return e.text.WriteTo(w)
  1068  }
  1069  
  1070  // Seek implements io.Seeker.
  1071  func (e *Editor) Seek(offset int64, whence int) (int64, error) {
  1072  	e.initBuffer()
  1073  	return e.text.Seek(offset, whence)
  1074  }
  1075  
  1076  // Read implements io.Reader.
  1077  func (e *Editor) Read(p []byte) (int, error) {
  1078  	e.initBuffer()
  1079  	return e.text.Read(p)
  1080  }
  1081  
  1082  // Regions returns visible regions covering the rune range [start,end).
  1083  func (e *Editor) Regions(start, end int, regions []Region) []Region {
  1084  	e.initBuffer()
  1085  	return e.text.Regions(start, end, regions)
  1086  }
  1087  
  1088  func max(a, b int) int {
  1089  	if a > b {
  1090  		return a
  1091  	}
  1092  	return b
  1093  }
  1094  
  1095  func min(a, b int) int {
  1096  	if a < b {
  1097  		return a
  1098  	}
  1099  	return b
  1100  }
  1101  
  1102  func abs(n int) int {
  1103  	if n < 0 {
  1104  		return -n
  1105  	}
  1106  	return n
  1107  }
  1108  
  1109  func sign(n int) int {
  1110  	switch {
  1111  	case n < 0:
  1112  		return -1
  1113  	case n > 0:
  1114  		return 1
  1115  	default:
  1116  		return 0
  1117  	}
  1118  }
  1119  
  1120  func (s ChangeEvent) isEditorEvent() {}
  1121  func (s SubmitEvent) isEditorEvent() {}
  1122  func (s SelectEvent) isEditorEvent() {}