Skip to content
Merged
Show file tree
Hide file tree
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
13 changes: 12 additions & 1 deletion core/widgets/ScrollArea.zig
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,12 @@ pub const Lead = struct {
/// Open (`by` < 0) or shut (`by` > 0) by up to `|by|`; returns the length used, never negative.
take: *const fn (ctx: *anyopaque, by: f32) f32,
};
pub const With = struct { lead: ?Lead = null };
pub const With = struct {
lead: ?Lead = null,
/// Points the caller moved the viewport since the last frame because the content above it
/// changed size (a list keeping its reader in place): motion in flight moves with it.
shift: f32 = 0,
};

pub fn init(src: std.builtin.SourceLocation, init_opts: dvui.ScrollAreaWidget.InitOpts, opts: dvui.Options) ScrollArea {
return initWith(src, init_opts, opts, .{});
Expand All @@ -168,6 +173,12 @@ pub fn initWith(src: std.builtin.SourceLocation, init_opts: dvui.ScrollAreaWidge
};

if (self.bands) self.lead = with.lead;
if (with.shift != 0) {
if (self.touch.seg_y.kind == .coast) self.touch.seg_y.origin += with.shift;
if (self.touch.glide_y) |*y| y.* += with.shift;
self.touch.glide_at.y += with.shift;
self.touch.seat_at += with.shift;
}
if (self.lead) |lead_piece| _ = lead_piece.extent(lead_piece.ctx);

const dragging = dvui.currentWindow().dragging;
Expand Down
52 changes: 51 additions & 1 deletion core/widgets/VirtualList.zig
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ pub fn init(src: std.builtin.SourceLocation, opts: InitOptions, widget_opts: dvu
const y = if (opts.follow_end and st.pinned)
st.index.endY(view_h)
else if (st.anchor) |a| st.index.resolve(a) orelse st.frame_y else st.frame_y;
const left = st.si.viewport.y;
st.si.scrollToOffset(.vertical, y);
st.frame_y = st.si.viewport.y;

const scroll = ScrollArea.init(src, .{ .scroll_info = &st.si, .horizontal = .none }, widget_opts);
const scroll = ScrollArea.initWith(src, .{ .scroll_info = &st.si, .horizontal = .none }, widget_opts, .{ .shift = st.frame_y - left });
const now = st.si.viewport;
const settled = now.w > 0 and now.h > 0 and @abs(now.w - width) < 0.5;
if (settled) st.view_h = now.h;
Expand Down Expand Up @@ -651,3 +652,52 @@ test "Row.key is the row's key" {
try settleOn(&m);
for (m.keys_seen[0..m.drawn]) |seen| try testing.expectEqual(m.first_id + seen.index, seen.key);
}

fn flickFrame(ms: u64) !void {
const cw = dvui.currentWindow();
_ = try current.frame();
_ = try cw.end(.{});
try cw.begin(cw.frame_time_ns + @as(i128, ms) * std.time.ns_per_ms);
}

test "a coast through rows of different heights only slows down" {
var t = try dvui.testing.init(.{ .window_size = .{ .w = 300, .h = 400 } });
defer t.deinit();
var m: Messages = .{ .count = 2000 };
defer m.state.deinit();
try settleOn(&m);
m.state.scrollTo(1500);
try settleOn(&m);

const cw = dvui.currentWindow();
const h = cw.data().rect.h;
var y: f32 = 0.2;
_ = try cw.addEventPointer(.{ .button = .touch0, .action = .press, .xynorm = .{ .x = 0.5, .y = y } });
try flickFrame(16);
// Six moves at 60 Hz establish a coast back through unmeasured rows.
for (0..6) |_| {
y += 40 / h;
_ = try cw.addEventTouchMotion(.touch0, 0.5, y, 0, 40 / h);
try flickFrame(16);
}
_ = try cw.addEventPointer(.{ .button = .touch0, .action = .release, .xynorm = .{ .x = 0.5, .y = y } });
try flickFrame(16);

var previous_key = m.top_key;
var previous_y = m.top_y;
var previous_step = std.math.floatMax(f32);
var checked: usize = 0;
for (0..60) |_| {
try flickFrame(16);
if (m.top_key == previous_key) {
const step = m.top_y - previous_y;
try testing.expect(step > 0);
try testing.expect(step <= previous_step + 0.5);
previous_step = step;
checked += 1;
}
previous_key = m.top_key;
previous_y = m.top_y;
}
try testing.expect(checked > 0);
}
Loading