New modules (13): - src/resize.zig: SIGWINCH terminal resize detection - src/drag.zig: Mouse drag state and Splitter panels - src/diagnostic.zig: Elm-style error messages with code snippets - src/debug.zig: Debug overlay (FPS, timing, widget count) - src/profile.zig: Performance profiling with scoped timers - src/sixel.zig: Sixel graphics encoding for terminal images - src/async_loop.zig: epoll-based async event loop with timers - src/compose.zig: Widget composition utilities - src/shortcuts.zig: Keyboard shortcut registry - src/widgets/logo.zig: ASCII art logo widget Enhanced modules: - src/layout.zig: Added Constraint.ratio(num, denom) - src/terminal.zig: Integrated resize handling - src/root.zig: Re-exports all new modules New examples (9): - resize_demo, splitter_demo, dirtree_demo - help_demo, markdown_demo, progress_demo - spinner_demo, syntax_demo, viewport_demo Package manager: - build.zig.zon: Zig package manager support Stats: 60+ source files, 186+ tests, 20 executables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
178 lines
5.9 KiB
Zig
178 lines
5.9 KiB
Zig
//! 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)),
|
|
);
|
|
}
|