From 317f634dca9281df5a584a1bbc23b77034757975 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Tue, 23 Jun 2026 21:50:32 -0500 Subject: [PATCH] fix: flickering replay buffer duration There was an issue on the UI when the duration jumped between 9.9 and 10.0 for example. It would flicker between 9 and 10. This change should fix that while also formatting the full duration for the replay buffer. --- src/store/capture_store.zig | 7 ++++--- src/ui/draw_bottom_panel.zig | 9 +++++++-- src/ui/draw_left_column.zig | 4 +++- src/util.zig | 39 ++++++++++++++++++++++++++++++++++-- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/store/capture_store.zig b/src/store/capture_store.zig index 82b249e..32cc56a 100644 --- a/src/store/capture_store.zig +++ b/src/store/capture_store.zig @@ -167,14 +167,15 @@ pub const CaptureStore = struct { }; } - pub fn duration_seconds(self: *const @This(), io: std.Io) ?u64 { + pub fn duration(self: *const @This(), io: std.Io) ?f64 { if (self.start_time) |start| { const now = std.Io.Timestamp.now(io, .awake).nanoseconds; - const elapsed_ns = now - start.nanoseconds; + const elapsed_ns: f64 = @floatFromInt(now - start.nanoseconds); if (elapsed_ns < 0) { return 0; } - return @intCast(@divTrunc(elapsed_ns, std.time.ns_per_s)); + + return elapsed_ns / @as(f64, @floatFromInt(std.time.ns_per_s)); } return null; } diff --git a/src/ui/draw_bottom_panel.zig b/src/ui/draw_bottom_panel.zig index 593e9fd..4bd4a2e 100644 --- a/src/ui/draw_bottom_panel.zig +++ b/src/ui/draw_bottom_panel.zig @@ -25,7 +25,10 @@ pub fn draw_bottom_panel(allocator: Allocator, store: *Store, state: *Store.Stat if (c.ImGui_CollapsingHeader("Video", c.ImGuiTreeNodeFlags_DefaultOpen)) { const replay_buffer_duration_label = try util.format_duration_label( allocator, - @intCast(state.capture.replay_buffer_metrics.duration_seconds(store.io) orelse 0), + .{ + .seconds = state.capture.replay_buffer_metrics.duration(store.io) orelse 0, + .max = state.user_settings.user_settings.replay_seconds, + }, ); defer allocator.free(replay_buffer_duration_label); @@ -132,7 +135,9 @@ pub fn draw_bottom_panel(allocator: Allocator, store: *Store, state: *Store.Stat _ = c.ImGui_TableNextColumn(); if (state.capture.recording_to_disk) { - const recording_duration_label = try util.format_duration_label(allocator, @intCast(state.capture.recording_metrics.duration_seconds(store.io) orelse 0)); + const recording_duration_label = try util.format_duration_label(allocator, .{ + .seconds = state.capture.recording_metrics.duration(store.io) orelse 0, + }); defer allocator.free(recording_duration_label); c.ImGui_TextUnformatted(recording_duration_label); } else { diff --git a/src/ui/draw_left_column.zig b/src/ui/draw_left_column.zig index 9b117c0..b366356 100644 --- a/src/ui/draw_left_column.zig +++ b/src/ui/draw_left_column.zig @@ -234,7 +234,9 @@ fn draw_capture_settings(allocator: std.mem.Allocator, store: *Store, state: *St replay_seconds_local = null; } - const replay_duration_label = try util.format_duration_label(allocator, @intCast(replay_seconds)); + const replay_duration_label = try util.format_duration_label(allocator, .{ + .seconds = @floatFromInt(replay_seconds), + }); defer allocator.free(replay_duration_label); c.ImGui_PushTextWrapPos(0); c.ImGui_TextDisabled("Duration: %s", replay_duration_label.ptr); diff --git a/src/util.zig b/src/util.zig index 6b96f5f..7c8a485 100644 --- a/src/util.zig +++ b/src/util.zig @@ -20,7 +20,22 @@ pub fn print_elapsed(io: std.Io, start_time: i128, prefix: []const u8) void { log.debug("[{s}] time elapsed {}ms\n", .{ prefix, total_time }); } -pub fn format_duration_label(allocator: std.mem.Allocator, total_seconds: u32) ![:0]u8 { +pub fn format_duration_label(allocator: std.mem.Allocator, args: struct { + seconds: f64, + max: ?u32 = null, +}) ![:0]u8 { + const input_seconds = @max(args.seconds, 0.0); + // If max is provided, round seconds up and then take + // the min of the two. This prevents any flicker on the UI + // when a number changes from 9.9 to 10.0 for example. + const total_seconds: u64 = if (args.max) |max_seconds| + @min( + @as(u64, @intFromFloat(@ceil(input_seconds))), + max_seconds, + ) + else + @intFromFloat(@trunc(input_seconds)); + const hours = total_seconds / 3600; const minutes = (total_seconds % 3600) / 60; const seconds = total_seconds % 60; @@ -281,7 +296,27 @@ test "Util - format_duration_label formats compact duration strings" { }; for (cases) |case| { - const label = try format_duration_label(allocator, case.seconds); + const label = try format_duration_label(allocator, .{ .seconds = @floatFromInt(case.seconds) }); + defer allocator.free(label); + + try std.testing.expectEqualStrings(case.expected, label); + } + + const replay_cases = [_]struct { + seconds: f64, + max: u32, + expected: []const u8, + }{ + .{ .seconds = 8.9, .max = 10, .expected = "9s" }, + .{ .seconds = 9.1, .max = 10, .expected = "10s" }, + .{ .seconds = 10.2, .max = 10, .expected = "10s" }, + }; + + for (replay_cases) |case| { + const label = try format_duration_label(allocator, .{ + .seconds = case.seconds, + .max = case.max, + }); defer allocator.free(label); try std.testing.expectEqualStrings(case.expected, label);