ui: add package input for merged input

To avoid passing a queue type for each kind of input (pointer, key),
introduce package input for mapping a handler key to all input events.

Future input sources can be added without changes to programs, and
as an added bonus, event ordering is preserved across input sources.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-06-07 21:41:09 +02:00
parent 28dd25736f
commit a35118d522
10 changed files with 172 additions and 130 deletions
+2 -4
View File
@@ -11,9 +11,9 @@ import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/app/internal/gpu" "gioui.org/ui/app/internal/gpu"
"gioui.org/ui/input"
"gioui.org/ui/internal/ops" "gioui.org/ui/internal/ops"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer"
) )
type WindowOptions struct { type WindowOptions struct {
@@ -238,9 +238,7 @@ func (w *Window) event(e Event) {
needAck := false needAck := false
needRedraw := false needRedraw := false
switch e := e.(type) { switch e := e.(type) {
case pointer.Event: case input.Event:
needRedraw = true
case key.Event:
needRedraw = true needRedraw = true
case *Command: case *Command:
needAck = true needAck = true
+11 -2
View File
@@ -9,6 +9,7 @@ import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/input"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
) )
@@ -80,9 +81,13 @@ func (c *Click) Add(ops *ui.Ops) {
op.Add(ops) op.Add(ops)
} }
func (c *Click) Update(q pointer.Events) []ClickEvent { func (c *Click) Update(q input.Events) []ClickEvent {
var events []ClickEvent var events []ClickEvent
for _, e := range q.For(c) { for _, e := range q.For(c) {
e, ok := e.(pointer.Event)
if !ok {
continue
}
switch e.Type { switch e.Type {
case pointer.Release: case pointer.Release:
if c.State == StatePressed { if c.State == StatePressed {
@@ -124,13 +129,17 @@ func (s *Scroll) Dragging() bool {
return s.dragging return s.dragging
} }
func (s *Scroll) Update(cfg *ui.Config, q pointer.Events, axis Axis) int { func (s *Scroll) Update(cfg *ui.Config, q input.Events, axis Axis) int {
if s.axis != axis { if s.axis != axis {
s.axis = axis s.axis = axis
return 0 return 0
} }
total := 0 total := 0
for _, e := range q.For(s) { for _, e := range q.For(s) {
e, ok := e.(pointer.Event)
if !ok {
continue
}
switch e.Type { switch e.Type {
case pointer.Press: case pointer.Press:
if s.dragging || e.Source != pointer.Touch { if s.dragging || e.Source != pointer.Touch {
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Unlicense OR MIT
// Package input exposes a unified interface to input sources. Subpackages
// such as pointer and key provide the interfaces for specific input types.
package input
// Events maps an event handler key to the events
// available to the handler.
type Events interface {
For(k Key) []Event
}
// Key is the stable identifier for an event handler. For a handler h, the
// key is typically &h.
type Key interface{}
// Event is the marker interface for input events.
type Event interface {
ImplementsInputEvent()
}
+24 -39
View File
@@ -1,22 +1,22 @@
// SPDX-License-Identifier: Unlicense OR MIT // SPDX-License-Identifier: Unlicense OR MIT
package key package input
import ( import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/internal/ops" "gioui.org/ui/internal/ops"
"gioui.org/ui/key"
) )
type Queue struct { type keyQueue struct {
focus Key focus Key
handlers map[Key]*handler handlers map[Key]*keyHandler
reader ui.OpsReader reader ui.OpsReader
state TextInputState state key.TextInputState
} }
type handler struct { type keyHandler struct {
active bool active bool
events []Event
} }
type listenerPriority uint8 type listenerPriority uint8
@@ -30,20 +30,19 @@ const (
// InputState returns the last text input state as // InputState returns the last text input state as
// determined in Frame. // determined in Frame.
func (q *Queue) InputState() TextInputState { func (q *keyQueue) InputState() key.TextInputState {
return q.state return q.state
} }
func (q *Queue) Frame(root *ui.Ops) { func (q *keyQueue) Frame(root *ui.Ops, events handlerEvents) {
if q.handlers == nil { if q.handlers == nil {
q.handlers = make(map[Key]*handler) q.handlers = make(map[Key]*keyHandler)
} }
for _, h := range q.handlers { for _, h := range q.handlers {
h.active = false h.active = false
h.events = h.events[:0]
} }
q.reader.Reset(root) q.reader.Reset(root)
focus, pri, hide := q.resolveFocus() focus, pri, hide := q.resolveFocus(events)
for k, h := range q.handlers { for k, h := range q.handlers {
if !h.active { if !h.active {
delete(q.handlers, k) delete(q.handlers, k)
@@ -55,46 +54,33 @@ func (q *Queue) Frame(root *ui.Ops) {
changed := focus != nil && focus != q.focus changed := focus != nil && focus != q.focus
if focus != q.focus { if focus != q.focus {
if q.focus != nil { if q.focus != nil {
if h, ok := q.handlers[q.focus]; ok { events[q.focus] = append(events[q.focus], key.Focus{Focus: false})
h.events = append(h.events, Focus{Focus: false})
}
} }
q.focus = focus q.focus = focus
if q.focus != nil { if q.focus != nil {
// A new focus always exists in the handler map. events[q.focus] = append(events[q.focus], key.Focus{Focus: true})
h := q.handlers[q.focus]
h.events = append(h.events, Focus{Focus: true})
} }
} }
switch { switch {
case pri == priNewFocus: case pri == priNewFocus:
q.state = TextInputOpen q.state = key.TextInputOpen
case hide: case hide:
q.state = TextInputClosed q.state = key.TextInputClosed
case changed: case changed:
q.state = TextInputFocus q.state = key.TextInputFocus
default: default:
q.state = TextInputKeep q.state = key.TextInputKeep
} }
} }
func (q *Queue) Push(e Event) { func (q *keyQueue) Push(e Event, events handlerEvents) {
if q.focus == nil { if q.focus == nil {
return return
} }
h := q.handlers[q.focus] events[q.focus] = append(events[q.focus], e)
h.events = append(h.events, e)
} }
func (q *Queue) For(k Key) []Event { func (q *keyQueue) resolveFocus(events handlerEvents) (Key, listenerPriority, bool) {
h := q.handlers[k]
if h == nil {
return nil
}
return h.events
}
func (q *Queue) resolveFocus() (Key, listenerPriority, bool) {
var k Key var k Key
var pri listenerPriority var pri listenerPriority
var hide bool var hide bool
@@ -106,7 +92,7 @@ loop:
} }
switch ops.OpType(encOp.Data[0]) { switch ops.OpType(encOp.Data[0]) {
case ops.TypeKeyHandler: case ops.TypeKeyHandler:
var op OpHandler var op key.OpHandler
op.Decode(encOp.Data, encOp.Refs) op.Decode(encOp.Data, encOp.Refs)
var newPri listenerPriority var newPri listenerPriority
switch { switch {
@@ -122,17 +108,16 @@ loop:
} }
h, ok := q.handlers[op.Key] h, ok := q.handlers[op.Key]
if !ok { if !ok {
h = &handler{ h = new(keyHandler)
// Reset the handler on (each) first appearance.
events: []Event{Focus{Focus: false}},
}
q.handlers[op.Key] = h q.handlers[op.Key] = h
// Reset the handler on (each) first appearance.
events[op.Key] = []Event{key.Focus{Focus: false}}
} }
h.active = true h.active = true
case ops.TypeHideInput: case ops.TypeHideInput:
hide = true hide = true
case ops.TypePush: case ops.TypePush:
newK, newPri, h := q.resolveFocus() newK, newPri, h := q.resolveFocus(events)
hide = hide || h hide = hide || h
if newPri >= pri { if newPri >= pri {
k, pri = newK, newPri k, pri = newK, newPri
+37 -51
View File
@@ -1,16 +1,17 @@
// SPDX-License-Identifier: Unlicense OR MIT // SPDX-License-Identifier: Unlicense OR MIT
package pointer package input
import ( import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/ops" "gioui.org/ui/internal/ops"
"gioui.org/ui/pointer"
) )
type Queue struct { type pointerQueue struct {
hitTree []hitNode hitTree []hitNode
handlers map[Key]*handler handlers map[Key]*pointerHandler
pointers []pointerInfo pointers []pointerInfo
reader ui.OpsReader reader ui.OpsReader
scratch []Key scratch []Key
@@ -25,22 +26,21 @@ type hitNode struct {
} }
type pointerInfo struct { type pointerInfo struct {
id ID id pointer.ID
pressed bool pressed bool
handlers []Key handlers []Key
} }
type handler struct { type pointerHandler struct {
area areaIntersection area areaIntersection
active bool active bool
transform ui.Transform transform ui.Transform
events []Event
wantsGrab bool wantsGrab bool
} }
type area struct { type area struct {
trans ui.Transform trans ui.Transform
area OpArea area pointer.OpArea
} }
type areaIntersection []area type areaIntersection []area
@@ -51,7 +51,7 @@ type areaStack struct {
backing []area backing []area
} }
func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) { func (q *pointerQueue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int, events handlerEvents) {
for { for {
encOp, ok := r.Decode() encOp, ok := r.Decode()
if !ok { if !ok {
@@ -60,7 +60,7 @@ func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) {
switch ops.OpType(encOp.Data[0]) { switch ops.OpType(encOp.Data[0]) {
case ops.TypePush: case ops.TypePush:
q.areas.push() q.areas.push()
q.collectHandlers(r, t, layer) q.collectHandlers(r, t, layer, events)
case ops.TypePop: case ops.TypePop:
q.areas.pop() q.areas.pop()
return return
@@ -68,24 +68,22 @@ func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) {
layer++ layer++
q.hitTree = append(q.hitTree, hitNode{level: layer}) q.hitTree = append(q.hitTree, hitNode{level: layer})
case ops.TypeArea: case ops.TypeArea:
var op OpArea var op pointer.OpArea
op.decode(encOp.Data) op.Decode(encOp.Data)
q.areas.add(t, op) q.areas.add(t, op)
case ops.TypeTransform: case ops.TypeTransform:
var op ui.OpTransform var op ui.OpTransform
op.Decode(encOp.Data) op.Decode(encOp.Data)
t = t.Mul(op.Transform) t = t.Mul(op.Transform)
case ops.TypePointerHandler: case ops.TypePointerHandler:
var op OpHandler var op pointer.OpHandler
op.Decode(encOp.Data, encOp.Refs) op.Decode(encOp.Data, encOp.Refs)
q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key}) q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key})
h, ok := q.handlers[op.Key] h, ok := q.handlers[op.Key]
if !ok { if !ok {
h = &handler{ h = new(pointerHandler)
// Reset the handler on (each) first appearance.
events: []Event{Event{Type: Cancel}},
}
q.handlers[op.Key] = h q.handlers[op.Key] = h
events[op.Key] = []Event{pointer.Event{Type: pointer.Cancel}}
} }
h.active = true h.active = true
h.area = q.areas.intersection() h.area = q.areas.intersection()
@@ -95,7 +93,7 @@ func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) {
} }
} }
func (q *Queue) opHit(handlers *[]Key, pos f32.Point) { func (q *pointerQueue) opHit(handlers *[]Key, pos f32.Point) {
level := 1 << 30 level := 1 << 30
opaque := false opaque := false
for i := len(q.hitTree) - 1; i >= 0; i-- { for i := len(q.hitTree) - 1; i >= 0; i-- {
@@ -114,31 +112,30 @@ func (q *Queue) opHit(handlers *[]Key, pos f32.Point) {
continue continue
} }
res := h.area.hit(pos) res := h.area.hit(pos)
opaque = opaque || res == hitOpaque opaque = opaque || res == pointer.HitOpaque
if res != hitNone { if res != pointer.HitNone {
*handlers = append(*handlers, n.key) *handlers = append(*handlers, n.key)
} }
} }
} }
} }
func (q *Queue) init() { func (q *pointerQueue) init() {
if q.handlers == nil { if q.handlers == nil {
q.handlers = make(map[Key]*handler) q.handlers = make(map[Key]*pointerHandler)
} }
} }
func (q *Queue) Frame(root *ui.Ops) { func (q *pointerQueue) Frame(root *ui.Ops, events handlerEvents) {
q.init() q.init()
for _, h := range q.handlers { for _, h := range q.handlers {
// Reset handler. // Reset handler.
h.active = false h.active = false
h.events = h.events[:0]
} }
q.hitTree = q.hitTree[:0] q.hitTree = q.hitTree[:0]
q.areas.reset() q.areas.reset()
q.reader.Reset(root) q.reader.Reset(root)
q.collectHandlers(&q.reader, ui.Transform{}, 0) q.collectHandlers(&q.reader, ui.Transform{}, 0, events)
for k, h := range q.handlers { for k, h := range q.handlers {
if !h.active { if !h.active {
q.dropHandler(k) q.dropHandler(k)
@@ -146,18 +143,7 @@ func (q *Queue) Frame(root *ui.Ops) {
} }
} }
func (q *Queue) For(k Key) []Event { func (q *pointerQueue) dropHandler(k Key) {
if k == nil {
panic("nil handler")
}
h := q.handlers[k]
if h == nil {
return nil
}
return h.events
}
func (q *Queue) dropHandler(k Key) {
delete(q.handlers, k) delete(q.handlers, k)
for i := range q.pointers { for i := range q.pointers {
p := &q.pointers[i] p := &q.pointers[i]
@@ -169,9 +155,9 @@ func (q *Queue) dropHandler(k Key) {
} }
} }
func (q *Queue) Push(e Event) { func (q *pointerQueue) Push(e pointer.Event, events handlerEvents) {
q.init() q.init()
if e.Type == Cancel { if e.Type == pointer.Cancel {
q.pointers = q.pointers[:0] q.pointers = q.pointers[:0]
for k := range q.handlers { for k := range q.handlers {
q.dropHandler(k) q.dropHandler(k)
@@ -190,7 +176,7 @@ func (q *Queue) Push(e Event) {
pidx = len(q.pointers) - 1 pidx = len(q.pointers) - 1
} }
p := &q.pointers[pidx] p := &q.pointers[pidx]
if !p.pressed && (e.Type == Move || e.Type == Press) { if !p.pressed && (e.Type == pointer.Move || e.Type == pointer.Press) {
p.handlers, q.scratch = q.scratch[:0], p.handlers p.handlers, q.scratch = q.scratch[:0], p.handlers
q.opHit(&p.handlers, e.Position) q.opHit(&p.handlers, e.Position)
// Drop handlers no longer hit. // Drop handlers no longer hit.
@@ -203,7 +189,7 @@ func (q *Queue) Push(e Event) {
} }
q.dropHandler(h) q.dropHandler(h)
} }
if e.Type == Press { if e.Type == pointer.Press {
p.pressed = true p.pressed = true
} }
} }
@@ -223,7 +209,7 @@ func (q *Queue) Push(e Event) {
q.dropHandler(k) q.dropHandler(k)
} }
} }
if e.Type == Release { if e.Type == pointer.Release {
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...) q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
} }
for i, k := range p.handlers { for i, k := range p.handlers {
@@ -231,14 +217,14 @@ func (q *Queue) Push(e Event) {
e := e e := e
switch { switch {
case p.pressed && len(p.handlers) == 1: case p.pressed && len(p.handlers) == 1:
e.Priority = Grabbed e.Priority = pointer.Grabbed
case i == 0: case i == 0:
e.Priority = Foremost e.Priority = pointer.Foremost
} }
e.Hit = h.area.hit(e.Position) != hitNone e.Hit = h.area.hit(e.Position) != pointer.HitNone
e.Position = h.transform.InvTransform(e.Position) e.Position = h.transform.InvTransform(e.Position)
h.events = append(h.events, e) events[k] = append(events[k], e)
if e.Type == Release { if e.Type == pointer.Release {
// Release grab when the number of grabs reaches zero. // Release grab when the number of grabs reaches zero.
grabs := 0 grabs := 0
for _, p := range q.pointers { for _, p := range q.pointers {
@@ -253,19 +239,19 @@ func (q *Queue) Push(e Event) {
} }
} }
func (a areaIntersection) hit(p f32.Point) hitResult { func (a areaIntersection) hit(p f32.Point) pointer.HitResult {
res := hitNone res := pointer.HitNone
for _, area := range a { for _, area := range a {
tp := area.trans.InvTransform(p) tp := area.trans.InvTransform(p)
res = area.area.hit(tp) res = area.area.Hit(tp)
if res == hitNone { if res == pointer.HitNone {
break break
} }
} }
return res return res
} }
func (s *areaStack) add(t ui.Transform, a OpArea) { func (s *areaStack) add(t ui.Transform, a pointer.OpArea) {
s.areas = append(s.areas, area{t, a}) s.areas = append(s.areas, area{t, a})
} }
+53
View File
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"gioui.org/ui"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
)
// Queue is an Events implementation that merges events from
// all available input sources.
type Queue struct {
pqueue pointerQueue
kqueue keyQueue
handlers handlerEvents
}
type handlerEvents map[Key][]Event
func (q *Queue) For(k Key) []Event {
return q.handlers[k]
}
func (q *Queue) Frame(ops *ui.Ops) {
q.init()
for k := range q.handlers {
delete(q.handlers, k)
}
q.pqueue.Frame(ops, q.handlers)
q.kqueue.Frame(ops, q.handlers)
}
func (q *Queue) Add(e Event) {
q.init()
switch e := e.(type) {
case pointer.Event:
q.pqueue.Push(e, q.handlers)
case key.Edit, key.Chord, key.Focus:
q.kqueue.Push(e, q.handlers)
}
}
func (q *Queue) InputState() key.TextInputState {
return q.kqueue.InputState()
}
func (q *Queue) init() {
if q.handlers == nil {
q.handlers = make(handlerEvents)
}
}
+6 -14
View File
@@ -16,14 +16,6 @@ type OpHideInput struct{}
type Key interface{} type Key interface{}
type Events interface {
For(k Key) []Event
}
type Event interface {
isKeyEvent()
}
type Focus struct { type Focus struct {
Focus bool Focus bool
} }
@@ -93,9 +85,9 @@ func (h OpHideInput) Add(o *ui.Ops) {
o.Write(data) o.Write(data)
} }
func (Edit) ImplementsEvent() {} func (Edit) ImplementsEvent() {}
func (Chord) ImplementsEvent() {} func (Chord) ImplementsEvent() {}
func (Focus) ImplementsEvent() {} func (Focus) ImplementsEvent() {}
func (Edit) isKeyEvent() {} func (Edit) ImplementsInputEvent() {}
func (Chord) isKeyEvent() {} func (Chord) ImplementsInputEvent() {}
func (Focus) isKeyEvent() {} func (Focus) ImplementsInputEvent() {}
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/draw" "gioui.org/ui/draw"
"gioui.org/ui/gesture" "gioui.org/ui/gesture"
"gioui.org/ui/input"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
) )
@@ -64,7 +65,7 @@ func (l *List) Dragging() bool {
return l.scroll.Dragging() return l.scroll.Dragging()
} }
func (l *List) Update(c *ui.Config, q pointer.Events) { func (l *List) Update(c *ui.Config, q input.Events) {
l.Distance = 0 l.Distance = 0
d := l.scroll.Update(c, q, gesture.Axis(l.Axis)) d := l.scroll.Update(c, q, gesture.Axis(l.Axis))
l.scrollDir = d l.scrollDir = d
+12 -15
View File
@@ -37,16 +37,12 @@ type OpHandler struct {
type Key interface{} type Key interface{}
type Events interface { type HitResult uint8
For(k Key) []Event
}
type hitResult uint8
const ( const (
hitNone hitResult = iota HitNone HitResult = iota
hitTransparent HitTransparent
hitOpaque HitOpaque
) )
type ID uint16 type ID uint16
@@ -103,7 +99,7 @@ func (op OpArea) Add(o *ui.Ops) {
o.Write(data) o.Write(data)
} }
func (op *OpArea) decode(d []byte) { func (op *OpArea) Decode(d []byte) {
if ops.OpType(d[0]) != ops.TypeArea { if ops.OpType(d[0]) != ops.TypeArea {
panic("invalid op") panic("invalid op")
} }
@@ -118,10 +114,10 @@ func (op *OpArea) decode(d []byte) {
} }
} }
func (op *OpArea) hit(pos f32.Point) hitResult { func (op *OpArea) Hit(pos f32.Point) HitResult {
res := hitOpaque res := HitOpaque
if op.Transparent { if op.Transparent {
res = hitTransparent res = HitTransparent
} }
switch op.kind { switch op.kind {
case areaRect: case areaRect:
@@ -129,7 +125,7 @@ func (op *OpArea) hit(pos f32.Point) hitResult {
0 <= pos.Y && pos.Y < float32(op.size.Y) { 0 <= pos.Y && pos.Y < float32(op.size.Y) {
return res return res
} else { } else {
return hitNone return HitNone
} }
case areaEllipse: case areaEllipse:
rx := float32(op.size.X) / 2 rx := float32(op.size.X) / 2
@@ -141,7 +137,7 @@ func (op *OpArea) hit(pos f32.Point) hitResult {
if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 { if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 {
return res return res
} else { } else {
return hitNone return HitNone
} }
default: default:
panic("invalid area kind") panic("invalid area kind")
@@ -206,4 +202,5 @@ func (s Source) String() string {
} }
} }
func (Event) ImplementsEvent() {} func (Event) ImplementsEvent() {}
func (Event) ImplementsInputEvent() {}
+5 -4
View File
@@ -11,6 +11,7 @@ import (
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/draw" "gioui.org/ui/draw"
"gioui.org/ui/gesture" "gioui.org/ui/gesture"
"gioui.org/ui/input"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/layout" "gioui.org/ui/layout"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
@@ -53,7 +54,7 @@ const (
maxBlinkDuration = 10 * time.Second maxBlinkDuration = 10 * time.Second
) )
func (e *Editor) Update(c *ui.Config, pq pointer.Events, kq key.Events) { func (e *Editor) Update(c *ui.Config, q input.Events) {
if e.cfg == nil || c.PxPerDp != e.cfg.PxPerDp || c.PxPerSp != e.cfg.PxPerSp { if e.cfg == nil || c.PxPerDp != e.cfg.PxPerDp || c.PxPerSp != e.cfg.PxPerSp {
e.invalidate() e.invalidate()
} }
@@ -68,7 +69,7 @@ func (e *Editor) Update(c *ui.Config, pq pointer.Events, kq key.Events) {
axis = gesture.Vertical axis = gesture.Vertical
smin, smax = sbounds.Min.Y, sbounds.Max.Y smin, smax = sbounds.Min.Y, sbounds.Max.Y
} }
sdist := e.scroller.Update(c, pq, axis) sdist := e.scroller.Update(c, q, axis)
var soff int var soff int
if e.SingleLine { if e.SingleLine {
e.scrollOff.X += sdist e.scrollOff.X += sdist
@@ -78,7 +79,7 @@ func (e *Editor) Update(c *ui.Config, pq pointer.Events, kq key.Events) {
soff = e.scrollOff.Y soff = e.scrollOff.Y
} }
scrollTo := false scrollTo := false
for _, evt := range e.clicker.Update(pq) { for _, evt := range e.clicker.Update(q) {
switch { switch {
case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse, case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
evt.Type == gesture.TypeClick && evt.Source == pointer.Touch: evt.Type == gesture.TypeClick && evt.Source == pointer.Touch:
@@ -92,7 +93,7 @@ func (e *Editor) Update(c *ui.Config, pq pointer.Events, kq key.Events) {
} }
} }
stop := (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin) stop := (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin)
for _, ke := range kq.For(e) { for _, ke := range q.For(e) {
e.blinkStart = c.Now e.blinkStart = c.Now
switch ke := ke.(type) { switch ke := ke.(type) {
case key.Focus: case key.Focus: