Files
gio-patched/io/input/pointer.go
Walter Werner SCHNEIDER 4e5a344cc2 f32: replace Affine2D{} with AffineId() for identity transformations
References: https://todo.sr.ht/~eliasnaur/gio/655
Signed-off-by: Walter Werner SCHNEIDER <contact@schnwalter.eu>
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-07-12 15:28:03 +02:00

1009 lines
24 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"image"
"io"
"slices"
"gioui.org/f32"
f32internal "gioui.org/internal/f32"
"gioui.org/internal/ops"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/io/semantic"
"gioui.org/io/system"
"gioui.org/io/transfer"
)
type pointerQueue struct {
hitTree []hitNode
areas []areaNode
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
// For handler nodes.
tag event.Tag
pass bool
}
// pointerState is the input state related to pointer events.
type pointerState struct {
cursor pointer.Cursor
pointers []pointerInfo
}
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 {
// areaPlusOne is the index into the list of pointerQueue.areas, plus 1.
areaPlusOne int
// setup tracks whether the handler has received
// the pointer.Cancel event that resets its state.
setup bool
}
// pointerFilter represents the union of a set of pointer filters.
type pointerFilter struct {
kinds pointer.Kind
// min and max horizontal/vertical scroll
scrollX, scrollY pointer.ScrollRange
sourceMimes []string
targetMimes []string
}
type areaOp struct {
kind areaKind
rect image.Rectangle
}
type areaNode struct {
trans f32.Affine2D
area areaOp
cursor pointer.Cursor
// Tree indices, with -1 being the sentinel.
parent int
firstChild int
lastChild int
sibling int
semantic struct {
valid bool
id SemanticID
content semanticContent
}
action system.Action
}
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 (c *pointerCollector) resetState() {
c.state = collectState{
t: f32.AffineId(),
}
c.nodeStack = c.nodeStack[:0]
// Pop every node except the root.
if len(c.q.hitTree) > 0 {
c.state.nodePlusOne = 0 + 1
}
}
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, op.Bounds)
}
func (c *pointerCollector) pushArea(kind areaKind, bounds image.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,
})
}
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) currentAreaBounds() image.Rectangle {
a := c.currentArea()
if a == -1 {
panic("no root area")
}
return c.q.areas[a].bounds()
}
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, state *pointerHandler) {
areaID := c.currentArea()
c.addHitNode(hitNode{
area: areaID,
tag: tag,
pass: c.state.pass > 0,
})
state.areaPlusOne = areaID + 1
}
func (s *pointerHandler) Reset() {
s.areaPlusOne = 0
}
func (c *pointerCollector) actionInputOp(act system.Action) {
areaID := c.currentArea()
area := &c.q.areas[areaID]
area.action = act
}
func (q *pointerQueue) grab(state pointerState, req pointer.GrabCmd) (pointerState, []taggedEvent) {
var evts []taggedEvent
for _, p := range state.pointers {
if !p.pressed || p.id != req.ID {
continue
}
// Verify that the grabber is among the handlers.
found := slices.Contains(p.handlers, req.Tag)
if !found {
continue
}
// Drop other handlers that lost their grab.
for i := len(p.handlers) - 1; i >= 0; i-- {
if tag := p.handlers[i]; tag != req.Tag {
evts = append(evts, taggedEvent{
tag: tag,
event: pointer.Event{Kind: pointer.Cancel},
})
state = dropHandler(state, tag)
}
}
break
}
return state, evts
}
func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) {
areaID := c.currentArea()
area := &c.q.areas[areaID]
area.semantic.content.tag = tag
c.newHandler(tag, state)
}
func (p *pointerFilter) Add(f event.Filter) {
switch f := f.(type) {
case transfer.SourceFilter:
if slices.Contains(p.sourceMimes, f.Type) {
return
}
p.sourceMimes = append(p.sourceMimes, f.Type)
case transfer.TargetFilter:
if slices.Contains(p.targetMimes, f.Type) {
return
}
p.targetMimes = append(p.targetMimes, f.Type)
case pointer.Filter:
p.kinds = p.kinds | f.Kinds
p.scrollX = p.scrollX.Union(f.ScrollX)
p.scrollY = p.scrollY.Union(f.ScrollY)
}
}
func (p *pointerFilter) Matches(e event.Event) bool {
switch e := e.(type) {
case pointer.Event:
return e.Kind&p.kinds == e.Kind
case transfer.CancelEvent, transfer.InitiateEvent:
return len(p.sourceMimes) > 0 || len(p.targetMimes) > 0
case transfer.RequestEvent:
if slices.Contains(p.sourceMimes, e.Type) {
return true
}
case transfer.DataEvent:
if slices.Contains(p.targetMimes, e.Type) {
return true
}
}
return false
}
func (p *pointerFilter) Merge(p2 pointerFilter) {
p.kinds = p.kinds | p2.kinds
p.scrollX = p.scrollX.Union(p2.scrollX)
p.scrollY = p.scrollY.Union(p2.scrollY)
p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
p.targetMimes = append(p.targetMimes, p2.targetMimes...)
}
// clampScroll splits a scroll distance in the remaining scroll and the
// scroll accepted by the filter.
func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) {
left.X, scrolled.X = clampSplit(scroll.X, p.scrollX.Min, p.scrollX.Max)
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollY.Min, p.scrollY.Max)
return
}
func clampSplit(v float32, min, max int) (float32, float32) {
if m := float32(max); v > m {
return v - m, m
}
if m := float32(min); v < m {
return v - m, m
}
return 0, v
}
func (s *pointerHandler) ResetEvent() (event.Event, bool) {
if s.setup {
return nil, false
}
s.setup = true
return pointer.Event{Kind: pointer.Cancel}, true
}
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) semanticEnabled(enabled bool) {
areaID := c.currentArea()
area := &c.q.areas[areaID]
area.semantic.valid = true
area.semantic.content.disabled = !enabled
}
func (c *pointerCollector) cursor(cursor pointer.Cursor) {
areaID := c.currentArea()
area := &c.q.areas[areaID]
area.cursor = cursor
}
func (q *pointerQueue) offerData(handlers map[event.Tag]*handler, state pointerState, req transfer.OfferCmd) (pointerState, []taggedEvent) {
var evts []taggedEvent
for i, p := range state.pointers {
if p.dataSource != req.Tag {
continue
}
if p.dataTarget != nil {
evts = append(evts, taggedEvent{tag: p.dataTarget, event: transfer.DataEvent{
Type: req.Type,
Open: func() io.ReadCloser {
return req.Data
},
}})
}
state.pointers = slices.Clone(state.pointers)
state.pointers[i], evts = q.deliverTransferCancelEvent(handlers, p, evts)
break
}
return state, evts
}
func (c *pointerCollector) Reset() {
c.q.reset()
c.resetState()
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, image.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) 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: a.bounds(),
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) ActionAt(pos f32.Point) (action system.Action, hasAction bool) {
q.hitTest(pos, func(n *hitNode) bool {
area := q.areas[n.area]
if area.action != 0 {
action = area.action
hasAction = true
return false
}
return true
})
return action, hasAction
}
func (q *pointerQueue) SemanticAt(pos f32.Point) (semID SemanticID, hasSemID bool) {
q.assignSemIDs()
q.hitTest(pos, func(n *hitNode) bool {
area := q.areas[n.area]
if area.semantic.id != 0 {
semID = area.semantic.id
hasSemID = true
return false
}
return true
})
return semID, hasSemID
}
// hitTest searches the hit tree for nodes matching pos. Any node matching pos will
// have the onNode func invoked on it to allow the caller to extract whatever information
// is necessary for further processing. onNode may return false to terminate the walk of
// the hit tree, or true to continue. Providing this algorithm in this generic way
// allows normal event routing and system action event routing to share the same traversal
// logic even though they are interested in different aspects of hit nodes.
func (q *pointerQueue) hitTest(pos f32.Point, onNode func(*hitNode) bool) pointer.Cursor {
// Track whether we're passing through hits.
pass := true
idx := len(q.hitTree) - 1
cursor := pointer.CursorDefault
for idx >= 0 {
n := &q.hitTree[idx]
hit, c := q.hit(n.area, pos)
if !hit {
idx--
continue
}
if cursor == pointer.CursorDefault {
cursor = c
}
pass = pass && n.pass
if pass {
idx--
} else {
idx = n.next
}
if !onNode(n) {
break
}
}
return cursor
}
func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
if areaIdx == -1 {
return p
}
return q.areas[areaIdx].trans.Invert().Transform(p)
}
func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, pointer.Cursor) {
c := pointer.CursorDefault
for areaIdx != -1 {
a := &q.areas[areaIdx]
if c == pointer.CursorDefault {
c = a.cursor
}
p := a.trans.Invert().Transform(p)
if !a.area.Hit(p) {
return false, c
}
areaIdx = a.parent
}
return true, c
}
func (q *pointerQueue) reset() {
q.hitTree = q.hitTree[:0]
q.areas = q.areas[: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 = slices.Delete(ids, i, i+1)
} else {
ids[i].used = false
}
}
if len(ids) > 0 {
q.semantic.contentIDs[k] = ids
} else {
delete(q.semantic.contentIDs, k)
}
}
}
func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState) (pointerState, []taggedEvent) {
for _, h := range handlers {
if h.pointer.areaPlusOne != 0 {
area := &q.areas[h.pointer.areaPlusOne-1]
if h.filter.pointer.kinds&(pointer.Press|pointer.Release) != 0 {
area.semantic.content.gestures |= ClickGesture
}
if h.filter.pointer.kinds&pointer.Scroll != 0 {
area.semantic.content.gestures |= ScrollGesture
}
area.semantic.valid = area.semantic.content.gestures != 0
}
}
var evts []taggedEvent
for i, p := range state.pointers {
changed := false
p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, p.last)
if changed {
state.pointers = slices.Clone(state.pointers)
state.pointers[i] = p
}
}
return state, evts
}
func dropHandler(state pointerState, tag event.Tag) pointerState {
pointers := state.pointers
state.pointers = nil
for _, p := range pointers {
handlers := p.handlers
p.handlers = nil
for _, h := range handlers {
if h != tag {
p.handlers = append(p.handlers, h)
}
}
entered := p.entered
p.entered = nil
for _, h := range entered {
if h != tag {
p.entered = append(p.entered, h)
}
}
state.pointers = append(state.pointers, p)
}
return state
}
// pointerOf returns the pointerInfo index corresponding to the pointer in e.
func (s pointerState) pointerOf(e pointer.Event) (pointerState, int) {
for i, p := range s.pointers {
if p.id == e.PointerID {
return s, i
}
}
n := len(s.pointers)
s.pointers = append(s.pointers[:n:n], pointerInfo{id: e.PointerID})
return s, len(s.pointers) - 1
}
// Deliver is like Push, but delivers an event to a particular area.
func (q *pointerQueue) Deliver(handlers map[event.Tag]*handler, areaIdx int, e pointer.Event) []taggedEvent {
scroll := e.Scroll
idx := len(q.hitTree) - 1
// Locate first potential receiver.
for idx != -1 {
n := &q.hitTree[idx]
if n.area == areaIdx {
break
}
idx--
}
var evts []taggedEvent
for idx != -1 {
n := &q.hitTree[idx]
idx = n.next
h, ok := handlers[n.tag]
if !ok || !h.filter.pointer.Matches(e) {
continue
}
e := e
if e.Kind == pointer.Scroll {
if scroll == (f32.Point{}) {
break
}
scroll, e.Scroll = h.filter.pointer.clampScroll(scroll)
}
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{tag: n.tag, event: e})
if e.Kind != pointer.Scroll {
break
}
}
return evts
}
// SemanticArea returns the sematic content for area, and its parent area.
func (q *pointerQueue) SemanticArea(areaIdx int) (semanticContent, int) {
for areaIdx != -1 {
a := &q.areas[areaIdx]
areaIdx = a.parent
if !a.semantic.valid {
continue
}
return a.semantic.content, areaIdx
}
return semanticContent{}, -1
}
func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState, e pointer.Event) (pointerState, []taggedEvent) {
var evts []taggedEvent
if e.Kind == pointer.Cancel {
for k := range handlers {
evts = append(evts, taggedEvent{
event: pointer.Event{Kind: pointer.Cancel},
tag: k,
})
}
state.pointers = nil
return state, evts
}
state, pidx := state.pointerOf(e)
p := state.pointers[pidx]
switch e.Kind {
case pointer.Press:
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
p.pressed = true
evts = q.deliverEvent(handlers, p, evts, e)
case pointer.Move:
if p.pressed {
e.Kind = pointer.Drag
}
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
evts = q.deliverEvent(handlers, p, evts, e)
if p.pressed {
p, evts = q.deliverDragEvent(handlers, p, evts)
}
case pointer.Release:
evts = q.deliverEvent(handlers, p, evts, e)
p.pressed = false
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
p, evts = q.deliverDropEvent(handlers, p, evts)
case pointer.Scroll:
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
evts = q.deliverEvent(handlers, p, evts, e)
default:
panic("unsupported pointer event type")
}
p.last = e
if !p.pressed && len(p.entered) == 0 {
// No longer need to track pointer.
state.pointers = slices.Concat(state.pointers[:pidx:pidx], state.pointers[pidx+1:])
} else {
state.pointers = slices.Clone(state.pointers)
state.pointers[pidx] = p
}
return state, evts
}
func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent {
if p.pressed && len(p.handlers) == 1 {
e.Priority = pointer.Grabbed
}
scroll := e.Scroll
for _, k := range p.handlers {
h, ok := handlers[k]
if !ok {
continue
}
f := h.filter.pointer
if !f.Matches(e) {
continue
}
if e.Kind == pointer.Scroll {
if scroll == (f32.Point{}) {
return evts
}
scroll, e.Scroll = f.clampScroll(scroll)
}
e := e
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{event: e, tag: k})
}
return evts
}
func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) {
changed := false
var hits []event.Tag
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
// Consider non-mouse pointers leaving when they're released.
} else {
var transSrc *pointerFilter
if p.dataSource != nil {
transSrc = &handlers[p.dataSource].filter.pointer
}
cursor = q.hitTest(e.Position, func(n *hitNode) bool {
h, ok := handlers[n.tag]
if !ok {
return true
}
add := true
if p.pressed {
add = false
// Filter out non-participating handlers,
// except potential transfer targets when a transfer has been initiated.
if _, found := searchTag(p.handlers, n.tag); found {
add = true
}
if transSrc != nil {
if _, ok := firstMimeMatch(transSrc, &h.filter.pointer); ok {
add = true
}
}
}
if add {
hits = addHandler(hits, n.tag)
}
return true
})
if !p.pressed {
changed = true
p.handlers = hits
}
}
// Deliver Leave events.
for _, k := range p.entered {
if _, found := searchTag(hits, k); found {
continue
}
h, ok := handlers[k]
if !ok {
continue
}
changed = true
e := e
e.Kind = pointer.Leave
if h.filter.pointer.Matches(e) {
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{tag: k, event: e})
}
}
// Deliver Enter events.
for _, k := range hits {
if _, found := searchTag(p.entered, k); found {
continue
}
h, ok := handlers[k]
if !ok {
continue
}
changed = true
e := e
e.Kind = pointer.Enter
if h.filter.pointer.Matches(e) {
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{tag: k, event: e})
}
}
p.entered = hits
return p, evts, cursor, changed
}
func (q *pointerQueue) deliverDragEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
if p.dataSource != nil {
return p, evts
}
// Identify the data source.
for _, k := range p.entered {
src := &handlers[k].filter.pointer
if len(src.sourceMimes) == 0 {
continue
}
// One data source handler per pointer.
p.dataSource = k
// Notify all potential targets.
for k, tgt := range handlers {
if _, ok := firstMimeMatch(src, &tgt.filter.pointer); ok {
evts = append(evts, taggedEvent{tag: k, event: transfer.InitiateEvent{}})
}
}
break
}
return p, evts
}
func (q *pointerQueue) deliverDropEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
if p.dataSource == nil {
return p, evts
}
// Request data from the source.
src := &handlers[p.dataSource].filter.pointer
for _, k := range p.entered {
h := handlers[k]
if m, ok := firstMimeMatch(src, &h.filter.pointer); ok {
p.dataTarget = k
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}})
return p, evts
}
}
// No valid target found, abort.
return q.deliverTransferCancelEvent(handlers, p, evts)
}
func (q *pointerQueue) deliverTransferCancelEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.CancelEvent{}})
// Cancel all potential targets.
src := &handlers[p.dataSource].filter.pointer
for k, h := range handlers {
if _, ok := firstMimeMatch(src, &h.filter.pointer); ok {
evts = append(evts, taggedEvent{tag: k, event: transfer.CancelEvent{}})
}
}
p.dataSource = nil
p.dataTarget = nil
return p, evts
}
// ClipFor clips r to the parents of area.
func (q *pointerQueue) ClipFor(area int, r image.Rectangle) image.Rectangle {
a := &q.areas[area]
parent := a.parent
for parent != -1 {
a := &q.areas[parent]
r = r.Intersect(a.bounds())
parent = a.parent
}
return r
}
func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
for i, t := range tags {
if t == tag {
return i, true
}
}
return 0, false
}
// addHandler adds tag to the slice if not present.
func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
if slices.Contains(tags, tag) {
return tags
}
return append(tags, tag)
}
// firstMimeMatch returns the first type match between src and tgt.
func firstMimeMatch(src, tgt *pointerFilter) (first string, matched bool) {
for _, m1 := range tgt.targetMimes {
if slices.Contains(src.sourceMimes, m1) {
return m1, true
}
}
return "", false
}
func (op *areaOp) Hit(pos f32.Point) bool {
pos = pos.Sub(f32internal.FPt(op.rect.Min))
size := f32internal.FPt(op.rect.Size())
switch op.kind {
case areaRect:
return 0 <= pos.X && pos.X < size.X &&
0 <= pos.Y && pos.Y < size.Y
case areaEllipse:
rx := size.X / 2
ry := size.Y / 2
xh := pos.X - rx
yk := pos.Y - ry
// The ellipse function works in all cases because
// 0/0 is not <= 1.
return (xh*xh)/(rx*rx)+(yk*yk)/(ry*ry) <= 1
default:
panic("invalid area kind")
}
}
func (a *areaNode) bounds() image.Rectangle {
return f32internal.Rectangle{
Min: a.trans.Transform(f32internal.FPt(a.area.rect.Min)),
Max: a.trans.Transform(f32internal.FPt(a.area.rect.Max)),
}.Round()
}