284 lines
6.2 KiB
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
|
|
)
|