From 2f828b1ae81c75a075780a1768cc72c2aecae784 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 4 Sep 2024 01:36:31 -0400 Subject: [PATCH] Add up/down keynav Paragraph jumping could be better, but that can be refined later. Progress on #10 --- internal/system/textbox.go | 100 +++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/internal/system/textbox.go b/internal/system/textbox.go index 0ccb8bd..3d7f059 100644 --- a/internal/system/textbox.go +++ b/internal/system/textbox.go @@ -1,6 +1,7 @@ package system import "image" +import "unicode" import "image/color" import "golang.org/x/image/font" import "git.tebibyte.media/tomo/tomo" @@ -31,9 +32,11 @@ type textBox struct { selecting bool selectStart int dot text.Dot + desiredX fixed.Int26_6 - drawer typeset.Drawer - face util.Cycler[font.Face] + drawer typeset.Drawer + face util.Cycler[font.Face] + lineHeight util.Memo[fixed.Int26_6] on struct { contentBoundsChange event.FuncBroadcaster @@ -46,6 +49,12 @@ func (this *System) NewTextBox () tomo.TextBox { box.box = this.newBox(box) box.attrTextColor.SetFallback(tomo.ATextColor(color.Black)) 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 } @@ -89,13 +98,20 @@ func (this *textBox) SetSelectable (selectable bool) { } func (this *textBox) Select (dot text.Dot) { - if !this.selectable { return } - if this.dot == dot { return } + if this.selectWithoutResettingDesiredX(dot) { + 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.dot = dot this.scrollToDot() this.on.dotChange.Broadcast() this.invalidateDraw() + return true } func (this *textBox) Dot () text.Dot { @@ -351,6 +367,42 @@ func (this *textBox) handleKeyDown (key input.Key, numberPad bool) bool { dot := this.Dot() sel := modifiers.Shift 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 { case key == input.KeyHome || (modifiers.Alt && key == input.KeyLeft): 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)) } 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: dot.Start = 0 dot.End = len(this.text) @@ -396,6 +454,10 @@ func (this *textBox) handleKeyUp (key input.Key, numberPad bool) bool { return true case key == input.KeyEnd || (modifiers.Alt && key == input.KeyRight): return true + case key == input.KeyUp: + return true + case key == input.KeyDown: + return true case key == input.KeyLeft: return true case key == input.KeyRight: @@ -502,6 +564,7 @@ func (this *textBox) handleFaceChange () { this.drawer.SetFace(face) this.invalidateMinimum() this.invalidateLayout() + this.lineHeight.Invalidate() } 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 +}