mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
io/key: improve InputOp focus and blur
The existing implementation cannot remove the focus of some widget, doesn't have an option to focus without display the on-screen keyboard and it automatically focuses the first InputOp, aggressively. That change aims to make possible: remove focus from any widget. Add focus without displaying the on-screen-keyboard/soft keyboard. Don't automatically focus any widget. Don't recover focus when the widget is visible again. Fixes gio#180. Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
+24
-21
@@ -21,7 +21,8 @@ const (
|
|||||||
TypePointerInput
|
TypePointerInput
|
||||||
TypePass
|
TypePass
|
||||||
TypeKeyInput
|
TypeKeyInput
|
||||||
TypeHideInput
|
TypeKeyFocus
|
||||||
|
TypeKeySoftKeyboard
|
||||||
TypePush
|
TypePush
|
||||||
TypePop
|
TypePop
|
||||||
TypeAux
|
TypeAux
|
||||||
@@ -30,25 +31,26 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeMacroLen = 1 + 4 + 4
|
TypeMacroLen = 1 + 4 + 4
|
||||||
TypeCallLen = 1 + 4 + 4
|
TypeCallLen = 1 + 4 + 4
|
||||||
TypeTransformLen = 1 + 4*6
|
TypeTransformLen = 1 + 4*6
|
||||||
TypeLayerLen = 1
|
TypeLayerLen = 1
|
||||||
TypeRedrawLen = 1 + 8
|
TypeRedrawLen = 1 + 8
|
||||||
TypeImageLen = 1
|
TypeImageLen = 1
|
||||||
TypePaintLen = 1
|
TypePaintLen = 1
|
||||||
TypeColorLen = 1 + 4
|
TypeColorLen = 1 + 4
|
||||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||||
TypeAreaLen = 1 + 1 + 4*4
|
TypeAreaLen = 1 + 1 + 4*4
|
||||||
TypePointerInputLen = 1 + 1 + 1
|
TypePointerInputLen = 1 + 1 + 1
|
||||||
TypePassLen = 1 + 1
|
TypePassLen = 1 + 1
|
||||||
TypeKeyInputLen = 1 + 1
|
TypeKeyInputLen = 1
|
||||||
TypeHideInputLen = 1
|
TypeKeyFocusLen = 1 + 1
|
||||||
TypePushLen = 1
|
TypeKeySoftKeyboardLen = 1 + 1
|
||||||
TypePopLen = 1
|
TypePushLen = 1
|
||||||
TypeAuxLen = 1
|
TypePopLen = 1
|
||||||
TypeClipLen = 1 + 4*4 + 4 + 2 + 4
|
TypeAuxLen = 1
|
||||||
TypeProfileLen = 1
|
TypeClipLen = 1 + 4*4 + 4 + 2 + 4
|
||||||
|
TypeProfileLen = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t OpType) Size() int {
|
func (t OpType) Size() int {
|
||||||
@@ -66,7 +68,8 @@ func (t OpType) Size() int {
|
|||||||
TypePointerInputLen,
|
TypePointerInputLen,
|
||||||
TypePassLen,
|
TypePassLen,
|
||||||
TypeKeyInputLen,
|
TypeKeyInputLen,
|
||||||
TypeHideInputLen,
|
TypeKeyFocusLen,
|
||||||
|
TypeKeySoftKeyboardLen,
|
||||||
TypePushLen,
|
TypePushLen,
|
||||||
TypePopLen,
|
TypePopLen,
|
||||||
TypeAuxLen,
|
TypeAuxLen,
|
||||||
|
|||||||
+25
-11
@@ -20,16 +20,22 @@ import (
|
|||||||
|
|
||||||
// InputOp declares a handler ready for key events.
|
// InputOp declares a handler ready for key events.
|
||||||
// Key events are in general only delivered to the
|
// Key events are in general only delivered to the
|
||||||
// focused key handler. Set the Focus flag to request
|
// focused key handler.
|
||||||
// the focus.
|
|
||||||
type InputOp struct {
|
type InputOp struct {
|
||||||
Tag event.Tag
|
Tag event.Tag
|
||||||
Focus bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HideInputOp request that any on screen text input
|
// SoftKeyboardOp shows or hide the on-screen keyboard, if available.
|
||||||
// be hidden.
|
type SoftKeyboardOp struct {
|
||||||
type HideInputOp struct{}
|
Show bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FocusOp sets or clears the keyboard focus.
|
||||||
|
type FocusOp struct {
|
||||||
|
// Focus, if set, moves the focus to the current InputOp. If Focus
|
||||||
|
// is false, the focus is cleared.
|
||||||
|
Focus bool
|
||||||
|
}
|
||||||
|
|
||||||
// A FocusEvent is generated when a handler gains or loses
|
// A FocusEvent is generated when a handler gains or loses
|
||||||
// focus.
|
// focus.
|
||||||
@@ -115,14 +121,22 @@ func (m Modifiers) Contain(m2 Modifiers) bool {
|
|||||||
func (h InputOp) Add(o *op.Ops) {
|
func (h InputOp) Add(o *op.Ops) {
|
||||||
data := o.Write1(opconst.TypeKeyInputLen, h.Tag)
|
data := o.Write1(opconst.TypeKeyInputLen, h.Tag)
|
||||||
data[0] = byte(opconst.TypeKeyInput)
|
data[0] = byte(opconst.TypeKeyInput)
|
||||||
if h.Focus {
|
}
|
||||||
|
|
||||||
|
func (h SoftKeyboardOp) Add(o *op.Ops) {
|
||||||
|
data := o.Write(opconst.TypeKeySoftKeyboardLen)
|
||||||
|
data[0] = byte(opconst.TypeKeySoftKeyboard)
|
||||||
|
if h.Show {
|
||||||
data[1] = 1
|
data[1] = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HideInputOp) Add(o *op.Ops) {
|
func (h FocusOp) Add(o *op.Ops) {
|
||||||
data := o.Write(opconst.TypeHideInputLen)
|
data := o.Write(opconst.TypeKeyFocusLen)
|
||||||
data[0] = byte(opconst.TypeHideInput)
|
data[0] = byte(opconst.TypeKeyFocus)
|
||||||
|
if h.Focus {
|
||||||
|
data[1] = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (EditEvent) ImplementsEvent() {}
|
func (EditEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+65
-43
@@ -20,15 +20,18 @@ type keyQueue struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type keyHandler struct {
|
type keyHandler struct {
|
||||||
active bool
|
// visible will be true if the InputOp is present
|
||||||
|
// in the current frame.
|
||||||
|
visible bool
|
||||||
|
new bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type listenerPriority uint8
|
type listenerPriority uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
priNone listenerPriority = iota
|
priDefault listenerPriority = iota
|
||||||
priDefault
|
|
||||||
priCurrentFocus
|
priCurrentFocus
|
||||||
|
priNone
|
||||||
priNewFocus
|
priNewFocus
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,18 +52,27 @@ func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
|||||||
q.handlers = make(map[event.Tag]*keyHandler)
|
q.handlers = make(map[event.Tag]*keyHandler)
|
||||||
}
|
}
|
||||||
for _, h := range q.handlers {
|
for _, h := range q.handlers {
|
||||||
h.active = false
|
h.visible, h.new = false, false
|
||||||
}
|
}
|
||||||
q.reader.Reset(root)
|
q.reader.Reset(root)
|
||||||
focus, pri, hide := q.resolveFocus(events)
|
|
||||||
|
focus, pri, keyboard := q.resolveFocus(events)
|
||||||
|
if pri == priNone {
|
||||||
|
focus = nil
|
||||||
|
}
|
||||||
for k, h := range q.handlers {
|
for k, h := range q.handlers {
|
||||||
if !h.active {
|
if !h.visible {
|
||||||
delete(q.handlers, k)
|
delete(q.handlers, k)
|
||||||
if q.focus == k {
|
if q.focus == k {
|
||||||
|
// Remove the focus from the handler that is no longer visible.
|
||||||
q.focus = nil
|
q.focus = nil
|
||||||
hide = true
|
keyboard = TextInputClose
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if h.new && k != focus {
|
||||||
|
// Reset the handler on (each) first appearance.
|
||||||
|
events.Add(k, key.FocusEvent{Focus: false})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if focus != q.focus {
|
if focus != q.focus {
|
||||||
if q.focus != nil {
|
if q.focus != nil {
|
||||||
@@ -70,17 +82,10 @@ func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
|||||||
if q.focus != nil {
|
if q.focus != nil {
|
||||||
events.Add(q.focus, key.FocusEvent{Focus: true})
|
events.Add(q.focus, key.FocusEvent{Focus: true})
|
||||||
} else {
|
} else {
|
||||||
hide = true
|
keyboard = TextInputClose
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
q.state = keyboard
|
||||||
case pri == priNewFocus:
|
|
||||||
q.state = TextInputOpen
|
|
||||||
case hide:
|
|
||||||
q.state = TextInputClose
|
|
||||||
default:
|
|
||||||
q.state = TextInputKeep
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
||||||
@@ -89,49 +94,49 @@ func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *keyQueue) resolveFocus(events *handlerEvents) (event.Tag, listenerPriority, bool) {
|
func (q *keyQueue) resolveFocus(events *handlerEvents) (tag event.Tag, pri listenerPriority, keyboard TextInputState) {
|
||||||
var k event.Tag
|
|
||||||
var pri listenerPriority
|
|
||||||
var hide bool
|
|
||||||
loop:
|
loop:
|
||||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||||
switch opconst.OpType(encOp.Data[0]) {
|
switch opconst.OpType(encOp.Data[0]) {
|
||||||
|
case opconst.TypeKeyFocus:
|
||||||
|
op := decodeFocusOp(encOp.Data, encOp.Refs)
|
||||||
|
if op.Focus {
|
||||||
|
pri = priNewFocus
|
||||||
|
} else {
|
||||||
|
pri, keyboard = priNone, TextInputClose
|
||||||
|
}
|
||||||
|
case opconst.TypeKeySoftKeyboard:
|
||||||
|
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
|
||||||
|
if op.Show {
|
||||||
|
keyboard = TextInputOpen
|
||||||
|
} else {
|
||||||
|
keyboard = TextInputClose
|
||||||
|
}
|
||||||
case opconst.TypeKeyInput:
|
case opconst.TypeKeyInput:
|
||||||
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
||||||
var newPri listenerPriority
|
if op.Tag == q.focus && pri < priCurrentFocus {
|
||||||
switch {
|
pri = priCurrentFocus
|
||||||
case op.Focus:
|
|
||||||
newPri = priNewFocus
|
|
||||||
case op.Tag == q.focus:
|
|
||||||
newPri = priCurrentFocus
|
|
||||||
default:
|
|
||||||
newPri = priDefault
|
|
||||||
}
|
|
||||||
// Switch focus if higher priority or if focus requested.
|
|
||||||
if newPri.replaces(pri) {
|
|
||||||
k, pri = op.Tag, newPri
|
|
||||||
}
|
}
|
||||||
h, ok := q.handlers[op.Tag]
|
h, ok := q.handlers[op.Tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
h = new(keyHandler)
|
h = &keyHandler{new: true}
|
||||||
q.handlers[op.Tag] = h
|
q.handlers[op.Tag] = h
|
||||||
// Reset the handler on (each) first appearance.
|
|
||||||
events.Add(op.Tag, key.FocusEvent{Focus: false})
|
|
||||||
}
|
}
|
||||||
h.active = true
|
h.visible = true
|
||||||
case opconst.TypeHideInput:
|
tag = op.Tag
|
||||||
hide = true
|
|
||||||
case opconst.TypePush:
|
case opconst.TypePush:
|
||||||
newK, newPri, h := q.resolveFocus(events)
|
newK, newPri, newKeyboard := q.resolveFocus(events)
|
||||||
hide = hide || h
|
if newKeyboard > keyboard {
|
||||||
|
keyboard = newKeyboard
|
||||||
|
}
|
||||||
if newPri.replaces(pri) {
|
if newPri.replaces(pri) {
|
||||||
k, pri = newK, newPri
|
tag, pri = newK, newPri
|
||||||
}
|
}
|
||||||
case opconst.TypePop:
|
case opconst.TypePop:
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return k, pri, hide
|
return tag, pri, keyboard
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p listenerPriority) replaces(p2 listenerPriority) bool {
|
func (p listenerPriority) replaces(p2 listenerPriority) bool {
|
||||||
@@ -144,7 +149,24 @@ func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
|
|||||||
panic("invalid op")
|
panic("invalid op")
|
||||||
}
|
}
|
||||||
return key.InputOp{
|
return key.InputOp{
|
||||||
Tag: refs[0].(event.Tag),
|
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,
|
Focus: d[1] != 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyMultiples(t *testing.T) {
|
||||||
|
handlers := make([]int, 3)
|
||||||
|
ops := new(op.Ops)
|
||||||
|
r := new(Router)
|
||||||
|
|
||||||
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
key.FocusOp{Focus: true}.Add(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
|
||||||
|
// The last one must be focused:
|
||||||
|
key.InputOp{Tag: &handlers[2]}.Add(ops)
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[1]), false)
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[2]), true)
|
||||||
|
assertFocus(t, r, &handlers[2])
|
||||||
|
assertKeyboard(t, r, TextInputOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyStacked(t *testing.T) {
|
||||||
|
handlers := make([]int, 4)
|
||||||
|
ops := new(op.Ops)
|
||||||
|
r := new(Router)
|
||||||
|
|
||||||
|
s := op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
// FocusOp must not overwrite the
|
||||||
|
// FocusOp{Focus: true}.
|
||||||
|
key.FocusOp{Focus: false}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.SoftKeyboardOp{Show: false}.Add(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
key.FocusOp{Focus: true}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[2]}.Add(ops)
|
||||||
|
// SoftwareKeyboardOp will open the keyboard,
|
||||||
|
// overwriting `SoftKeyboardOp{Show: false}`.
|
||||||
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.SoftKeyboardOp{Show: false}.Add(ops)
|
||||||
|
key.InputOp{Tag: &handlers[3]}.Add(ops)
|
||||||
|
// FocusOp must not overwrite the
|
||||||
|
// FocusOp{Focus: true}.
|
||||||
|
key.FocusOp{Focus: false}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[1]), true)
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[2]), false)
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[3]), false)
|
||||||
|
assertFocus(t, r, &handlers[1])
|
||||||
|
assertKeyboard(t, r, TextInputOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeySoftKeyboardNoFocus(t *testing.T) {
|
||||||
|
ops := new(op.Ops)
|
||||||
|
r := new(Router)
|
||||||
|
|
||||||
|
// It's possible to open the keyboard
|
||||||
|
// without any active focus:
|
||||||
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertFocus(t, r, nil)
|
||||||
|
assertKeyboard(t, r, TextInputOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyRemoveFocus(t *testing.T) {
|
||||||
|
handlers := make([]int, 2)
|
||||||
|
ops := new(op.Ops)
|
||||||
|
r := new(Router)
|
||||||
|
|
||||||
|
// New InputOp with Focus and Keyboard:
|
||||||
|
s := op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
key.FocusOp{Focus: true}.Add(ops)
|
||||||
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
// New InputOp without any focus:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
// Add some key events:
|
||||||
|
event := event.Event(key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press})
|
||||||
|
r.Add(event)
|
||||||
|
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[0]), true, event)
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[1]), false)
|
||||||
|
assertFocus(t, r, &handlers[0])
|
||||||
|
assertKeyboard(t, r, TextInputOpen)
|
||||||
|
|
||||||
|
ops.Reset()
|
||||||
|
|
||||||
|
// Will get the focus removed:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
// Unchanged:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
// Removing any Focus:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.FocusOp{Focus: false}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
||||||
|
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||||
|
assertFocus(t, r, nil)
|
||||||
|
assertKeyboard(t, r, TextInputClose)
|
||||||
|
|
||||||
|
ops.Reset()
|
||||||
|
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
// Setting Focus without InputOp:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.FocusOp{Focus: true}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEventUnexpected(t, r.Events(&handlers[0]))
|
||||||
|
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||||
|
assertFocus(t, r, nil)
|
||||||
|
assertKeyboard(t, r, TextInputKeep)
|
||||||
|
|
||||||
|
ops.Reset()
|
||||||
|
|
||||||
|
// Set focus to InputOp which already
|
||||||
|
// exists in the previous frame:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.FocusOp{Focus: true}.Add(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
// Tries to remove focus:
|
||||||
|
// It must not overwrite the previous `FocusOp`.
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
key.FocusOp{Focus: false}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[0]), true)
|
||||||
|
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||||
|
assertFocus(t, r, &handlers[0])
|
||||||
|
assertKeyboard(t, r, TextInputOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyFocusedInvisible(t *testing.T) {
|
||||||
|
handlers := make([]int, 2)
|
||||||
|
ops := new(op.Ops)
|
||||||
|
r := new(Router)
|
||||||
|
|
||||||
|
// Set new InputOp with focus:
|
||||||
|
s := op.Push(ops)
|
||||||
|
key.FocusOp{Focus: true}.Add(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
// Set new InputOp without focus:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[0]), true)
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[1]), false)
|
||||||
|
assertFocus(t, r, &handlers[0])
|
||||||
|
assertKeyboard(t, r, TextInputOpen)
|
||||||
|
|
||||||
|
ops.Reset()
|
||||||
|
|
||||||
|
//
|
||||||
|
// Removed first (focused) element!
|
||||||
|
//
|
||||||
|
|
||||||
|
// Unchanged:
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEventUnexpected(t, r.Events(&handlers[0]))
|
||||||
|
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||||
|
assertFocus(t, r, nil)
|
||||||
|
assertKeyboard(t, r, TextInputClose)
|
||||||
|
|
||||||
|
ops.Reset()
|
||||||
|
|
||||||
|
// Respawn the first element:
|
||||||
|
// It must receive one `Event{Focus: false}`.
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
// Unchanged
|
||||||
|
s = op.Push(ops)
|
||||||
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
r.Frame(ops)
|
||||||
|
|
||||||
|
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
||||||
|
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||||
|
assertFocus(t, r, nil)
|
||||||
|
assertKeyboard(t, r, TextInputKeep)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertKeyEvent(t *testing.T, events []event.Event, expected bool, expectedInputs ...event.Event) {
|
||||||
|
t.Helper()
|
||||||
|
var evtFocus int
|
||||||
|
var evtKeyPress int
|
||||||
|
for _, e := range events {
|
||||||
|
switch ev := e.(type) {
|
||||||
|
case key.FocusEvent:
|
||||||
|
if ev.Focus != expected {
|
||||||
|
t.Errorf("focus is expected to be %v, got %v", expected, ev.Focus)
|
||||||
|
}
|
||||||
|
evtFocus++
|
||||||
|
case key.Event, key.EditEvent:
|
||||||
|
if len(expectedInputs) <= evtKeyPress {
|
||||||
|
t.Errorf("unexpected key events")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ev, expectedInputs[evtKeyPress]) {
|
||||||
|
t.Errorf("expected %v events, got %v", expectedInputs[evtKeyPress], ev)
|
||||||
|
}
|
||||||
|
evtKeyPress++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if evtFocus <= 0 {
|
||||||
|
t.Errorf("expected focus event")
|
||||||
|
}
|
||||||
|
if evtFocus > 1 {
|
||||||
|
t.Errorf("expected single focus event")
|
||||||
|
}
|
||||||
|
if evtKeyPress != len(expectedInputs) {
|
||||||
|
t.Errorf("expected key events")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertKeyEventUnexpected(t *testing.T, events []event.Event) {
|
||||||
|
t.Helper()
|
||||||
|
var evtFocus int
|
||||||
|
for _, e := range events {
|
||||||
|
switch e.(type) {
|
||||||
|
case key.FocusEvent:
|
||||||
|
evtFocus++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if evtFocus > 1 {
|
||||||
|
t.Errorf("unexpected focus 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-1
@@ -399,7 +399,11 @@ func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
e.shapes = append(e.shapes, line{off, path})
|
e.shapes = append(e.shapes, line{off, path})
|
||||||
}
|
}
|
||||||
|
|
||||||
key.InputOp{Tag: &e.eventKey, Focus: e.requestFocus}.Add(gtx.Ops)
|
key.InputOp{Tag: &e.eventKey}.Add(gtx.Ops)
|
||||||
|
if e.requestFocus {
|
||||||
|
key.FocusOp{Focus: true}.Add(gtx.Ops)
|
||||||
|
key.SoftKeyboardOp{Show: true}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
e.requestFocus = false
|
e.requestFocus = false
|
||||||
pointerPadding := gtx.Px(unit.Dp(4))
|
pointerPadding := gtx.Px(unit.Dp(4))
|
||||||
r := image.Rectangle{Max: e.viewSize}
|
r := image.Rectangle{Max: e.viewSize}
|
||||||
|
|||||||
Reference in New Issue
Block a user