mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
io/router: expose the event router
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>
This commit is contained in:
@@ -1,150 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
type pointerQueue struct {
|
||||
hitTree []hitNode
|
||||
areas []areaNode
|
||||
handlers map[event.Key]*pointerHandler
|
||||
pointers []pointerInfo
|
||||
reader ops.Reader
|
||||
scratch []event.Key
|
||||
}
|
||||
|
||||
type hitNode struct {
|
||||
next int
|
||||
area int
|
||||
// Pass tracks the most recent PassOp mode.
|
||||
pass bool
|
||||
|
||||
// For handler nodes.
|
||||
key event.Key
|
||||
}
|
||||
|
||||
type pointerInfo struct {
|
||||
id pointer.ID
|
||||
pressed bool
|
||||
handlers []event.Key
|
||||
}
|
||||
|
||||
type pointerHandler struct {
|
||||
area int
|
||||
active bool
|
||||
transform op.TransformOp
|
||||
wantsGrab bool
|
||||
}
|
||||
|
||||
type areaOp struct {
|
||||
kind areaKind
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
type areaNode struct {
|
||||
trans op.TransformOp
|
||||
next int
|
||||
area areaOp
|
||||
}
|
||||
|
||||
type areaKind uint8
|
||||
|
||||
const (
|
||||
areaRect areaKind = iota
|
||||
areaEllipse
|
||||
)
|
||||
|
||||
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t op.TransformOp, area, node int, pass bool) {
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypePush:
|
||||
q.collectHandlers(r, events, t, area, node, pass)
|
||||
case opconst.TypePop:
|
||||
return
|
||||
case opconst.TypePass:
|
||||
op := decodePassOp(encOp.Data)
|
||||
pass = op.Pass
|
||||
case opconst.TypeArea:
|
||||
var op areaOp
|
||||
op.Decode(encOp.Data)
|
||||
q.areas = append(q.areas, areaNode{trans: t, next: area, area: op})
|
||||
area = len(q.areas) - 1
|
||||
q.hitTree = append(q.hitTree, hitNode{
|
||||
next: node,
|
||||
area: area,
|
||||
pass: pass,
|
||||
})
|
||||
node = len(q.hitTree) - 1
|
||||
case opconst.TypeTransform:
|
||||
dop := ops.DecodeTransformOp(encOp.Data)
|
||||
t = t.Multiply(op.TransformOp(dop))
|
||||
case opconst.TypePointerInput:
|
||||
op := decodePointerInputOp(encOp.Data, encOp.Refs)
|
||||
q.hitTree = append(q.hitTree, hitNode{
|
||||
next: node,
|
||||
area: area,
|
||||
pass: pass,
|
||||
key: op.Key,
|
||||
})
|
||||
node = len(q.hitTree) - 1
|
||||
h, ok := q.handlers[op.Key]
|
||||
if !ok {
|
||||
h = new(pointerHandler)
|
||||
q.handlers[op.Key] = h
|
||||
events.Set(op.Key, []event.Event{pointer.Event{Type: pointer.Cancel}})
|
||||
}
|
||||
h.active = true
|
||||
h.area = area
|
||||
h.transform = t
|
||||
h.wantsGrab = h.wantsGrab || op.Grab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) opHit(handlers *[]event.Key, pos f32.Point) {
|
||||
// Track whether we're passing through hits.
|
||||
pass := true
|
||||
idx := len(q.hitTree) - 1
|
||||
for idx >= 0 {
|
||||
n := &q.hitTree[idx]
|
||||
if !q.hit(n.area, pos) {
|
||||
idx--
|
||||
continue
|
||||
}
|
||||
pass = pass && n.pass
|
||||
if pass {
|
||||
idx--
|
||||
} else {
|
||||
idx = n.next
|
||||
}
|
||||
if n.key != nil {
|
||||
if _, exists := q.handlers[n.key]; exists {
|
||||
*handlers = append(*handlers, n.key)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
|
||||
for areaIdx != -1 {
|
||||
a := &q.areas[areaIdx]
|
||||
if !a.hit(p) {
|
||||
return false
|
||||
}
|
||||
areaIdx = a.next
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *areaNode) hit(p f32.Point) bool {
|
||||
p = a.trans.Invert().Transform(p)
|
||||
return a.area.Hit(p)
|
||||
}
|
||||
|
||||
func (q *pointerQueue) init() {
|
||||
if q.handlers == nil {
|
||||
q.handlers = make(map[event.Key]*pointerHandler)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
|
||||
q.init()
|
||||
for _, h := range q.handlers {
|
||||
// Reset handler.
|
||||
h.active = false
|
||||
}
|
||||
q.hitTree = q.hitTree[:0]
|
||||
q.areas = q.areas[:0]
|
||||
q.reader.Reset(root)
|
||||
q.collectHandlers(&q.reader, events, op.TransformOp{}, -1, -1, false)
|
||||
for k, h := range q.handlers {
|
||||
if !h.active {
|
||||
q.dropHandler(k, events)
|
||||
delete(q.handlers, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) dropHandler(k event.Key, events *handlerEvents) {
|
||||
events.Add(k, pointer.Event{Type: pointer.Cancel})
|
||||
q.handlers[k].wantsGrab = false
|
||||
for i := range q.pointers {
|
||||
p := &q.pointers[i]
|
||||
for i := len(p.handlers) - 1; i >= 0; i-- {
|
||||
if p.handlers[i] == k {
|
||||
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
|
||||
q.init()
|
||||
if e.Type == pointer.Cancel {
|
||||
q.pointers = q.pointers[:0]
|
||||
for k := range q.handlers {
|
||||
q.dropHandler(k, events)
|
||||
}
|
||||
return
|
||||
}
|
||||
pidx := -1
|
||||
for i, p := range q.pointers {
|
||||
if p.id == e.PointerID {
|
||||
pidx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if pidx == -1 {
|
||||
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
|
||||
pidx = len(q.pointers) - 1
|
||||
}
|
||||
p := &q.pointers[pidx]
|
||||
if !p.pressed && (e.Type == pointer.Move || e.Type == pointer.Press) {
|
||||
p.handlers, q.scratch = q.scratch[:0], p.handlers
|
||||
q.opHit(&p.handlers, e.Position)
|
||||
if e.Type == pointer.Press {
|
||||
p.pressed = true
|
||||
}
|
||||
}
|
||||
if p.pressed {
|
||||
// Resolve grabs.
|
||||
q.scratch = q.scratch[:0]
|
||||
for i, k := range p.handlers {
|
||||
h := q.handlers[k]
|
||||
if h.wantsGrab {
|
||||
q.scratch = append(q.scratch, p.handlers[:i]...)
|
||||
q.scratch = append(q.scratch, p.handlers[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Drop handlers that lost their grab.
|
||||
for _, k := range q.scratch {
|
||||
q.dropHandler(k, events)
|
||||
}
|
||||
}
|
||||
if e.Type == pointer.Release {
|
||||
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
|
||||
}
|
||||
for _, k := range p.handlers {
|
||||
h := q.handlers[k]
|
||||
e := e
|
||||
if p.pressed && len(p.handlers) == 1 {
|
||||
e.Priority = pointer.Grabbed
|
||||
}
|
||||
e.Hit = q.hit(h.area, e.Position)
|
||||
e.Position = h.transform.Invert().Transform(e.Position)
|
||||
events.Add(k, e)
|
||||
if e.Type == pointer.Release {
|
||||
// Release grab when the number of grabs reaches zero.
|
||||
grabs := 0
|
||||
for _, p := range q.pointers {
|
||||
if p.pressed && len(p.handlers) == 1 && p.handlers[0] == k {
|
||||
grabs++
|
||||
}
|
||||
}
|
||||
if grabs == 0 {
|
||||
h.wantsGrab = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (op *areaOp) Decode(d []byte) {
|
||||
if opconst.OpType(d[0]) != opconst.TypeArea {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
rect := image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: int(int32(bo.Uint32(d[2:]))),
|
||||
Y: int(int32(bo.Uint32(d[6:]))),
|
||||
},
|
||||
Max: image.Point{
|
||||
X: int(int32(bo.Uint32(d[10:]))),
|
||||
Y: int(int32(bo.Uint32(d[14:]))),
|
||||
},
|
||||
}
|
||||
*op = areaOp{
|
||||
kind: areaKind(d[1]),
|
||||
rect: rect,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *areaOp) Hit(pos f32.Point) bool {
|
||||
min := f32.Point{
|
||||
X: float32(op.rect.Min.X),
|
||||
Y: float32(op.rect.Min.Y),
|
||||
}
|
||||
pos = pos.Sub(min)
|
||||
size := op.rect.Size()
|
||||
switch op.kind {
|
||||
case areaRect:
|
||||
if 0 <= pos.X && pos.X < float32(size.X) &&
|
||||
0 <= pos.Y && pos.Y < float32(size.Y) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case areaEllipse:
|
||||
rx := float32(size.X) / 2
|
||||
ry := float32(size.Y) / 2
|
||||
rx2 := rx * rx
|
||||
ry2 := ry * ry
|
||||
xh := pos.X - rx
|
||||
yk := pos.Y - ry
|
||||
if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
panic("invalid area kind")
|
||||
}
|
||||
}
|
||||
|
||||
func decodePointerInputOp(d []byte, refs []interface{}) pointer.InputOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypePointerInput {
|
||||
panic("invalid op")
|
||||
}
|
||||
return pointer.InputOp{
|
||||
Grab: d[1] != 0,
|
||||
Key: refs[0].(event.Key),
|
||||
}
|
||||
}
|
||||
|
||||
func decodePassOp(d []byte) pointer.PassOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypePass {
|
||||
panic("invalid op")
|
||||
}
|
||||
return pointer.PassOp{
|
||||
Pass: d[1] != 0,
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/profile"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Router is a Queue implementation that routes events from
|
||||
// all available input sources to registered handlers.
|
||||
type Router struct {
|
||||
pqueue pointerQueue
|
||||
kqueue keyQueue
|
||||
|
||||
handlers handlerEvents
|
||||
|
||||
reader ops.Reader
|
||||
|
||||
// InvalidateOp summary.
|
||||
wakeup bool
|
||||
wakeupTime time.Time
|
||||
|
||||
// ProfileOp summary.
|
||||
profiling bool
|
||||
profHandlers map[event.Key]struct{}
|
||||
profile profile.Event
|
||||
}
|
||||
|
||||
type handlerEvents struct {
|
||||
handlers map[event.Key][]event.Event
|
||||
hadEvents bool
|
||||
}
|
||||
|
||||
func (q *Router) Events(k event.Key) []event.Event {
|
||||
events := q.handlers.Events(k)
|
||||
if _, isprof := q.profHandlers[k]; isprof {
|
||||
delete(q.profHandlers, k)
|
||||
events = append(events, q.profile)
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
func (q *Router) Frame(ops *op.Ops) {
|
||||
q.handlers.Clear()
|
||||
q.wakeup = false
|
||||
q.profiling = false
|
||||
for k := range q.profHandlers {
|
||||
delete(q.profHandlers, k)
|
||||
}
|
||||
q.reader.Reset(ops)
|
||||
q.collect()
|
||||
|
||||
q.pqueue.Frame(ops, &q.handlers)
|
||||
q.kqueue.Frame(ops, &q.handlers)
|
||||
if q.handlers.HadEvents() {
|
||||
q.wakeup = true
|
||||
q.wakeupTime = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Router) Add(e event.Event) bool {
|
||||
switch e := e.(type) {
|
||||
case profile.Event:
|
||||
q.profile = e
|
||||
case pointer.Event:
|
||||
q.pqueue.Push(e, &q.handlers)
|
||||
case key.EditEvent, key.Event, key.FocusEvent:
|
||||
q.kqueue.Push(e, &q.handlers)
|
||||
}
|
||||
return q.handlers.HadEvents()
|
||||
}
|
||||
|
||||
func (q *Router) TextInputState() TextInputState {
|
||||
return q.kqueue.InputState()
|
||||
}
|
||||
|
||||
func (q *Router) collect() {
|
||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypeInvalidate:
|
||||
op := decodeInvalidateOp(encOp.Data)
|
||||
if !q.wakeup || op.At.Before(q.wakeupTime) {
|
||||
q.wakeup = true
|
||||
q.wakeupTime = op.At
|
||||
}
|
||||
case opconst.TypeProfile:
|
||||
op := decodeProfileOp(encOp.Data, encOp.Refs)
|
||||
if q.profHandlers == nil {
|
||||
q.profHandlers = make(map[event.Key]struct{})
|
||||
}
|
||||
q.profiling = true
|
||||
q.profHandlers[op.Key] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Router) Profiling() bool {
|
||||
return q.profiling
|
||||
}
|
||||
|
||||
func (q *Router) WakeupTime() (time.Time, bool) {
|
||||
return q.wakeupTime, q.wakeup
|
||||
}
|
||||
|
||||
func (h *handlerEvents) init() {
|
||||
if h.handlers == nil {
|
||||
h.handlers = make(map[event.Key][]event.Event)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Set(k event.Key, evts []event.Event) {
|
||||
h.init()
|
||||
h.handlers[k] = evts
|
||||
h.hadEvents = true
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Add(k event.Key, e event.Event) {
|
||||
h.init()
|
||||
h.handlers[k] = append(h.handlers[k], e)
|
||||
h.hadEvents = true
|
||||
}
|
||||
|
||||
func (h *handlerEvents) HadEvents() bool {
|
||||
u := h.hadEvents
|
||||
h.hadEvents = false
|
||||
return u
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Events(k event.Key) []event.Event {
|
||||
if events, ok := h.handlers[k]; ok {
|
||||
h.handlers[k] = h.handlers[k][:0]
|
||||
// Schedule another frame if we delivered events to the user
|
||||
// to flush half-updated state. This is important when a an
|
||||
// event changes UI state that has already been laid out. In
|
||||
// the worst case, we waste a frame, increasing power usage.
|
||||
//
|
||||
// Gio is expected to grow the ability to construct
|
||||
// frame-to-frame differences and only render to changed
|
||||
// areas. In that case, the waste of a spurious frame should
|
||||
// be minimal.
|
||||
h.hadEvents = h.hadEvents || len(events) > 0
|
||||
return events
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Clear() {
|
||||
for k := range h.handlers {
|
||||
delete(h.handlers, k)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeProfileOp(d []byte, refs []interface{}) profile.Op {
|
||||
if opconst.OpType(d[0]) != opconst.TypeProfile {
|
||||
panic("invalid op")
|
||||
}
|
||||
return profile.Op{
|
||||
Key: refs[0].(event.Key),
|
||||
}
|
||||
}
|
||||
|
||||
func decodeInvalidateOp(d []byte) op.InvalidateOp {
|
||||
bo := binary.LittleEndian
|
||||
if opconst.OpType(d[0]) != opconst.TypeInvalidate {
|
||||
panic("invalid op")
|
||||
}
|
||||
var o op.InvalidateOp
|
||||
if nanos := bo.Uint64(d[1:]); nanos > 0 {
|
||||
o.At = time.Unix(0, int64(nanos))
|
||||
}
|
||||
return o
|
||||
}
|
||||
+4
-4
@@ -8,10 +8,10 @@ import (
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"gioui.org/app/internal/input"
|
||||
"gioui.org/app/internal/window"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/profile"
|
||||
"gioui.org/io/router"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
@@ -56,7 +56,7 @@ type callbacks struct {
|
||||
// Queue is an event.Queue implementation that distributes system events
|
||||
// to the input handlers declared in the most recent frame.
|
||||
type Queue struct {
|
||||
q input.Router
|
||||
q router.Router
|
||||
}
|
||||
|
||||
// driverEvent is sent when a new native driver
|
||||
@@ -128,9 +128,9 @@ func (w *Window) draw(frameStart time.Time, size image.Point, frame *op.Ops) {
|
||||
sync := w.loop.Draw(size, frame)
|
||||
w.queue.q.Frame(frame)
|
||||
switch w.queue.q.TextInputState() {
|
||||
case input.TextInputOpen:
|
||||
case router.TextInputOpen:
|
||||
w.driver.ShowTextInput(true)
|
||||
case input.TextInputClose:
|
||||
case router.TextInputClose:
|
||||
w.driver.ShowTextInput(false)
|
||||
}
|
||||
if w.queue.q.Profiling() {
|
||||
|
||||
Reference in New Issue
Block a user