Files
prevara/editor.go

284 lines
6.2 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package gel
import (
"image"
"image/color"
"time"
"gioui.org/f32"
"gioui.org/font"
"gioui.org/io/key"
l "gioui.org/layout"
"gioui.org/op"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
"golang.org/x/image/math/fixed"
)
func (w *Window) Editor() *Editor {
e := &Editor{
editor: new(widget.Editor),
submitHook: func(string) {},
changeHook: func(string) {},
focusHook: func(bool) {},
}
return e
}
// Editor implements an editable and scrollable text area.
// This is a wrapper around the GIO widget.Editor that maintains API compatibility.
type Editor struct {
editor *widget.Editor
alignment text.Alignment
singleLine bool
submit bool
mask rune
font font.Font
textSize fixed.Int26_6
focused bool
maxWidth int
viewSize image.Point
requestFocus bool // request focus on next layout
// the following are hooks for change events on the editor
submitHook func(string)
changeHook func(string)
focusHook func(bool)
}
// EditorEvent represents editor events
type EditorEvent interface {
isEditorEvent()
}
// ChangeEvent is generated for every user change to the text.
type ChangeEvent struct{}
func (ChangeEvent) isEditorEvent() {}
// SubmitEvent is generated when Submit is set and a carriage return key is pressed.
type SubmitEvent struct {
Text string
}
func (SubmitEvent) isEditorEvent() {}
// SelectEvent is generated when the user selects some text, or changes the selection (e.g. with a shift-click).
type SelectEvent struct{}
func (SelectEvent) isEditorEvent() {}
// Alignment sets the text alignment
func (e *Editor) Alignment(alignment text.Alignment) *Editor {
e.alignment = alignment
e.editor.Alignment = alignment
return e
}
// SingleLine sets single line mode
func (e *Editor) SingleLine() *Editor {
e.singleLine = true
e.editor.SingleLine = true
return e
}
// Submit enables submit events
func (e *Editor) Submit(enabled bool) *Editor {
e.submit = enabled
e.editor.Submit = enabled
return e
}
// Mask sets the mask rune for password fields
func (e *Editor) Mask(r rune) *Editor {
e.mask = r
e.editor.Mask = r
return e
}
// SetSubmitHook sets a callback for submit events
func (e *Editor) SetSubmitHook(fn func(string)) *Editor {
e.submitHook = fn
return e
}
// SetChangeHook sets a callback for change events
func (e *Editor) SetChangeHook(fn func(string)) *Editor {
e.changeHook = fn
return e
}
// SetFocusHook sets a callback for focus events
func (e *Editor) SetFocusHook(fn func(bool)) *Editor {
e.focusHook = fn
return e
}
// SetFocus is an alias for SetFocusHook for API compatibility
func (e *Editor) SetFocus(fn func(bool)) *Editor {
return e.SetFocusHook(fn)
}
// SetSubmit sets the submit callback
func (e *Editor) SetSubmit(fn func(string)) *Editor {
e.submitHook = fn
return e
}
// SetChange sets the change callback
func (e *Editor) SetChange(fn func(string)) *Editor {
e.changeHook = fn
return e
}
// Focus requests focus for the editor (deferred until next layout)
func (e *Editor) Focus() {
e.requestFocus = true
}
// FocusNow requests immediate focus for the editor (requires gtx)
func (e *Editor) FocusNow(gtx l.Context) {
gtx.Execute(key.FocusCmd{Tag: e.editor})
}
// Focused returns whether the editor has focus
func (e *Editor) Focused(gtx l.Context) bool {
return gtx.Focused(e.editor)
}
// Len returns the length of the editor contents
func (e *Editor) Len() int {
return e.editor.Len()
}
// Text returns the editor contents as a string
func (e *Editor) Text() string {
return e.editor.Text()
}
// SetText replaces the editor contents
func (e *Editor) SetText(s string) {
e.editor.SetText(s)
}
// Insert inserts text at the caret
func (e *Editor) Insert(s string) {
e.editor.Insert(s)
}
// Delete deletes runes from the editor
func (e *Editor) Delete(runes int) {
e.editor.Delete(runes)
}
// MoveCaret moves the caret
func (e *Editor) MoveCaret(start, end int) {
e.editor.MoveCaret(start, end)
}
// Selection returns the current selection
func (e *Editor) Selection() (start, end int) {
return e.editor.Selection()
}
// SetSelection sets the selection
func (e *Editor) SetSelection(start, end int) {
e.editor.SetCaret(start, end)
}
// SelectedText returns the selected text
func (e *Editor) SelectedText() string {
return e.editor.SelectedText()
}
// ClearSelection clears the selection
func (e *Editor) ClearSelection() {
s, _ := e.editor.Selection()
e.editor.SetCaret(s, s)
}
// Layout lays out the editor with default materials
func (e *Editor) Layout(gtx l.Context, sh *text.Shaper, fnt font.Font, size unit.Sp, textColor, selectColor color.NRGBA) l.Dimensions {
// Process pending focus request
if e.requestFocus {
gtx.Execute(key.FocusCmd{Tag: e.editor})
e.requestFocus = false
}
// Process events and call hooks
for {
ev, ok := e.editor.Update(gtx)
if !ok {
break
}
switch ev := ev.(type) {
case widget.ChangeEvent:
if e.changeHook != nil {
e.changeHook(e.Text())
}
case widget.SubmitEvent:
if e.submitHook != nil {
e.submitHook(ev.Text)
}
case widget.SelectEvent:
// Selection changed
}
}
// Track focus changes
focused := gtx.Focused(e.editor)
if focused != e.focused {
e.focused = focused
if e.focusHook != nil {
e.focusHook(focused)
}
}
// Create material for text
textM := op.Record(gtx.Ops)
paint.ColorOp{Color: textColor}.Add(gtx.Ops)
textMaterial := textM.Stop()
// Create material for selection
selectM := op.Record(gtx.Ops)
paint.ColorOp{Color: selectColor}.Add(gtx.Ops)
selectMaterial := selectM.Stop()
return e.editor.Layout(gtx, sh, fnt, size, textMaterial, selectMaterial)
}
// Caret returns the caret position in pixels
func (e *Editor) Caret() f32.Point {
return e.editor.CaretCoords()
}
// CaretPos returns the caret line and column
func (e *Editor) CaretPos() (line, col int) {
return e.editor.CaretPos()
}
// screenPos describes a character position
type screenPos image.Point
func (p1 screenPos) LessOrEqual(p2 screenPos) bool {
return p1.Y < p2.Y || (p1.Y == p2.Y && p1.X <= p2.X)
}
func (p1 screenPos) Less(p2 screenPos) bool {
return p1.Y < p2.Y || (p1.Y == p2.Y && p1.X < p2.X)
}
// Blank imports to satisfy any potential dependencies
var (
_ = key.Event{}
_ time.Time
_ color.NRGBA
)