Add up/down keynav

Paragraph jumping could be better, but that can be refined later.
Progress on #10
This commit is contained in:
Sasha Koshka 2024-09-04 01:36:31 -04:00
parent c1c0d2125d
commit 2f828b1ae8

View File

@ -1,6 +1,7 @@
package system package system
import "image" import "image"
import "unicode"
import "image/color" import "image/color"
import "golang.org/x/image/font" import "golang.org/x/image/font"
import "git.tebibyte.media/tomo/tomo" import "git.tebibyte.media/tomo/tomo"
@ -31,9 +32,11 @@ type textBox struct {
selecting bool selecting bool
selectStart int selectStart int
dot text.Dot dot text.Dot
desiredX fixed.Int26_6
drawer typeset.Drawer drawer typeset.Drawer
face util.Cycler[font.Face] face util.Cycler[font.Face]
lineHeight util.Memo[fixed.Int26_6]
on struct { on struct {
contentBoundsChange event.FuncBroadcaster contentBoundsChange event.FuncBroadcaster
@ -46,6 +49,12 @@ func (this *System) NewTextBox () tomo.TextBox {
box.box = this.newBox(box) box.box = this.newBox(box)
box.attrTextColor.SetFallback(tomo.ATextColor(color.Black)) box.attrTextColor.SetFallback(tomo.ATextColor(color.Black))
box.attrDotColor.SetFallback(tomo.ADotColor(color.RGBA { G: 255, B: 255, A: 255})) box.attrDotColor.SetFallback(tomo.ADotColor(color.RGBA { G: 255, B: 255, A: 255}))
box.lineHeight = util.NewMemo(func () fixed.Int26_6 {
face := box.face.Value()
if face == nil { return 0 }
metrics := face.Metrics()
return metrics.Height
})
return box return box
} }
@ -89,13 +98,20 @@ func (this *textBox) SetSelectable (selectable bool) {
} }
func (this *textBox) Select (dot text.Dot) { func (this *textBox) Select (dot text.Dot) {
if !this.selectable { return } if this.selectWithoutResettingDesiredX(dot) {
if this.dot == dot { return } this.desiredX = fixed.I(0)
}
}
func (this *textBox) selectWithoutResettingDesiredX (dot text.Dot) bool {
if !this.selectable { return false }
if this.dot == dot { return false }
this.SetFocused(true) this.SetFocused(true)
this.dot = dot this.dot = dot
this.scrollToDot() this.scrollToDot()
this.on.dotChange.Broadcast() this.on.dotChange.Broadcast()
this.invalidateDraw() this.invalidateDraw()
return true
} }
func (this *textBox) Dot () text.Dot { func (this *textBox) Dot () text.Dot {
@ -351,6 +367,42 @@ func (this *textBox) handleKeyDown (key input.Key, numberPad bool) bool {
dot := this.Dot() dot := this.Dot()
sel := modifiers.Shift sel := modifiers.Shift
word := modifiers.Control word := modifiers.Control
moveVertically := func (delta fixed.Int26_6) {
currentDot := 0
if sel {
currentDot = dot.End
} else {
currentDot = dot.Canon().Start
if delta > fixed.I(0) { currentDot = dot.Canon().End }
}
nextDot := 0
if word {
if delta > fixed.I(0) {
nextDot = nextParagraph(this.runes, currentDot)
} else {
nextDot = previousParagraph(this.runes, currentDot)
}
} else {
currentPosition := this.drawer.PositionAt(currentDot)
if this.desiredX != fixed.I(0) {
currentPosition.X = this.desiredX
}
nextPosition := currentPosition
nextPosition.Y += this.lineHeight.Value().Mul(delta)
this.desiredX = nextPosition.X
nextDot = this.drawer.AtPosition(nextPosition)
}
if sel {
dot.End = nextDot
this.selectWithoutResettingDesiredX(dot)
} else {
this.selectWithoutResettingDesiredX(text.EmptyDot(nextDot))
}
}
switch { switch {
case key == input.KeyHome || (modifiers.Alt && key == input.KeyLeft): case key == input.KeyHome || (modifiers.Alt && key == input.KeyLeft):
dot.End = 0 dot.End = 0
@ -376,6 +428,12 @@ func (this *textBox) handleKeyDown (key input.Key, numberPad bool) bool {
this.Select(text.MoveRight(this.runes, dot, word)) this.Select(text.MoveRight(this.runes, dot, word))
} }
return true return true
case key == input.KeyUp:
moveVertically(fixed.I(-1))
return true
case key == input.KeyDown:
moveVertically(fixed.I(1))
return true
case key == input.Key('a') && modifiers.Control: case key == input.Key('a') && modifiers.Control:
dot.Start = 0 dot.Start = 0
dot.End = len(this.text) dot.End = len(this.text)
@ -396,6 +454,10 @@ func (this *textBox) handleKeyUp (key input.Key, numberPad bool) bool {
return true return true
case key == input.KeyEnd || (modifiers.Alt && key == input.KeyRight): case key == input.KeyEnd || (modifiers.Alt && key == input.KeyRight):
return true return true
case key == input.KeyUp:
return true
case key == input.KeyDown:
return true
case key == input.KeyLeft: case key == input.KeyLeft:
return true return true
case key == input.KeyRight: case key == input.KeyRight:
@ -502,6 +564,7 @@ func (this *textBox) handleFaceChange () {
this.drawer.SetFace(face) this.drawer.SetFace(face)
this.invalidateMinimum() this.invalidateMinimum()
this.invalidateLayout() this.invalidateLayout()
this.lineHeight.Invalidate()
} }
func (this *textBox) recursiveReApply () { func (this *textBox) recursiveReApply () {
@ -523,3 +586,32 @@ func (this *textBox) recursiveReApply () {
} }
} }
} }
func previousParagraph (text []rune, index int) int {
consecLF := 0
if index >= len(text) { index = len(text) - 1 }
for ; index > 0; index -- {
char := text[index]
if char == '\n' {
consecLF ++
} else if !unicode.IsSpace(char) {
if consecLF >= 2 { return index + 1 }
consecLF = 0
}
}
return index
}
func nextParagraph (text []rune, index int) int {
consecLF := 0
for ; index < len(text); index ++ {
char := text[index]
if char == '\n' {
consecLF ++
} else if !unicode.IsSpace(char) {
if consecLF >= 2 { return index }
consecLF = 0
}
}
return index
}