Two feature bodies accumulated in the working tree:
1. Pinch to change the app font size, continuously (no snapping):
- internal/ui/pinch_tracker.go: logic-free touch state machine.
Two-mover formation (the resting palm can land first or last;
movement is the only signal valid for both), pair = the mover
pair whose distance changed most, baseline = press distance
(formDist), lazy pending releases, survivor-scroll forwarding
after a pair break. Robust to ~1 fps frames: a whole pinch can
land in one drain (formDist/brokeFactor/lazy releases).
- render.go: pinch probe (raw pointer events) + grab lifecycle so
the pair is exclusive (scroll sees nothing of the pair) and the
survivor's finger keeps working as a scroll after the pinch.
- state.go/logic.go/session.go/frame.go: app-local float font
scale, content-point pin (buffer byte + offset from baseline,
not a layout point, so rewrap keeps the same character under
the center), restore/font pins, session persistence.
- pinch_test.go, pinch_font_test.go, tag_identity_test.go,
real_draw_probe_test.go: unit + real-Renderer/real-Router tests.
2. Soft keyboard must not shift content:
- Root cause: gioui.org/app calls Router.RevealFocus on any frame
the viewport shrinks (IME open under adjustResize) and
synthesizes a pointer.Scroll nudge aimed at the focused field's
stale pre-resize bounds; gesture.Scroll consumed it -> a 32 dp
content jump.
- Fix: main.go flags the shrink frame; render.go drains that one
synthetic scroll for the gesture's tag before Update (scroll-
range clamping cannot work: the router UNIONs ranges across
frames). Finger scroll (pointer.Drag) and the flinger are
untouched. reveal_focus_drain_test.go reproduces RevealFocus at
the router level and verifies the drain + zero delta.
Also: tools/touchinject (platform-signed emulator multi-touch
injection harness + e2e script, adb has no two-finger input),
docs (spec 2.2 + development_plan 18-20), .gitignore, gofmt.
129 lines
4.7 KiB
Bash
Executable File
129 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Pinch e2e test harness for the Pad emulator (aosp_atd AVD).
|
|
#
|
|
# The platform-signed touch.inject app (real MotionEvents through
|
|
# InputManager — the only multi-touch path that works in the emulator)
|
|
# drives the renderer's pinch probe; each gesture below is ONE script in
|
|
# ONE process (the injector's finger map is per-process).
|
|
#
|
|
# KNOWN FLAKES (both verified, both handled below):
|
|
# 1. The injector process is "cached" while its script thread runs and the
|
|
# 1.5 GB emulator OOM-kills it mid-script occasionally — the script
|
|
# never reaches "=== done". ti() checks logcat for the done line and
|
|
# re-runs (a re-run starts a fresh gesture; a stuck pointer from a
|
|
# half-run is replaced by the next ACTION_DOWN).
|
|
# 2. The app is on-demand-rendering at ~1 fps: a burst of moves within
|
|
# one frame's drain does not scroll (single-frame delta pattern).
|
|
# Scroll tests therefore space the moves >= 40 ms apart — which is
|
|
# also what a real finger produces over several frames.
|
|
#
|
|
# READING RESULTS: AppFontScale in the session file is the reliable pinch
|
|
# signal. Scroll in the session is NOT (the restore re-derives it from the
|
|
# pinned line anchor); for scroll tests compare the before/after
|
|
# screenshots instead.
|
|
SER=emulator-5554
|
|
export ANDROID_SERIAL=$SER
|
|
|
|
ti() {
|
|
local script="$1"
|
|
for attempt in 1 2 3; do
|
|
adb logcat -c 2>/dev/null
|
|
adb shell 'am broadcast -n "touch.inject/.Injector$ScriptReceiver" --es script "'"$script"'"' >/dev/null 2>&1
|
|
sleep 4
|
|
if adb shell logcat -d 2>/dev/null | grep -aq "TouchInject: === done"; then
|
|
return 0
|
|
fi
|
|
echo " (injection incomplete, attempt $attempt — retrying)"
|
|
done
|
|
echo " !! injection failed after 3 attempts"
|
|
return 1
|
|
}
|
|
# ensure Pad is in the foreground (the ATD launcher intermittently holds
|
|
# focus after a cold start; a backgrounded app receives no touch)
|
|
fg() {
|
|
for i in 1 2 3 4 5; do
|
|
FOC=$(adb shell 'dumpsys window | grep mCurrentFocus')
|
|
case "$FOC" in *pad.pad*) return 0;; esac
|
|
adb shell am start -n pad.pad/org.gioui.GioActivity >/dev/null 2>&1
|
|
sleep 8
|
|
done
|
|
echo " !! Pad never gained focus: $FOC"
|
|
return 1
|
|
}
|
|
dbg() { adb shell "echo '$1' > /sdcard/PadPerf/cmd"; sleep 1.5; }
|
|
shot() { adb exec-out screencap -p > "$1"; }
|
|
# force-stop (flushes the session), print it, relaunch + reopen the file
|
|
measure() {
|
|
adb shell am force-stop pad.pad
|
|
sleep 4
|
|
echo "session: $(adb shell cat /sdcard/Pad/session.json 2>/dev/null)"
|
|
adb shell am start -n pad.pad/org.gioui.GioActivity >/dev/null 2>&1
|
|
sleep 4
|
|
}
|
|
|
|
case "$1" in
|
|
t1) # single-finger scroll must NEVER change the font (device regression)
|
|
echo "=== T1: single-finger scroll (font must not change) ==="
|
|
fg || exit 1
|
|
shot /tmp/t1_before.png
|
|
S="down 1 720 1400 wait 80"
|
|
for y in 1320 1240 1160 1080 1000 920 840 760 680 600 520 440 360 280 200; do
|
|
S="$S move 1 720 $y wait 40"
|
|
done
|
|
S="$S wait 100 up 1"
|
|
ti "$S"
|
|
sleep 2
|
|
shot /tmp/t1_after.png # compare to before: content moved, same font size
|
|
measure
|
|
;;
|
|
t2) # two-finger pinch-out: font must scale, center anchored
|
|
echo "=== T2: two-finger pinch-out (font must grow) ==="
|
|
fg || exit 1
|
|
shot /tmp/t2_before.png
|
|
S="down 1 570 1200 wait 40 down 2 870 1200 wait 60"
|
|
for i in 1 2 3 4 5 6 7 8; do
|
|
x1=$((570 - i*37)); x2=$((870 + i*37))
|
|
S="$S move 1 $x1 1200 move 2 $x2 1200 wait 50"
|
|
done
|
|
S="$S wait 150 up 1 up 2"
|
|
ti "$S"
|
|
sleep 1
|
|
shot /tmp/t2_after.png
|
|
measure
|
|
;;
|
|
t3) # palm-first: the resting finger lands FIRST, the pinch pair is the
|
|
# two that move — the palm must never enter the distance
|
|
echo "=== T3: palm-first three fingers (pinch pair = the two movers) ==="
|
|
fg || exit 1
|
|
shot /tmp/t3_before.png
|
|
S="down 1 720 400 wait 100 down 2 570 1200 wait 60 down 3 870 1200 wait 150"
|
|
for i in 1 2 3 4 5 6 7 8; do
|
|
x2=$((570 - i*37)); x3=$((870 + i*37))
|
|
S="$S move 2 $x2 1200 move 3 $x3 1200 wait 50"
|
|
done
|
|
S="$S wait 150 up 2 up 3 wait 50 up 1"
|
|
ti "$S"
|
|
sleep 1
|
|
shot /tmp/t3_after.png
|
|
measure
|
|
;;
|
|
t4) # lift one finger mid-pinch: survivor must SCROLL, not zoom
|
|
echo "=== T4: lift finger mid-pinch (survivor scrolls, no more zoom) ==="
|
|
fg || exit 1
|
|
shot /tmp/t4_before.png
|
|
S="down 1 570 1200 wait 40 down 2 870 1200 wait 60 move 1 533 1200 move 2 907 1200 wait 80 up 2 wait 80"
|
|
for y in 1280 1360 1440 1520 1600 1680 1760 1840 1920 2000; do
|
|
S="$S move 1 533 $y wait 40" # survivor (finger 1) keeps its own x
|
|
done
|
|
S="$S wait 100 up 1"
|
|
ti "$S"
|
|
sleep 1
|
|
shot /tmp/t4_after.png # compare to before: content scrolled, font frozen
|
|
measure
|
|
;;
|
|
reset) # back to a known state (one command per file — no pipes)
|
|
dbg "fontsize 1"; dbg "top"
|
|
;;
|
|
*) echo "usage: $0 t1|t2|t3|t4|reset" ;;
|
|
esac
|