diff options
Diffstat (limited to 'vendor/gioui.org/io/router/pointer.go')
-rw-r--r-- | vendor/gioui.org/io/router/pointer.go | 830 |
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 } |