initial commit
This commit is contained in:
283
editor.go
Normal file
283
editor.go
Normal file
@@ -0,0 +1,283 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gel
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"github.com/p9c/p9/pkg/gel/gio/font"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
||||
"github.com/p9c/p9/pkg/gel/gio/text"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
"github.com/p9c/p9/pkg/gel/gio/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
|
||||
)
|
||||
Reference in New Issue
Block a user