zcatgui/examples/macro_demo.zig
reugenio 59c597fc18 feat: zCatGui v0.1.0 - Initial project setup
Immediate Mode GUI library for Zig with software rendering.

Core features:
- SDL2 backend for cross-platform window/events
- Software rasterizer (works everywhere, including SSH)
- Macro recording system (cornerstone feature, like Vim)
- Command-list rendering (DrawRect, DrawText, etc.)
- Layout system with constraints
- Color/Style system with themes

Project structure:
- src/core/: context, command, input, layout, style
- src/macro/: MacroRecorder, MacroPlayer, MacroStorage
- src/render/: Framebuffer, SoftwareRenderer, Font
- src/backend/: Backend interface, SDL2 implementation
- examples/: hello.zig, macro_demo.zig
- docs/: Architecture, research (Gio, immediate-mode libs, Simifactu)

Build: zig build (requires SDL2-devel)
Tests: 16 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 01:30:05 +01:00

176 lines
6.1 KiB
Zig

//! Macro Demo - Demonstrates the macro recording system
//!
//! Press:
//! - R: Start/stop recording
//! - P: Play back recorded macro
//! - S: Save macro to file
//! - L: Load macro from file
//! - ESC: Quit
//!
//! Type any keys while recording to capture them.
const std = @import("std");
const zcatgui = @import("zcatgui");
const Framebuffer = zcatgui.render.Framebuffer;
const SoftwareRenderer = zcatgui.render.SoftwareRenderer;
const Sdl2Backend = zcatgui.backend.Sdl2Backend;
const MacroRecorder = zcatgui.MacroRecorder;
const MacroPlayer = zcatgui.MacroPlayer;
const Color = zcatgui.Color;
const Command = zcatgui.Command;
const KeyEvent = zcatgui.KeyEvent;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Initialize backend
var backend = try Sdl2Backend.init("zCatGui - Macro Demo", 800, 600);
defer backend.deinit();
// Create framebuffer
var fb = try Framebuffer.init(allocator, 800, 600);
defer fb.deinit();
// Create renderer
var renderer = SoftwareRenderer.init(&fb);
// Create macro recorder
var recorder = MacroRecorder.init(allocator);
defer recorder.deinit();
// State
var running = true;
var last_keys: [10]KeyEvent = undefined;
var last_key_count: usize = 0;
var status_message: []const u8 = "Press R to start recording";
// Event injection function for playback
const inject = struct {
var injected_count: usize = 0;
fn injectKey(key: KeyEvent) void {
_ = key;
injected_count += 1;
std.debug.print("Injected key #{d}\n", .{injected_count});
}
}.injectKey;
while (running) {
// Poll events
while (backend.pollEvent()) |event| {
switch (event) {
.quit => running = false,
.key => |key| {
if (!key.pressed) continue;
// Record if recording
recorder.record(key);
// Store for display
if (last_key_count < last_keys.len) {
last_keys[last_key_count] = key;
last_key_count += 1;
} else {
// Shift left
for (0..last_keys.len - 1) |i| {
last_keys[i] = last_keys[i + 1];
}
last_keys[last_keys.len - 1] = key;
}
// Handle special keys
switch (key.key) {
.escape => running = false,
.r => {
if (recorder.isRecording()) {
_ = recorder.stop();
status_message = "Recording stopped";
std.debug.print("Stopped recording. {d} events captured.\n", .{recorder.eventCount()});
} else {
recorder.start();
status_message = "Recording...";
std.debug.print("Started recording.\n", .{});
}
},
.p => {
if (!recorder.isRecording() and recorder.eventCount() > 0) {
status_message = "Playing back...";
std.debug.print("Playing back {d} events.\n", .{recorder.eventCount()});
MacroPlayer.play(recorder.stop(), inject);
status_message = "Playback complete";
}
},
.s => {
if (recorder.eventCount() > 0) {
recorder.save("macro.zcm") catch |err| {
std.debug.print("Save failed: {}\n", .{err});
};
status_message = "Saved to macro.zcm";
std.debug.print("Saved macro to macro.zcm\n", .{});
}
},
.l => {
recorder.load("macro.zcm") catch |err| {
std.debug.print("Load failed: {}\n", .{err});
};
status_message = "Loaded from macro.zcm";
std.debug.print("Loaded macro: {d} events\n", .{recorder.eventCount()});
},
else => {},
}
},
.resize => |size| {
try fb.resize(size.width, size.height);
},
else => {},
}
}
// Clear
renderer.clear(Color.background);
// Draw title area
renderer.execute(Command.rect(0, 0, fb.width, 50, Color.rgb(40, 40, 40)));
// Draw status
const status_color = if (recorder.isRecording()) Color.danger else Color.foreground;
renderer.execute(Command.rect(10, 60, 200, 20, status_color));
// Draw event count
renderer.execute(Command.rect(10, 90, @intCast(recorder.eventCount() * 10), 20, Color.primary));
// Draw last keys as blocks
for (0..last_key_count) |i| {
const x: i32 = @intCast(10 + i * 50);
renderer.execute(Command.rect(x, 150, 40, 40, Color.secondary));
}
// Draw instructions area
renderer.execute(Command.rect(10, 250, 400, 120, Color.rgb(35, 35, 35)));
// Text would go here with a proper font
// Recording indicator
if (recorder.isRecording()) {
renderer.execute(Command.rect(@as(i32, @intCast(fb.width)) - 30, 10, 20, 20, Color.danger));
}
// Present
backend.present(&fb);
// Cap at ~60 FPS
std.Thread.sleep(16 * std.time.ns_per_ms);
}
std.debug.print("Final status: {s}\n", .{status_message});
}