diff --git a/core/widgets/ScrollArea.zig b/core/widgets/ScrollArea.zig index f35a503fa..f4d5af278 100644 --- a/core/widgets/ScrollArea.zig +++ b/core/widgets/ScrollArea.zig @@ -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. @@ -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; @@ -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; } @@ -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" {