mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
widget: maintain Editor caret position
Only call layoutCaret when the text layout changes, adjust position for caret movement. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -152,18 +152,6 @@ func (e *editBuffer) dump() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *editBuffer) move(runes int) {
|
|
||||||
for ; runes < 0 && e.caret > 0; runes++ {
|
|
||||||
_, s := e.runeBefore(e.caret)
|
|
||||||
e.caret -= s
|
|
||||||
}
|
|
||||||
for ; runes > 0 && e.caret < e.len(); runes-- {
|
|
||||||
_, s := e.runeAt(e.caret)
|
|
||||||
e.caret += s
|
|
||||||
}
|
|
||||||
e.dump()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *editBuffer) runeBefore(idx int) (rune, int) {
|
func (e *editBuffer) runeBefore(idx int) (rune, int) {
|
||||||
if idx > e.gapstart {
|
if idx > e.gapstart {
|
||||||
idx += e.gapLen()
|
idx += e.gapLen()
|
||||||
|
|||||||
+123
-66
@@ -55,6 +55,14 @@ type Editor struct {
|
|||||||
// xoff is the offset to the current caret
|
// xoff is the offset to the current caret
|
||||||
// position when moving between lines.
|
// position when moving between lines.
|
||||||
xoff fixed.Int26_6
|
xoff fixed.Int26_6
|
||||||
|
|
||||||
|
// line is the caret line position as an index into lines.
|
||||||
|
line int
|
||||||
|
// col is the caret column measured in runes.
|
||||||
|
col int
|
||||||
|
// (x, y) are the caret coordinates.
|
||||||
|
x fixed.Int26_6
|
||||||
|
y int
|
||||||
}
|
}
|
||||||
|
|
||||||
scroller gesture.Scroll
|
scroller gesture.Scroll
|
||||||
@@ -109,7 +117,6 @@ func (e *Editor) processEvents(gtx layout.Context) {
|
|||||||
// Can't process events without a shaper.
|
// Can't process events without a shaper.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.makeValid()
|
|
||||||
e.processPointer(gtx)
|
e.processPointer(gtx)
|
||||||
e.processKey(gtx)
|
e.processKey(gtx)
|
||||||
}
|
}
|
||||||
@@ -117,6 +124,11 @@ func (e *Editor) processEvents(gtx layout.Context) {
|
|||||||
func (e *Editor) makeValid() {
|
func (e *Editor) makeValid() {
|
||||||
if !e.valid {
|
if !e.valid {
|
||||||
e.lines, e.dims = e.layoutText(e.shaper)
|
e.lines, e.dims = e.layoutText(e.shaper)
|
||||||
|
line, col, x, y := e.layoutCaret()
|
||||||
|
e.caret.line = line
|
||||||
|
e.caret.col = col
|
||||||
|
e.caret.x = x
|
||||||
|
e.caret.y = y
|
||||||
e.valid = true
|
e.valid = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,11 +218,9 @@ func (e *Editor) command(k key.Event) bool {
|
|||||||
case key.NameDeleteForward:
|
case key.NameDeleteForward:
|
||||||
e.Delete(1)
|
e.Delete(1)
|
||||||
case key.NameUpArrow:
|
case key.NameUpArrow:
|
||||||
line, _, carX, _ := e.layoutCaret()
|
e.caret.xoff = e.moveToLine(e.caret.x+e.caret.xoff, e.caret.line-1)
|
||||||
e.caret.xoff = e.moveToLine(carX+e.caret.xoff, line-1)
|
|
||||||
case key.NameDownArrow:
|
case key.NameDownArrow:
|
||||||
line, _, carX, _ := e.layoutCaret()
|
e.caret.xoff = e.moveToLine(e.caret.x+e.caret.xoff, e.caret.line+1)
|
||||||
e.caret.xoff = e.moveToLine(carX+e.caret.xoff, line+1)
|
|
||||||
case key.NameLeftArrow:
|
case key.NameLeftArrow:
|
||||||
e.Move(-1)
|
e.Move(-1)
|
||||||
case key.NameRightArrow:
|
case key.NameRightArrow:
|
||||||
@@ -260,14 +270,19 @@ func (e *Editor) Layout(gtx layout.Context, sh text.Shaper, font text.Font, size
|
|||||||
e.invalidate()
|
e.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.makeValid()
|
||||||
e.processEvents(gtx)
|
e.processEvents(gtx)
|
||||||
|
|
||||||
|
if viewSize := gtx.Constraints.Constrain(e.dims.Size); viewSize != e.viewSize {
|
||||||
|
e.viewSize = viewSize
|
||||||
|
e.invalidate()
|
||||||
|
}
|
||||||
|
e.makeValid()
|
||||||
|
|
||||||
return e.layout(gtx)
|
return e.layout(gtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
|
func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
|
||||||
e.makeValid()
|
|
||||||
|
|
||||||
e.viewSize = gtx.Constraints.Constrain(e.dims.Size)
|
|
||||||
// Adjust scrolling for new viewport and layout.
|
// Adjust scrolling for new viewport and layout.
|
||||||
e.scrollRel(0, 0)
|
e.scrollRel(0, 0)
|
||||||
|
|
||||||
@@ -343,12 +358,14 @@ func (e *Editor) PaintCaret(gtx layout.Context) {
|
|||||||
if !e.caret.on {
|
if !e.caret.on {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
e.makeValid()
|
||||||
carWidth := fixed.I(gtx.Px(unit.Dp(1)))
|
carWidth := fixed.I(gtx.Px(unit.Dp(1)))
|
||||||
carLine, _, carX, carY := e.layoutCaret()
|
carX := e.caret.x
|
||||||
|
carY := e.caret.y
|
||||||
|
|
||||||
stack := op.Push(gtx.Ops)
|
stack := op.Push(gtx.Ops)
|
||||||
carX -= carWidth / 2
|
carX -= carWidth / 2
|
||||||
carAsc, carDesc := -e.lines[carLine].Bounds.Min.Y, e.lines[carLine].Bounds.Max.Y
|
carAsc, carDesc := -e.lines[e.caret.line].Bounds.Min.Y, e.lines[e.caret.line].Bounds.Max.Y
|
||||||
carRect := image.Rectangle{
|
carRect := image.Rectangle{
|
||||||
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
|
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
|
||||||
Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
|
Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
|
||||||
@@ -444,7 +461,7 @@ func (e *Editor) moveCoord(pos image.Point) {
|
|||||||
carLine++
|
carLine++
|
||||||
}
|
}
|
||||||
x := fixed.I(pos.X + e.scrollOff.X)
|
x := fixed.I(pos.X + e.scrollOff.X)
|
||||||
e.carXOff = 0
|
e.caret.xoff = 0
|
||||||
e.moveToLine(x, carLine)
|
e.moveToLine(x, carLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,39 +485,39 @@ func (e *Editor) layoutText(s text.Shaper) ([]text.Line, layout.Dimensions) {
|
|||||||
|
|
||||||
// CaretPos returns the line & column numbers of the caret.
|
// CaretPos returns the line & column numbers of the caret.
|
||||||
func (e *Editor) CaretPos() (line, col int) {
|
func (e *Editor) CaretPos() (line, col int) {
|
||||||
line, col, _, _ = e.layoutCaret()
|
e.makeValid()
|
||||||
return
|
return e.caret.line, e.caret.col
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaretCoords returns the coordinates of the caret, relative to the
|
// CaretCoords returns the coordinates of the caret, relative to the
|
||||||
// editor itself.
|
// editor itself.
|
||||||
func (e *Editor) CaretCoords() f32.Point {
|
func (e *Editor) CaretCoords() f32.Point {
|
||||||
_, _, x, y := e.layoutCaret()
|
e.makeValid()
|
||||||
return f32.Pt(float32(x)/64, float32(y))
|
return f32.Pt(float32(e.caret.x)/64, float32(e.caret.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) layoutCaret() (carLine, carCol int, x fixed.Int26_6, y int) {
|
func (e *Editor) layoutCaret() (line, col int, x fixed.Int26_6, y int) {
|
||||||
var idx int
|
var idx int
|
||||||
var prevDesc fixed.Int26_6
|
var prevDesc fixed.Int26_6
|
||||||
loop:
|
loop:
|
||||||
for carLine = 0; carLine < len(e.lines); carLine++ {
|
for line = 0; line < len(e.lines); line++ {
|
||||||
l := e.lines[carLine]
|
l := e.lines[line]
|
||||||
y += (prevDesc + l.Ascent).Ceil()
|
y += (prevDesc + l.Ascent).Ceil()
|
||||||
prevDesc = l.Descent
|
prevDesc = l.Descent
|
||||||
if carLine == len(e.lines)-1 || idx+l.Len > e.rr.caret {
|
if line == len(e.lines)-1 || idx+l.Len > e.rr.caret {
|
||||||
for _, g := range l.Layout {
|
for _, g := range l.Layout {
|
||||||
if idx == e.rr.caret {
|
if idx == e.rr.caret {
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
x += g.Advance
|
x += g.Advance
|
||||||
idx += utf8.RuneLen(g.Rune)
|
idx += utf8.RuneLen(g.Rune)
|
||||||
carCol++
|
col++
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
idx += l.Len
|
idx += l.Len
|
||||||
}
|
}
|
||||||
x += align(e.Alignment, e.lines[carLine].Width, e.viewSize.X)
|
x += align(e.Alignment, e.lines[line].Width, e.viewSize.X)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,8 +555,8 @@ func (e *Editor) prepend(s string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) movePages(pages int) {
|
func (e *Editor) movePages(pages int) {
|
||||||
_, _, carX, carY := e.layoutCaret()
|
e.makeValid()
|
||||||
y := carY + pages*e.viewSize.Y
|
y := e.caret.y + pages*e.viewSize.Y
|
||||||
var (
|
var (
|
||||||
prevDesc fixed.Int26_6
|
prevDesc fixed.Int26_6
|
||||||
carLine2 int
|
carLine2 int
|
||||||
@@ -558,108 +575,148 @@ func (e *Editor) movePages(pages int) {
|
|||||||
y2 += h
|
y2 += h
|
||||||
carLine2++
|
carLine2++
|
||||||
}
|
}
|
||||||
e.caret.xoff = e.moveToLine(carX+e.caret.xoff, carLine2)
|
e.caret.xoff = e.moveToLine(e.caret.x+e.caret.xoff, carLine2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveToLine(carX fixed.Int26_6, carLine2 int) fixed.Int26_6 {
|
func (e *Editor) moveToLine(x fixed.Int26_6, line int) fixed.Int26_6 {
|
||||||
carLine, carCol, _, _ := e.layoutCaret()
|
e.makeValid()
|
||||||
if carLine2 < 0 {
|
if line < 0 {
|
||||||
carLine2 = 0
|
line = 0
|
||||||
}
|
}
|
||||||
if carLine2 >= len(e.lines) {
|
if line >= len(e.lines) {
|
||||||
carLine2 = len(e.lines) - 1
|
line = len(e.lines) - 1
|
||||||
}
|
}
|
||||||
// Move to start of line.
|
// Move to start of line.
|
||||||
for i := carCol - 1; i >= 0; i-- {
|
for i := e.caret.col - 1; i >= 0; i-- {
|
||||||
_, s := e.rr.runeBefore(e.rr.caret)
|
_, s := e.rr.runeBefore(e.rr.caret)
|
||||||
e.rr.caret -= s
|
e.rr.caret -= s
|
||||||
}
|
}
|
||||||
if carLine2 != carLine {
|
e.caret.col = 0
|
||||||
// Move to start of line2.
|
if line != e.caret.line {
|
||||||
if carLine2 > carLine {
|
prevDesc := e.lines[line].Descent
|
||||||
for i := carLine; i < carLine2; i++ {
|
if line > e.caret.line {
|
||||||
e.rr.caret += e.lines[i].Len
|
for i := e.caret.line; i < line; i++ {
|
||||||
|
l := e.lines[i]
|
||||||
|
e.rr.caret += l.Len
|
||||||
|
e.caret.y += (prevDesc + l.Ascent).Ceil()
|
||||||
|
prevDesc = l.Descent
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := carLine - 1; i >= carLine2; i-- {
|
for i := e.caret.line - 1; i >= line; i-- {
|
||||||
e.rr.caret -= e.lines[i].Len
|
l := e.lines[i]
|
||||||
|
e.rr.caret -= l.Len
|
||||||
|
e.caret.y -= (prevDesc + l.Ascent).Ceil()
|
||||||
|
prevDesc = l.Descent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.caret.line = line
|
||||||
}
|
}
|
||||||
l2 := e.lines[carLine2]
|
l := e.lines[line]
|
||||||
carX2 := align(e.Alignment, l2.Width, e.viewSize.X)
|
e.caret.x = align(e.Alignment, l.Width, e.viewSize.X)
|
||||||
// Only move past the end of the last line
|
// Only move past the end of the last line
|
||||||
end := 0
|
end := 0
|
||||||
if carLine2 < len(e.lines)-1 {
|
if line < len(e.lines)-1 {
|
||||||
end = 1
|
end = 1
|
||||||
}
|
}
|
||||||
// Move to rune closest to previous horizontal position.
|
// Move to rune closest to x.
|
||||||
for i := 0; i < len(l2.Layout)-end; i++ {
|
for i := 0; i < len(l.Layout)-end; i++ {
|
||||||
g := l2.Layout[i]
|
g := l.Layout[i]
|
||||||
if carX2 >= carX {
|
if e.caret.x >= x {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if carX2+g.Advance-carX >= carX-carX2 {
|
if e.caret.x+g.Advance-x >= x-e.caret.x {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
carX2 += g.Advance
|
e.caret.x += g.Advance
|
||||||
_, s := e.rr.runeAt(e.rr.caret)
|
_, s := e.rr.runeAt(e.rr.caret)
|
||||||
e.rr.caret += s
|
e.rr.caret += s
|
||||||
|
e.caret.col++
|
||||||
}
|
}
|
||||||
return carX - carX2
|
return x - e.caret.x
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the caret: positive distance moves forward, negative distance moves
|
// Move the caret: positive distance moves forward, negative distance moves
|
||||||
// backward.
|
// backward.
|
||||||
func (e *Editor) Move(distance int) {
|
func (e *Editor) Move(distance int) {
|
||||||
e.rr.move(distance)
|
e.makeValid()
|
||||||
|
for ; distance < 0 && e.rr.caret > 0; distance++ {
|
||||||
|
if e.caret.col == 0 {
|
||||||
|
// Move to end of previous line.
|
||||||
|
e.moveToLine(fixed.I(e.maxWidth), e.caret.line-1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l := e.lines[e.caret.line].Layout
|
||||||
|
_, s := e.rr.runeBefore(e.rr.caret)
|
||||||
|
e.rr.caret -= s
|
||||||
|
e.caret.col--
|
||||||
|
e.caret.x -= l[e.caret.col].Advance
|
||||||
|
}
|
||||||
|
for ; distance > 0 && e.rr.caret < e.rr.len(); distance-- {
|
||||||
|
l := e.lines[e.caret.line].Layout
|
||||||
|
// Only move past the end of the last line
|
||||||
|
end := 0
|
||||||
|
if e.caret.line < len(e.lines)-1 {
|
||||||
|
end = 1
|
||||||
|
}
|
||||||
|
if e.caret.col >= len(l)-end {
|
||||||
|
// Move to start of next line.
|
||||||
|
e.moveToLine(0, e.caret.line+1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
e.caret.x += l[e.caret.col].Advance
|
||||||
|
_, s := e.rr.runeAt(e.rr.caret)
|
||||||
|
e.rr.caret += s
|
||||||
|
e.caret.col++
|
||||||
|
}
|
||||||
e.caret.xoff = 0
|
e.caret.xoff = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveStart() {
|
func (e *Editor) moveStart() {
|
||||||
carLine, carCol, x, _ := e.layoutCaret()
|
e.makeValid()
|
||||||
layout := e.lines[carLine].Layout
|
layout := e.lines[e.caret.line].Layout
|
||||||
for i := carCol - 1; i >= 0; i-- {
|
for i := e.caret.col - 1; i >= 0; i-- {
|
||||||
_, s := e.rr.runeBefore(e.rr.caret)
|
_, s := e.rr.runeBefore(e.rr.caret)
|
||||||
e.rr.caret -= s
|
e.rr.caret -= s
|
||||||
x -= layout[i].Advance
|
e.caret.x -= layout[i].Advance
|
||||||
}
|
}
|
||||||
e.caret.xoff = -x
|
e.caret.col = 0
|
||||||
|
e.caret.xoff = -e.caret.x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveEnd() {
|
func (e *Editor) moveEnd() {
|
||||||
carLine, carCol, x, _ := e.layoutCaret()
|
e.makeValid()
|
||||||
l := e.lines[carLine]
|
l := e.lines[e.caret.line]
|
||||||
// Only move past the end of the last line
|
// Only move past the end of the last line
|
||||||
end := 0
|
end := 0
|
||||||
if carLine < len(e.lines)-1 {
|
if e.caret.line < len(e.lines)-1 {
|
||||||
end = 1
|
end = 1
|
||||||
}
|
}
|
||||||
layout := l.Layout
|
layout := l.Layout
|
||||||
for i := carCol; i < len(layout)-end; i++ {
|
for i := e.caret.col; i < len(layout)-end; i++ {
|
||||||
adv := layout[i].Advance
|
adv := layout[i].Advance
|
||||||
_, s := e.rr.runeAt(e.rr.caret)
|
_, s := e.rr.runeAt(e.rr.caret)
|
||||||
e.rr.caret += s
|
e.rr.caret += s
|
||||||
x += adv
|
e.caret.x += adv
|
||||||
|
e.caret.col++
|
||||||
}
|
}
|
||||||
a := align(e.Alignment, l.Width, e.viewSize.X)
|
a := align(e.Alignment, l.Width, e.viewSize.X)
|
||||||
e.caret.xoff = l.Width + a - x
|
e.caret.xoff = l.Width + a - e.caret.x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) scrollToCaret() {
|
func (e *Editor) scrollToCaret() {
|
||||||
carLine, _, x, y := e.layoutCaret()
|
e.makeValid()
|
||||||
l := e.lines[carLine]
|
l := e.lines[e.caret.line]
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
var dist int
|
var dist int
|
||||||
if d := x.Floor() - e.scrollOff.X; d < 0 {
|
if d := e.caret.x.Floor() - e.scrollOff.X; d < 0 {
|
||||||
dist = d
|
dist = d
|
||||||
} else if d := x.Ceil() - (e.scrollOff.X + e.viewSize.X); d > 0 {
|
} else if d := e.caret.x.Ceil() - (e.scrollOff.X + e.viewSize.X); d > 0 {
|
||||||
dist = d
|
dist = d
|
||||||
}
|
}
|
||||||
e.scrollRel(dist, 0)
|
e.scrollRel(dist, 0)
|
||||||
} else {
|
} else {
|
||||||
miny := y - l.Ascent.Ceil()
|
miny := e.caret.y - l.Ascent.Ceil()
|
||||||
maxy := y + l.Descent.Ceil()
|
maxy := e.caret.y + l.Descent.Ceil()
|
||||||
var dist int
|
var dist int
|
||||||
if d := miny - e.scrollOff.Y; d < 0 {
|
if d := miny - e.scrollOff.Y; d < 0 {
|
||||||
dist = d
|
dist = d
|
||||||
|
|||||||
Reference in New Issue
Block a user