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