aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/io/router
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/io/router')
-rw-r--r--vendor/gioui.org/io/router/clipboard.go57
-rw-r--r--vendor/gioui.org/io/router/key.go157
-rw-r--r--vendor/gioui.org/io/router/pointer.go830
-rw-r--r--vendor/gioui.org/io/router/router.go270
4 files changed, 1035 insertions, 279 deletions
diff --git a/vendor/gioui.org/io/router/clipboard.go b/vendor/gioui.org/io/router/clipboard.go
new file mode 100644
index 0000000..5f1623c
--- /dev/null
+++ b/vendor/gioui.org/io/router/clipboard.go
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package router
+
+import (
+ "gioui.org/io/event"
+)
+
+type clipboardQueue struct {
+ receivers map[event.Tag]struct{}
+ // request avoid read clipboard every frame while waiting.
+ requested bool
+ text *string
+}
+
+// WriteClipboard returns the most recent text to be copied
+// to the clipboard, if any.
+func (q *clipboardQueue) WriteClipboard() (string, bool) {
+ if q.text == nil {
+ return "", false
+ }
+ text := *q.text
+ q.text = nil
+ return text, true
+}
+
+// ReadClipboard reports if any new handler is waiting
+// to read the clipboard.
+func (q *clipboardQueue) ReadClipboard() bool {
+ if len(q.receivers) == 0 || q.requested {
+ return false
+ }
+ q.requested = true
+ return true
+}
+
+func (q *clipboardQueue) Push(e event.Event, events *handlerEvents) {
+ for r := range q.receivers {
+ events.Add(r, e)
+ delete(q.receivers, r)
+ }
+}
+
+func (q *clipboardQueue) ProcessWriteClipboard(refs []interface{}) {
+ q.text = refs[0].(*string)
+}
+
+func (q *clipboardQueue) ProcessReadClipboard(refs []interface{}) {
+ if q.receivers == nil {
+ q.receivers = make(map[event.Tag]struct{})
+ }
+ tag := refs[0].(event.Tag)
+ if _, ok := q.receivers[tag]; !ok {
+ q.receivers[tag] = struct{}{}
+ q.requested = false
+ }
+}
diff --git a/vendor/gioui.org/io/router/key.go b/vendor/gioui.org/io/router/key.go
index a64544b..9fef7dc 100644
--- a/vendor/gioui.org/io/router/key.go
+++ b/vendor/gioui.org/io/router/key.go
@@ -3,11 +3,8 @@
package router
import (
- "gioui.org/internal/opconst"
- "gioui.org/internal/ops"
"gioui.org/io/event"
"gioui.org/io/key"
- "gioui.org/op"
)
type TextInputState uint8
@@ -15,22 +12,25 @@ type TextInputState uint8
type keyQueue struct {
focus event.Tag
handlers map[event.Tag]*keyHandler
- reader ops.Reader
state TextInputState
+ hint key.InputHint
}
type keyHandler struct {
- active bool
+ // visible will be true if the InputOp is present
+ // in the current frame.
+ visible bool
+ new bool
+ hint key.InputHint
}
-type listenerPriority uint8
-
-const (
- priNone listenerPriority = iota
- priDefault
- priCurrentFocus
- priNewFocus
-)
+// 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
@@ -44,43 +44,60 @@ func (q *keyQueue) InputState() TextInputState {
return q.state
}
-func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
+// InputHint returns the input mode from the most recent key.InputOp.
+func (q *keyQueue) InputHint() (key.InputHint, bool) {
+ if q.focus == nil {
+ return q.hint, false
+ }
+ focused, ok := q.handlers[q.focus]
+ if !ok {
+ return q.hint, false
+ }
+ old := q.hint
+ q.hint = focused.hint
+ return q.hint, old != q.hint
+}
+
+func (q *keyQueue) Reset() {
if q.handlers == nil {
q.handlers = make(map[event.Tag]*keyHandler)
}
for _, h := range q.handlers {
- h.active = false
+ h.visible, h.new = false, false
}
- q.reader.Reset(root)
- focus, pri, hide := q.resolveFocus(events)
+ q.state = TextInputKeep
+}
+
+func (q *keyQueue) Frame(events *handlerEvents, collector keyCollector) {
for k, h := range q.handlers {
- if !h.active {
+ 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
- hide = true
+ q.state = TextInputClose
}
+ } 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 collector.changed && collector.focus != nil {
+ if _, exists := q.handlers[collector.focus]; !exists {
+ collector.focus = nil
}
}
- if 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 {
- hide = true
+ q.state = TextInputClose
}
}
- 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) {
@@ -89,62 +106,38 @@ func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
}
}
-func (q *keyQueue) resolveFocus(events *handlerEvents) (event.Tag, listenerPriority, bool) {
- var k event.Tag
- 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.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]
- if !ok {
- h = new(keyHandler)
- q.handlers[op.Tag] = h
- // Reset the handler on (each) first appearance.
- events.Add(op.Tag, 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 (k *keyCollector) focusOp(tag event.Tag) {
+ k.focus = tag
+ k.changed = true
}
-func (p listenerPriority) replaces(p2 listenerPriority) bool {
- // Favor earliest default focus or latest requested focus.
- return p > p2 || p == p2 && p == priNewFocus
+func (k *keyCollector) softKeyboard(show bool) {
+ if show {
+ k.q.state = TextInputOpen
+ } else {
+ k.q.state = TextInputClose
+ }
}
-func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
- if opconst.OpType(d[0]) != opconst.TypeKeyInput {
- panic("invalid op")
+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
}
- return key.InputOp{
- Tag: refs[0].(event.Tag),
- Focus: d[1] != 0,
+ h.visible = true
+ h.hint = op.Hint
+}
+
+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/vendor/gioui.org/io/router/pointer.go b/vendor/gioui.org/io/router/pointer.go
index 0d0977f..cb30f63 100644
--- a/vendor/gioui.org/io/router/pointer.go
+++ b/vendor/gioui.org/io/router/pointer.go
@@ -3,44 +3,65 @@
package router
import (
- "encoding/binary"
"image"
+ "io"
"gioui.org/f32"
- "gioui.org/internal/opconst"
"gioui.org/internal/ops"
"gioui.org/io/event"
"gioui.org/io/pointer"
- "gioui.org/op"
+ "gioui.org/io/semantic"
+ "gioui.org/io/transfer"
)
type pointerQueue struct {
- hitTree []hitNode
- areas []areaNode
- handlers map[event.Tag]*pointerHandler
- pointers []pointerInfo
- reader ops.Reader
+ hitTree []hitNode
+ areas []areaNode
+ cursors []cursorNode
+ cursor pointer.CursorName
+ handlers map[event.Tag]*pointerHandler
+ pointers []pointerInfo
+ transfers []io.ReadCloser // pending data transfers
scratch []event.Tag
+
+ semantic struct {
+ idsAssigned bool
+ lastID SemanticID
+ // contentIDs maps semantic content to a list of semantic IDs
+ // previously assigned. It is used to maintain stable IDs across
+ // frames.
+ contentIDs map[semanticContent][]semanticID
+ }
}
type hitNode struct {
next int
area int
- // Pass tracks the most recent PassOp mode.
- pass bool
// For handler nodes.
- tag event.Tag
+ tag event.Tag
+ pass bool
+}
+
+type cursorNode struct {
+ name pointer.CursorName
+ area int
}
type pointerInfo struct {
id pointer.ID
pressed bool
handlers []event.Tag
+ // last tracks the last pointer event received,
+ // used while processing frame events.
+ last pointer.Event
// entered tracks the tags that contain the pointer.
entered []event.Tag
+
+ dataSource event.Tag // dragging source tag
+ dataTarget event.Tag // dragging target tag
}
type pointerHandler struct {
@@ -48,80 +69,381 @@ type pointerHandler struct {
active bool
wantsGrab bool
types pointer.Type
+ // min and max horizontal/vertical scroll
+ scrollRange image.Rectangle
+
+ sourceMimes []string
+ targetMimes []string
+ offeredMime string
+ data io.ReadCloser
}
type areaOp struct {
kind areaKind
- rect image.Rectangle
+ rect f32.Rectangle
}
type areaNode struct {
trans f32.Affine2D
- next int
area areaOp
+
+ // Tree indices, with -1 being the sentinel.
+ parent int
+ firstChild int
+ lastChild int
+ sibling int
+
+ semantic struct {
+ valid bool
+ id SemanticID
+ content semanticContent
+ }
}
type areaKind uint8
+// collectState represents the state for pointerCollector.
+type collectState struct {
+ t f32.Affine2D
+ // nodePlusOne is the current node index, plus one to
+ // make the zero value collectState the initial state.
+ nodePlusOne int
+ pass int
+}
+
+// pointerCollector tracks the state needed to update an pointerQueue
+// from pointer ops.
+type pointerCollector struct {
+ q *pointerQueue
+ state collectState
+ nodeStack []int
+}
+
+type semanticContent struct {
+ tag event.Tag
+ label string
+ desc string
+ class semantic.ClassOp
+ gestures SemanticGestures
+ selected bool
+ disabled bool
+}
+
+type semanticID struct {
+ id SemanticID
+ used bool
+}
+
const (
areaRect areaKind = iota
areaEllipse
)
-func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f32.Affine2D, 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.DecodeTransform(encOp.Data)
- t = t.Mul(dop)
- case opconst.TypePointerInput:
- op := decodePointerInputOp(encOp.Data, encOp.Refs)
- q.hitTree = append(q.hitTree, hitNode{
- next: node,
- area: area,
- pass: pass,
- tag: op.Tag,
- })
- node = len(q.hitTree) - 1
- h, ok := q.handlers[op.Tag]
- if !ok {
- h = new(pointerHandler)
- q.handlers[op.Tag] = h
- events.Add(op.Tag, pointer.Event{Type: pointer.Cancel})
- }
- h.active = true
- h.area = area
- h.wantsGrab = h.wantsGrab || op.Grab
- h.types = h.types | op.Types
+func (c *pointerCollector) resetState() {
+ c.state = collectState{}
+}
+
+func (c *pointerCollector) setTrans(t f32.Affine2D) {
+ c.state.t = t
+}
+
+func (c *pointerCollector) clip(op ops.ClipOp) {
+ kind := areaRect
+ if op.Shape == ops.Ellipse {
+ kind = areaEllipse
+ }
+ c.pushArea(kind, frect(op.Bounds))
+}
+
+func (c *pointerCollector) pushArea(kind areaKind, bounds f32.Rectangle) {
+ parentID := c.currentArea()
+ areaID := len(c.q.areas)
+ areaOp := areaOp{kind: kind, rect: bounds}
+ if parentID != -1 {
+ parent := &c.q.areas[parentID]
+ if parent.firstChild == -1 {
+ parent.firstChild = areaID
+ }
+ if siblingID := parent.lastChild; siblingID != -1 {
+ c.q.areas[siblingID].sibling = areaID
+ }
+ parent.lastChild = areaID
+ }
+ an := areaNode{
+ trans: c.state.t,
+ area: areaOp,
+ parent: parentID,
+ sibling: -1,
+ firstChild: -1,
+ lastChild: -1,
+ }
+
+ c.q.areas = append(c.q.areas, an)
+ c.nodeStack = append(c.nodeStack, c.state.nodePlusOne-1)
+ c.addHitNode(hitNode{
+ area: areaID,
+ pass: true,
+ })
+}
+
+// frect converts a rectangle to a f32.Rectangle.
+func frect(r image.Rectangle) f32.Rectangle {
+ return f32.Rectangle{
+ Min: fpt(r.Min), Max: fpt(r.Max),
+ }
+}
+
+// fpt converts a point to a f32.Point.
+func fpt(p image.Point) f32.Point {
+ return f32.Point{
+ X: float32(p.X), Y: float32(p.Y),
+ }
+}
+
+func (c *pointerCollector) popArea() {
+ n := len(c.nodeStack)
+ c.state.nodePlusOne = c.nodeStack[n-1] + 1
+ c.nodeStack = c.nodeStack[:n-1]
+}
+
+func (c *pointerCollector) pass() {
+ c.state.pass++
+}
+
+func (c *pointerCollector) popPass() {
+ c.state.pass--
+}
+
+func (c *pointerCollector) currentArea() int {
+ if i := c.state.nodePlusOne - 1; i != -1 {
+ n := c.q.hitTree[i]
+ return n.area
+ }
+ return -1
+}
+
+func (c *pointerCollector) addHitNode(n hitNode) {
+ n.next = c.state.nodePlusOne - 1
+ c.q.hitTree = append(c.q.hitTree, n)
+ c.state.nodePlusOne = len(c.q.hitTree) - 1 + 1
+}
+
+// newHandler returns the current handler or a new one for tag.
+func (c *pointerCollector) newHandler(tag event.Tag, events *handlerEvents) *pointerHandler {
+ areaID := c.currentArea()
+ c.addHitNode(hitNode{
+ area: areaID,
+ tag: tag,
+ pass: c.state.pass > 0,
+ })
+ h, ok := c.q.handlers[tag]
+ if !ok {
+ h = new(pointerHandler)
+ c.q.handlers[tag] = h
+ // Cancel handlers on (each) first appearance, but don't
+ // trigger redraw.
+ events.AddNoRedraw(tag, pointer.Event{Type: pointer.Cancel})
+ }
+ h.active = true
+ h.area = areaID
+ return h
+}
+
+func (c *pointerCollector) inputOp(op pointer.InputOp, events *handlerEvents) {
+ areaID := c.currentArea()
+ area := &c.q.areas[areaID]
+ area.semantic.content.tag = op.Tag
+ if op.Types&(pointer.Press|pointer.Release) != 0 {
+ area.semantic.content.gestures |= ClickGesture
+ }
+ area.semantic.valid = area.semantic.content.gestures != 0
+ h := c.newHandler(op.Tag, events)
+ h.wantsGrab = h.wantsGrab || op.Grab
+ h.types = h.types | op.Types
+ h.scrollRange = op.ScrollBounds
+}
+
+func (c *pointerCollector) semanticLabel(lbl string) {
+ areaID := c.currentArea()
+ area := &c.q.areas[areaID]
+ area.semantic.valid = true
+ area.semantic.content.label = lbl
+}
+
+func (c *pointerCollector) semanticDesc(desc string) {
+ areaID := c.currentArea()
+ area := &c.q.areas[areaID]
+ area.semantic.valid = true
+ area.semantic.content.desc = desc
+}
+
+func (c *pointerCollector) semanticClass(class semantic.ClassOp) {
+ areaID := c.currentArea()
+ area := &c.q.areas[areaID]
+ area.semantic.valid = true
+ area.semantic.content.class = class
+}
+
+func (c *pointerCollector) semanticSelected(selected bool) {
+ areaID := c.currentArea()
+ area := &c.q.areas[areaID]
+ area.semantic.valid = true
+ area.semantic.content.selected = selected
+}
+
+func (c *pointerCollector) semanticDisabled(disabled bool) {
+ areaID := c.currentArea()
+ area := &c.q.areas[areaID]
+ area.semantic.valid = true
+ area.semantic.content.disabled = disabled
+}
+
+func (c *pointerCollector) cursor(name pointer.CursorName) {
+ c.q.cursors = append(c.q.cursors, cursorNode{
+ name: name,
+ area: len(c.q.areas) - 1,
+ })
+}
+
+func (c *pointerCollector) sourceOp(op transfer.SourceOp, events *handlerEvents) {
+ h := c.newHandler(op.Tag, events)
+ h.sourceMimes = append(h.sourceMimes, op.Type)
+}
+
+func (c *pointerCollector) targetOp(op transfer.TargetOp, events *handlerEvents) {
+ h := c.newHandler(op.Tag, events)
+ h.targetMimes = append(h.targetMimes, op.Type)
+}
+
+func (c *pointerCollector) offerOp(op transfer.OfferOp, events *handlerEvents) {
+ h := c.newHandler(op.Tag, events)
+ h.offeredMime = op.Type
+ h.data = op.Data
+}
+
+func (c *pointerCollector) reset() {
+ c.q.reset()
+ c.resetState()
+ c.nodeStack = c.nodeStack[:0]
+ c.ensureRoot()
+}
+
+// Ensure implicit root area for semantic descriptions to hang onto.
+func (c *pointerCollector) ensureRoot() {
+ if len(c.q.areas) > 0 {
+ return
+ }
+ c.pushArea(areaRect, f32.Rect(-1e6, -1e6, 1e6, 1e6))
+ // Make it semantic to ensure a single semantic root.
+ c.q.areas[0].semantic.valid = true
+}
+
+func (q *pointerQueue) assignSemIDs() {
+ if q.semantic.idsAssigned {
+ return
+ }
+ q.semantic.idsAssigned = true
+ for i, a := range q.areas {
+ if a.semantic.valid {
+ q.areas[i].semantic.id = q.semanticIDFor(a.semantic.content)
}
}
}
-func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
+func (q *pointerQueue) AppendSemantics(nodes []SemanticNode) []SemanticNode {
+ q.assignSemIDs()
+ nodes = q.appendSemanticChildren(nodes, 0)
+ nodes = q.appendSemanticArea(nodes, 0, 0)
+ return nodes
+}
+
+func (q *pointerQueue) appendSemanticArea(nodes []SemanticNode, parentID SemanticID, nodeIdx int) []SemanticNode {
+ areaIdx := nodes[nodeIdx].areaIdx
+ a := q.areas[areaIdx]
+ childStart := len(nodes)
+ nodes = q.appendSemanticChildren(nodes, a.firstChild)
+ childEnd := len(nodes)
+ for i := childStart; i < childEnd; i++ {
+ nodes = q.appendSemanticArea(nodes, a.semantic.id, i)
+ }
+ n := &nodes[nodeIdx]
+ n.ParentID = parentID
+ n.Children = nodes[childStart:childEnd]
+ return nodes
+}
+
+func (q *pointerQueue) appendSemanticChildren(nodes []SemanticNode, areaIdx int) []SemanticNode {
+ if areaIdx == -1 {
+ return nodes
+ }
+ a := q.areas[areaIdx]
+ if semID := a.semantic.id; semID != 0 {
+ cnt := a.semantic.content
+ nodes = append(nodes, SemanticNode{
+ ID: semID,
+ Desc: SemanticDesc{
+ Bounds: f32.Rectangle{
+ Min: a.trans.Transform(a.area.rect.Min),
+ Max: a.trans.Transform(a.area.rect.Max),
+ },
+ Label: cnt.label,
+ Description: cnt.desc,
+ Class: cnt.class,
+ Gestures: cnt.gestures,
+ Selected: cnt.selected,
+ Disabled: cnt.disabled,
+ },
+ areaIdx: areaIdx,
+ })
+ } else {
+ nodes = q.appendSemanticChildren(nodes, a.firstChild)
+ }
+ return q.appendSemanticChildren(nodes, a.sibling)
+}
+
+func (q *pointerQueue) semanticIDFor(content semanticContent) SemanticID {
+ ids := q.semantic.contentIDs[content]
+ for i, id := range ids {
+ if !id.used {
+ ids[i].used = true
+ return id.id
+ }
+ }
+ // No prior assigned ID; allocate a new one.
+ q.semantic.lastID++
+ id := semanticID{id: q.semantic.lastID, used: true}
+ if q.semantic.contentIDs == nil {
+ q.semantic.contentIDs = make(map[semanticContent][]semanticID)
+ }
+ q.semantic.contentIDs[content] = append(q.semantic.contentIDs[content], id)
+ return id.id
+}
+
+func (q *pointerQueue) SemanticAt(pos f32.Point) (SemanticID, bool) {
+ q.assignSemIDs()
+ for i := len(q.hitTree) - 1; i >= 0; i-- {
+ n := &q.hitTree[i]
+ hit := q.hit(n.area, pos)
+ if !hit {
+ continue
+ }
+ area := q.areas[n.area]
+ if area.semantic.id != 0 {
+ return area.semantic.id, true
+ }
+ }
+ return 0, false
+}
+
+func (q *pointerQueue) opHit(pos f32.Point) []event.Tag {
// Track whether we're passing through hits.
pass := true
+ hits := q.scratch[:0]
idx := len(q.hitTree) - 1
for idx >= 0 {
n := &q.hitTree[idx]
- if !q.hit(n.area, pos) {
+ hit := q.hit(n.area, pos)
+ if !hit {
idx--
continue
}
@@ -133,10 +455,12 @@ func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
}
if n.tag != nil {
if _, exists := q.handlers[n.tag]; exists {
- *handlers = append(*handlers, n.tag)
+ hits = addHandler(hits, n.tag)
}
}
}
+ q.scratch = hits[:0]
+ return hits
}
func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
@@ -153,32 +477,53 @@ func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
if !a.area.Hit(p) {
return false
}
- areaIdx = a.next
+ areaIdx = a.parent
}
return true
}
-func (q *pointerQueue) init() {
+func (q *pointerQueue) reset() {
if q.handlers == nil {
q.handlers = make(map[event.Tag]*pointerHandler)
}
-}
-
-func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
- q.init()
for _, h := range q.handlers {
// Reset handler.
h.active = false
h.wantsGrab = false
h.types = 0
+ h.sourceMimes = h.sourceMimes[:0]
+ h.targetMimes = h.targetMimes[:0]
}
q.hitTree = q.hitTree[:0]
q.areas = q.areas[:0]
- q.reader.Reset(root)
- q.collectHandlers(&q.reader, events, f32.Affine2D{}, -1, -1, false)
+ q.cursors = q.cursors[:0]
+ q.semantic.idsAssigned = false
+ for k, ids := range q.semantic.contentIDs {
+ for i := len(ids) - 1; i >= 0; i-- {
+ if !ids[i].used {
+ ids = append(ids[:i], ids[i+1:]...)
+ } else {
+ ids[i].used = false
+ }
+ }
+ if len(ids) > 0 {
+ q.semantic.contentIDs[k] = ids
+ } else {
+ delete(q.semantic.contentIDs, k)
+ }
+ }
+ for _, rc := range q.transfers {
+ if rc != nil {
+ rc.Close()
+ }
+ }
+ q.transfers = nil
+}
+
+func (q *pointerQueue) Frame(events *handlerEvents) {
for k, h := range q.handlers {
if !h.active {
- q.dropHandlers(events, k)
+ q.dropHandler(nil, k)
delete(q.handlers, k)
}
if h.wantsGrab {
@@ -189,86 +534,93 @@ func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
for i, k2 := range p.handlers {
if k2 == k {
// Drop other handlers that lost their grab.
- q.dropHandlers(events, p.handlers[i+1:]...)
- q.dropHandlers(events, p.handlers[:i]...)
+ dropped := q.scratch[:0]
+ dropped = append(dropped, p.handlers[:i]...)
+ dropped = append(dropped, p.handlers[i+1:]...)
+ for _, tag := range dropped {
+ q.dropHandler(events, tag)
+ }
break
}
}
}
}
}
+ for i := range q.pointers {
+ p := &q.pointers[i]
+ q.deliverEnterLeaveEvents(p, events, p.last)
+ q.deliverTransferDataEvent(p, events)
+ }
}
-func (q *pointerQueue) dropHandlers(events *handlerEvents, tags ...event.Tag) {
- for _, k := range tags {
- events.Add(k, pointer.Event{Type: pointer.Cancel})
- 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) dropHandler(events *handlerEvents, tag event.Tag) {
+ if events != nil {
+ events.Add(tag, pointer.Event{Type: pointer.Cancel})
+ }
+ for i := range q.pointers {
+ p := &q.pointers[i]
+ for i := len(p.handlers) - 1; i >= 0; i-- {
+ if p.handlers[i] == tag {
+ p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
}
- for i := len(p.entered) - 1; i >= 0; i-- {
- if p.entered[i] == k {
- p.entered = append(p.entered[:i], p.entered[i+1:]...)
- }
+ }
+ for i := len(p.entered) - 1; i >= 0; i-- {
+ if p.entered[i] == tag {
+ p.entered = append(p.entered[:i], p.entered[i+1:]...)
}
}
}
}
+// pointerOf returns the pointerInfo index corresponding to the pointer in e.
+func (q *pointerQueue) pointerOf(e pointer.Event) int {
+ for i, p := range q.pointers {
+ if p.id == e.PointerID {
+ return i
+ }
+ }
+ q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
+ return len(q.pointers) - 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.dropHandlers(events, k)
+ q.dropHandler(events, k)
}
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
- }
+ pidx := q.pointerOf(e)
p := &q.pointers[pidx]
+ p.last = e
- if e.Type == pointer.Move && p.pressed {
- e.Type = pointer.Drag
- }
-
- if e.Type == pointer.Release {
+ switch e.Type {
+ case pointer.Press:
+ q.deliverEnterLeaveEvents(p, events, e)
+ p.pressed = true
q.deliverEvent(p, events, e)
- p.pressed = false
- }
- q.scratch = q.scratch[:0]
- q.opHit(&q.scratch, e.Position)
- if p.pressed {
- // Filter out non-participating handlers.
- for i := len(q.scratch) - 1; i >= 0; i-- {
- if _, found := searchTag(p.handlers, q.scratch[i]); !found {
- q.scratch = append(q.scratch[:i], q.scratch[i+1:]...)
- }
+ case pointer.Move:
+ if p.pressed {
+ e.Type = pointer.Drag
}
- }
- q.deliverEnterLeaveEvents(p, q.scratch, events, e)
-
- if !p.pressed {
- p.handlers = append(p.handlers[:0], q.scratch...)
- }
- if e.Type == pointer.Press {
- p.pressed = true
- }
- if e.Type != pointer.Release {
+ q.deliverEnterLeaveEvents(p, events, e)
q.deliverEvent(p, events, e)
+ if p.pressed {
+ q.deliverDragEvent(p, events)
+ }
+ case pointer.Release:
+ q.deliverEvent(p, events, e)
+ p.pressed = false
+ q.deliverEnterLeaveEvents(p, events, e)
+ q.deliverDropEvent(p, events)
+ case pointer.Scroll:
+ q.deliverEnterLeaveEvents(p, events, e)
+ q.deliverScrollEvent(p, events, e)
+ default:
+ panic("unsupported pointer event type")
}
+
if !p.pressed && len(p.entered) == 0 {
// No longer need to track pointer.
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
@@ -277,28 +629,77 @@ func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
foremost := true
+ if p.pressed && len(p.handlers) == 1 {
+ e.Priority = pointer.Grabbed
+ foremost = false
+ }
for _, k := range p.handlers {
h := q.handlers[k]
+ if e.Type&h.types == 0 {
+ continue
+ }
e := e
- if p.pressed && len(p.handlers) == 1 {
- e.Priority = pointer.Grabbed
- } else if foremost {
+ if foremost {
+ foremost = false
e.Priority = pointer.Foremost
}
-
e.Position = q.invTransform(h.area, e.Position)
+ events.Add(k, e)
+ }
+}
- if e.Type&h.types == e.Type {
+func (q *pointerQueue) deliverScrollEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
+ foremost := true
+ if p.pressed && len(p.handlers) == 1 {
+ e.Priority = pointer.Grabbed
+ foremost = false
+ }
+ var sx, sy = e.Scroll.X, e.Scroll.Y
+ for _, k := range p.handlers {
+ if sx == 0 && sy == 0 {
+ return
+ }
+ h := q.handlers[k]
+ // Distribute the scroll to the handler based on its ScrollRange.
+ sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
+ sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
+ e := e
+ if foremost {
foremost = false
- events.Add(k, e)
+ e.Priority = pointer.Foremost
}
+ e.Position = q.invTransform(h.area, e.Position)
+ events.Add(k, e)
}
}
-func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, hits []event.Tag, events *handlerEvents, e pointer.Event) {
+func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
+ var hits []event.Tag
if e.Source != pointer.Mouse && !p.pressed && e.Type != pointer.Press {
// Consider non-mouse pointers leaving when they're released.
- hits = nil
+ } else {
+ hits = q.opHit(e.Position)
+ if p.pressed {
+ // Filter out non-participating handlers,
+ // except potential transfer targets when a transfer has been initiated.
+ var hitsHaveTarget bool
+ if p.dataSource != nil {
+ transferSource := q.handlers[p.dataSource]
+ for _, hit := range hits {
+ if _, ok := firstMimeMatch(transferSource, q.handlers[hit]); ok {
+ hitsHaveTarget = true
+ break
+ }
+ }
+ }
+ for i := len(hits) - 1; i >= 0; i-- {
+ if _, found := searchTag(p.handlers, hits[i]); !found && !hitsHaveTarget {
+ hits = append(hits[:i], hits[i+1:]...)
+ }
+ }
+ } else {
+ p.handlers = append(p.handlers[:0], hits...)
+ }
}
// Deliver Leave events.
for _, k := range p.entered {
@@ -307,28 +708,116 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, hits []event.Tag,
}
h := q.handlers[k]
e.Type = pointer.Leave
- e.Position = q.invTransform(h.area, e.Position)
- if e.Type&h.types == e.Type {
+ if e.Type&h.types != 0 {
+ e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e)
}
}
- // Deliver Enter events.
+ // Deliver Enter events and update cursor.
+ q.cursor = pointer.CursorDefault
for _, k := range hits {
+ h := q.handlers[k]
+ for i := len(q.cursors) - 1; i >= 0; i-- {
+ if c := q.cursors[i]; c.area == h.area {
+ q.cursor = c.name
+ break
+ }
+ }
if _, found := searchTag(p.entered, k); found {
continue
}
- h := q.handlers[k]
e.Type = pointer.Enter
- e.Position = q.invTransform(h.area, e.Position)
- if e.Type&h.types == e.Type {
+ if e.Type&h.types != 0 {
+ e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e)
}
}
p.entered = append(p.entered[:0], hits...)
}
+func (q *pointerQueue) deliverDragEvent(p *pointerInfo, events *handlerEvents) {
+ if p.dataSource != nil {
+ return
+ }
+ // Identify the data source.
+ for _, k := range p.entered {
+ src := q.handlers[k]
+ if len(src.sourceMimes) == 0 {
+ continue
+ }
+ // One data source handler per pointer.
+ p.dataSource = k
+ // Notify all potential targets.
+ for k, tgt := range q.handlers {
+ if _, ok := firstMimeMatch(src, tgt); ok {
+ events.Add(k, transfer.InitiateEvent{})
+ }
+ }
+ break
+ }
+}
+
+func (q *pointerQueue) deliverDropEvent(p *pointerInfo, events *handlerEvents) {
+ if p.dataSource == nil {
+ return
+ }
+ // Request data from the source.
+ src := q.handlers[p.dataSource]
+ for _, k := range p.entered {
+ h := q.handlers[k]
+ if m, ok := firstMimeMatch(src, h); ok {
+ p.dataTarget = k
+ events.Add(p.dataSource, transfer.RequestEvent{Type: m})
+ return
+ }
+ }
+ // No valid target found, abort.
+ q.deliverTransferCancelEvent(p, events)
+}
+
+func (q *pointerQueue) deliverTransferDataEvent(p *pointerInfo, events *handlerEvents) {
+ if p.dataSource == nil {
+ return
+ }
+ src := q.handlers[p.dataSource]
+ if src.data == nil {
+ // Data not received yet.
+ return
+ }
+ if p.dataTarget == nil {
+ q.deliverTransferCancelEvent(p, events)
+ return
+ }
+ // Send the offered data to the target.
+ transferIdx := len(q.transfers)
+ events.Add(p.dataTarget, transfer.DataEvent{
+ Type: src.offeredMime,
+ Open: func() io.ReadCloser {
+ q.transfers[transferIdx] = nil
+ return src.data
+ },
+ })
+ q.transfers = append(q.transfers, src.data)
+ p.dataTarget = nil
+}
+
+func (q *pointerQueue) deliverTransferCancelEvent(p *pointerInfo, events *handlerEvents) {
+ events.Add(p.dataSource, transfer.CancelEvent{})
+ // Cancel all potential targets.
+ src := q.handlers[p.dataSource]
+ for k, h := range q.handlers {
+ if _, ok := firstMimeMatch(src, h); ok {
+ events.Add(k, transfer.CancelEvent{})
+ }
+ }
+ src.offeredMime = ""
+ src.data = nil
+ p.dataSource = nil
+ p.dataTarget = nil
+}
+
func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
for i, t := range tags {
if t == tag {
@@ -338,41 +827,38 @@ func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
return 0, 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:]))),
- },
+// addHandler adds tag to the slice if not present.
+func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
+ for _, t := range tags {
+ if t == tag {
+ return tags
+ }
}
- *op = areaOp{
- kind: areaKind(d[1]),
- rect: rect,
+ return append(tags, tag)
+}
+
+// firstMimeMatch returns the first type match between src and tgt.
+func firstMimeMatch(src, tgt *pointerHandler) (first string, matched bool) {
+ for _, m1 := range tgt.targetMimes {
+ for _, m2 := range src.sourceMimes {
+ if m1 == m2 {
+ return m1, true
+ }
+ }
}
+ return "", false
}
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)
+ pos = pos.Sub(op.rect.Min)
size := op.rect.Size()
switch op.kind {
case areaRect:
- return 0 <= pos.X && pos.X < float32(size.X) &&
- 0 <= pos.Y && pos.Y < float32(size.Y)
+ return 0 <= pos.X && pos.X < size.X &&
+ 0 <= pos.Y && pos.Y < size.Y
case areaEllipse:
- rx := float32(size.X) / 2
- ry := float32(size.Y) / 2
+ rx := size.X / 2
+ ry := size.Y / 2
xh := pos.X - rx
yk := pos.Y - ry
// The ellipse function works in all cases because
@@ -383,22 +869,12 @@ func (op *areaOp) Hit(pos f32.Point) bool {
}
}
-func decodePointerInputOp(d []byte, refs []interface{}) pointer.InputOp {
- if opconst.OpType(d[0]) != opconst.TypePointerInput {
- panic("invalid op")
- }
- return pointer.InputOp{
- Tag: refs[0].(event.Tag),
- Grab: d[1] != 0,
- Types: pointer.Type(d[2]),
- }
-}
-
-func decodePassOp(d []byte) pointer.PassOp {
- if opconst.OpType(d[0]) != opconst.TypePass {
- panic("invalid op")
+func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) {
+ if v := float32(max); scroll > v {
+ return scroll - v, v
}
- return pointer.PassOp{
- Pass: d[1] != 0,
+ if v := float32(min); scroll < v {
+ return scroll - v, v
}
+ return 0, scroll
}
diff --git a/vendor/gioui.org/io/router/router.go b/vendor/gioui.org/io/router/router.go
index d24d214..a02f0f6 100644
--- a/vendor/gioui.org/io/router/router.go
+++ b/vendor/gioui.org/io/router/router.go
@@ -12,22 +12,37 @@ package router
import (
"encoding/binary"
+ "image"
+ "io"
+ "strings"
"time"
- "gioui.org/internal/opconst"
+ "gioui.org/f32"
"gioui.org/internal/ops"
+ "gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/profile"
+ "gioui.org/io/semantic"
+ "gioui.org/io/transfer"
"gioui.org/op"
)
// Router is a Queue implementation that routes events
// to handlers declared in operation lists.
type Router struct {
- pqueue pointerQueue
- kqueue keyQueue
+ savedTrans []f32.Affine2D
+ transStack []f32.Affine2D
+ pointer struct {
+ queue pointerQueue
+ collector pointerCollector
+ }
+ key struct {
+ queue keyQueue
+ collector keyCollector
+ }
+ cqueue clipboardQueue
handlers handlerEvents
@@ -38,11 +53,44 @@ type Router struct {
wakeupTime time.Time
// ProfileOp summary.
- profiling bool
profHandlers map[event.Tag]struct{}
profile profile.Event
}
+// SemanticNode represents a node in the tree describing the components
+// contained in a frame.
+type SemanticNode struct {
+ ID SemanticID
+ ParentID SemanticID
+ Children []SemanticNode
+ Desc SemanticDesc
+
+ areaIdx int
+}
+
+// SemanticDesc provides a semantic description of a UI component.
+type SemanticDesc struct {
+ Class semantic.ClassOp
+ Description string
+ Label string
+ Selected bool
+ Disabled bool
+ Gestures SemanticGestures
+ Bounds f32.Rectangle
+}
+
+// SemanticGestures is a bit-set of supported gestures.
+type SemanticGestures int
+
+const (
+ ClickGesture SemanticGestures = 1 << iota
+)
+
+// SemanticID uniquely identifies a SemanticDescription.
+//
+// By convention, the zero value denotes the non-existent ID.
+type SemanticID uint64
+
type handlerEvents struct {
handlers map[event.Tag][]event.Event
hadEvents bool
@@ -61,33 +109,39 @@ func (q *Router) Events(k event.Tag) []event.Event {
// Frame replaces the declared handlers from the supplied
// operation list. The text input state, wakeup time and whether
// there are active profile handlers is also saved.
-func (q *Router) Frame(ops *op.Ops) {
+func (q *Router) Frame(frame *op.Ops) {
q.handlers.Clear()
q.wakeup = false
- q.profiling = false
for k := range q.profHandlers {
delete(q.profHandlers, k)
}
+ var ops *ops.Ops
+ if frame != nil {
+ ops = &frame.Internal
+ }
q.reader.Reset(ops)
q.collect()
- q.pqueue.Frame(ops, &q.handlers)
- q.kqueue.Frame(ops, &q.handlers)
+ q.pointer.queue.Frame(&q.handlers)
+ q.key.queue.Frame(&q.handlers, q.key.collector)
if q.handlers.HadEvents() {
q.wakeup = true
q.wakeupTime = time.Time{}
}
}
-func (q *Router) Add(events ...event.Event) bool {
+// Queue an event and report whether at least one handler had an event queued.
+func (q *Router) Queue(events ...event.Event) bool {
for _, e := range events {
switch e := e.(type) {
case profile.Event:
q.profile = e
case pointer.Event:
- q.pqueue.Push(e, &q.handlers)
+ 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)
}
}
return q.handlers.HadEvents()
@@ -96,25 +150,189 @@ func (q *Router) Add(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.key.queue.InputHint()
+}
+
+// WriteClipboard returns the most recent text to be copied
+// to the clipboard, if any.
+func (q *Router) WriteClipboard() (string, bool) {
+ return q.cqueue.WriteClipboard()
+}
+
+// ReadClipboard reports if any new handler is waiting
+// to read the clipboard.
+func (q *Router) ReadClipboard() bool {
+ return q.cqueue.ReadClipboard()
+}
+
+// Cursor returns the last cursor set.
+func (q *Router) Cursor() pointer.CursorName {
+ return q.pointer.queue.cursor
+}
+
+// SemanticAt returns the first semantic description under pos, if any.
+func (q *Router) SemanticAt(pos f32.Point) (SemanticID, bool) {
+ return q.pointer.queue.SemanticAt(pos)
+}
+
+// AppendSemantics appends the semantic tree to nodes, and returns the result.
+// The root node is the first added.
+func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode {
+ q.pointer.collector.q = &q.pointer.queue
+ q.pointer.collector.ensureRoot()
+ return q.pointer.queue.AppendSemantics(nodes)
}
func (q *Router) collect() {
+ q.transStack = q.transStack[:0]
+ pc := &q.pointer.collector
+ pc.q = &q.pointer.queue
+ pc.reset()
+ kc := &q.key.collector
+ *kc = keyCollector{q: &q.key.queue}
+ q.key.queue.Reset()
+ var t f32.Affine2D
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
- switch opconst.OpType(encOp.Data[0]) {
- case opconst.TypeInvalidate:
+ switch ops.OpType(encOp.Data[0]) {
+ case ops.TypeInvalidate:
op := decodeInvalidateOp(encOp.Data)
if !q.wakeup || op.At.Before(q.wakeupTime) {
q.wakeup = true
q.wakeupTime = op.At
}
- case opconst.TypeProfile:
+ case ops.TypeProfile:
op := decodeProfileOp(encOp.Data, encOp.Refs)
if q.profHandlers == nil {
q.profHandlers = make(map[event.Tag]struct{})
}
- q.profiling = true
q.profHandlers[op.Tag] = struct{}{}
+ case ops.TypeClipboardRead:
+ q.cqueue.ProcessReadClipboard(encOp.Refs)
+ case ops.TypeClipboardWrite:
+ q.cqueue.ProcessWriteClipboard(encOp.Refs)
+ case ops.TypeSave:
+ id := ops.DecodeSave(encOp.Data)
+ if extra := id - len(q.savedTrans) + 1; extra > 0 {
+ q.savedTrans = append(q.savedTrans, make([]f32.Affine2D, extra)...)
+ }
+ q.savedTrans[id] = t
+ case ops.TypeLoad:
+ id := ops.DecodeLoad(encOp.Data)
+ t = q.savedTrans[id]
+ pc.resetState()
+ pc.setTrans(t)
+
+ case ops.TypeClip:
+ var op ops.ClipOp
+ op.Decode(encOp.Data)
+ pc.clip(op)
+ case ops.TypePopClip:
+ pc.popArea()
+ case ops.TypeTransform:
+ t2, push := ops.DecodeTransform(encOp.Data)
+ if push {
+ q.transStack = append(q.transStack, t)
+ }
+ t = t.Mul(t2)
+ pc.setTrans(t)
+ case ops.TypePopTransform:
+ n := len(q.transStack)
+ t = q.transStack[n-1]
+ q.transStack = q.transStack[:n-1]
+ pc.setTrans(t)
+
+ // Pointer ops.
+ case ops.TypePass:
+ pc.pass()
+ case ops.TypePopPass:
+ pc.popPass()
+ case ops.TypePointerInput:
+ bo := binary.LittleEndian
+ op := pointer.InputOp{
+ Tag: encOp.Refs[0].(event.Tag),
+ Grab: encOp.Data[1] != 0,
+ Types: pointer.Type(bo.Uint16(encOp.Data[2:])),
+ ScrollBounds: image.Rectangle{
+ Min: image.Point{
+ X: int(int32(bo.Uint32(encOp.Data[4:]))),
+ Y: int(int32(bo.Uint32(encOp.Data[8:]))),
+ },
+ Max: image.Point{
+ X: int(int32(bo.Uint32(encOp.Data[12:]))),
+ Y: int(int32(bo.Uint32(encOp.Data[16:]))),
+ },
+ },
+ }
+ pc.inputOp(op, &q.handlers)
+ case ops.TypeCursor:
+ name := encOp.Refs[0].(pointer.CursorName)
+ pc.cursor(name)
+ case ops.TypeSource:
+ op := transfer.SourceOp{
+ Tag: encOp.Refs[0].(event.Tag),
+ Type: encOp.Refs[1].(string),
+ }
+ pc.sourceOp(op, &q.handlers)
+ case ops.TypeTarget:
+ op := transfer.TargetOp{
+ Tag: encOp.Refs[0].(event.Tag),
+ Type: encOp.Refs[1].(string),
+ }
+ pc.targetOp(op, &q.handlers)
+ case ops.TypeOffer:
+ op := transfer.OfferOp{
+ Tag: encOp.Refs[0].(event.Tag),
+ Type: encOp.Refs[1].(string),
+ Data: encOp.Refs[2].(io.ReadCloser),
+ }
+ pc.offerOp(op, &q.handlers)
+
+ // Key ops.
+ case ops.TypeKeyFocus:
+ tag, _ := encOp.Refs[0].(event.Tag)
+ op := key.FocusOp{
+ Tag: tag,
+ }
+ kc.focusOp(op.Tag)
+ case ops.TypeKeySoftKeyboard:
+ op := key.SoftKeyboardOp{
+ Show: encOp.Data[1] != 0,
+ }
+ kc.softKeyboard(op.Show)
+ case ops.TypeKeyInput:
+ op := key.InputOp{
+ Tag: encOp.Refs[0].(event.Tag),
+ Hint: key.InputHint(encOp.Data[1]),
+ }
+ kc.inputOp(op)
+
+ // Semantic ops.
+ case ops.TypeSemanticLabel:
+ lbl := encOp.Refs[0].(*string)
+ pc.semanticLabel(*lbl)
+ case ops.TypeSemanticDesc:
+ desc := encOp.Refs[0].(*string)
+ pc.semanticDesc(*desc)
+ case ops.TypeSemanticClass:
+ class := semantic.ClassOp(encOp.Data[1])
+ pc.semanticClass(class)
+ case ops.TypeSemanticSelected:
+ if encOp.Data[1] != 0 {
+ pc.semanticSelected(true)
+ } else {
+ pc.semanticSelected(false)
+ }
+ case ops.TypeSemanticDisabled:
+ if encOp.Data[1] != 0 {
+ pc.semanticDisabled(true)
+ } else {
+ pc.semanticDisabled(false)
+ }
}
}
}
@@ -122,7 +340,7 @@ func (q *Router) collect() {
// Profiling reports whether there was profile handlers in the
// most recent Frame call.
func (q *Router) Profiling() bool {
- return q.profiling
+ return len(q.profHandlers) > 0
}
// WakeupTime returns the most recent time for doing another frame,
@@ -137,9 +355,13 @@ func (h *handlerEvents) init() {
}
}
-func (h *handlerEvents) Add(k event.Tag, e event.Event) {
+func (h *handlerEvents) AddNoRedraw(k event.Tag, e event.Event) {
h.init()
h.handlers[k] = append(h.handlers[k], e)
+}
+
+func (h *handlerEvents) Add(k event.Tag, e event.Event) {
+ h.AddNoRedraw(k, e)
h.hadEvents = true
}
@@ -174,7 +396,7 @@ func (h *handlerEvents) Clear() {
}
func decodeProfileOp(d []byte, refs []interface{}) profile.Op {
- if opconst.OpType(d[0]) != opconst.TypeProfile {
+ if ops.OpType(d[0]) != ops.TypeProfile {
panic("invalid op")
}
return profile.Op{
@@ -184,7 +406,7 @@ func decodeProfileOp(d []byte, refs []interface{}) profile.Op {
func decodeInvalidateOp(d []byte) op.InvalidateOp {
bo := binary.LittleEndian
- if opconst.OpType(d[0]) != opconst.TypeInvalidate {
+ if ops.OpType(d[0]) != ops.TypeInvalidate {
panic("invalid op")
}
var o op.InvalidateOp
@@ -193,3 +415,11 @@ func decodeInvalidateOp(d []byte) op.InvalidateOp {
}
return o
}
+
+func (s SemanticGestures) String() string {
+ var gestures []string
+ if s&ClickGesture != 0 {
+ gestures = append(gestures, "Click")
+ }
+ return strings.Join(gestures, ",")
+}