editor.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package gel
4
5 import (
6 "bufio"
7 "bytes"
8 "image"
9 "io"
10 "math"
11 "runtime"
12 "sort"
13 "strings"
14 "time"
15 "unicode"
16 "unicode/utf8"
17
18 "github.com/p9c/gio/f32"
19 "github.com/p9c/gio/io/clipboard"
20 "github.com/p9c/gio/io/event"
21 "github.com/p9c/gio/io/key"
22 "github.com/p9c/gio/io/pointer"
23 "github.com/p9c/gio/layout"
24 "github.com/p9c/gio/op"
25 "github.com/p9c/gio/op/clip"
26 "github.com/p9c/gio/op/paint"
27 "github.com/p9c/gio/text"
28 "github.com/p9c/gio/unit"
29
30 "github.com/p9c/gio/gesture"
31
32 "golang.org/x/image/math/fixed"
33
34 clipboard3 "github.com/p9c/gel/clipboard"
35 )
36
37 func (w *Window) Editor() *Editor {
38 e := &Editor{
39 submitHook: func(string) {},
40 changeHook: func(string) {},
41 focusHook: func(bool) {},
42 }
43 return e
44 }
45
46 // Editor implements an editable and scrollable text area.
47 type Editor struct {
48 alignment text.Alignment
49 // singleLine force the text to stay on a single line. singleLine also sets the scrolling direction to horizontal.
50 singleLine bool
51 // submit enabled translation of carriage return keys to SubmitEvents. If not enabled, carriage returns are inserted
52 // as newlines in the text.
53 submit bool
54 // mask replaces the visual display of each rune in the contents with the given rune. Newline characters are not
55 // masked. When non-zero, the unmasked contents are accessed by Len, Text, and SetText.
56 mask rune
57
58 eventKey int
59 font text.Font
60 shaper text.Shaper
61 textSize fixed.Int26_6
62 blinkStart time.Time
63 focused bool
64 editBuffer editBuffer
65 maskReader maskReader
66 lastMask rune
67 maxWidth int
68 viewSize image.Point
69 valid bool
70 lines []text.Line
71 shapes []line
72 dims layout.Dimensions
73 requestFocus bool
74 caret struct {
75 on bool
76 scroll bool
77 // start is the current caret position, and also the start position of
78 // selected text. end is the end positon of selected text. If start.ofs
79 // == end.ofs, then there's no selection. Note that it's possible (and
80 // common) that the caret (start) is after the end, e.g. after
81 // Shift-DownArrow.
82 start combinedPos
83 end combinedPos
84 }
85 dragging bool
86 dragger gesture.Drag
87 scroller gesture.Scroll
88 scrollOff image.Point
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 // the following are hooks for change events on the editor
97 submitHook func(string)
98 changeHook func(string)
99 focusHook func(bool)
100 }
101
102 type maskReader struct {
103 // rr is the underlying reader.
104 rr io.RuneReader
105 maskBuf [utf8.UTFMax]byte
106 // mask is the utf-8 encoded mask rune.
107 mask []byte
108 // overflow contains excess mask bytes left over after the last Read call.
109 overflow []byte
110 }
111
112 // combinedPos is a point in the editor.
113 type combinedPos struct {
114 // editorBuffer offset. The other three fields are based off of this one.
115 ofs int
116
117 // lineCol.Y = line (offset into Editor.lines), and X = col (offset into
118 // Editor.lines[Y])
119 lineCol screenPos
120
121 // Pixel coordinates
122 x fixed.Int26_6
123 y int
124
125 // xoff is the offset to the current position when moving between lines.
126 xoff fixed.Int26_6
127 }
128
129 type selectionAction int
130
131 const (
132 selectionExtend selectionAction = iota
133 selectionClear
134 )
135
136 func (m *maskReader) Reset(r io.RuneReader, mr rune) {
137 m.rr = r
138 n := utf8.EncodeRune(m.maskBuf[:], mr)
139 m.mask = m.maskBuf[:n]
140 }
141
142 // Read reads from the underlying reader and replaces every rune with the mask rune.
143 func (m *maskReader) Read(b []byte) (n int, e 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, _, e = m.rr.ReadRune()
151 if e != 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, e
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 and a carriage return key is pressed.
176 type SubmitEvent struct {
177 Text string
178 }
179
180 // A SelectEvent is generated when the user selects some text, or changes the
181 // selection (e.g. with a shift-click), including if they remove the
182 // selection. The selected text is not part of the event, on the theory that
183 // it could be a relatively expensive operation (for a large editor), most
184 // applications won't actually care about it, and those that do can call
185 // Editor.SelectedText() (which can be empty).
186 type SelectEvent struct{}
187
188 type line struct {
189 offset image.Point
190 clip op.CallOp
191 selected bool
192 selectionYOffs int
193 selectionSize image.Point
194 }
195
196 const (
197 blinksPerSecond = 1
198 maxBlinkDuration = 10 * time.Second
199 )
200
201 // Events returns available editor events.
202 func (e *Editor) Events() []EditorEvent {
203 events := e.events
204 e.events = nil
205 e.prevEvents = 0
206 return events
207 }
208
209 func (e *Editor) processEvents(gtx layout.Context) {
210 // Flush events from before the previous layout.
211 n := copy(e.events, e.events[e.prevEvents:])
212 e.events = e.events[:n]
213 e.prevEvents = n
214 if e.shaper == nil {
215 // Can't process events without a shaper.
216 return
217 }
218 oldStart, oldLen := min(e.caret.start.ofs, e.caret.end.ofs), e.SelectionLen()
219 e.processPointer(gtx)
220 e.processKey(gtx)
221 if newStart, newLen := min(e.caret.start.ofs, e.caret.end.ofs),
222 e.SelectionLen(); oldStart != newStart || oldLen != newLen {
223 e.events = append(e.events, SelectEvent{})
224 if e.SelectionLen() != 0 {
225 st := e.SelectedText()
226 _ = clipboard3.SetPrimary(st)
227 // I.F("new primary buffer string: '%s'", st)
228 }
229 }
230 }
231
232 func (e *Editor) makeValid(positions ...*combinedPos) {
233 if e.valid {
234 return
235 }
236 e.lines, e.dims = e.layoutText(e.shaper)
237 e.makeValidCaret(positions...)
238 e.valid = true
239 }
240
241 func (e *Editor) processPointer(gtx layout.Context) {
242 sbounds := e.scrollBounds()
243 var smin, smax int
244 var axis gesture.Axis
245 if e.singleLine {
246 axis = gesture.Horizontal
247 smin, smax = sbounds.Min.X, sbounds.Max.X
248 } else {
249 axis = gesture.Vertical
250 smin, smax = sbounds.Min.Y, sbounds.Max.Y
251 }
252 sdist := e.scroller.Scroll(gtx.Metric, gtx, gtx.Now, axis)
253 var soff int
254 if e.singleLine {
255 e.scrollRel(sdist, 0)
256 soff = e.scrollOff.X
257 } else {
258 e.scrollRel(0, sdist)
259 soff = e.scrollOff.Y
260 }
261 for _, evt := range e.clickDragEvents(gtx) {
262 switch evt := evt.(type) {
263 case gesture.ClickEvent:
264 switch {
265 case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
266 evt.Type == gesture.TypeClick:
267 if evt.Button == pointer.ButtonPrimary {
268 prevCaretPos := e.caret.start
269 e.blinkStart = gtx.Now
270 e.moveCoord(image.Point{
271 X: int(math.Round(float64(evt.Position.X))),
272 Y: int(math.Round(float64(evt.Position.Y))),
273 })
274 e.requestFocus = true
275 if e.scroller.State() != gesture.StateFlinging {
276 e.caret.scroll = true
277 }
278 if evt.Modifiers == key.ModShift {
279 // If they clicked closer to the end, then change the end to
280 // where the caret used to be (effectively swapping start & end).
281 if abs(e.caret.end.ofs-e.caret.start.ofs) < abs(e.caret.start.ofs-prevCaretPos.ofs) {
282 e.caret.end = prevCaretPos
283 }
284 } else {
285 e.ClearSelection()
286 }
287 }
288 e.dragging = true
289 // I.S(evt)
290 // Process a double-click.
291 // Double and triple clicks are primary button only
292 if evt.Button == pointer.ButtonPrimary {
293 if evt.NumClicks == 2 {
294 e.moveWord(-1, selectionClear)
295 e.moveWord(1, selectionExtend)
296 e.dragging = false
297 }
298 // process a triple click - select all. This required forking gioui.org/gesture
299 if evt.NumClicks == 3 {
300 e.dragging = false
301 e.caret.end, e.caret.start = e.offsetToScreenPos2(0, e.Len())
302 evt.NumClicks = 0
303 }
304 }
305 if evt.Button == pointer.ButtonTertiary && evt.Type == gesture.TypeClick {
306 e.blinkStart = gtx.Now
307 e.moveCoord(image.Point{
308 X: int(math.Round(float64(evt.Position.X))),
309 Y: int(math.Round(float64(evt.Position.Y))),
310 })
311 e.ClearSelection()
312 primary := clipboard3.GetPrimary()
313 e.prepend(primary)
314 distance := utf8.RuneCountInString(primary)
315 e.MoveCaret(distance, int(selectionExtend))
316 }
317 }
318 case pointer.Event:
319 release := false
320 switch {
321 // on X11 process middle click as insert Primary at pointer position
322 case evt.Buttons == pointer.ButtonTertiary && evt.Source == pointer.Mouse:
323 e.moveCoord(image.Point{
324 X: int(math.Round(float64(evt.Position.X))),
325 Y: int(math.Round(float64(evt.Position.Y))),
326 })
327 e.prepend(clipboard3.GetPrimary())
328 case evt.Type == pointer.Release && evt.Source == pointer.Mouse:
329 release = true
330 fallthrough
331 case evt.Type == pointer.Drag && evt.Source == pointer.Mouse:
332 if e.dragging {
333 e.blinkStart = gtx.Now
334 e.moveCoord(image.Point{
335 X: int(math.Round(float64(evt.Position.X))),
336 Y: int(math.Round(float64(evt.Position.Y))),
337 })
338 e.caret.scroll = true
339
340 if release {
341 e.dragging = false
342 }
343 }
344 default:
345 // I.S(evt)
346 }
347 }
348 }
349
350 if (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin) {
351 e.scroller.Stop()
352 }
353 }
354
355 func (e *Editor) clickDragEvents(gtx layout.Context) []event.Event {
356 var combinedEvents []event.Event
357 for _, evt := range e.clicker.Events(gtx) {
358 combinedEvents = append(combinedEvents, evt)
359 }
360 for _, evt := range e.dragger.Events(gtx.Metric, gtx, gesture.Both) {
361 combinedEvents = append(combinedEvents, evt)
362 }
363 return combinedEvents
364 }
365
366 func (e *Editor) processKey(gtx layout.Context) {
367 if e.editBuffer.Changed() {
368 e.events = append(e.events, ChangeEvent{})
369 }
370 for _, ke := range gtx.Events(&e.eventKey) {
371 e.blinkStart = gtx.Now
372 switch ke := ke.(type) {
373 case key.FocusEvent:
374 e.focused = ke.Focus
375 e.focusHook(ke.Focus)
376 case key.Event:
377 if !e.focused || ke.State != key.Press {
378 break
379 }
380 if e.submit && (ke.Name == key.NameReturn || ke.Name == key.NameEnter) {
381 if !ke.Modifiers.Contain(key.ModShift) {
382 e.events = append(e.events, SubmitEvent{
383 Text: e.Text(),
384 })
385 continue
386 }
387 }
388 if e.command(gtx, ke) {
389 e.caret.scroll = true
390 e.scroller.Stop()
391 }
392 case key.EditEvent:
393 e.caret.scroll = true
394 e.scroller.Stop()
395 e.append(ke.Text)
396 // Complete a paste event, initiated by Shortcut-V in Editor.command().
397 case clipboard.Event:
398 e.caret.scroll = true
399 e.scroller.Stop()
400 e.append(ke.Text)
401 }
402 if e.editBuffer.Changed() {
403 e.events = append(e.events, ChangeEvent{})
404 e.changeHook(e.Text())
405 }
406 }
407 }
408
409 func (e *Editor) moveLines(distance int, selAct selectionAction) {
410 e.caret.start = e.movePosToLine(e.caret.start, e.caret.start.x+e.caret.start.xoff,
411 e.caret.start.lineCol.Y+distance)
412 e.updateSelection(selAct)
413 }
414
415 func (e *Editor) command(gtx layout.Context, k key.Event) bool {
416 modSkip := key.ModCtrl
417 if runtime.GOOS == "darwin" {
418 modSkip = key.ModAlt
419 }
420 moveByWord := k.Modifiers.Contain(modSkip)
421 selAct := selectionClear
422 if k.Modifiers.Contain(key.ModShift) {
423 selAct = selectionExtend
424 }
425 switch k.Name {
426 case key.NameReturn, key.NameEnter:
427 e.append("\n")
428 case key.NameDeleteBackward:
429 if moveByWord {
430 e.deleteWord(-1)
431 } else {
432 e.Delete(-1)
433 }
434 case key.NameDeleteForward:
435 if moveByWord {
436 e.deleteWord(1)
437 } else {
438 e.Delete(1)
439 }
440 case key.NameUpArrow:
441 e.moveLines(-1, selAct)
442 case key.NameDownArrow:
443 e.moveLines(+1, selAct)
444 case key.NameLeftArrow:
445 if moveByWord {
446 e.moveWord(-1, selAct)
447 } else {
448 if selAct == selectionClear {
449 e.ClearSelection()
450 }
451 e.MoveCaret(-1, -1*int(selAct))
452 }
453 case key.NameRightArrow:
454 if moveByWord {
455 e.moveWord(1, selAct)
456 } else {
457 if selAct == selectionClear {
458 e.ClearSelection()
459 }
460 e.MoveCaret(1, int(selAct))
461 }
462 case key.NamePageUp:
463 e.movePages(-1, selAct)
464 case key.NamePageDown:
465 e.movePages(+1, selAct)
466 case key.NameHome:
467 e.moveStart(selAct)
468 case key.NameEnd:
469 e.moveEnd(selAct)
470 // Initiate a paste operation, by requesting the clipboard contents; other
471 // half is in Editor.processKey() under clipboard.Event.
472 case "V":
473 if k.Modifiers != key.ModShortcut {
474 return false
475 }
476 clipboard.ReadOp{Tag: &e.eventKey}.Add(gtx.Ops)
477 // Copy or Cut selection -- ignored if nothing selected.
478 case "C", "X":
479 if k.Modifiers != key.ModShortcut {
480 return false
481 }
482 if text := e.SelectedText(); text != "" {
483 clipboard.WriteOp{Text: text}.Add(gtx.Ops)
484 if k.Name == "X" {
485 e.Delete(1)
486 }
487 }
488 // Select all
489 case "A":
490 if k.Modifiers != key.ModShortcut {
491 return false
492 }
493 e.caret.end, e.caret.start = e.offsetToScreenPos2(0, e.Len())
494 default:
495 return false
496 }
497 return true
498 }
499
500 // Focus requests the input focus for the _editor.
501 func (e *Editor) Focus() {
502 e.requestFocus = true
503 }
504
505 // Focused returns whether the editor is focused or not.
506 func (e *Editor) Focused() bool {
507 return e.focused
508 }
509
510 // Layout lays out the editor.
511 func (e *Editor) Layout(gtx layout.Context, sh text.Shaper, font text.Font, size unit.Value) layout.Dimensions {
512 textSize := fixed.I(gtx.Px(size))
513 if e.font != font || e.textSize != textSize {
514 e.invalidate()
515 e.font = font
516 e.textSize = textSize
517 }
518 maxWidth := gtx.Constraints.Max.X
519 if e.singleLine {
520 maxWidth = Inf
521 }
522 if maxWidth != e.maxWidth {
523 e.maxWidth = maxWidth
524 e.invalidate()
525 }
526 if sh != e.shaper {
527 e.shaper = sh
528 e.invalidate()
529 }
530 if e.mask != e.lastMask {
531 e.lastMask = e.mask
532 e.invalidate()
533 }
534 e.makeValid()
535 e.processEvents(gtx)
536 e.makeValid()
537 if viewSize := gtx.Constraints.Constrain(e.dims.Size); viewSize != e.viewSize {
538 e.viewSize = viewSize
539 e.invalidate()
540 }
541 e.makeValid()
542 return e.layout(gtx)
543 }
544
545 func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
546 // Adjust scrolling for new viewport and layout.
547 e.scrollRel(0, 0)
548 if e.caret.scroll {
549 e.caret.scroll = false
550 e.scrollToCaret()
551 }
552 off := image.Point{
553 X: -e.scrollOff.X,
554 Y: -e.scrollOff.Y,
555 }
556 cl := textPadding(e.lines)
557 cl.Max = cl.Max.Add(e.viewSize)
558 startSel, endSel := sortPoints(e.caret.start.lineCol, e.caret.end.lineCol)
559 it := segmentIterator{
560 startSel: startSel,
561 endSel: endSel,
562 Lines: e.lines,
563 Clip: cl,
564 Alignment: e.alignment,
565 Width: e.viewSize.X,
566 Offset: off,
567 }
568 e.shapes = e.shapes[:0]
569 for {
570 lo, off, selected, yOffs, size, ok := it.Next()
571 if !ok {
572 break
573 }
574 path := e.shaper.Shape(e.font, e.textSize, lo)
575 e.shapes = append(e.shapes, line{off, path, selected, yOffs, size})
576 }
577 key.InputOp{Tag: &e.eventKey}.Add(gtx.Ops)
578 if e.requestFocus {
579 key.FocusOp{Tag: &e.eventKey}.Add(gtx.Ops)
580 key.SoftKeyboardOp{Show: true}.Add(gtx.Ops)
581 }
582 e.requestFocus = false
583 // todo: this should be scaled
584 pointerPadding := gtx.Px(unit.Dp(4))
585 r := image.Rectangle{Max: e.viewSize}
586 r.Min.X -= pointerPadding
587 r.Min.Y -= pointerPadding
588 r.Max.X += pointerPadding
589 r.Max.X += pointerPadding
590 pointer.Rect(r).Add(gtx.Ops)
591 pointer.CursorNameOp{Name: pointer.CursorText}.Add(gtx.Ops)
592 var scrollRange image.Rectangle
593 if e.singleLine {
594 scrollRange.Min.X = -e.scrollOff.X
595 scrollRange.Max.X = max(0, e.dims.Size.X-(e.scrollOff.X+e.viewSize.X))
596 } else {
597 scrollRange.Min.Y = -e.scrollOff.Y
598 scrollRange.Max.Y = max(0, e.dims.Size.Y-(e.scrollOff.Y+e.viewSize.Y))
599 }
600 e.scroller.Add(gtx.Ops, scrollRange)
601
602 e.clicker.Add(gtx.Ops)
603 e.dragger.Add(gtx.Ops)
604 e.caret.on = false
605 if e.focused {
606 now := gtx.Now
607 dt := now.Sub(e.blinkStart)
608 blinking := dt < maxBlinkDuration
609 const timePerBlink = time.Second / blinksPerSecond
610 nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
611 if blinking {
612 redraw := op.InvalidateOp{At: nextBlink}
613 redraw.Add(gtx.Ops)
614 }
615 e.caret.on = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2)
616 }
617
618 return layout.Dimensions{Size: e.viewSize, Baseline: e.dims.Baseline}
619 }
620
621 // PaintSelection paints the contrasting background for selected text.
622 func (e *Editor) PaintSelection(gtx layout.Context) {
623 cl := textPadding(e.lines)
624 cl.Max = cl.Max.Add(e.viewSize)
625 clip.Rect(cl).Add(gtx.Ops)
626 for _, shape := range e.shapes {
627 if !shape.selected {
628 continue
629 }
630 stack := op.Save(gtx.Ops)
631 offset := shape.offset
632 offset.Y += shape.selectionYOffs
633 op.Offset(layout.FPt(offset)).Add(gtx.Ops)
634 clip.Rect(image.Rectangle{Max: shape.selectionSize}).Add(gtx.Ops)
635 paint.PaintOp{}.Add(gtx.Ops)
636 stack.Load()
637 }
638 }
639
640 func (e *Editor) PaintText(gtx layout.Context) {
641 cl := textPadding(e.lines)
642 cl.Max = cl.Max.Add(e.viewSize)
643 clip.Rect(cl).Add(gtx.Ops)
644 for _, shape := range e.shapes {
645 stack := op.Save(gtx.Ops)
646 op.Offset(layout.FPt(shape.offset)).Add(gtx.Ops)
647 shape.clip.Add(gtx.Ops)
648 paint.PaintOp{}.Add(gtx.Ops)
649 stack.Load()
650 }
651 }
652
653 func (e *Editor) PaintCaret(gtx layout.Context) {
654 if !e.caret.on {
655 return
656 }
657 e.makeValid()
658 // todo: this should also be scaled and like blink/sec, configured in theme
659 carWidth := fixed.I(gtx.Px(unit.Dp(1)))
660 carX := e.caret.start.x
661 carY := e.caret.start.y
662
663 defer op.Save(gtx.Ops).Load()
664 carX -= carWidth / 2
665 carAsc, carDesc := -e.lines[e.caret.start.lineCol.Y].Bounds.Min.Y,
666 e.lines[e.caret.start.lineCol.Y].Bounds.Max.Y
667 carRect := image.Rectangle{
668 Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
669 Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
670 }
671 carRect = carRect.Add(image.Point{
672 X: -e.scrollOff.X,
673 Y: -e.scrollOff.Y,
674 })
675 cl := textPadding(e.lines)
676 // Account for caret width to each side.
677 whalf := (carWidth / 2).Ceil()
678 if cl.Max.X < whalf {
679 cl.Max.X = whalf
680 }
681 if cl.Min.X > -whalf {
682 cl.Min.X = -whalf
683 }
684 cl.Max = cl.Max.Add(e.viewSize)
685 carRect = cl.Intersect(carRect)
686 if !carRect.Empty() {
687 st := op.Save(gtx.Ops)
688 clip.Rect(carRect).Add(gtx.Ops)
689 paint.PaintOp{}.Add(gtx.Ops)
690 st.Load()
691 }
692 }
693
694 // Len is the length of the editor contents.
695 func (e *Editor) Len() int {
696 return e.editBuffer.len()
697 }
698
699 // Text returns the contents of the editor.
700 func (e *Editor) Text() string {
701 return e.editBuffer.String()
702 }
703
704 // SetText replaces the contents of the editor, clearing any selection first.
705 func (e *Editor) SetText(s string) *Editor {
706 e.editBuffer = editBuffer{}
707 e.caret.start = combinedPos{}
708 e.caret.end = combinedPos{}
709 e.prepend(s)
710 return e
711 }
712
713 func (e *Editor) scrollBounds() image.Rectangle {
714 var b image.Rectangle
715 if e.singleLine {
716 if len(e.lines) > 0 {
717 b.Min.X = align(e.alignment, e.lines[0].Width, e.viewSize.X).Floor()
718 if b.Min.X > 0 {
719 b.Min.X = 0
720 }
721 }
722 b.Max.X = e.dims.Size.X + b.Min.X - e.viewSize.X
723 } else {
724 b.Max.Y = e.dims.Size.Y - e.viewSize.Y
725 }
726 return b
727 }
728
729 func (e *Editor) scrollRel(dx, dy int) {
730 e.scrollAbs(e.scrollOff.X+dx, e.scrollOff.Y+dy)
731 }
732
733 func (e *Editor) scrollAbs(x, y int) {
734 e.scrollOff.X = x
735 e.scrollOff.Y = y
736 b := e.scrollBounds()
737 if e.scrollOff.X > b.Max.X {
738 e.scrollOff.X = b.Max.X
739 }
740 if e.scrollOff.X < b.Min.X {
741 e.scrollOff.X = b.Min.X
742 }
743 if e.scrollOff.Y > b.Max.Y {
744 e.scrollOff.Y = b.Max.Y
745 }
746 if e.scrollOff.Y < b.Min.Y {
747 e.scrollOff.Y = b.Min.Y
748 }
749 }
750
751 func (e *Editor) moveCoord(pos image.Point) {
752 var (
753 prevDesc fixed.Int26_6
754 carLine int
755 y int
756 )
757 for _, l := range e.lines {
758 y += (prevDesc + l.Ascent).Ceil()
759 prevDesc = l.Descent
760 if y+prevDesc.Ceil() >= pos.Y+e.scrollOff.Y {
761 break
762 }
763 carLine++
764 }
765 x := fixed.I(pos.X + e.scrollOff.X)
766 e.caret.start = e.movePosToLine(e.caret.start, x, carLine)
767 e.caret.start.xoff = 0
768 }
769
770 func (e *Editor) layoutText(s text.Shaper) ([]text.Line, layout.Dimensions) {
771 e.editBuffer.Reset()
772 var r io.Reader = &e.editBuffer
773 if e.mask != 0 {
774 e.maskReader.Reset(&e.editBuffer, e.mask)
775 r = &e.maskReader
776 }
777 var lines []text.Line
778 if s != nil {
779 lines, _ = s.Layout(e.font, e.textSize, e.maxWidth, r)
780 } else {
781 lines, _ = nullLayout(r)
782 }
783 dims := linesDimens(lines)
784 for i := 0; i < len(lines)-1; i++ {
785 // To avoid l flickering while editing, assume a soft newline takes up all available space.
786 if lay := lines[i].Layout; len(lay.Text) > 0 {
787 r := lay.Text[len(lay.Text)-1]
788 if r != '\n' {
789 dims.Size.X = e.maxWidth
790 break
791 }
792 }
793 }
794 return lines, dims
795 }
796
797 // CaretPos returns the line & column numbers of the caret.
798 func (e *Editor) CaretPos() (line, col int) {
799 e.makeValid()
800 return e.caret.start.lineCol.Y, e.caret.start.lineCol.X
801 }
802
803 // CaretCoords returns the coordinates of the caret, relative to the
804 // editor itself.
805 func (e *Editor) CaretCoords() f32.Point {
806 e.makeValid()
807 return f32.Pt(float32(e.caret.start.x)/64, float32(e.caret.start.y))
808 }
809
810 // offsetToScreenPos2 is a utility function to shortcut the common case of
811 // wanting the positions of exactly two offsets.
812 func (e *Editor) offsetToScreenPos2(o1, o2 int) (combinedPos, combinedPos) {
813 cp1, iter := e.offsetToScreenPos(o1)
814 return cp1, iter(o2)
815 }
816
817 // offsetToScreenPos takes an offset into the editor text (e.g.
818 // e.caret.end.ofs) and returns a combinedPos that corresponds to its current
819 // screen position, as well as an iterator that lets you get the combinedPos
820 // of a later offset. The offsets given to offsetToScreenPos and to the
821 // returned iterator must be sorted, lowest first, and they must be valid (0
822 // <= offset <= e.Len()).
823 //
824 // This function is written this way to take advantage of previous work done
825 // for offsets after the first. Otherwise you have to start from the top each
826 // time.
827 func (e *Editor) offsetToScreenPos(offset int) (combinedPos, func(int) combinedPos) {
828 var col, line, idx int
829 var x fixed.Int26_6
830
831 l := e.lines[line]
832 y := l.Ascent.Ceil()
833 prevDesc := l.Descent
834
835 iter := func(offset int) combinedPos {
836 LOOP:
837 for {
838 for ; col < len(l.Layout.Advances); col++ {
839 if idx >= offset {
840 break LOOP
841 }
842
843 x += l.Layout.Advances[col]
844 _, s := e.editBuffer.runeAt(idx)
845 idx += s
846 }
847 if lastLine := line == len(e.lines)-1; lastLine || idx > offset {
848 break LOOP
849 }
850
851 line++
852 x = 0
853 col = 0
854 l = e.lines[line]
855 y += (prevDesc + l.Ascent).Ceil()
856 prevDesc = l.Descent
857 }
858 return combinedPos{
859 lineCol: screenPos{Y: line, X: col},
860 x: x + align(e.alignment, e.lines[line].Width, e.viewSize.X),
861 y: y,
862 ofs: offset,
863 }
864 }
865 return iter(offset), iter
866 }
867
868 func (e *Editor) invalidate() {
869 e.valid = false
870 }
871
872 // Delete runes from the caret position. The sign of runes specifies the
873 // direction to delete: positive is forward, negative is backward.
874 //
875 // If there is a selection, it is deleted and counts as a single rune.
876 func (e *Editor) Delete(runes int) {
877 if runes == 0 {
878 return
879 }
880
881 if l := e.caret.end.ofs - e.caret.start.ofs; l != 0 {
882 e.caret.start.ofs = e.editBuffer.deleteRunes(e.caret.start.ofs, l)
883 runes -= sign(runes)
884 }
885
886 e.caret.start.ofs = e.editBuffer.deleteRunes(e.caret.start.ofs, runes)
887 e.caret.start.xoff = 0
888 e.ClearSelection()
889 e.invalidate()
890 }
891
892 // Insert inserts text at the caret, moving the caret forward. If there is a
893 // selection, Insert overwrites it.
894 func (e *Editor) Insert(s string) {
895 e.append(s)
896 e.caret.scroll = true
897 }
898
899 func (e *Editor) append(s string) {
900 e.prepend(s)
901 e.caret.start.ofs += len(s)
902 e.caret.end.ofs = e.caret.start.ofs
903 }
904
905 // prepend inserts s after the cursor; the caret does not change. If there is
906 // a selection, prepend overwrites it.
907 // xxx|yyy + prepend zzz => xxx|zzzyyy
908 func (e *Editor) prepend(s string) {
909 if e.singleLine {
910 s = strings.ReplaceAll(s, "\n", " ")
911 }
912 e.caret.start.ofs = e.editBuffer.deleteRunes(e.caret.start.ofs,
913 e.caret.end.ofs-e.caret.start.ofs) // Delete any selection first.
914 e.editBuffer.prepend(e.caret.start.ofs, s)
915 e.caret.start.xoff = 0
916 e.invalidate()
917 }
918
919 func (e *Editor) movePages(pages int, selAct selectionAction) {
920 e.makeValid()
921 y := e.caret.start.y + pages*e.viewSize.Y
922 var (
923 prevDesc fixed.Int26_6
924 carLine2 int
925 )
926 y2 := e.lines[0].Ascent.Ceil()
927 for i := 1; i < len(e.lines); i++ {
928 if y2 >= y {
929 break
930 }
931 l := e.lines[i]
932 h := (prevDesc + l.Ascent).Ceil()
933 prevDesc = l.Descent
934 if y2+h-y >= y-y2 {
935 break
936 }
937 y2 += h
938 carLine2++
939 }
940 e.caret.start = e.movePosToLine(e.caret.start, e.caret.start.x+e.caret.start.xoff, carLine2)
941 e.updateSelection(selAct)
942 }
943
944 func (e *Editor) movePosToLine(pos combinedPos, x fixed.Int26_6, line int) combinedPos {
945 e.makeValid(&pos)
946 if line < 0 {
947 line = 0
948 }
949 if line >= len(e.lines) {
950 line = len(e.lines) - 1
951 }
952
953 prevDesc := e.lines[line].Descent
954 for pos.lineCol.Y < line {
955 pos = e.movePosToEnd(pos)
956 l := e.lines[pos.lineCol.Y]
957 _, s := e.editBuffer.runeAt(pos.ofs)
958 pos.ofs += s
959 pos.y += (prevDesc + l.Ascent).Ceil()
960 pos.lineCol.X = 0
961 prevDesc = l.Descent
962 pos.lineCol.Y++
963 }
964 for pos.lineCol.Y > line {
965 pos = e.movePosToStart(pos)
966 l := e.lines[pos.lineCol.Y]
967 _, s := e.editBuffer.runeBefore(pos.ofs)
968 pos.ofs -= s
969 pos.y -= (prevDesc + l.Ascent).Ceil()
970 prevDesc = l.Descent
971 pos.lineCol.Y--
972 l = e.lines[pos.lineCol.Y]
973 pos.lineCol.X = len(l.Layout.Advances) - 1
974 }
975
976 pos = e.movePosToStart(pos)
977 l := e.lines[line]
978 pos.x = align(e.alignment, l.Width, e.viewSize.X)
979 // Only move past the end of the last line
980 end := 0
981 if line < len(e.lines)-1 {
982 end = 1
983 }
984 // Move to rune closest to x.
985 for i := 0; i < len(l.Layout.Advances)-end; i++ {
986 adv := l.Layout.Advances[i]
987 if pos.x >= x {
988 break
989 }
990 if pos.x+adv-x >= x-pos.x {
991 break
992 }
993 pos.x += adv
994 _, s := e.editBuffer.runeAt(pos.ofs)
995 pos.ofs += s
996 pos.lineCol.X++
997 }
998 pos.xoff = x - pos.x
999 return pos
1000 }
1001
1002 // MoveCaret moves the caret (aka selection start) and the selection end
1003 // relative to their current positions. Positive distances moves forward,
1004 // negative distances moves backward. Distances are in runes.
1005 func (e *Editor) MoveCaret(startDelta, endDelta int) {
1006 e.makeValid()
1007 keepSame := e.caret.start.ofs == e.caret.end.ofs && startDelta == endDelta
1008 e.caret.start = e.movePos(e.caret.start, startDelta)
1009 e.caret.start.xoff = 0
1010 // If they were in the same place, and we're moving them the same distance,
1011 // just assign the new position, instead of recalculating it.
1012 if keepSame {
1013 e.caret.end = e.caret.start
1014 } else {
1015 e.caret.end = e.movePos(e.caret.end, endDelta)
1016 e.caret.end.xoff = 0
1017 }
1018 }
1019
1020 func (e *Editor) movePos(pos combinedPos, distance int) combinedPos {
1021 for ; distance < 0 && pos.ofs > 0; distance++ {
1022 if pos.lineCol.X == 0 {
1023 // Move to end of previous line.
1024 pos = e.movePosToLine(pos, fixed.I(e.maxWidth), pos.lineCol.Y-1)
1025 continue
1026 }
1027 l := e.lines[pos.lineCol.Y].Layout
1028 _, s := e.editBuffer.runeBefore(pos.ofs)
1029 pos.ofs -= s
1030 pos.lineCol.X--
1031 pos.x -= l.Advances[pos.lineCol.X]
1032 }
1033 for ; distance > 0 && pos.ofs < e.editBuffer.len(); distance-- {
1034 l := e.lines[pos.lineCol.Y].Layout
1035 // Only move past the end of the last line
1036 end := 0
1037 if pos.lineCol.Y < len(e.lines)-1 {
1038 end = 1
1039 }
1040 if pos.lineCol.X >= len(l.Advances)-end {
1041 // Move to start of next line.
1042 pos = e.movePosToLine(pos, 0, pos.lineCol.Y+1)
1043 continue
1044 }
1045 pos.x += l.Advances[pos.lineCol.X]
1046 _, s := e.editBuffer.runeAt(pos.ofs)
1047 pos.ofs += s
1048 pos.lineCol.X++
1049 }
1050 return pos
1051 }
1052
1053 func (e *Editor) moveStart(selAct selectionAction) {
1054 e.caret.start = e.movePosToStart(e.caret.start)
1055 e.updateSelection(selAct)
1056 }
1057
1058 func (e *Editor) movePosToStart(pos combinedPos) combinedPos {
1059 e.makeValid(&pos)
1060 layout := e.lines[pos.lineCol.Y].Layout
1061 for i := pos.lineCol.X - 1; i >= 0; i-- {
1062 _, s := e.editBuffer.runeBefore(pos.ofs)
1063 pos.ofs -= s
1064 pos.x -= layout.Advances[i]
1065 }
1066 pos.lineCol.X = 0
1067 pos.xoff = -pos.x
1068 return pos
1069 }
1070
1071 func (e *Editor) moveEnd(selAct selectionAction) {
1072 e.caret.start = e.movePosToEnd(e.caret.start)
1073 e.updateSelection(selAct)
1074 }
1075
1076 func (e *Editor) movePosToEnd(pos combinedPos) combinedPos {
1077 e.makeValid(&pos)
1078 l := e.lines[pos.lineCol.Y]
1079 // Only move past the end of the last line
1080 end := 0
1081 if pos.lineCol.Y < len(e.lines)-1 {
1082 end = 1
1083 }
1084 layout := l.Layout
1085 for i := pos.lineCol.X; i < len(layout.Advances)-end; i++ {
1086 adv := layout.Advances[i]
1087 _, s := e.editBuffer.runeAt(pos.ofs)
1088 pos.ofs += s
1089 pos.x += adv
1090 pos.lineCol.X++
1091 }
1092 a := align(e.alignment, l.Width, e.viewSize.X)
1093 pos.xoff = l.Width + a - pos.x
1094 return pos
1095 }
1096
1097 // moveWord moves the caret to the next word in the specified direction.
1098 // Positive is forward, negative is backward.
1099 // Absolute values greater than one will skip that many words.
1100 func (e *Editor) moveWord(distance int, selAct selectionAction) {
1101 e.makeValid()
1102 // split the distance information into constituent parts to be
1103 // used independently.
1104 words, direction := distance, 1
1105 if distance < 0 {
1106 words, direction = distance*-1, -1
1107 }
1108 // atEnd if caret is at either side of the buffer.
1109 atEnd := func() bool {
1110 return e.caret.start.ofs == 0 || e.caret.start.ofs == e.editBuffer.len()
1111 }
1112 // next returns the appropriate rune given the direction.
1113 next := func() (r rune) {
1114 if direction < 0 {
1115 r, _ = e.editBuffer.runeBefore(e.caret.start.ofs)
1116 } else {
1117 r, _ = e.editBuffer.runeAt(e.caret.start.ofs)
1118 }
1119 return r
1120 }
1121 for ii := 0; ii < words; ii++ {
1122 for r := next(); unicode.IsSpace(r) && !atEnd(); r = next() {
1123 e.MoveCaret(direction, 0)
1124 }
1125 e.MoveCaret(direction, 0)
1126 for r := next(); !unicode.IsSpace(r) && !atEnd(); r = next() {
1127 e.MoveCaret(direction, 0)
1128 }
1129 }
1130 e.updateSelection(selAct)
1131 }
1132
1133 // deleteWord the next word(s) in the specified direction. Unlike moveWord, deleteWord treats whitespace as a word
1134 // itself.
1135 //
1136 // Positive is forward, negative is backward.
1137 //
1138 // Absolute values greater than one will delete that many words.
1139 func (e *Editor) deleteWord(distance int) {
1140 if distance == 0 {
1141 return
1142 }
1143
1144 e.makeValid()
1145
1146 if e.caret.start.ofs != e.caret.end.ofs {
1147 e.Delete(1)
1148 distance -= sign(distance)
1149 }
1150 if distance == 0 {
1151 return
1152 }
1153
1154 // split the distance information into constituent parts to be
1155 // used independently.
1156 words, direction := distance, 1
1157 if distance < 0 {
1158 words, direction = distance*-1, -1
1159 }
1160 // atEnd if offset is at or beyond either side of the buffer.
1161 atEnd := func(offset int) bool {
1162 idx := e.caret.start.ofs + offset*direction
1163 return idx <= 0 || idx >= e.editBuffer.len()
1164 }
1165 // next returns the appropriate rune given the direction and offset.
1166 next := func(offset int) (r rune) {
1167 idx := e.caret.start.ofs + offset*direction
1168 if idx < 0 {
1169 idx = 0
1170 } else if idx > e.editBuffer.len() {
1171 idx = e.editBuffer.len()
1172 }
1173 if direction < 0 {
1174 r, _ = e.editBuffer.runeBefore(idx)
1175 } else {
1176 r, _ = e.editBuffer.runeAt(idx)
1177 }
1178 return r
1179 }
1180 var runes = 1
1181 for ii := 0; ii < words; ii++ {
1182 if r := next(runes); unicode.IsSpace(r) {
1183 for r := next(runes); unicode.IsSpace(r) && !atEnd(runes); r = next(runes) {
1184 runes += 1
1185 }
1186 } else {
1187 for r := next(runes); !unicode.IsSpace(r) && !atEnd(runes); r = next(runes) {
1188 runes += 1
1189 }
1190 }
1191 }
1192 e.Delete(runes * direction)
1193 }
1194
1195 func (e *Editor) scrollToCaret() {
1196 e.makeValid()
1197 l := e.lines[e.caret.start.lineCol.Y]
1198 if e.singleLine {
1199 var dist int
1200 if d := e.caret.start.x.Floor() - e.scrollOff.X; d < 0 {
1201 dist = d
1202 } else if d = e.caret.start.x.Ceil() - (e.scrollOff.X + e.viewSize.X); d > 0 {
1203 dist = d
1204 }
1205 e.scrollRel(dist, 0)
1206 } else {
1207 miny := e.caret.start.y - l.Ascent.Ceil()
1208 maxy := e.caret.start.y + l.Descent.Ceil()
1209 var dist int
1210 if d := miny - e.scrollOff.Y; d < 0 {
1211 dist = d
1212 } else if d = maxy - (e.scrollOff.Y + e.viewSize.Y); d > 0 {
1213 dist = d
1214 }
1215 e.scrollRel(0, dist)
1216 }
1217 }
1218
1219 // NumLines returns the number of lines in the editor.
1220 func (e *Editor) NumLines() int {
1221 e.makeValid()
1222 return len(e.lines)
1223 }
1224
1225 // SelectionLen returns the length of the selection, in bytes; it is
1226 // equivalent to len(e.SelectedText()).
1227 func (e *Editor) SelectionLen() int {
1228 return abs(e.caret.start.ofs - e.caret.end.ofs)
1229 }
1230
1231 // Selection returns the start and end of the selection, as offsets into the
1232 // editor text. start can be > end.
1233 func (e *Editor) Selection() (start, end int) {
1234 return e.caret.start.ofs, e.caret.end.ofs
1235 }
1236
1237 // SetCaret moves the caret to start, and sets the selection end to end. start
1238 // and end are in bytes, and represent offsets into the editor text. start and
1239 // end must be at a rune boundary.
1240 func (e *Editor) SetCaret(start, end int) {
1241 e.makeValid()
1242 // Constrain start and end to [0, e.Len()].
1243 l := e.Len()
1244 start = max(min(start, l), 0)
1245 end = max(min(end, l), 0)
1246 e.caret.start.ofs, e.caret.end.ofs = start, end
1247 e.makeValidCaret()
1248 e.caret.scroll = true
1249 e.scroller.Stop()
1250 }
1251
1252 func (e *Editor) makeValidCaret(positions ...*combinedPos) {
1253 // Jump through some hoops to order the offsets given to offsetToScreenPos,
1254 // but still be able to update them correctly with the results thereof.
1255 positions = append(positions, &e.caret.start, &e.caret.end)
1256 sort.Slice(positions, func(i, j int) bool {
1257 return positions[i].ofs < positions[j].ofs
1258 })
1259 var iter func(offset int) combinedPos
1260 *positions[0], iter = e.offsetToScreenPos(positions[0].ofs)
1261 for _, cp := range positions[1:] {
1262 *cp = iter(cp.ofs)
1263 }
1264 }
1265
1266 // SelectedText returns the currently selected text (if any) from the editor.
1267 func (e *Editor) SelectedText() string {
1268 l := e.SelectionLen()
1269 if l == 0 {
1270 return ""
1271 }
1272 buf := make([]byte, l)
1273 e.editBuffer.Seek(int64(min(e.caret.start.ofs, e.caret.end.ofs)), io.SeekStart)
1274 _, err := e.editBuffer.Read(buf)
1275 if err != nil {
1276 // The only error that rr.Read can return is EOF, which just means no
1277 // selection, but we've already made sure that shouldn't happen.
1278 panic("impossible error because end is before e.rr.Len()")
1279 }
1280 return string(buf)
1281 }
1282
1283 func (e *Editor) updateSelection(selAct selectionAction) {
1284 if selAct == selectionClear {
1285 e.ClearSelection()
1286 }
1287 }
1288
1289 // ClearSelection clears the selection, by setting the selection end equal to
1290 // the selection start.
1291 func (e *Editor) ClearSelection() {
1292 e.caret.end = e.caret.start
1293 }
1294
1295 func max(a, b int) int {
1296 if a > b {
1297 return a
1298 }
1299 return b
1300 }
1301
1302 func min(a, b int) int {
1303 if a < b {
1304 return a
1305 }
1306 return b
1307 }
1308
1309 func abs(n int) int {
1310 if n < 0 {
1311 return -n
1312 }
1313 return n
1314 }
1315
1316 func sign(n int) int {
1317 switch {
1318 case n < 0:
1319 return -1
1320 case n > 0:
1321 return 1
1322 default:
1323 return 0
1324 }
1325 }
1326
1327 // sortPoints returns a and b sorted such that a2 <= b2.
1328 func sortPoints(a, b screenPos) (a2, b2 screenPos) {
1329 if b.Less(a) {
1330 return b, a
1331 }
1332 return a, b
1333 }
1334
1335 func nullLayout(r io.Reader) ([]text.Line, error) {
1336 rr := bufio.NewReader(r)
1337 var rerr error
1338 var n int
1339 var buf bytes.Buffer
1340 for {
1341 r, s, e := rr.ReadRune()
1342 n += s
1343 buf.WriteRune(r)
1344 if e != nil {
1345 rerr = e
1346 break
1347 }
1348 }
1349 return []text.Line{
1350 {
1351 Layout: text.Layout{
1352 Text: buf.String(),
1353 Advances: make([]fixed.Int26_6, n),
1354 },
1355 },
1356 }, rerr
1357 }
1358
1359 func (s ChangeEvent) isEditorEvent() {}
1360 func (s SubmitEvent) isEditorEvent() {}
1361 func (s SelectEvent) isEditorEvent() {}
1362
1363 // func (e *Editor) moveToLine(x fixed.Int26_6, line int) {
1364 // e.makeValid()
1365 // if line < 0 {
1366 // line = 0
1367 // }
1368 // if line >= len(e.lines) {
1369 // line = len(e.lines) - 1
1370 // }
1371 //
1372 // prevDesc := e.lines[line].Descent
1373 // for e.caret.Line < line {
1374 // e.moveEnd()
1375 // l := e.lines[e.caret.Line]
1376 // _, s := e.editBuffer.runeAt(e.editBuffer.caret)
1377 // e.editBuffer.caret += s
1378 // e.caret.y += (prevDesc + l.Ascent).Ceil()
1379 // e.caret.Col = 0
1380 // prevDesc = l.Descent
1381 // e.caret.Line++
1382 // }
1383 // for e.caret.Line > line {
1384 // e.moveStart()
1385 // l := e.lines[e.caret.Line]
1386 // _, s := e.editBuffer.runeBefore(e.editBuffer.caret)
1387 // e.editBuffer.caret -= s
1388 // e.caret.y -= (prevDesc + l.Ascent).Ceil()
1389 // prevDesc = l.Descent
1390 // e.caret.Line--
1391 // l = e.lines[e.caret.Line]
1392 // e.caret.Col = len(l.Layout.Advances) - 1
1393 // }
1394 //
1395 // e.moveStart()
1396 // l := e.lines[line]
1397 // e.caret.x = align(e.alignment, l.Width, e.viewSize.X)
1398 // // Only move past the end of the last line
1399 // end := 0
1400 // if line < len(e.lines)-1 {
1401 // end = 1
1402 // }
1403 // // Move to rune closest to x.
1404 // for i := 0; i < len(l.Layout.Advances)-end; i++ {
1405 // adv := l.Layout.Advances[i]
1406 // if e.caret.x >= x {
1407 // break
1408 // }
1409 // if e.caret.x+adv-x >= x-e.caret.x {
1410 // break
1411 // }
1412 // e.caret.x += adv
1413 // _, s := e.editBuffer.runeAt(e.editBuffer.caret)
1414 // e.editBuffer.caret += s
1415 // e.caret.Col++
1416 // }
1417 // e.caret.xoff = x - e.caret.x
1418 // }
1419 //
1420 // // Move the caret: positive distance moves forward, negative distance moves
1421 // // backward.
1422 // func (e *Editor) Move(distance int) {
1423 // e.makeValid()
1424 // for ; distance < 0 && e.editBuffer.caret > 0; distance++ {
1425 // if e.caret.Col == 0 {
1426 // // Move to end of previous line.
1427 // e.moveToLine(fixed.I(e.maxWidth), e.caret.Line-1)
1428 // continue
1429 // }
1430 // l := e.lines[e.caret.Line].Layout
1431 // _, s := e.editBuffer.runeBefore(e.editBuffer.caret)
1432 // e.editBuffer.caret -= s
1433 // e.caret.Col--
1434 // e.caret.x -= l.Advances[e.caret.Col]
1435 // }
1436 // for ; distance > 0 && e.editBuffer.caret < e.editBuffer.len(); distance-- {
1437 // l := e.lines[e.caret.Line].Layout
1438 // // Only move past the end of the last line
1439 // end := 0
1440 // if e.caret.Line < len(e.lines)-1 {
1441 // end = 1
1442 // }
1443 // if e.caret.Col >= len(l.Advances)-end {
1444 // // Move to start of next line.
1445 // e.moveToLine(0, e.caret.Line+1)
1446 // continue
1447 // }
1448 // e.caret.x += l.Advances[e.caret.Col]
1449 // _, s := e.editBuffer.runeAt(e.editBuffer.caret)
1450 // e.editBuffer.caret += s
1451 // e.caret.Col++
1452 // }
1453 // e.caret.xoff = 0
1454 // }
1455
1456 func (e *Editor) layoutCaret() (line, col int, x fixed.Int26_6, y int) {
1457 var idx int
1458 var prevDesc fixed.Int26_6
1459 loop:
1460 for {
1461 x = 0
1462 col = 0
1463 l := e.lines[line]
1464 y += (prevDesc + l.Ascent).Ceil()
1465 prevDesc = l.Descent
1466 for _, adv := range l.Layout.Advances {
1467 if idx == e.editBuffer.caret {
1468 break loop
1469 }
1470 x += adv
1471 _, s := e.editBuffer.runeAt(idx)
1472 idx += s
1473 col++
1474 }
1475 if line == len(e.lines)-1 || idx > e.editBuffer.caret {
1476 break
1477 }
1478 line++
1479 }
1480 x += align(e.alignment, e.lines[line].Width, e.viewSize.X)
1481 return
1482 }
1483
1484 func (e *Editor) SingleLine() *Editor {
1485 e.singleLine = true
1486 return e
1487 }
1488
1489 func (e *Editor) Submit(submit bool) *Editor {
1490 e.submit = submit
1491 return e
1492 }
1493
1494 func (e *Editor) Mask(mask rune) *Editor {
1495 e.mask = mask
1496 return e
1497 }
1498
1499 func (e *Editor) SetSubmit(submitFn func(txt string)) *Editor {
1500 e.submitHook = submitFn
1501 return e
1502 }
1503
1504 func (e *Editor) SetChange(changeFn func(txt string)) *Editor {
1505 e.changeHook = changeFn
1506 return e
1507 }
1508
1509 func (e *Editor) SetFocus(focusFn func(is bool)) *Editor {
1510 e.focusHook = focusFn
1511 return e
1512 }
1513
1514 func (e *Editor) Alignment(alignment text.Alignment) *Editor {
1515 e.alignment = alignment
1516 return e
1517 }
1518