//! Progress Bar Demo - Shows progress with ETA //! //! Run with: zig build progress-demo const std = @import("std"); const zcatui = @import("zcatui"); const Terminal = zcatui.Terminal; const Rect = zcatui.Rect; const Buffer = zcatui.Buffer; const Style = zcatui.Style; const Color = zcatui.Color; const Block = zcatui.widgets.Block; const Borders = zcatui.widgets.Borders; const Task = struct { name: []const u8, current: u64, total: u64, speed: f64, // Items per frame color: Color, }; /// State for the demo const State = struct { tasks: [4]Task = .{ .{ .name = "Downloading...", .current = 0, .total = 100, .speed = 0.8, .color = Color.blue }, .{ .name = "Compiling...", .current = 0, .total = 50, .speed = 0.3, .color = Color.green }, .{ .name = "Installing...", .current = 0, .total = 200, .speed = 1.2, .color = Color.yellow }, .{ .name = "Verifying...", .current = 0, .total = 80, .speed = 0.5, .color = Color.magenta }, }, frame: u64 = 0, paused: bool = false, running: bool = true, }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var term = try Terminal.init(allocator); defer term.deinit(); var state = State{}; while (state.running) { try term.drawWithContext(&state, render); if (try term.pollEvent(50)) |event| { switch (event) { .key => |key| { switch (key.code) { .char => |c| { if (c == 'q') state.running = false; if (c == 'r') { // Reset all tasks for (&state.tasks) |*task| { task.current = 0; } state.frame = 0; } if (c == 'p' or c == ' ') state.paused = !state.paused; }, .esc => state.running = false, else => {}, } }, else => {}, } } // Update progress (simulate work) if (!state.paused) { for (&state.tasks) |*task| { if (task.current < task.total) { // Add randomness to speed const variation = @as(f64, @floatFromInt(state.frame % 10)) / 20.0; const effective_speed = task.speed * (0.8 + variation); const increment: u64 = @intFromFloat(effective_speed); task.current = @min(task.current + @max(increment, 1), task.total); } } } state.frame +%= 1; } } fn render(state: *State, area: Rect, buf: *Buffer) void { // Main border const status_text = if (state.paused) " Progress Demo [PAUSED] " else " Progress Demo "; const block = Block.init() .title(status_text) .setBorders(Borders.all) .borderStyle((Style{}).fg(Color.cyan)); block.render(area, buf); // Calculate total progress var total_current: u64 = 0; var total_total: u64 = 0; for (state.tasks) |task| { total_current += task.current; total_total += task.total; } // Overall progress at top const overall_y: u16 = 2; _ = buf.setString(2, overall_y, "Overall Progress:", (Style{}).fg(Color.white).bold()); const overall_pct = if (total_total > 0) total_current * 100 / total_total else 0; const overall_bar_width = area.width -| 6; const overall_filled = @as(u16, @intCast(overall_bar_width * overall_pct / 100)); // Draw overall progress bar var i: u16 = 0; while (i < overall_bar_width) : (i += 1) { const char: []const u8 = if (i < overall_filled) "=" else "-"; const col = if (i < overall_filled) Color.cyan else Color.rgb(60, 60, 60); _ = buf.setString(2 + i, overall_y + 1, char, (Style{}).fg(col)); } // Overall percentage var pct_buf: [16]u8 = undefined; const pct_str = std.fmt.bufPrint(&pct_buf, " {d}%", .{overall_pct}) catch "?%"; _ = buf.setString(2 + overall_bar_width -| 5, overall_y + 1, pct_str, (Style{}).fg(Color.white).bold()); // Individual task progress var y: u16 = overall_y + 4; for (state.tasks) |task| { if (y >= area.height -| 3) break; // Task name _ = buf.setString(2, y, task.name, (Style{}).fg(task.color)); // Progress bar const bar_width = area.width -| 30; const pct = if (task.total > 0) task.current * 100 / task.total else 0; const filled = @as(u16, @intCast(bar_width * pct / 100)); var j: u16 = 0; while (j < bar_width) : (j += 1) { const char: []const u8 = if (j < filled) "=" else "-"; const col = if (j < filled) task.color else Color.rgb(60, 60, 60); _ = buf.setString(18 + j, y, char, (Style{}).fg(col)); } // Stats var stats_buf: [32]u8 = undefined; const stats = std.fmt.bufPrint(&stats_buf, "{d}/{d} ({d}%)", .{ task.current, task.total, pct, }) catch "..."; _ = buf.setString(area.width -| 18, y, stats, (Style{}).fg(Color.white)); y += 2; } // Completion message if (total_current >= total_total) { const msg = "All tasks completed!"; const msg_x = (area.width -| @as(u16, @intCast(msg.len))) / 2; _ = buf.setString(msg_x, y + 1, msg, (Style{}).fg(Color.green).bold()); } // Footer const help = if (state.paused) "SPACE/p resume | r restart | q quit" else "SPACE/p pause | r restart | q quit"; _ = buf.setString( 2, area.height -| 1, help, (Style{}).fg(Color.rgb(100, 100, 100)), ); }