Rudimentary text selection with the mouse

This commit is contained in:
Sasha Koshka 2023-02-13 18:29:49 -05:00
parent 88502cf628
commit d18da8b07a
2 changed files with 96 additions and 20 deletions

View File

@ -10,6 +10,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
type characterLayout struct {
x int
width int
character rune
}
@ -38,13 +39,14 @@ const (
// text, and calculating text bounds. It avoids doing redundant work
// automatically.
type TextDrawer struct {
runes []rune
face font.Face
width int
height int
align Align
wrap bool
cut bool
runes []rune
face font.Face
width int
height int
align Align
wrap bool
cut bool
metrics font.Metrics
layout []wordLayout
layoutClean bool
@ -205,6 +207,38 @@ func (drawer *TextDrawer) PositionOf (index int) (position image.Point) {
return
}
// AtPosition returns the index at the specified position relative to the
// baseline.
func (drawer *TextDrawer) AtPosition (position image.Point) (index int) {
cursor := 0
if !drawer.layoutClean { drawer.recalculate() }
for _, word := range drawer.layout {
for _, character := range word.text {
bounds := drawer.boundsOfChar(character).Add(word.position)
if position.In(bounds) {
return cursor
}
cursor ++
}
for _, character := range word.whitespace {
bounds := drawer.boundsOfChar(character).Add(word.position)
if position.In(bounds) {
return cursor
}
cursor ++
}
}
return -1
}
func (drawer *TextDrawer) boundsOfChar (char characterLayout) (image.Rectangle) {
return image.Rect (
char.x, 0,
char.x + char.width,
drawer.metrics.Height.Ceil()).
Sub(image.Pt(0, drawer.metrics.Descent.Round()))
}
// Length returns the amount of runes in the drawer's text.
func (drawer *TextDrawer) Length () (length int) {
return len(drawer.runes)
@ -217,7 +251,7 @@ func (drawer *TextDrawer) recalculate () {
if drawer.runes == nil { return }
if drawer.face == nil { return }
metrics := drawer.face.Metrics()
drawer.metrics = drawer.face.Metrics()
dot := fixed.Point26_6 { 0, 0 }
index := 0
horizontalExtent := 0
@ -241,6 +275,7 @@ func (drawer *TextDrawer) recalculate () {
word.text = append(word.text, characterLayout {
x: currentCharacterX.Round(),
character: character,
width: advance.Ceil(),
})
dot.X += advance
@ -264,9 +299,9 @@ func (drawer *TextDrawer) recalculate () {
word.width + word.position.X > drawer.width &&
word.position.X > 0 {
word.position.Y += metrics.Height.Round()
word.position.Y += drawer.metrics.Height.Round()
word.position.X = 0
dot.Y += metrics.Height
dot.Y += drawer.metrics.Height
dot.X = wordWidth
}
@ -281,12 +316,13 @@ func (drawer *TextDrawer) recalculate () {
word.whitespace = append(word.whitespace, characterLayout {
x: currentCharacterX.Round(),
character: character,
width: advance.Ceil(),
})
spaceWidth += advance
currentCharacterX += advance
if character == '\n' {
dot.Y += metrics.Height
dot.Y += drawer.metrics.Height
dot.X = 0
word.breaksAfter ++
break
@ -309,8 +345,9 @@ func (drawer *TextDrawer) recalculate () {
// stop processing more words. and remove any words that have
// also crossed the line.
if
drawer.cut &&
(dot.Y - metrics.Ascent - metrics.Descent).Round() >
drawer.cut && (
dot.Y - drawer.metrics.Ascent -
drawer.metrics.Descent).Round() >
drawer.height {
for
@ -343,11 +380,15 @@ func (drawer *TextDrawer) recalculate () {
}
if drawer.cut {
drawer.layoutBounds.Min.Y = 0 - metrics.Ascent.Round()
drawer.layoutBounds.Max.Y = drawer.height - metrics.Ascent.Round()
drawer.layoutBounds.Min.Y = 0 - drawer.metrics.Ascent.Round()
drawer.layoutBounds.Max.Y =
drawer.height -
drawer.metrics.Ascent.Round()
} else {
drawer.layoutBounds.Min.Y = 0 - metrics.Ascent.Round()
drawer.layoutBounds.Max.Y = dot.Y.Round() + metrics.Descent.Round()
drawer.layoutBounds.Min.Y = 0 - drawer.metrics.Ascent.Round()
drawer.layoutBounds.Max.Y =
dot.Y.Round() +
drawer.metrics.Descent.Round()
}
// TODO:

View File

@ -14,7 +14,8 @@ type TextBox struct {
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
dragging bool
dot textmanip.Dot
scroll int
placeholder string
@ -63,10 +64,44 @@ func (element *TextBox) handleResize () {
func (element *TextBox) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return }
if !element.Focused() { element.Focus() }
if button == input.ButtonLeft {
point := image.Pt(x, y)
offset := element.Bounds().Min.Add (image.Pt (
element.config.Padding() - element.scroll,
element.config.Padding()))
runeIndex := element.valueDrawer.AtPosition(point.Sub(offset))
element.dragging = true
if runeIndex > -1 {
element.dot = textmanip.EmptyDot(runeIndex)
element.redo()
}
}
}
func (element *TextBox) HandleMouseMove (x, y int) {
if !element.Enabled() { return }
if !element.Focused() { element.Focus() }
if element.dragging {
point := image.Pt(x, y)
offset := element.Bounds().Min.Add (image.Pt (
element.config.Padding() - element.scroll,
element.config.Padding()))
runeIndex := element.valueDrawer.AtPosition(point.Sub(offset))
if runeIndex > -1 {
element.dot.End = runeIndex
element.redo()
}
}
}
func (element *TextBox) HandleMouseUp (x, y int, button input.Button) {
if button == input.ButtonLeft {
element.dragging = false
}
}
func (element *TextBox) HandleMouseUp (x, y int, button input.Button) { }
func (element *TextBox) HandleMouseMove (x, y int) { }
func (element *TextBox) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers) {