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