mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 23:55:39 +00:00
d6886737a5
It turns out restoring all operation state from the moment Defer is executed is too much; for example, a right-click pop-up needs the transformation, but not the current clip. Change Defer to only restore the transformation, and reset all other state. Other combinations may be needed in future; we'll deal with them then, possibly by exposing the load state mask. Signed-off-by: Elias Naur <mail@eliasnaur.com>
190 lines
4.2 KiB
Go
190 lines
4.2 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package router
|
|
|
|
import (
|
|
"gioui.org/internal/opconst"
|
|
"gioui.org/internal/ops"
|
|
"gioui.org/io/event"
|
|
"gioui.org/io/key"
|
|
"gioui.org/op"
|
|
)
|
|
|
|
type TextInputState uint8
|
|
|
|
type keyQueue struct {
|
|
focus event.Tag
|
|
handlers map[event.Tag]*keyHandler
|
|
reader ops.Reader
|
|
state TextInputState
|
|
// states store states during resolveFocus
|
|
states []resolveState
|
|
}
|
|
|
|
type resolveState struct {
|
|
tag event.Tag
|
|
pri listenerPriority
|
|
keyboard TextInputState
|
|
}
|
|
|
|
type keyHandler struct {
|
|
// visible will be true if the InputOp is present
|
|
// in the current frame.
|
|
visible bool
|
|
new bool
|
|
}
|
|
|
|
type listenerPriority uint8
|
|
|
|
const (
|
|
priDefault listenerPriority = iota
|
|
priCurrentFocus
|
|
priNone
|
|
priNewFocus
|
|
)
|
|
|
|
const (
|
|
TextInputKeep TextInputState = iota
|
|
TextInputClose
|
|
TextInputOpen
|
|
)
|
|
|
|
// InputState returns the last text input state as
|
|
// determined in Frame.
|
|
func (q *keyQueue) InputState() TextInputState {
|
|
return q.state
|
|
}
|
|
|
|
func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
|
if q.handlers == nil {
|
|
q.handlers = make(map[event.Tag]*keyHandler)
|
|
}
|
|
for _, h := range q.handlers {
|
|
h.visible, h.new = false, false
|
|
}
|
|
q.reader.Reset(root)
|
|
|
|
state := q.resolveFocus(events)
|
|
if state.pri == priNone {
|
|
state.tag = nil
|
|
}
|
|
for k, h := range q.handlers {
|
|
if !h.visible {
|
|
delete(q.handlers, k)
|
|
if q.focus == k {
|
|
// Remove the focus from the handler that is no longer visible.
|
|
q.focus = nil
|
|
state.keyboard = TextInputClose
|
|
}
|
|
}
|
|
if h.new && k != state.tag {
|
|
// Reset the handler on (each) first appearance.
|
|
events.Add(k, key.FocusEvent{Focus: false})
|
|
}
|
|
}
|
|
if state.tag != q.focus {
|
|
if q.focus != nil {
|
|
events.Add(q.focus, key.FocusEvent{Focus: false})
|
|
}
|
|
q.focus = state.tag
|
|
if q.focus != nil {
|
|
events.Add(q.focus, key.FocusEvent{Focus: true})
|
|
} else {
|
|
state.keyboard = TextInputClose
|
|
}
|
|
}
|
|
q.state = state.keyboard
|
|
}
|
|
|
|
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
|
if q.focus != nil {
|
|
events.Add(q.focus, e)
|
|
}
|
|
}
|
|
|
|
func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState {
|
|
var state resolveState
|
|
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
|
switch opconst.OpType(encOp.Data[0]) {
|
|
case opconst.TypeKeyFocus:
|
|
op := decodeFocusOp(encOp.Data, encOp.Refs)
|
|
if op.Focus {
|
|
state.pri = priNewFocus
|
|
} else {
|
|
state.pri, state.keyboard = priNone, TextInputClose
|
|
}
|
|
case opconst.TypeKeySoftKeyboard:
|
|
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
|
|
if op.Show {
|
|
state.keyboard = TextInputOpen
|
|
} else {
|
|
state.keyboard = TextInputClose
|
|
}
|
|
case opconst.TypeKeyInput:
|
|
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
|
if op.Tag == q.focus && state.pri < priCurrentFocus {
|
|
state.pri = priCurrentFocus
|
|
}
|
|
h, ok := q.handlers[op.Tag]
|
|
if !ok {
|
|
h = &keyHandler{new: true}
|
|
q.handlers[op.Tag] = h
|
|
}
|
|
h.visible = true
|
|
state.tag = op.Tag
|
|
case opconst.TypeSave:
|
|
id := ops.DecodeSave(encOp.Data)
|
|
if extra := id - len(q.states) + 1; extra > 0 {
|
|
q.states = append(q.states, make([]resolveState, extra)...)
|
|
}
|
|
q.states[id] = state
|
|
state = resolveState{}
|
|
case opconst.TypeLoad:
|
|
id, mask := ops.DecodeLoad(encOp.Data)
|
|
restored := q.states[id]
|
|
if state.keyboard > restored.keyboard {
|
|
restored.keyboard = state.keyboard
|
|
}
|
|
if state.pri.replaces(restored.pri) {
|
|
restored.tag, restored.pri = state.tag, state.pri
|
|
}
|
|
if mask != 0 {
|
|
state = restored
|
|
}
|
|
}
|
|
}
|
|
return state
|
|
}
|
|
|
|
func (p listenerPriority) replaces(p2 listenerPriority) bool {
|
|
// Favor earliest default focus or latest requested focus.
|
|
return p > p2 || p == p2 && p == priNewFocus
|
|
}
|
|
|
|
func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
|
|
if opconst.OpType(d[0]) != opconst.TypeKeyInput {
|
|
panic("invalid op")
|
|
}
|
|
return key.InputOp{
|
|
Tag: refs[0].(event.Tag),
|
|
}
|
|
}
|
|
|
|
func decodeSoftKeyboardOp(d []byte, refs []interface{}) key.SoftKeyboardOp {
|
|
if opconst.OpType(d[0]) != opconst.TypeKeySoftKeyboard {
|
|
panic("invalid op")
|
|
}
|
|
return key.SoftKeyboardOp{
|
|
Show: d[1] != 0,
|
|
}
|
|
}
|
|
|
|
func decodeFocusOp(d []byte, refs []interface{}) key.FocusOp {
|
|
if opconst.OpType(d[0]) != opconst.TypeKeyFocus {
|
|
panic("invalid op")
|
|
}
|
|
return key.FocusOp{
|
|
Focus: d[1] != 0,
|
|
}
|
|
}
|