Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions core/widgets/ScrollArea.zig
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,15 @@ fn scrollBy(self: *ScrollArea, by: dvui.Point) void {

var t_viewport_y: f32 = 0;
var t_release_velocity: f32 = 0;
/// Written by swipe on the test thread before it draws the release frame.
var t_release_time: i128 = 0;
var t_use_dvui: bool = false;
/// Where a child of the area is drawn this frame (physical y, as the renderer would place it),
/// and whether the renderer snaps there.
var t_drawn_y: f32 = 0;
var t_raw_y: f32 = 0;
/// Written by testFrame on the test thread from the child's physical draw scale.
var t_draw_scale: f32 = 1;
var t_snapping: bool = false;
/// Lay the area out as a column of `t_rows` rows, `t_top` points below the window's top, and
/// record where each row is drawn.
Expand Down Expand Up @@ -324,6 +328,7 @@ fn testFrame() !dvui.App.Result {
}
const child = dvui.spacer(@src(), .{ .min_size_content = .{ .w = 100, .h = 20000 } });
t_snapping = dvui.snapToPixels();
t_draw_scale = child.rectScale().s;
const y = child.rectScale().r.y;
t_raw_y = y;
t_drawn_y = if (t_snapping) @round(y) else y;
Expand Down Expand Up @@ -357,6 +362,7 @@ fn swipe() !f32 {
_ = try cw.addEventTouchMotion(.touch0, x, y - 1 / h, 0, -1 / h);
try frameAfter(16);
_ = try cw.addEventPointer(.{ .button = .touch0, .action = .release, .xynorm = .{ .x = x, .y = y } });
t_release_time = cw.frame_time_ns;
try frameAfter(16);
return t_viewport_y;
}
Expand Down Expand Up @@ -401,26 +407,26 @@ test "coast displacement follows each frame gap" {
defer t.deinit();
t_use_dvui = false;
_ = try swipe();
const release_time = t_release_time;
const release_speed: f64 = t_release_velocity * t_draw_scale;
var previous_time = dvui.currentWindow().frame_time_ns;
try frameAfter(25);
var previous_y = t_raw_y;
var previous_speed: f32 = std.math.inf(f32);
var follows_gaps = true;
var previous_y = t_drawn_y;
// frameAfter draws at the current timestamp, then begins the next frame with this gap.
// Alternating long and short gaps exposes a displacement applied one frame late.
for ([_]u64{ 8, 25, 8, 25, 8, 25, 8, 25 }) |next_gap| {
const time = dvui.currentWindow().frame_time_ns;
const gap_ms: f32 = @as(f32, @floatFromInt(time - previous_time)) / std.time.ns_per_ms;
try frameAfter(next_gap);
const displacement = previous_y - t_raw_y;
const speed = displacement / gap_ms;
std.debug.print("coast: gap={d:.0} ms, displacement={d:.6} points\n", .{ gap_ms, displacement });
follows_gaps = follows_gaps and displacement > 0 and speed < previous_speed;
const displacement = previous_y - t_drawn_y;
const from_s = @as(f64, @floatFromInt(previous_time - release_time)) / std.time.ns_per_s;
const to_s = @as(f64, @floatFromInt(time - release_time)) / std.time.ns_per_s;
const expected: f32 = @floatCast(release_speed / tuning.decay *
(@exp(-tuning.decay * from_s) - @exp(-tuning.decay * to_s)));
// Check what reaches the renderer against the release integral, including its decay.
try std.testing.expectApproxEqAbs(expected, displacement, 0.0001);
previous_time = time;
previous_y = t_raw_y;
previous_speed = speed;
previous_y = t_drawn_y;
}
try std.testing.expect(follows_gaps);
}

test "a coast requests another frame even when its timestamp has not advanced" {
Expand Down
Loading