chore(v2): vendor dependencies for offline/China builds
go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor so go run/build work without hitting proxy.golang.org (blocked/slow in China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and GOOS=windows go build -mod=vendor ./internal/ui both pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+479
@@ -0,0 +1,479 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package gesture implements common pointer gestures.
|
||||
|
||||
Gestures accept low level pointer Events from an event
|
||||
Queue and detect higher level actions such as clicks
|
||||
and scrolling.
|
||||
*/
|
||||
package gesture
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/fling"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// The duration is somewhat arbitrary.
|
||||
const doubleClickDuration = 200 * time.Millisecond
|
||||
|
||||
// Hover detects the hover gesture for a pointer area.
|
||||
type Hover struct {
|
||||
// entered tracks whether the pointer is inside the gesture.
|
||||
entered bool
|
||||
// pid is the pointer.ID.
|
||||
pid pointer.ID
|
||||
}
|
||||
|
||||
// Add the gesture to detect hovering over the current pointer area.
|
||||
func (h *Hover) Add(ops *op.Ops) {
|
||||
event.Op(ops, h)
|
||||
}
|
||||
|
||||
// Update state and report whether a pointer is inside the area.
|
||||
func (h *Hover) Update(q input.Source) bool {
|
||||
for {
|
||||
ev, ok := q.Event(pointer.Filter{
|
||||
Target: h,
|
||||
Kinds: pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := ev.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Kind {
|
||||
case pointer.Leave, pointer.Cancel:
|
||||
if h.entered && h.pid == e.PointerID {
|
||||
h.entered = false
|
||||
}
|
||||
case pointer.Enter:
|
||||
h.pid = e.PointerID
|
||||
h.entered = true
|
||||
}
|
||||
}
|
||||
return h.entered
|
||||
}
|
||||
|
||||
// Click detects click gestures in the form
|
||||
// of ClickEvents.
|
||||
type Click struct {
|
||||
// clickedAt is the timestamp at which
|
||||
// the last click occurred.
|
||||
clickedAt time.Duration
|
||||
// clicks is incremented if successive clicks
|
||||
// are performed within a fixed duration.
|
||||
clicks int
|
||||
// pressed tracks whether the pointer is pressed.
|
||||
pressed bool
|
||||
// hovered tracks whether the pointer is inside the gesture.
|
||||
hovered bool
|
||||
// entered tracks whether an Enter event has been received.
|
||||
entered bool
|
||||
// pid is the pointer.ID.
|
||||
pid pointer.ID
|
||||
}
|
||||
|
||||
// ClickEvent represent a click action, either a
|
||||
// KindPress for the beginning of a click or a
|
||||
// KindClick for a completed click.
|
||||
type ClickEvent struct {
|
||||
Kind ClickKind
|
||||
Position image.Point
|
||||
Source pointer.Source
|
||||
Modifiers key.Modifiers
|
||||
// NumClicks records successive clicks occurring
|
||||
// within a short duration of each other.
|
||||
NumClicks int
|
||||
}
|
||||
|
||||
type ClickKind uint8
|
||||
|
||||
// Drag detects drag gestures in the form of pointer.Drag events.
|
||||
type Drag struct {
|
||||
dragging bool
|
||||
pressed bool
|
||||
pid pointer.ID
|
||||
start f32.Point
|
||||
}
|
||||
|
||||
// Scroll detects scroll gestures and reduces them to
|
||||
// scroll distances. Scroll recognizes mouse wheel
|
||||
// movements as well as drag and fling touch gestures.
|
||||
type Scroll struct {
|
||||
dragging bool
|
||||
estimator fling.Extrapolation
|
||||
flinger fling.Animation
|
||||
pid pointer.ID
|
||||
last int
|
||||
// Leftover scroll.
|
||||
scroll float32
|
||||
}
|
||||
|
||||
type ScrollState uint8
|
||||
|
||||
type Axis uint8
|
||||
|
||||
const (
|
||||
Horizontal Axis = iota
|
||||
Vertical
|
||||
Both
|
||||
)
|
||||
|
||||
const (
|
||||
// KindPress is reported for the first pointer
|
||||
// press.
|
||||
KindPress ClickKind = iota
|
||||
// KindClick is reported when a click action
|
||||
// is complete.
|
||||
KindClick
|
||||
// KindCancel is reported when the gesture is
|
||||
// cancelled.
|
||||
KindCancel
|
||||
)
|
||||
|
||||
const (
|
||||
// StateIdle is the default scroll state.
|
||||
StateIdle ScrollState = iota
|
||||
// StateDragging is reported during drag gestures.
|
||||
StateDragging
|
||||
// StateFlinging is reported when a fling is
|
||||
// in progress.
|
||||
StateFlinging
|
||||
)
|
||||
|
||||
const touchSlop = unit.Dp(3)
|
||||
|
||||
// Add the handler to the operation list to receive click events.
|
||||
func (c *Click) Add(ops *op.Ops) {
|
||||
event.Op(ops, c)
|
||||
}
|
||||
|
||||
// Hovered returns whether a pointer is inside the area.
|
||||
func (c *Click) Hovered() bool {
|
||||
return c.hovered
|
||||
}
|
||||
|
||||
// Pressed returns whether a pointer is pressing.
|
||||
func (c *Click) Pressed() bool {
|
||||
return c.pressed
|
||||
}
|
||||
|
||||
// Update state and return the next click events, if any.
|
||||
func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
||||
for {
|
||||
evt, ok := q.Event(pointer.Filter{
|
||||
Target: c,
|
||||
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Kind {
|
||||
case pointer.Release:
|
||||
if !c.pressed || c.pid != e.PointerID {
|
||||
break
|
||||
}
|
||||
c.pressed = false
|
||||
if !c.entered || c.hovered {
|
||||
return ClickEvent{
|
||||
Kind: KindClick,
|
||||
Position: e.Position.Round(),
|
||||
Source: e.Source,
|
||||
Modifiers: e.Modifiers,
|
||||
NumClicks: c.clicks,
|
||||
}, true
|
||||
} else {
|
||||
return ClickEvent{Kind: KindCancel}, true
|
||||
}
|
||||
case pointer.Cancel:
|
||||
wasPressed := c.pressed
|
||||
c.pressed = false
|
||||
c.hovered = false
|
||||
c.entered = false
|
||||
if wasPressed {
|
||||
return ClickEvent{Kind: KindCancel}, true
|
||||
}
|
||||
case pointer.Press:
|
||||
if c.pressed {
|
||||
break
|
||||
}
|
||||
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
|
||||
break
|
||||
}
|
||||
c.pid = e.PointerID
|
||||
c.pressed = true
|
||||
if e.Time-c.clickedAt < doubleClickDuration {
|
||||
c.clicks++
|
||||
} else {
|
||||
c.clicks = 1
|
||||
}
|
||||
c.clickedAt = e.Time
|
||||
return ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}, true
|
||||
case pointer.Leave:
|
||||
if !c.pressed {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid == e.PointerID {
|
||||
c.hovered = false
|
||||
}
|
||||
case pointer.Enter:
|
||||
if !c.pressed {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid == e.PointerID {
|
||||
c.hovered = true
|
||||
c.entered = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return ClickEvent{}, false
|
||||
}
|
||||
|
||||
func (ClickEvent) ImplementsEvent() {}
|
||||
|
||||
// Add the handler to the operation list to receive scroll events.
|
||||
// The bounds variable refers to the scrolling boundaries
|
||||
// as defined in [pointer.Filter].
|
||||
func (s *Scroll) Add(ops *op.Ops) {
|
||||
event.Op(ops, s)
|
||||
}
|
||||
|
||||
// Stop any remaining fling movement.
|
||||
func (s *Scroll) Stop() {
|
||||
s.flinger = fling.Animation{}
|
||||
}
|
||||
|
||||
// Update state and report the scroll distance along axis.
|
||||
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
|
||||
total := 0
|
||||
f := pointer.Filter{
|
||||
Target: s,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||
ScrollX: scrollx,
|
||||
ScrollY: scrolly,
|
||||
}
|
||||
for {
|
||||
evt, ok := q.Event(f)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Kind {
|
||||
case pointer.Press:
|
||||
if s.dragging {
|
||||
break
|
||||
}
|
||||
// Only scroll on touch drags, or on Android where mice
|
||||
// drags also scroll by convention.
|
||||
if e.Source != pointer.Touch && runtime.GOOS != "android" {
|
||||
break
|
||||
}
|
||||
s.Stop()
|
||||
s.estimator = fling.Extrapolation{}
|
||||
v := s.val(axis, e.Position)
|
||||
s.last = int(math.Round(float64(v)))
|
||||
s.estimator.Sample(e.Time, v)
|
||||
s.dragging = true
|
||||
s.pid = e.PointerID
|
||||
case pointer.Release:
|
||||
if s.pid != e.PointerID {
|
||||
break
|
||||
}
|
||||
fling := s.estimator.Estimate()
|
||||
if slop, d := float32(cfg.Dp(touchSlop)), fling.Distance; d < -slop || d > slop {
|
||||
s.flinger.Start(cfg, t, fling.Velocity)
|
||||
}
|
||||
fallthrough
|
||||
case pointer.Cancel:
|
||||
s.dragging = false
|
||||
case pointer.Scroll:
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
s.scroll += e.Scroll.X
|
||||
case Vertical:
|
||||
s.scroll += e.Scroll.Y
|
||||
case Both:
|
||||
s.scroll += e.Scroll.X + e.Scroll.Y
|
||||
}
|
||||
iscroll := int(s.scroll)
|
||||
s.scroll -= float32(iscroll)
|
||||
total += iscroll
|
||||
case pointer.Drag:
|
||||
if !s.dragging || s.pid != e.PointerID {
|
||||
continue
|
||||
}
|
||||
val := s.val(axis, e.Position)
|
||||
s.estimator.Sample(e.Time, val)
|
||||
v := int(math.Round(float64(val)))
|
||||
dist := s.last - v
|
||||
if e.Priority < pointer.Grabbed {
|
||||
slop := cfg.Dp(touchSlop)
|
||||
if dist := dist; dist >= slop || -slop >= dist {
|
||||
q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
|
||||
}
|
||||
} else {
|
||||
s.last = v
|
||||
total += dist
|
||||
}
|
||||
}
|
||||
}
|
||||
total += s.flinger.Tick(t)
|
||||
if s.flinger.Active() {
|
||||
q.Execute(op.InvalidateCmd{})
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (s *Scroll) val(axis Axis, p f32.Point) float32 {
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
return p.X
|
||||
case Vertical:
|
||||
return p.Y
|
||||
case Both:
|
||||
return p.X + p.Y
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// State reports the scroll state.
|
||||
func (s *Scroll) State() ScrollState {
|
||||
switch {
|
||||
case s.flinger.Active():
|
||||
return StateFlinging
|
||||
case s.dragging:
|
||||
return StateDragging
|
||||
default:
|
||||
return StateIdle
|
||||
}
|
||||
}
|
||||
|
||||
// Add the handler to the operation list to receive drag events.
|
||||
func (d *Drag) Add(ops *op.Ops) {
|
||||
event.Op(ops, d)
|
||||
}
|
||||
|
||||
// Update state and return the next drag event, if any.
|
||||
func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
|
||||
for {
|
||||
ev, ok := q.Event(pointer.Filter{
|
||||
Target: d,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := ev.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch e.Kind {
|
||||
case pointer.Press:
|
||||
if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) {
|
||||
continue
|
||||
}
|
||||
d.pressed = true
|
||||
if d.dragging {
|
||||
continue
|
||||
}
|
||||
d.dragging = true
|
||||
d.pid = e.PointerID
|
||||
d.start = e.Position
|
||||
case pointer.Drag:
|
||||
if !d.dragging || e.PointerID != d.pid {
|
||||
continue
|
||||
}
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
e.Position.Y = d.start.Y
|
||||
case Vertical:
|
||||
e.Position.X = d.start.X
|
||||
case Both:
|
||||
// Do nothing
|
||||
}
|
||||
if e.Priority < pointer.Grabbed {
|
||||
diff := e.Position.Sub(d.start)
|
||||
slop := cfg.Dp(touchSlop)
|
||||
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
||||
q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
|
||||
}
|
||||
}
|
||||
case pointer.Release, pointer.Cancel:
|
||||
d.pressed = false
|
||||
if !d.dragging || e.PointerID != d.pid {
|
||||
continue
|
||||
}
|
||||
d.dragging = false
|
||||
}
|
||||
|
||||
return e, true
|
||||
}
|
||||
|
||||
return pointer.Event{}, false
|
||||
}
|
||||
|
||||
// Dragging reports whether it is currently in use.
|
||||
func (d *Drag) Dragging() bool { return d.dragging }
|
||||
|
||||
// Pressed returns whether a pointer is pressing.
|
||||
func (d *Drag) Pressed() bool { return d.pressed }
|
||||
|
||||
func (a Axis) String() string {
|
||||
switch a {
|
||||
case Horizontal:
|
||||
return "Horizontal"
|
||||
case Vertical:
|
||||
return "Vertical"
|
||||
default:
|
||||
panic("invalid Axis")
|
||||
}
|
||||
}
|
||||
|
||||
func (ct ClickKind) String() string {
|
||||
switch ct {
|
||||
case KindPress:
|
||||
return "KindPress"
|
||||
case KindClick:
|
||||
return "KindClick"
|
||||
case KindCancel:
|
||||
return "KindCancel"
|
||||
default:
|
||||
panic("invalid ClickKind")
|
||||
}
|
||||
}
|
||||
|
||||
func (s ScrollState) String() string {
|
||||
switch s {
|
||||
case StateIdle:
|
||||
return "StateIdle"
|
||||
case StateDragging:
|
||||
return "StateDragging"
|
||||
case StateFlinging:
|
||||
return "StateFlinging"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user