diff --git a/io/router/key.go b/io/router/key.go index 130027c2..357272cd 100644 --- a/io/router/key.go +++ b/io/router/key.go @@ -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") + } +} diff --git a/io/router/key_test.go b/io/router/key_test.go index a391747f..7824014b 100644 --- a/io/router/key_test.go +++ b/io/router/key_test.go @@ -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) } } diff --git a/io/router/router.go b/io/router/router.go index c83877f1..17712952 100644 --- a/io/router/router.go +++ b/io/router/router.go @@ -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) } } }