mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
io/router/key: add explicit tag to FocusOp; make last SoftKeyboardOp apply
The target of FocusOp is too subtle; be explicit instead and remove any doubt. Multiple SoftKeyboardOp in a single frame is rare, but if they do occur, they should behave as if they were from separate frames: the last one applies. As a side-effect the key event router can be much simplified. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -54,7 +54,7 @@ const (
|
|||||||
TypeClipboardReadLen = 1
|
TypeClipboardReadLen = 1
|
||||||
TypeClipboardWriteLen = 1
|
TypeClipboardWriteLen = 1
|
||||||
TypeKeyInputLen = 1
|
TypeKeyInputLen = 1
|
||||||
TypeKeyFocusLen = 1 + 1
|
TypeKeyFocusLen = 1
|
||||||
TypeKeySoftKeyboardLen = 1 + 1
|
TypeKeySoftKeyboardLen = 1 + 1
|
||||||
TypeSaveLen = 1 + 4
|
TypeSaveLen = 1 + 4
|
||||||
TypeLoadLen = 1 + 1 + 4
|
TypeLoadLen = 1 + 1 + 4
|
||||||
@@ -115,7 +115,7 @@ func (t OpType) Size() int {
|
|||||||
|
|
||||||
func (t OpType) NumRefs() int {
|
func (t OpType) NumRefs() int {
|
||||||
switch t {
|
switch t {
|
||||||
case TypeKeyInput, TypePointerInput, TypeProfile, TypeCall, TypeClipboardRead, TypeClipboardWrite, TypeCursor:
|
case TypeKeyInput, TypeKeyFocus, TypePointerInput, TypeProfile, TypeCall, TypeClipboardRead, TypeClipboardWrite, TypeCursor:
|
||||||
return 1
|
return 1
|
||||||
case TypeImage:
|
case TypeImage:
|
||||||
return 2
|
return 2
|
||||||
|
|||||||
+7
-8
@@ -26,15 +26,17 @@ type InputOp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SoftKeyboardOp shows or hide the on-screen keyboard, if available.
|
// SoftKeyboardOp shows or hide the on-screen keyboard, if available.
|
||||||
|
// It replaces any previous SoftKeyboardOp.
|
||||||
type SoftKeyboardOp struct {
|
type SoftKeyboardOp struct {
|
||||||
Show bool
|
Show bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FocusOp sets or clears the keyboard focus.
|
// FocusOp sets or clears the keyboard focus. It replaces any previous
|
||||||
|
// FocusOp in the same frame.
|
||||||
type FocusOp struct {
|
type FocusOp struct {
|
||||||
// Focus, if set, moves the focus to the current InputOp. If Focus
|
// Tag is the new focus. The focus is cleared if Tag is nil, or if Tag
|
||||||
// is false, the focus is cleared.
|
// has no InputOp in the same frame.
|
||||||
Focus bool
|
Tag event.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// A FocusEvent is generated when a handler gains or loses
|
// A FocusEvent is generated when a handler gains or loses
|
||||||
@@ -132,11 +134,8 @@ func (h SoftKeyboardOp) Add(o *op.Ops) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h FocusOp) Add(o *op.Ops) {
|
func (h FocusOp) Add(o *op.Ops) {
|
||||||
data := o.Write(opconst.TypeKeyFocusLen)
|
data := o.Write1(opconst.TypeKeyFocusLen, h.Tag)
|
||||||
data[0] = byte(opconst.TypeKeyFocus)
|
data[0] = byte(opconst.TypeKeyFocus)
|
||||||
if h.Focus {
|
|
||||||
data[1] = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (EditEvent) ImplementsEvent() {}
|
func (EditEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+20
-68
@@ -17,14 +17,6 @@ type keyQueue struct {
|
|||||||
handlers map[event.Tag]*keyHandler
|
handlers map[event.Tag]*keyHandler
|
||||||
reader ops.Reader
|
reader ops.Reader
|
||||||
state TextInputState
|
state TextInputState
|
||||||
// states store states during resolveFocus
|
|
||||||
states []resolveState
|
|
||||||
}
|
|
||||||
|
|
||||||
type resolveState struct {
|
|
||||||
tag event.Tag
|
|
||||||
pri listenerPriority
|
|
||||||
keyboard TextInputState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyHandler struct {
|
type keyHandler struct {
|
||||||
@@ -34,15 +26,6 @@ type keyHandler struct {
|
|||||||
new bool
|
new bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type listenerPriority uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
priDefault listenerPriority = iota
|
|
||||||
priCurrentFocus
|
|
||||||
priNone
|
|
||||||
priNewFocus
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TextInputKeep TextInputState = iota
|
TextInputKeep TextInputState = iota
|
||||||
TextInputClose
|
TextInputClose
|
||||||
@@ -64,36 +47,37 @@ func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
|||||||
}
|
}
|
||||||
q.reader.Reset(root)
|
q.reader.Reset(root)
|
||||||
|
|
||||||
state := q.resolveFocus(events)
|
focus, changed, state := q.resolveFocus(events)
|
||||||
if state.pri == priNone {
|
|
||||||
state.tag = nil
|
|
||||||
}
|
|
||||||
for k, h := range q.handlers {
|
for k, h := range q.handlers {
|
||||||
if !h.visible {
|
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.
|
// Remove the focus from the handler that is no longer visible.
|
||||||
q.focus = nil
|
q.focus = nil
|
||||||
state.keyboard = TextInputClose
|
state = TextInputClose
|
||||||
}
|
}
|
||||||
}
|
} else if h.new && k != focus {
|
||||||
if h.new && k != state.tag {
|
// Reset the handler on (each) first appearance, but don't trigger redraw.
|
||||||
// Reset the handler on (each) first appearance.
|
|
||||||
events.Add(k, key.FocusEvent{Focus: false})
|
events.Add(k, key.FocusEvent{Focus: false})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state.tag != q.focus {
|
if changed && focus != nil {
|
||||||
|
if _, exists := q.handlers[focus]; !exists {
|
||||||
|
focus = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if changed && focus != q.focus {
|
||||||
if q.focus != nil {
|
if q.focus != nil {
|
||||||
events.Add(q.focus, key.FocusEvent{Focus: false})
|
events.Add(q.focus, key.FocusEvent{Focus: false})
|
||||||
}
|
}
|
||||||
q.focus = state.tag
|
q.focus = focus
|
||||||
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 {
|
||||||
state.keyboard = TextInputClose
|
state = TextInputClose
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
q.state = state.keyboard
|
q.state = state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
||||||
@@ -102,63 +86,31 @@ func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState {
|
func (q *keyQueue) resolveFocus(events *handlerEvents) (focus event.Tag, changed bool, state TextInputState) {
|
||||||
var state resolveState
|
|
||||||
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:
|
case opconst.TypeKeyFocus:
|
||||||
op := decodeFocusOp(encOp.Data, encOp.Refs)
|
op := decodeFocusOp(encOp.Data, encOp.Refs)
|
||||||
if op.Focus {
|
changed = true
|
||||||
state.pri = priNewFocus
|
focus = op.Tag
|
||||||
} else {
|
|
||||||
state.pri, state.keyboard = priNone, TextInputClose
|
|
||||||
}
|
|
||||||
case opconst.TypeKeySoftKeyboard:
|
case opconst.TypeKeySoftKeyboard:
|
||||||
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
|
op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs)
|
||||||
if op.Show {
|
if op.Show {
|
||||||
state.keyboard = TextInputOpen
|
state = TextInputOpen
|
||||||
} else {
|
} else {
|
||||||
state.keyboard = TextInputClose
|
state = TextInputClose
|
||||||
}
|
}
|
||||||
case opconst.TypeKeyInput:
|
case opconst.TypeKeyInput:
|
||||||
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
||||||
if op.Tag == q.focus && state.pri < priCurrentFocus {
|
|
||||||
state.pri = priCurrentFocus
|
|
||||||
}
|
|
||||||
h, ok := q.handlers[op.Tag]
|
h, ok := q.handlers[op.Tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
h = &keyHandler{new: true}
|
h = &keyHandler{new: true}
|
||||||
q.handlers[op.Tag] = h
|
q.handlers[op.Tag] = h
|
||||||
}
|
}
|
||||||
h.visible = true
|
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
|
return
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
|
||||||
@@ -184,6 +136,6 @@ func decodeFocusOp(d []byte, refs []interface{}) key.FocusOp {
|
|||||||
panic("invalid op")
|
panic("invalid op")
|
||||||
}
|
}
|
||||||
return key.FocusOp{
|
return key.FocusOp{
|
||||||
Focus: d[1] != 0,
|
Tag: refs[0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-27
@@ -18,7 +18,7 @@ func TestKeyMultiples(t *testing.T) {
|
|||||||
|
|
||||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
key.FocusOp{Focus: true}.Add(ops)
|
key.FocusOp{Tag: &handlers[2]}.Add(ops)
|
||||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
|
|
||||||
// The last one must be focused:
|
// The last one must be focused:
|
||||||
@@ -40,27 +40,19 @@ func TestKeyStacked(t *testing.T) {
|
|||||||
|
|
||||||
s := op.Save(ops)
|
s := op.Save(ops)
|
||||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
// FocusOp must not overwrite the
|
key.FocusOp{Tag: nil}.Add(ops)
|
||||||
// FocusOp{Focus: true}.
|
|
||||||
key.FocusOp{Focus: false}.Add(ops)
|
|
||||||
s.Load()
|
s.Load()
|
||||||
s = op.Save(ops)
|
s = op.Save(ops)
|
||||||
key.SoftKeyboardOp{Show: false}.Add(ops)
|
key.SoftKeyboardOp{Show: false}.Add(ops)
|
||||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
key.FocusOp{Focus: true}.Add(ops)
|
key.FocusOp{Tag: &handlers[1]}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
s = op.Save(ops)
|
s = op.Save(ops)
|
||||||
key.InputOp{Tag: &handlers[2]}.Add(ops)
|
key.InputOp{Tag: &handlers[2]}.Add(ops)
|
||||||
// SoftwareKeyboardOp will open the keyboard,
|
|
||||||
// overwriting `SoftKeyboardOp{Show: false}`.
|
|
||||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
s = op.Save(ops)
|
s = op.Save(ops)
|
||||||
key.SoftKeyboardOp{Show: false}.Add(ops)
|
|
||||||
key.InputOp{Tag: &handlers[3]}.Add(ops)
|
key.InputOp{Tag: &handlers[3]}.Add(ops)
|
||||||
// FocusOp must not overwrite the
|
|
||||||
// FocusOp{Focus: true}.
|
|
||||||
key.FocusOp{Focus: false}.Add(ops)
|
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
@@ -95,7 +87,7 @@ func TestKeyRemoveFocus(t *testing.T) {
|
|||||||
// New InputOp with Focus and Keyboard:
|
// New InputOp with Focus and Keyboard:
|
||||||
s := op.Save(ops)
|
s := op.Save(ops)
|
||||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
key.FocusOp{Focus: true}.Add(ops)
|
key.FocusOp{Tag: &handlers[0]}.Add(ops)
|
||||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|
||||||
@@ -127,14 +119,13 @@ func TestKeyRemoveFocus(t *testing.T) {
|
|||||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|
||||||
// Removing any Focus:
|
// Remove focus by focusing on a tag that don't exist.
|
||||||
s = op.Save(ops)
|
s = op.Save(ops)
|
||||||
key.FocusOp{Focus: false}.Add(ops)
|
key.FocusOp{Tag: new(int)}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
|
|
||||||
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
|
||||||
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||||
assertFocus(t, r, nil)
|
assertFocus(t, r, nil)
|
||||||
assertKeyboard(t, r, TextInputClose)
|
assertKeyboard(t, r, TextInputClose)
|
||||||
@@ -145,11 +136,6 @@ func TestKeyRemoveFocus(t *testing.T) {
|
|||||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|
||||||
// Setting Focus without InputOp:
|
|
||||||
s = op.Save(ops)
|
|
||||||
key.FocusOp{Focus: true}.Add(ops)
|
|
||||||
s.Load()
|
|
||||||
|
|
||||||
s = op.Save(ops)
|
s = op.Save(ops)
|
||||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
@@ -166,23 +152,21 @@ func TestKeyRemoveFocus(t *testing.T) {
|
|||||||
// Set focus to InputOp which already
|
// Set focus to InputOp which already
|
||||||
// exists in the previous frame:
|
// exists in the previous frame:
|
||||||
s = op.Save(ops)
|
s = op.Save(ops)
|
||||||
key.FocusOp{Focus: true}.Add(ops)
|
key.FocusOp{Tag: &handlers[0]}.Add(ops)
|
||||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|
||||||
// Tries to remove focus:
|
// Remove focus.
|
||||||
// It must not overwrite the previous `FocusOp`.
|
|
||||||
s = op.Save(ops)
|
s = op.Save(ops)
|
||||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||||
key.FocusOp{Focus: false}.Add(ops)
|
key.FocusOp{Tag: nil}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
|
|
||||||
assertKeyEvent(t, r.Events(&handlers[0]), true)
|
|
||||||
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||||
assertFocus(t, r, &handlers[0])
|
assertFocus(t, r, nil)
|
||||||
assertKeyboard(t, r, TextInputOpen)
|
assertKeyboard(t, r, TextInputOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +177,7 @@ func TestKeyFocusedInvisible(t *testing.T) {
|
|||||||
|
|
||||||
// Set new InputOp with focus:
|
// Set new InputOp with focus:
|
||||||
s := op.Save(ops)
|
s := op.Save(ops)
|
||||||
key.FocusOp{Focus: true}.Add(ops)
|
key.FocusOp{Tag: &handlers[0]}.Add(ops)
|
||||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||||
s.Load()
|
s.Load()
|
||||||
|
|||||||
+1
-1
@@ -416,7 +416,7 @@ func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
|
|
||||||
key.InputOp{Tag: &e.eventKey}.Add(gtx.Ops)
|
key.InputOp{Tag: &e.eventKey}.Add(gtx.Ops)
|
||||||
if e.requestFocus {
|
if e.requestFocus {
|
||||||
key.FocusOp{Focus: true}.Add(gtx.Ops)
|
key.FocusOp{Tag: &e.eventKey}.Add(gtx.Ops)
|
||||||
key.SoftKeyboardOp{Show: true}.Add(gtx.Ops)
|
key.SoftKeyboardOp{Show: true}.Add(gtx.Ops)
|
||||||
}
|
}
|
||||||
e.requestFocus = false
|
e.requestFocus = false
|
||||||
|
|||||||
Reference in New Issue
Block a user