Files
gio-patched/io/input/pointer.go
T
Elias Naur ef8171b971 io: [API] introduce event filters; convert pointer input to use them
Instead of having to supply the predicates for event filtering at the
time of layout, the new Filter type allows widgets to filter at the time
of calling Source.Events. There is then only the need for a single input
op type, in package event.

Filters most importantly allow the use of one tag for several event types,
and we can define that a widget w has &w as its primary tag, by convention.
This allows the replacement of per-widget Focus methods with direct uses
of FocusCmd{&w}, and the later addition of Source.Focused(&w) queries.

Note that the TestCursor test needed restructuring to avoid its use of
InputOps.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-05 10:59:51 +00:00

981 lines
23 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"image"
"io"
"gioui.org/f32"
f32internal "gioui.org/internal/f32"
"gioui.org/internal/ops"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/semantic"
"gioui.org/io/system"
"gioui.org/io/transfer"
)
type pointerQueue struct {
hitTree []hitNode
areas []areaNode
cursor pointer.Cursor
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
// For handler nodes.
tag event.Tag
ktag event.Tag
pass bool
}
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 {
area int
active bool
types pointer.Kind
// min and max horizontal/vertical scroll
scrollRange image.Rectangle
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{}
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, events *handlerEvents) *pointerHandler {
areaID := c.currentArea()
c.addHitNode(hitNode{
area: areaID,
tag: tag,
pass: c.state.pass > 0,
})
h := c.q.handlerFor(tag, events)
h.area = areaID
return h
}
func (q *pointerQueue) handlerFor(tag event.Tag, events *handlerEvents) *pointerHandler {
h, ok := q.handlers[tag]
if !ok {
h = &pointerHandler{
area: -1,
}
if q.handlers == nil {
q.handlers = make(map[event.Tag]*pointerHandler)
}
q.handlers[tag] = h
// Cancel handlers on (each) first appearance, but don't
// trigger redraw.
events.AddNoRedraw(tag, pointer.Event{Kind: pointer.Cancel})
}
if !h.active {
h.types = 0
h.scrollRange = image.Rectangle{}
h.sourceMimes = h.sourceMimes[:0]
h.targetMimes = h.targetMimes[:0]
}
h.active = true
return h
}
func (c *pointerCollector) keyInputOp(op key.InputOp) {
areaID := c.currentArea()
c.addHitNode(hitNode{
area: areaID,
ktag: op.Tag,
pass: true,
})
}
func (c *pointerCollector) actionInputOp(act system.Action) {
areaID := c.currentArea()
area := &c.q.areas[areaID]
area.action = act
}
func (q *pointerQueue) grab(req pointer.GrabCmd, events *handlerEvents) {
for _, p := range q.pointers {
if !p.pressed || p.id != req.ID {
continue
}
for i, k2 := range p.handlers {
if k2 == req.Tag {
// Drop other handlers that lost their grab.
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)
}
return
}
}
}
}
func (c *pointerCollector) inputOp(tag event.Tag, events *handlerEvents) {
areaID := c.currentArea()
area := &c.q.areas[areaID]
area.semantic.content.tag = tag
c.newHandler(tag, events)
}
func (q *pointerQueue) filterTag(tag event.Tag, f pointer.Filter, events *handlerEvents) {
h := q.handlerFor(tag, events)
h.types = h.types | f.Kinds
h.scrollRange = h.scrollRange.Union(f.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) 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) sourceFilter(tag event.Tag, f transfer.SourceFilter, events *handlerEvents) {
h := q.handlerFor(tag, events)
h.sourceMimes = append(h.sourceMimes, f.Type)
}
func (q *pointerQueue) targetFilter(tag event.Tag, f transfer.TargetFilter, events *handlerEvents) {
h := q.handlerFor(tag, events)
h.targetMimes = append(h.targetMimes, f.Type)
}
func (q *pointerQueue) offerData(req transfer.OfferCmd, events *handlerEvents) {
transferIdx := len(q.transfers)
q.transfers = append(q.transfers, req.Data)
for i := range q.pointers {
p := &q.pointers[i]
if p.dataSource != req.Tag {
continue
}
defer q.deliverTransferCancelEvent(p, events)
if p.dataTarget == nil {
return
}
events.Add(p.dataTarget, transfer.DataEvent{
Type: req.Type,
Open: func() io.ReadCloser {
q.transfers[transferIdx] = nil
return req.Data
},
})
break
}
}
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) opHit(pos f32.Point) ([]event.Tag, pointer.Cursor) {
hits := q.scratch[:0]
cursor := q.hitTest(pos, func(n *hitNode) bool {
if n.tag != nil {
if _, exists := q.handlers[n.tag]; exists {
hits = addHandler(hits, n.tag)
}
}
return true
})
q.scratch = hits[:0]
return hits, 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() {
for _, h := range q.handlers {
// Reset handler.
h.area = -1
}
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 = 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.dropHandler(nil, k)
delete(q.handlers, k)
continue
}
h.active = false
if h.area != -1 {
area := &q.areas[h.area]
if h.types&(pointer.Press|pointer.Release) != 0 {
area.semantic.content.gestures |= ClickGesture
}
if h.types&pointer.Scroll != 0 {
area.semantic.content.gestures |= ScrollGesture
}
area.semantic.valid = area.semantic.content.gestures != 0
}
}
for i := range q.pointers {
p := &q.pointers[i]
q.deliverEnterLeaveEvents(p, events, p.last)
}
}
func (q *pointerQueue) dropHandler(events *handlerEvents, tag event.Tag) {
if events != nil {
events.Add(tag, pointer.Event{Kind: 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] == 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
}
// Deliver is like Push, but delivers an event to a particular area.
func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEvents) {
var sx, sy = e.Scroll.X, e.Scroll.Y
idx := len(q.hitTree) - 1
// Locate first potential receiver.
for idx != -1 {
n := &q.hitTree[idx]
if n.area == areaIdx {
break
}
idx--
}
for idx != -1 {
n := &q.hitTree[idx]
idx = n.next
if n.tag == nil {
continue
}
h := q.handlers[n.tag]
if h == nil || e.Kind&h.types == 0 {
continue
}
e := e
if e.Kind == pointer.Scroll {
if sx == 0 && sy == 0 {
break
}
// 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.Position = q.invTransform(h.area, e.Position)
events.Add(n.tag, e)
if e.Kind != pointer.Scroll {
break
}
}
}
// 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(e pointer.Event, events *handlerEvents) {
if e.Kind == pointer.Cancel {
q.pointers = q.pointers[:0]
for k := range q.handlers {
q.dropHandler(events, k)
}
return
}
pidx := q.pointerOf(e)
p := &q.pointers[pidx]
p.last = e
switch e.Kind {
case pointer.Press:
q.deliverEnterLeaveEvents(p, events, e)
p.pressed = true
q.deliverEvent(p, events, e)
case pointer.Move:
if p.pressed {
e.Kind = pointer.Drag
}
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.deliverEvent(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:]...)
}
}
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
}
var sx, sy = e.Scroll.X, e.Scroll.Y
for _, k := range p.handlers {
h := q.handlers[k]
if e.Kind == pointer.Scroll {
if sx == 0 && sy == 0 {
return
}
// 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)
}
if e.Kind&h.types == 0 {
continue
}
e := e
if foremost {
foremost = false
e.Priority = pointer.Foremost
}
e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e)
}
}
func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
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 {
hits, q.cursor = 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 {
if _, found := searchTag(hits, k); found {
continue
}
h := q.handlers[k]
e := e
e.Kind = pointer.Leave
if e.Kind&h.types != 0 {
e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e)
}
}
// Deliver Enter events.
for _, k := range hits {
h := q.handlers[k]
if _, found := searchTag(p.entered, k); found {
continue
}
e := e
e.Kind = pointer.Enter
if e.Kind&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) 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{})
}
}
p.dataSource = nil
p.dataTarget = nil
}
// 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 {
for _, t := range tags {
if t == tag {
return tags
}
}
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 {
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()
}
func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) {
if v := float32(max); scroll > v {
return scroll - v, v
}
if v := float32(min); scroll < v {
return scroll - v, v
}
return 0, scroll
}