TextBox supports copy/paste with keyboard commands

This commit is contained in:
Sasha Koshka 2023-03-31 03:25:46 -04:00
parent ab78bc640d
commit c1e2bf46a6
8 changed files with 82 additions and 5 deletions

View File

@ -122,6 +122,10 @@ func (window *window) NotifyMinimumSizeChange (child tomo.Element) {
window.childMinimumSizeChangeCallback(child.MinimumSize()) window.childMinimumSizeChangeCallback(child.MinimumSize())
} }
func (window *window) Window () tomo.Window {
return window
}
func (window *window) RequestFocus ( func (window *window) RequestFocus (
child tomo.Focusable, child tomo.Focusable,
) ( ) (

View File

@ -204,6 +204,10 @@ func (element *Container) redoAll () {
} }
} }
func (element *Container) Window () tomo.Window {
return element.core.Window()
}
// NotifyMinimumSizeChange notifies the container that the minimum size of a // NotifyMinimumSizeChange notifies the container that the minimum size of a
// child element has changed. // child element has changed.
func (element *Container) NotifyMinimumSizeChange (child tomo.Element) { func (element *Container) NotifyMinimumSizeChange (child tomo.Element) {

View File

@ -202,6 +202,10 @@ func (element *DocumentContainer) partition () {
} }
} }
func (element *DocumentContainer) Window () tomo.Window {
return element.core.Window()
}
// NotifyMinimumSizeChange notifies the container that the minimum size of a // NotifyMinimumSizeChange notifies the container that the minimum size of a
// child element has changed. // child element has changed.
func (element *DocumentContainer) NotifyMinimumSizeChange (child tomo.Element) { func (element *DocumentContainer) NotifyMinimumSizeChange (child tomo.Element) {

View File

@ -110,6 +110,10 @@ func (element *ScrollContainer) disownChild (child tomo.Scrollable) {
} }
} }
func (element *ScrollContainer) Window () tomo.Window {
return element.core.Window()
}
// NotifyMinimumSizeChange notifies the container that the minimum size of a // NotifyMinimumSizeChange notifies the container that the minimum size of a
// child element has changed. // child element has changed.
func (element *ScrollContainer) NotifyMinimumSizeChange (child tomo.Element) { func (element *ScrollContainer) NotifyMinimumSizeChange (child tomo.Element) {

View File

@ -122,6 +122,16 @@ func (control CoreControl) Parent () tomo.Parent {
return control.core.parent return control.core.parent
} }
// Window returns the window containing the element.
func (control CoreControl) Window () tomo.Window {
parent := control.Parent()
if parent == nil {
return nil
} else {
return parent.Window()
}
}
// Outer returns the outer element given when the control was constructed. // Outer returns the outer element given when the control was constructed.
func (control CoreControl) Outer () tomo.Element { func (control CoreControl) Outer () tomo.Element {
return control.core.outer return control.core.outer

View File

@ -1,7 +1,9 @@
package elements package elements
import "io"
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/canvas"
@ -181,6 +183,34 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
case key == 'a' && modifiers.Control: case key == 'a' && modifiers.Control:
element.dot.Start = 0 element.dot.Start = 0
element.dot.End = len(element.text) element.dot.End = len(element.text)
case key == 'x' && modifiers.Control:
var lifted []rune
element.text, element.dot, lifted = textmanip.Lift (
element.text,
element.dot)
if lifted != nil {
element.clipboardPut(lifted)
textChanged = true
}
case key == 'c' && modifiers.Control:
element.clipboardPut(element.dot.Slice(element.text))
case key == 'v' && modifiers.Control:
window := element.core.Window()
if window == nil { break }
window.Paste (func (d data.Data, err error) {
if err != nil { return }
reader, ok := d[data.MimePlain]
if !ok { return }
bytes, _ := io.ReadAll(reader)
element.text, element.dot = textmanip.Type (
element.text,
element.dot,
[]rune(string(bytes))...)
element.notifyAsyncTextChange()
})
case key.Printable(): case key.Printable():
element.text, element.dot = textmanip.Type ( element.text, element.dot = textmanip.Type (
@ -213,6 +243,13 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
} }
} }
func (element *TextBox) clipboardPut (text []rune) {
window := element.core.Window()
if window != nil {
window.Copy(data.Bytes(data.MimePlain, []byte(string(text))))
}
}
func (element *TextBox) HandleKeyUp(key input.Key, modifiers input.Modifiers) { } func (element *TextBox) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
func (element *TextBox) SetPlaceholder (placeholder string) { func (element *TextBox) SetPlaceholder (placeholder string) {
@ -366,6 +403,16 @@ func (element *TextBox) updateMinimumSize () {
element.placeholderDrawer.LineHeight().Round()) element.placeholderDrawer.LineHeight().Round())
} }
func (element *TextBox) notifyAsyncTextChange () {
element.runOnChange()
element.valueDrawer.SetText(element.text)
element.scrollToCursor()
if parent, ok := element.core.Parent().(tomo.ScrollableParent); ok {
parent.NotifyScrollBoundsChange(element)
}
element.redo()
}
func (element *TextBox) redo () { func (element *TextBox) redo () {
if element.core.HasImage () { if element.core.HasImage () {
element.draw() element.draw()

View File

@ -6,6 +6,9 @@ type Parent interface {
// minimum size has changed. This method is expected to be called by // minimum size has changed. This method is expected to be called by
// child elements when their minimum size changes. // child elements when their minimum size changes.
NotifyMinimumSizeChange (child Element) NotifyMinimumSizeChange (child Element)
// Window returns the window containing the parent.
Window () Window
} }
// FocusableParent represents a parent with keyboard navigation support. // FocusableParent represents a parent with keyboard navigation support.

View File

@ -48,6 +48,7 @@ func (dot Dot) Width () int {
} }
func (dot Dot) Slice (text []rune) []rune { func (dot Dot) Slice (text []rune) []rune {
dot = dot.Canon().Constrain(len(text))
return text[dot.Start:dot.End] return text[dot.Start:dot.End]
} }
@ -146,22 +147,22 @@ func Lift (text []rune, dot Dot) (result []rune, moved Dot, lifted []rune) {
return return
} }
func Type (text []rune, dot Dot, character rune) (result []rune, moved Dot) { func Type (text []rune, dot Dot, characters ...rune) (result []rune, moved Dot) {
dot = dot.Constrain(len(text)) dot = dot.Constrain(len(text))
if dot.Empty() { if dot.Empty() {
result = append(result, text[:dot.End]...) result = append(result, text[:dot.End]...)
result = append(result, character) result = append(result, characters...)
if dot.End < len(text) { if dot.End < len(text) {
result = append(result, text[dot.End:]...) result = append(result, text[dot.End:]...)
} }
moved = EmptyDot(dot.Add(1).End) moved = EmptyDot(dot.Add(len(characters)).End)
return return
} else { } else {
dot = dot.Canon() dot = dot.Canon()
result = append(result, text[:dot.Start]...) result = append(result, text[:dot.Start]...)
result = append(result, character) result = append(result, characters...)
result = append(result, text[dot.End:]...) result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Add(1).Start) moved = EmptyDot(dot.Add(len(characters)).Start)
return return
} }
} }