aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/io/router/pointer.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/io/router/pointer.go')
-rw-r--r--vendor/gioui.org/io/router/pointer.go830
1 files changed, 653 insertions, 177 deletions
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
}