mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
34c6a2f735
For integrating with external window implementations (replacing package app), access to the event router is required. Extract it and put it into the new package router. Router may belong in package io/event, but can't without introducing import cycles. Signed-off-by: Elias Naur <mail@eliasnaur.com>
151 lines
3.1 KiB
Go
151 lines
3.1 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.Key
|
|
handlers map[event.Key]*keyHandler
|
|
reader ops.Reader
|
|
state TextInputState
|
|
}
|
|
|
|
type keyHandler struct {
|
|
active bool
|
|
}
|
|
|
|
type listenerPriority uint8
|
|
|
|
const (
|
|
priNone listenerPriority = iota
|
|
priDefault
|
|
priCurrentFocus
|
|
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.Key]*keyHandler)
|
|
}
|
|
for _, h := range q.handlers {
|
|
h.active = false
|
|
}
|
|
q.reader.Reset(root)
|
|
focus, pri, hide := q.resolveFocus(events)
|
|
for k, h := range q.handlers {
|
|
if !h.active {
|
|
delete(q.handlers, k)
|
|
if q.focus == k {
|
|
q.focus = nil
|
|
hide = true
|
|
}
|
|
}
|
|
}
|
|
if focus != q.focus {
|
|
if q.focus != nil {
|
|
events.Add(q.focus, key.FocusEvent{Focus: false})
|
|
}
|
|
q.focus = focus
|
|
if q.focus != nil {
|
|
events.Add(q.focus, key.FocusEvent{Focus: true})
|
|
} else {
|
|
hide = true
|
|
}
|
|
}
|
|
switch {
|
|
case pri == priNewFocus:
|
|
q.state = TextInputOpen
|
|
case hide:
|
|
q.state = TextInputClose
|
|
default:
|
|
q.state = TextInputKeep
|
|
}
|
|
}
|
|
|
|
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
|
if q.focus != nil {
|
|
events.Add(q.focus, e)
|
|
}
|
|
}
|
|
|
|
func (q *keyQueue) resolveFocus(events *handlerEvents) (event.Key, listenerPriority, bool) {
|
|
var k event.Key
|
|
var pri listenerPriority
|
|
var hide bool
|
|
loop:
|
|
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
|
switch opconst.OpType(encOp.Data[0]) {
|
|
case opconst.TypeKeyInput:
|
|
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
|
var newPri listenerPriority
|
|
switch {
|
|
case op.Focus:
|
|
newPri = priNewFocus
|
|
case op.Key == q.focus:
|
|
newPri = priCurrentFocus
|
|
default:
|
|
newPri = priDefault
|
|
}
|
|
// Switch focus if higher priority or if focus requested.
|
|
if newPri.replaces(pri) {
|
|
k, pri = op.Key, newPri
|
|
}
|
|
h, ok := q.handlers[op.Key]
|
|
if !ok {
|
|
h = new(keyHandler)
|
|
q.handlers[op.Key] = h
|
|
// Reset the handler on (each) first appearance.
|
|
events.Set(op.Key, []event.Event{key.FocusEvent{Focus: false}})
|
|
}
|
|
h.active = true
|
|
case opconst.TypeHideInput:
|
|
hide = true
|
|
case opconst.TypePush:
|
|
newK, newPri, h := q.resolveFocus(events)
|
|
hide = hide || h
|
|
if newPri.replaces(pri) {
|
|
k, pri = newK, newPri
|
|
}
|
|
case opconst.TypePop:
|
|
break loop
|
|
}
|
|
}
|
|
return k, pri, hide
|
|
}
|
|
|
|
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{
|
|
Focus: d[1] != 0,
|
|
Key: refs[0].(event.Key),
|
|
}
|
|
}
|