mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-04 17:05:38 +00:00
text: refactor Editor
To prepare for separating drawing from state tracking, refactor Editor so that only its top level Layout method touches font parameters and materials. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+183
-145
@@ -21,14 +21,30 @@ import (
|
|||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type editorFont struct {
|
||||||
|
// Family defines the font and style of the text.
|
||||||
|
Family Family
|
||||||
|
// Face specifies the font family configuration.
|
||||||
|
Face Face
|
||||||
|
// Size is the text size.
|
||||||
|
Size unit.Value
|
||||||
|
}
|
||||||
|
|
||||||
// Editor implements an editable and scrollable text area.
|
// Editor implements an editable and scrollable text area.
|
||||||
type Editor struct {
|
type Editor struct {
|
||||||
// Family defines the font and style of the text.
|
// Family defines the font and style of the text.
|
||||||
Family Family
|
Family Family
|
||||||
// Face specifies the font family configuration.
|
// Face specifies the font family configuration.
|
||||||
Face Face
|
Face Face
|
||||||
// Size is the text size. If zero, a default size is used.
|
// Size is the text size.
|
||||||
Size unit.Value
|
Size unit.Value
|
||||||
|
// Material for drawing the text.
|
||||||
|
Material op.MacroOp
|
||||||
|
// Hint contains the text displayed to the user when the
|
||||||
|
// Editor is empty.
|
||||||
|
Hint string
|
||||||
|
// Material is used to draw the hint.
|
||||||
|
HintMaterial op.MacroOp
|
||||||
|
|
||||||
Alignment Alignment
|
Alignment Alignment
|
||||||
// SingleLine force the text to stay on a single line.
|
// SingleLine force the text to stay on a single line.
|
||||||
@@ -39,15 +55,8 @@ type Editor struct {
|
|||||||
// If not enabled, carriage returns are inserted as newlines in the text.
|
// If not enabled, carriage returns are inserted as newlines in the text.
|
||||||
Submit bool
|
Submit bool
|
||||||
|
|
||||||
// Material for drawing the text.
|
scale int
|
||||||
Material op.MacroOp
|
font editorFont
|
||||||
// Hint contains the text displayed to the user when the
|
|
||||||
// Editor is empty.
|
|
||||||
Hint string
|
|
||||||
// Material is used to draw the hint.
|
|
||||||
HintMaterial op.MacroOp
|
|
||||||
|
|
||||||
oldScale int
|
|
||||||
blinkStart time.Time
|
blinkStart time.Time
|
||||||
focused bool
|
focused bool
|
||||||
rr editBuffer
|
rr editBuffer
|
||||||
@@ -58,9 +67,10 @@ type Editor struct {
|
|||||||
dims layout.Dimensions
|
dims layout.Dimensions
|
||||||
padTop, padBottom int
|
padTop, padBottom int
|
||||||
padLeft, padRight int
|
padLeft, padRight int
|
||||||
|
carWidth fixed.Int26_6
|
||||||
requestFocus bool
|
requestFocus bool
|
||||||
|
caretOn bool
|
||||||
it lineIterator
|
caretScroll bool
|
||||||
|
|
||||||
// carXOff is the offset to the current caret
|
// carXOff is the offset to the current caret
|
||||||
// position when moving between lines.
|
// position when moving between lines.
|
||||||
@@ -93,11 +103,6 @@ const (
|
|||||||
|
|
||||||
// Event returns the next available editor event, or false if none are available.
|
// Event returns the next available editor event, or false if none are available.
|
||||||
func (e *Editor) Event(gtx *layout.Context) (EditorEvent, bool) {
|
func (e *Editor) Event(gtx *layout.Context) (EditorEvent, bool) {
|
||||||
// Crude configuration change detection.
|
|
||||||
if scale := gtx.Px(unit.Sp(100)); scale != e.oldScale {
|
|
||||||
e.invalidate()
|
|
||||||
e.oldScale = scale
|
|
||||||
}
|
|
||||||
sbounds := e.scrollBounds()
|
sbounds := e.scrollBounds()
|
||||||
var smin, smax int
|
var smin, smax int
|
||||||
var axis gesture.Axis
|
var axis gesture.Axis
|
||||||
@@ -128,7 +133,7 @@ func (e *Editor) Event(gtx *layout.Context) (EditorEvent, bool) {
|
|||||||
})
|
})
|
||||||
e.requestFocus = true
|
e.requestFocus = true
|
||||||
if e.scroller.State() != gesture.StateFlinging {
|
if e.scroller.State() != gesture.StateFlinging {
|
||||||
e.scrollToCaret(gtx)
|
e.caretScroll = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,12 +162,12 @@ func (e *Editor) editorEvent(gtx *layout.Context) (EditorEvent, bool) {
|
|||||||
return SubmitEvent{}, true
|
return SubmitEvent{}, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.command(gtx, ke) {
|
if e.command(ke) {
|
||||||
e.scrollToCaret(gtx.Config)
|
e.caretScroll = true
|
||||||
e.scroller.Stop()
|
e.scroller.Stop()
|
||||||
}
|
}
|
||||||
case key.EditEvent:
|
case key.EditEvent:
|
||||||
e.scrollToCaret(gtx)
|
e.caretScroll = true
|
||||||
e.scroller.Stop()
|
e.scroller.Stop()
|
||||||
e.append(ke.Text)
|
e.append(ke.Text)
|
||||||
}
|
}
|
||||||
@@ -173,11 +178,6 @@ func (e *Editor) editorEvent(gtx *layout.Context) (EditorEvent, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) caretWidth(c unit.Converter) fixed.Int26_6 {
|
|
||||||
oneDp := c.Px(unit.Dp(1))
|
|
||||||
return fixed.Int26_6(oneDp * 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus requests the input focus for the Editor.
|
// Focus requests the input focus for the Editor.
|
||||||
func (e *Editor) Focus() {
|
func (e *Editor) Focus() {
|
||||||
e.requestFocus = true
|
e.requestFocus = true
|
||||||
@@ -185,10 +185,44 @@ func (e *Editor) Focus() {
|
|||||||
|
|
||||||
// Layout flushes any remaining events and lays out the editor.
|
// Layout flushes any remaining events and lays out the editor.
|
||||||
func (e *Editor) Layout(gtx *layout.Context) {
|
func (e *Editor) Layout(gtx *layout.Context) {
|
||||||
cs := gtx.Constraints
|
font := editorFont{
|
||||||
|
e.Family,
|
||||||
|
e.Face,
|
||||||
|
e.Size,
|
||||||
|
}
|
||||||
|
e.layout(gtx, font)
|
||||||
|
var stack op.StackOp
|
||||||
|
stack.Push(gtx.Ops)
|
||||||
|
if e.Len() > 0 {
|
||||||
|
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
|
||||||
|
e.Material.Add(gtx.Ops)
|
||||||
|
} else {
|
||||||
|
paint.ColorOp{Color: color.RGBA{A: 0xaa}}.Add(gtx.Ops)
|
||||||
|
e.HintMaterial.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
e.draw(gtx, font)
|
||||||
|
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
|
||||||
|
e.Material.Add(gtx.Ops)
|
||||||
|
e.drawCaret(gtx)
|
||||||
|
stack.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Editor) layout(gtx *layout.Context, font editorFont) {
|
||||||
for _, ok := e.Event(gtx); ok; _, ok = e.Event(gtx) {
|
for _, ok := e.Event(gtx); ok; _, ok = e.Event(gtx) {
|
||||||
}
|
}
|
||||||
|
if e.font != font {
|
||||||
|
e.invalidate()
|
||||||
|
e.font = font
|
||||||
|
}
|
||||||
|
// Crude configuration change detection.
|
||||||
|
if scale := gtx.Px(unit.Sp(100)); scale != e.scale {
|
||||||
|
e.invalidate()
|
||||||
|
e.scale = scale
|
||||||
|
}
|
||||||
|
cs := gtx.Constraints
|
||||||
twoDp := gtx.Px(unit.Dp(2))
|
twoDp := gtx.Px(unit.Dp(2))
|
||||||
|
e.carWidth = fixed.I(gtx.Px(unit.Dp(1)))
|
||||||
|
|
||||||
e.padLeft, e.padRight = twoDp, twoDp
|
e.padLeft, e.padRight = twoDp, twoDp
|
||||||
maxWidth := cs.Width.Max
|
maxWidth := cs.Width.Max
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
@@ -202,84 +236,21 @@ func (e *Editor) Layout(gtx *layout.Context) {
|
|||||||
e.invalidate()
|
e.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
e.layout(gtx)
|
if !e.valid {
|
||||||
lines, size := e.lines, e.dims.Size
|
e.layoutText(gtx, font)
|
||||||
e.viewSize = cs.Constrain(size)
|
e.valid = true
|
||||||
|
|
||||||
carLine, _, carX, carY := e.layoutCaret(gtx)
|
|
||||||
|
|
||||||
off := image.Point{
|
|
||||||
X: -e.scrollOff.X + e.padLeft,
|
|
||||||
Y: -e.scrollOff.Y + e.padTop,
|
|
||||||
}
|
}
|
||||||
clip := image.Rectangle{
|
|
||||||
Min: image.Point{X: 0, Y: 0},
|
e.viewSize = cs.Constrain(e.dims.Size)
|
||||||
Max: image.Point{X: e.viewSize.X, Y: e.viewSize.Y},
|
e.adjustScroll()
|
||||||
|
|
||||||
|
if e.caretScroll {
|
||||||
|
e.caretScroll = false
|
||||||
|
e.scrollToCaret()
|
||||||
}
|
}
|
||||||
|
|
||||||
key.InputOp{Key: e, Focus: e.requestFocus}.Add(gtx.Ops)
|
key.InputOp{Key: e, Focus: e.requestFocus}.Add(gtx.Ops)
|
||||||
e.requestFocus = false
|
e.requestFocus = false
|
||||||
e.it = lineIterator{
|
|
||||||
Lines: lines,
|
|
||||||
Clip: clip,
|
|
||||||
Alignment: e.Alignment,
|
|
||||||
Width: e.viewWidth(),
|
|
||||||
Offset: off,
|
|
||||||
}
|
|
||||||
var stack op.StackOp
|
|
||||||
stack.Push(gtx.Ops)
|
|
||||||
// Apply material. Set a default color in case the material is empty.
|
|
||||||
if e.rr.len() > 0 {
|
|
||||||
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
|
|
||||||
e.Material.Add(gtx.Ops)
|
|
||||||
} else {
|
|
||||||
paint.ColorOp{Color: color.RGBA{A: 0xaa}}.Add(gtx.Ops)
|
|
||||||
e.HintMaterial.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
tsize := textSize(gtx, e.Size)
|
|
||||||
for {
|
|
||||||
str, lineOff, ok := e.it.Next()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var stack op.StackOp
|
|
||||||
stack.Push(gtx.Ops)
|
|
||||||
op.TransformOp{}.Offset(lineOff).Add(gtx.Ops)
|
|
||||||
e.Family.Shape(e.Face, tsize, str).Add(gtx.Ops)
|
|
||||||
paint.PaintOp{Rect: toRectF(clip).Sub(lineOff)}.Add(gtx.Ops)
|
|
||||||
stack.Pop()
|
|
||||||
}
|
|
||||||
if e.focused {
|
|
||||||
now := gtx.Now()
|
|
||||||
dt := now.Sub(e.blinkStart)
|
|
||||||
blinking := dt < maxBlinkDuration
|
|
||||||
const timePerBlink = time.Second / blinksPerSecond
|
|
||||||
nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
|
|
||||||
on := !blinking || dt%timePerBlink < timePerBlink/2
|
|
||||||
if on {
|
|
||||||
carWidth := e.caretWidth(gtx)
|
|
||||||
carX -= carWidth / 2
|
|
||||||
carAsc, carDesc := -lines[carLine].Bounds.Min.Y, lines[carLine].Bounds.Max.Y
|
|
||||||
carRect := image.Rectangle{
|
|
||||||
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
|
|
||||||
Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
|
|
||||||
}
|
|
||||||
carRect = carRect.Add(image.Point{
|
|
||||||
X: -e.scrollOff.X + e.padLeft,
|
|
||||||
Y: -e.scrollOff.Y + e.padTop,
|
|
||||||
})
|
|
||||||
carRect = clip.Intersect(carRect)
|
|
||||||
if !carRect.Empty() {
|
|
||||||
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
|
|
||||||
e.Material.Add(gtx.Ops)
|
|
||||||
paint.PaintOp{Rect: toRectF(carRect)}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if blinking {
|
|
||||||
redraw := op.InvalidateOp{At: nextBlink}
|
|
||||||
redraw.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stack.Pop()
|
|
||||||
|
|
||||||
baseline := e.padTop + e.dims.Baseline
|
baseline := e.padTop + e.dims.Baseline
|
||||||
pointerPadding := gtx.Px(unit.Dp(4))
|
pointerPadding := gtx.Px(unit.Dp(4))
|
||||||
@@ -292,6 +263,85 @@ func (e *Editor) Layout(gtx *layout.Context) {
|
|||||||
e.scroller.Add(gtx.Ops)
|
e.scroller.Add(gtx.Ops)
|
||||||
e.clicker.Add(gtx.Ops)
|
e.clicker.Add(gtx.Ops)
|
||||||
gtx.Dimensions = layout.Dimensions{Size: e.viewSize, Baseline: baseline}
|
gtx.Dimensions = layout.Dimensions{Size: e.viewSize, Baseline: baseline}
|
||||||
|
e.caretOn = false
|
||||||
|
if e.focused {
|
||||||
|
now := gtx.Now()
|
||||||
|
dt := now.Sub(e.blinkStart)
|
||||||
|
blinking := dt < maxBlinkDuration
|
||||||
|
const timePerBlink = time.Second / blinksPerSecond
|
||||||
|
nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
|
||||||
|
if blinking {
|
||||||
|
redraw := op.InvalidateOp{At: nextBlink}
|
||||||
|
redraw.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
e.caretOn = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Editor) draw(gtx *layout.Context, font editorFont) {
|
||||||
|
var stack op.StackOp
|
||||||
|
stack.Push(gtx.Ops)
|
||||||
|
off := image.Point{
|
||||||
|
X: -e.scrollOff.X + e.padLeft,
|
||||||
|
Y: -e.scrollOff.Y + e.padTop,
|
||||||
|
}
|
||||||
|
clip := image.Rectangle{
|
||||||
|
Max: image.Point{X: e.viewSize.X, Y: e.viewSize.Y},
|
||||||
|
}
|
||||||
|
it := lineIterator{
|
||||||
|
Lines: e.lines,
|
||||||
|
Clip: clip,
|
||||||
|
Alignment: e.Alignment,
|
||||||
|
Width: e.viewWidth(),
|
||||||
|
Offset: off,
|
||||||
|
}
|
||||||
|
fsize := float32(gtx.Px(font.Size))
|
||||||
|
for {
|
||||||
|
str, lineOff, ok := it.Next()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var stack op.StackOp
|
||||||
|
stack.Push(gtx.Ops)
|
||||||
|
op.TransformOp{}.Offset(lineOff).Add(gtx.Ops)
|
||||||
|
font.Family.Shape(font.Face, fsize, str).Add(gtx.Ops)
|
||||||
|
paint.PaintOp{Rect: toRectF(clip).Sub(lineOff)}.Add(gtx.Ops)
|
||||||
|
stack.Pop()
|
||||||
|
}
|
||||||
|
stack.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Editor) drawCaret(gtx *layout.Context) {
|
||||||
|
if !e.caretOn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
carLine, _, carX, carY := e.layoutCaret()
|
||||||
|
|
||||||
|
clip := image.Rectangle{
|
||||||
|
Max: e.viewSize,
|
||||||
|
}
|
||||||
|
var stack op.StackOp
|
||||||
|
stack.Push(gtx.Ops)
|
||||||
|
carX -= e.carWidth / 2
|
||||||
|
carAsc, carDesc := -e.lines[carLine].Bounds.Min.Y, e.lines[carLine].Bounds.Max.Y
|
||||||
|
carRect := image.Rectangle{
|
||||||
|
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
|
||||||
|
Max: image.Point{X: carX.Ceil() + e.carWidth.Ceil(), Y: carY + carDesc.Ceil()},
|
||||||
|
}
|
||||||
|
carRect = carRect.Add(image.Point{
|
||||||
|
X: -e.scrollOff.X + e.padLeft,
|
||||||
|
Y: -e.scrollOff.Y + e.padTop,
|
||||||
|
})
|
||||||
|
carRect = clip.Intersect(carRect)
|
||||||
|
if !carRect.Empty() {
|
||||||
|
paint.PaintOp{Rect: toRectF(carRect)}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
stack.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len is the length of the editor contents.
|
||||||
|
func (e *Editor) Len() int {
|
||||||
|
return e.rr.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text returns the contents of the editor.
|
// Text returns the contents of the editor.
|
||||||
@@ -306,15 +356,6 @@ func (e *Editor) SetText(s string) {
|
|||||||
e.prepend(s)
|
e.prepend(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) layout(c unit.Converter) {
|
|
||||||
e.adjustScroll()
|
|
||||||
if e.valid {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.layoutText(c)
|
|
||||||
e.valid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Editor) scrollBounds() image.Rectangle {
|
func (e *Editor) scrollBounds() image.Rectangle {
|
||||||
var b image.Rectangle
|
var b image.Rectangle
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
@@ -348,7 +389,7 @@ func (e *Editor) adjustScroll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveCoord(c unit.Converter, pos image.Point) {
|
func (e *Editor) moveCoord(c unit.Converter, pos image.Point) {
|
||||||
e.layout(c)
|
e.adjustScroll()
|
||||||
var (
|
var (
|
||||||
prevDesc fixed.Int26_6
|
prevDesc fixed.Int26_6
|
||||||
carLine int
|
carLine int
|
||||||
@@ -363,17 +404,17 @@ func (e *Editor) moveCoord(c unit.Converter, pos image.Point) {
|
|||||||
carLine++
|
carLine++
|
||||||
}
|
}
|
||||||
x := fixed.I(pos.X + e.scrollOff.X - e.padLeft)
|
x := fixed.I(pos.X + e.scrollOff.X - e.padLeft)
|
||||||
e.moveToLine(c, x, carLine)
|
e.moveToLine(x, carLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) layoutText(c unit.Converter) {
|
func (e *Editor) layoutText(c unit.Converter, font editorFont) {
|
||||||
s := e.rr.String()
|
txt := e.rr.String()
|
||||||
if s == "" {
|
if txt == "" {
|
||||||
s = e.Hint
|
txt = e.Hint
|
||||||
}
|
}
|
||||||
tsize := textSize(c, e.Size)
|
|
||||||
opts := LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth}
|
opts := LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth}
|
||||||
textLayout := e.Family.Layout(e.Face, tsize, s, opts)
|
fsize := float32(c.Px(font.Size))
|
||||||
|
textLayout := font.Family.Layout(font.Face, fsize, txt, opts)
|
||||||
lines := textLayout.Lines
|
lines := textLayout.Lines
|
||||||
dims := linesDimens(lines)
|
dims := linesDimens(lines)
|
||||||
for i := 0; i < len(lines)-1; i++ {
|
for i := 0; i < len(lines)-1; i++ {
|
||||||
@@ -400,8 +441,8 @@ func (e *Editor) viewWidth() int {
|
|||||||
return e.viewSize.X - e.padLeft - e.padRight
|
return e.viewSize.X - e.padLeft - e.padRight
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) layoutCaret(c unit.Converter) (carLine, carCol int, x fixed.Int26_6, y int) {
|
func (e *Editor) layoutCaret() (carLine, carCol int, x fixed.Int26_6, y int) {
|
||||||
e.layout(c)
|
e.adjustScroll()
|
||||||
var idx int
|
var idx int
|
||||||
var prevDesc fixed.Int26_6
|
var prevDesc fixed.Int26_6
|
||||||
loop:
|
loop:
|
||||||
@@ -459,9 +500,8 @@ func (e *Editor) prepend(s string) {
|
|||||||
e.invalidate()
|
e.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) movePages(c unit.Converter, pages int) {
|
func (e *Editor) movePages(pages int) {
|
||||||
e.layout(c)
|
_, _, carX, carY := e.layoutCaret()
|
||||||
_, _, carX, carY := e.layoutCaret(c)
|
|
||||||
y := carY + pages*e.viewSize.Y
|
y := carY + pages*e.viewSize.Y
|
||||||
var (
|
var (
|
||||||
prevDesc fixed.Int26_6
|
prevDesc fixed.Int26_6
|
||||||
@@ -481,12 +521,11 @@ func (e *Editor) movePages(c unit.Converter, pages int) {
|
|||||||
y2 += h
|
y2 += h
|
||||||
carLine2++
|
carLine2++
|
||||||
}
|
}
|
||||||
e.carXOff = e.moveToLine(c, carX+e.carXOff, carLine2)
|
e.carXOff = e.moveToLine(carX+e.carXOff, carLine2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveToLine(c unit.Converter, carX fixed.Int26_6, carLine2 int) fixed.Int26_6 {
|
func (e *Editor) moveToLine(carX fixed.Int26_6, carLine2 int) fixed.Int26_6 {
|
||||||
e.layout(c)
|
carLine, carCol, _, _ := e.layoutCaret()
|
||||||
carLine, carCol, _, _ := e.layoutCaret(c)
|
|
||||||
if carLine2 < 0 {
|
if carLine2 < 0 {
|
||||||
carLine2 = 0
|
carLine2 = 0
|
||||||
}
|
}
|
||||||
@@ -543,8 +582,8 @@ func (e *Editor) moveRight() {
|
|||||||
e.carXOff = 0
|
e.carXOff = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveStart(c unit.Converter) {
|
func (e *Editor) moveStart() {
|
||||||
carLine, carCol, x, _ := e.layoutCaret(c)
|
carLine, carCol, x, _ := e.layoutCaret()
|
||||||
advances := e.lines[carLine].Text.Advances
|
advances := e.lines[carLine].Text.Advances
|
||||||
for i := carCol - 1; i >= 0; i-- {
|
for i := carCol - 1; i >= 0; i-- {
|
||||||
_, s := e.rr.runeBefore(e.rr.caret)
|
_, s := e.rr.runeBefore(e.rr.caret)
|
||||||
@@ -554,8 +593,8 @@ func (e *Editor) moveStart(c unit.Converter) {
|
|||||||
e.carXOff = -x
|
e.carXOff = -x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveEnd(c unit.Converter) {
|
func (e *Editor) moveEnd() {
|
||||||
carLine, carCol, x, _ := e.layoutCaret(c)
|
carLine, carCol, x, _ := e.layoutCaret()
|
||||||
l := e.lines[carLine]
|
l := e.lines[carLine]
|
||||||
// Only move past the end of the last line
|
// Only move past the end of the last line
|
||||||
end := 0
|
end := 0
|
||||||
@@ -572,16 +611,15 @@ func (e *Editor) moveEnd(c unit.Converter) {
|
|||||||
e.carXOff = l.Width + a - x
|
e.carXOff = l.Width + a - x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) scrollToCaret(c unit.Converter) {
|
func (e *Editor) scrollToCaret() {
|
||||||
carWidth := e.caretWidth(c)
|
carLine, _, x, y := e.layoutCaret()
|
||||||
carLine, _, x, y := e.layoutCaret(c)
|
|
||||||
l := e.lines[carLine]
|
l := e.lines[carLine]
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
minx := (x - carWidth/2).Ceil()
|
minx := (x - e.carWidth/2).Ceil()
|
||||||
if d := minx - e.scrollOff.X + e.padLeft; d < 0 {
|
if d := minx - e.scrollOff.X + e.padLeft; d < 0 {
|
||||||
e.scrollOff.X += d
|
e.scrollOff.X += d
|
||||||
}
|
}
|
||||||
maxx := (x + carWidth/2).Ceil()
|
maxx := (x + e.carWidth/2).Ceil()
|
||||||
if d := maxx - (e.scrollOff.X + e.viewSize.X - e.padRight); d > 0 {
|
if d := maxx - (e.scrollOff.X + e.viewSize.X - e.padRight); d > 0 {
|
||||||
e.scrollOff.X += d
|
e.scrollOff.X += d
|
||||||
}
|
}
|
||||||
@@ -597,7 +635,7 @@ func (e *Editor) scrollToCaret(c unit.Converter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) command(c unit.Converter, k key.Event) bool {
|
func (e *Editor) command(k key.Event) bool {
|
||||||
switch k.Name {
|
switch k.Name {
|
||||||
case key.NameReturn, key.NameEnter:
|
case key.NameReturn, key.NameEnter:
|
||||||
e.append("\n")
|
e.append("\n")
|
||||||
@@ -606,23 +644,23 @@ func (e *Editor) command(c unit.Converter, k key.Event) bool {
|
|||||||
case key.NameDeleteForward:
|
case key.NameDeleteForward:
|
||||||
e.deleteRuneForward()
|
e.deleteRuneForward()
|
||||||
case key.NameUpArrow:
|
case key.NameUpArrow:
|
||||||
line, _, carX, _ := e.layoutCaret(c)
|
line, _, carX, _ := e.layoutCaret()
|
||||||
e.carXOff = e.moveToLine(c, carX+e.carXOff, line-1)
|
e.carXOff = e.moveToLine(carX+e.carXOff, line-1)
|
||||||
case key.NameDownArrow:
|
case key.NameDownArrow:
|
||||||
line, _, carX, _ := e.layoutCaret(c)
|
line, _, carX, _ := e.layoutCaret()
|
||||||
e.carXOff = e.moveToLine(c, carX+e.carXOff, line+1)
|
e.carXOff = e.moveToLine(carX+e.carXOff, line+1)
|
||||||
case key.NameLeftArrow:
|
case key.NameLeftArrow:
|
||||||
e.moveLeft()
|
e.moveLeft()
|
||||||
case key.NameRightArrow:
|
case key.NameRightArrow:
|
||||||
e.moveRight()
|
e.moveRight()
|
||||||
case key.NamePageUp:
|
case key.NamePageUp:
|
||||||
e.movePages(c, -1)
|
e.movePages(-1)
|
||||||
case key.NamePageDown:
|
case key.NamePageDown:
|
||||||
e.movePages(c, +1)
|
e.movePages(+1)
|
||||||
case key.NameHome:
|
case key.NameHome:
|
||||||
e.moveStart(c)
|
e.moveStart()
|
||||||
case key.NameEnd:
|
case key.NameEnd:
|
||||||
e.moveEnd(c)
|
e.moveEnd()
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user