mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
io/router: avoid a decode round for key ops
Like a previous change for pointer ops, process key ops during the router decode of ops. This is a performance optimization and preparation for processing future accessibility ops without without another decode loop. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+53
-39
@@ -6,7 +6,6 @@ import (
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
type TextInputState uint8
|
||||
@@ -14,7 +13,6 @@ type TextInputState uint8
|
||||
type keyQueue struct {
|
||||
focus event.Tag
|
||||
handlers map[event.Tag]*keyHandler
|
||||
reader ops.Reader
|
||||
state TextInputState
|
||||
hint key.InputHint
|
||||
}
|
||||
@@ -27,6 +25,14 @@ type keyHandler struct {
|
||||
hint key.InputHint
|
||||
}
|
||||
|
||||
// keyCollector tracks state required to update a keyQueue
|
||||
// from key ops.
|
||||
type keyCollector struct {
|
||||
q *keyQueue
|
||||
focus event.Tag
|
||||
changed bool
|
||||
}
|
||||
|
||||
const (
|
||||
TextInputKeep TextInputState = iota
|
||||
TextInputClose
|
||||
@@ -53,46 +59,46 @@ func (q *keyQueue) InputHint() (key.InputHint, bool) {
|
||||
return q.hint, old != q.hint
|
||||
}
|
||||
|
||||
func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
||||
func (q *keyQueue) Reset() {
|
||||
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.Internal)
|
||||
q.state = TextInputKeep
|
||||
}
|
||||
|
||||
focus, changed, state := q.resolveFocus(events)
|
||||
func (q *keyQueue) Frame(events *handlerEvents, collector keyCollector) {
|
||||
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 = TextInputClose
|
||||
q.state = TextInputClose
|
||||
}
|
||||
} else if h.new && k != focus {
|
||||
} else if h.new && k != collector.focus {
|
||||
// Reset the handler on (each) first appearance, but don't trigger redraw.
|
||||
events.AddNoRedraw(k, key.FocusEvent{Focus: false})
|
||||
}
|
||||
}
|
||||
if changed && focus != nil {
|
||||
if _, exists := q.handlers[focus]; !exists {
|
||||
focus = nil
|
||||
if collector.changed && collector.focus != nil {
|
||||
if _, exists := q.handlers[collector.focus]; !exists {
|
||||
collector.focus = nil
|
||||
}
|
||||
}
|
||||
if changed && focus != q.focus {
|
||||
if collector.changed && collector.focus != q.focus {
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, key.FocusEvent{Focus: false})
|
||||
}
|
||||
q.focus = focus
|
||||
q.focus = collector.focus
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, key.FocusEvent{Focus: true})
|
||||
} else {
|
||||
state = TextInputClose
|
||||
q.state = TextInputClose
|
||||
}
|
||||
}
|
||||
q.state = state
|
||||
}
|
||||
|
||||
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
||||
@@ -101,32 +107,27 @@ func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
||||
}
|
||||
}
|
||||
|
||||
func (q *keyQueue) resolveFocus(events *handlerEvents) (focus event.Tag, changed bool, state TextInputState) {
|
||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||
switch ops.OpType(encOp.Data[0]) {
|
||||
case ops.TypeKeyFocus:
|
||||
op := decodeFocusOp(encOp.Data, encOp.Refs)
|
||||
changed = true
|
||||
focus = op.Tag
|
||||
case ops.TypeKeySoftKeyboard:
|
||||
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
|
||||
if op.Show {
|
||||
state = TextInputOpen
|
||||
} else {
|
||||
state = TextInputClose
|
||||
}
|
||||
case ops.TypeKeyInput:
|
||||
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
||||
h, ok := q.handlers[op.Tag]
|
||||
if !ok {
|
||||
h = &keyHandler{new: true}
|
||||
q.handlers[op.Tag] = h
|
||||
}
|
||||
h.visible = true
|
||||
h.hint = op.Hint
|
||||
}
|
||||
func (k *keyCollector) focusOp(tag event.Tag) {
|
||||
k.focus = tag
|
||||
k.changed = true
|
||||
}
|
||||
|
||||
func (k *keyCollector) softKeyboard(show bool) {
|
||||
if show {
|
||||
k.q.state = TextInputOpen
|
||||
} else {
|
||||
k.q.state = TextInputClose
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (k *keyCollector) inputOp(op key.InputOp) {
|
||||
h, ok := k.q.handlers[op.Tag]
|
||||
if !ok {
|
||||
h = &keyHandler{new: true}
|
||||
k.q.handlers[op.Tag] = h
|
||||
}
|
||||
h.visible = true
|
||||
h.hint = op.Hint
|
||||
}
|
||||
|
||||
func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
|
||||
@@ -156,3 +157,16 @@ func decodeFocusOp(d []byte, refs []interface{}) key.FocusOp {
|
||||
Tag: refs[0],
|
||||
}
|
||||
}
|
||||
|
||||
func (t TextInputState) String() string {
|
||||
switch t {
|
||||
case TextInputKeep:
|
||||
return "Keep"
|
||||
case TextInputClose:
|
||||
return "Close"
|
||||
case TextInputOpen:
|
||||
return "Open"
|
||||
default:
|
||||
panic("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,14 +268,14 @@ func assertKeyEventUnexpected(t *testing.T, events []event.Event) {
|
||||
|
||||
func assertFocus(t *testing.T, router *Router, expected event.Tag) {
|
||||
t.Helper()
|
||||
if router.kqueue.focus != expected {
|
||||
t.Errorf("expected %v to be focused, got %v", expected, router.kqueue.focus)
|
||||
if got := router.key.queue.focus; got != expected {
|
||||
t.Errorf("expected %v to be focused, got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func assertKeyboard(t *testing.T, router *Router, expected TextInputState) {
|
||||
t.Helper()
|
||||
if router.kqueue.state != expected {
|
||||
t.Errorf("expected %v keyboard, got %v", expected, router.kqueue.state)
|
||||
if got := router.key.queue.state; got != expected {
|
||||
t.Errorf("expected %v keyboard, got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
+24
-5
@@ -31,7 +31,10 @@ type Router struct {
|
||||
queue pointerQueue
|
||||
collector pointerCollector
|
||||
}
|
||||
kqueue keyQueue
|
||||
key struct {
|
||||
queue keyQueue
|
||||
collector keyCollector
|
||||
}
|
||||
cqueue clipboardQueue
|
||||
|
||||
handlers handlerEvents
|
||||
@@ -75,7 +78,7 @@ func (q *Router) Frame(ops *op.Ops) {
|
||||
q.collect()
|
||||
|
||||
q.pointer.queue.Frame(&q.handlers)
|
||||
q.kqueue.Frame(ops, &q.handlers)
|
||||
q.key.queue.Frame(&q.handlers, q.key.collector)
|
||||
if q.handlers.HadEvents() {
|
||||
q.wakeup = true
|
||||
q.wakeupTime = time.Time{}
|
||||
@@ -91,7 +94,7 @@ func (q *Router) Queue(events ...event.Event) bool {
|
||||
case pointer.Event:
|
||||
q.pointer.queue.Push(e, &q.handlers)
|
||||
case key.EditEvent, key.Event, key.FocusEvent:
|
||||
q.kqueue.Push(e, &q.handlers)
|
||||
q.key.queue.Push(e, &q.handlers)
|
||||
case clipboard.Event:
|
||||
q.cqueue.Push(e, &q.handlers)
|
||||
}
|
||||
@@ -102,12 +105,12 @@ func (q *Router) Queue(events ...event.Event) bool {
|
||||
// TextInputState returns the input state from the most recent
|
||||
// call to Frame.
|
||||
func (q *Router) TextInputState() TextInputState {
|
||||
return q.kqueue.InputState()
|
||||
return q.key.queue.InputState()
|
||||
}
|
||||
|
||||
// TextInputHint returns the input mode from the most recent key.InputOp.
|
||||
func (q *Router) TextInputHint() (key.InputHint, bool) {
|
||||
return q.kqueue.InputHint()
|
||||
return q.key.queue.InputHint()
|
||||
}
|
||||
|
||||
// WriteClipboard returns the most recent text to be copied
|
||||
@@ -130,6 +133,9 @@ func (q *Router) Cursor() pointer.CursorName {
|
||||
func (q *Router) collect() {
|
||||
pc := &q.pointer.collector
|
||||
pc.reset(&q.pointer.queue)
|
||||
kc := &q.key.collector
|
||||
*kc = keyCollector{q: &q.key.queue}
|
||||
q.key.queue.Reset()
|
||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||
switch ops.OpType(encOp.Data[0]) {
|
||||
case ops.TypeInvalidate:
|
||||
@@ -154,6 +160,8 @@ func (q *Router) collect() {
|
||||
case ops.TypeLoad:
|
||||
id := ops.DecodeLoad(encOp.Data)
|
||||
pc.load(id)
|
||||
|
||||
// Pointer ops.
|
||||
case ops.TypeArea:
|
||||
var op areaOp
|
||||
op.Decode(encOp.Data)
|
||||
@@ -190,6 +198,17 @@ func (q *Router) collect() {
|
||||
case ops.TypeCursor:
|
||||
name := encOp.Refs[0].(pointer.CursorName)
|
||||
pc.cursor(name)
|
||||
|
||||
// Key ops.
|
||||
case ops.TypeKeyFocus:
|
||||
op := decodeFocusOp(encOp.Data, encOp.Refs)
|
||||
kc.focusOp(op.Tag)
|
||||
case ops.TypeKeySoftKeyboard:
|
||||
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
|
||||
kc.softKeyboard(op.Show)
|
||||
case ops.TypeKeyInput:
|
||||
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
||||
kc.inputOp(op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user