ui/app: (wayland) emulate fling behaviour for touchpads

Unlike macOS, Wayland leaves it up to the client to animate the
implied fling gesture when scrolling on a touchpad.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-09-13 18:14:17 +02:00
parent e6f0e0582d
commit 5bdb1d9e54
+68 -14
View File
@@ -20,6 +20,7 @@ import (
"unsafe" "unsafe"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/internal/fling"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
syscall "golang.org/x/sys/unix" syscall "golang.org/x/sys/unix"
@@ -105,13 +106,19 @@ type window struct {
notRead, notWrite int notRead, notWrite int
ppdp, ppsp float32 ppdp, ppsp float32
scrollTime time.Duration scrollTime time.Duration
discScroll struct { discScroll image.Point
x, y int
}
scroll f32.Point scroll f32.Point
lastPos f32.Point lastPos f32.Point
lastTouch f32.Point lastTouch f32.Point
// Flinging.
yExtrapolation fling.Extrapolation
xExtrapolation fling.Extrapolation
flinger fling.Animation
startFling bool
// Fling direction.
flingDir f32.Point
stage Stage stage Stage
dead bool dead bool
lastFrameCallback *C.struct_wl_callback lastFrameCallback *C.struct_wl_callback
@@ -535,6 +542,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
typ = pointer.Press typ = pointer.Press
} }
w.flushScroll() w.flushScroll()
w.resetFling()
w.w.event(pointer.Event{ w.w.event(pointer.Event{
Type: typ, Type: typ,
Source: pointer.Mouse, Source: pointer.Mouse,
@@ -547,6 +555,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
func gio_onPointerAxis(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.uint32_t, value C.wl_fixed_t) { func gio_onPointerAxis(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.uint32_t, value C.wl_fixed_t) {
w := winMap[ptr] w := winMap[ptr]
v := fromFixed(value) v := fromFixed(value)
w.resetFling()
if w.scroll == (f32.Point{}) { if w.scroll == (f32.Point{}) {
w.scrollTime = time.Duration(t) * time.Millisecond w.scrollTime = time.Duration(t) * time.Millisecond
} }
@@ -562,6 +571,28 @@ func gio_onPointerAxis(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.
func gio_onPointerFrame(data unsafe.Pointer, pointer *C.struct_wl_pointer) { func gio_onPointerFrame(data unsafe.Pointer, pointer *C.struct_wl_pointer) {
w := winMap[pointer] w := winMap[pointer]
w.flushScroll() w.flushScroll()
w.flushFling()
}
func (w *window) flushFling() {
if !w.startFling {
return
}
w.startFling = false
estx, esty := w.xExtrapolation.Estimate(), w.yExtrapolation.Estimate()
w.xExtrapolation = fling.Extrapolation{}
w.yExtrapolation = fling.Extrapolation{}
vel := float32(math.Sqrt(float64(estx.Velocity*estx.Velocity + esty.Velocity*esty.Velocity)))
_, _, c := w.config()
c.now = time.Now()
if !w.flinger.Start(&c, vel) {
return
}
invDist := 1 / vel
w.flingDir.X = estx.Velocity * invDist
w.flingDir.Y = esty.Velocity * invDist
// Wake up the window loop.
w.notify()
} }
//export gio_onPointerAxisSource //export gio_onPointerAxisSource
@@ -569,20 +600,28 @@ func gio_onPointerAxisSource(data unsafe.Pointer, pointer *C.struct_wl_pointer,
} }
//export gio_onPointerAxisStop //export gio_onPointerAxisStop
func gio_onPointerAxisStop(data unsafe.Pointer, pointer *C.struct_wl_pointer, time, axis C.uint32_t) { func gio_onPointerAxisStop(data unsafe.Pointer, ptr *C.struct_wl_pointer, t, axis C.uint32_t) {
w := winMap[ptr]
w.startFling = true
} }
//export gio_onPointerAxisDiscrete //export gio_onPointerAxisDiscrete
func gio_onPointerAxisDiscrete(data unsafe.Pointer, pointer *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) { func gio_onPointerAxisDiscrete(data unsafe.Pointer, pointer *C.struct_wl_pointer, axis C.uint32_t, discrete C.int32_t) {
w := winMap[pointer] w := winMap[pointer]
w.resetFling()
switch axis { switch axis {
case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL: case C.WL_POINTER_AXIS_HORIZONTAL_SCROLL:
w.discScroll.x += int(discrete) w.discScroll.X += int(discrete)
case C.WL_POINTER_AXIS_VERTICAL_SCROLL: case C.WL_POINTER_AXIS_VERTICAL_SCROLL:
w.discScroll.y += int(discrete) w.discScroll.Y += int(discrete)
} }
} }
func (w *window) resetFling() {
w.startFling = false
w.flinger = fling.Animation{}
}
//export gio_onKeyboardKeymap //export gio_onKeyboardKeymap
func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) { func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) {
conn.repeat.Stop(0) conn.repeat.Stop(0)
@@ -810,8 +849,9 @@ loop:
func (w *window) setAnimating(anim bool) { func (w *window) setAnimating(anim bool) {
w.mu.Lock() w.mu.Lock()
w.animating = anim w.animating = anim
animating := w.isAnimating()
w.mu.Unlock() w.mu.Unlock()
if anim { if animating {
w.notify() w.notify()
} }
} }
@@ -954,29 +994,38 @@ func (c *wlOutput) ppmm() (float32, error) {
} }
func (w *window) flushScroll() { func (w *window) flushScroll() {
if w.scroll == (f32.Point{}) { var fling f32.Point
if w.flinger.Active() {
dist := float32(w.flinger.Tick(time.Now()))
fling = w.flingDir.Mul(dist)
}
total := w.scroll.Add(fling)
if total == (f32.Point{}) {
return return
} }
// The Wayland reported scroll distance for // The Wayland reported scroll distance for
// discrete scroll axis is only 10 pixels, where // discrete scroll axis is only 10 pixels, where
// 100 seems more appropriate. // 100 seems more appropriate.
const discreteScale = 10 const discreteScale = 10
if w.discScroll.x != 0 { if w.discScroll.X != 0 {
w.scroll.X *= discreteScale w.scroll.X *= discreteScale
} }
if w.discScroll.y != 0 { if w.discScroll.Y != 0 {
w.scroll.Y *= discreteScale w.scroll.Y *= discreteScale
} }
w.w.event(pointer.Event{ w.w.event(pointer.Event{
Type: pointer.Move, Type: pointer.Move,
Source: pointer.Mouse, Source: pointer.Mouse,
Position: w.lastPos, Position: w.lastPos,
Scroll: w.scroll, Scroll: total,
Time: w.scrollTime, Time: w.scrollTime,
}) })
if w.discScroll == (image.Point{}) {
w.xExtrapolation.SampleDelta(w.scrollTime, -w.scroll.X)
w.yExtrapolation.SampleDelta(w.scrollTime, -w.scroll.Y)
}
w.scroll = f32.Point{} w.scroll = f32.Point{}
w.discScroll.x = 0 w.discScroll = image.Point{}
w.discScroll.y = 0
} }
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) { func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
@@ -1032,9 +1081,14 @@ func (w *window) config() (int, int, Config) {
} }
} }
func (w *window) isAnimating() bool {
return w.animating || w.flinger.Active()
}
func (w *window) draw(sync bool) { func (w *window) draw(sync bool) {
w.flushScroll()
w.mu.Lock() w.mu.Lock()
animating := w.animating animating := w.isAnimating()
dead := w.dead dead := w.dead
w.mu.Unlock() w.mu.Unlock()
if dead || (!animating && !sync) { if dead || (!animating && !sync) {