From f9179b4e9a32d796d73dfe93088041a610b6c2c8 Mon Sep 17 00:00:00 2001 From: "R.Eugenio" Date: Sun, 18 Jan 2026 02:01:04 +0100 Subject: [PATCH] build: Migrar a Zig 0.16 - Crear src/utils/time.zig para centralizar helpers de tiempo (timestamp, milliTimestamp, nanoTimestamp). - Reemplazar std.io.fixedBufferStream por std.Io.Writer.fixed. - Migrar de std.fs.cwd() a std.Io.Dir.cwd() y actualizar firmas de lectura/escritura. - Reemplazar std.Thread.sleep por std.posix.system.nanosleep. - Corregir capturas descartadas en switches (text_input => {}). - Actualizar tests y ejemplos para compatibilidad con std.Io y nuevos helpers. - Actualizar build.zig.zon a 0.16.0. Co-Authored-By: Gemini --- build.zig.zon | 2 +- examples/hello.zig | 5 ++-- examples/macro_demo.zig | 7 +++--- examples/table_demo.zig | 3 ++- src/core/accessibility.zig | 43 +++++++++++++++++--------------- src/core/mainloop.zig | 2 +- src/core/shortcuts.zig | 5 ++-- src/macro/macro.zig | 28 ++++++++++++--------- src/panels/detail/base.zig | 4 +-- src/render/ttf.zig | 15 +++++------ src/utils/benchmark.zig | 20 +++++++++------ src/utils/time.zig | 19 ++++++++++++++ src/utils/utils.zig | 5 ++++ src/widgets/datepicker.zig | 3 ++- src/widgets/image.zig | 5 ++-- src/widgets/progress/spinner.zig | 3 ++- src/widgets/toast.zig | 7 +++--- src/widgets/tooltip.zig | 3 ++- 18 files changed, 111 insertions(+), 68 deletions(-) create mode 100644 src/utils/time.zig diff --git a/build.zig.zon b/build.zig.zon index 25ef7f9..f4b6320 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .fingerprint = 0x30a5cd33d0b0066c, .name = .zcatgui, .version = "0.1.0", - .minimum_zig_version = "0.15.0", + .minimum_zig_version = "0.16.0", .dependencies = .{ .zcatttf = .{ diff --git a/examples/hello.zig b/examples/hello.zig index e777fbe..3ed5eab 100644 --- a/examples/hello.zig +++ b/examples/hello.zig @@ -75,7 +75,8 @@ pub fn main() !void { frame += 1; - // Cap at ~60 FPS - std.Thread.sleep(16 * std.time.ns_per_ms); + // Control frame rate (approx 60 FPS) + const ts = std.posix.timespec{ .sec = 0, .nsec = 16 * std.time.ns_per_ms }; + _ = std.posix.system.nanosleep(&ts, null); } } diff --git a/examples/macro_demo.zig b/examples/macro_demo.zig index 162c0f9..1f76618 100644 --- a/examples/macro_demo.zig +++ b/examples/macro_demo.zig @@ -109,7 +109,7 @@ pub fn main() !void { .s => { if (recorder.eventCount() > 0) { - recorder.save("macro.zcm") catch |err| { + recorder.save(std.Options.debug_io, "macro.zcm") catch |err| { std.debug.print("Save failed: {}\n", .{err}); }; status_message = "Saved to macro.zcm"; @@ -118,7 +118,7 @@ pub fn main() !void { }, .l => { - recorder.load("macro.zcm") catch |err| { + recorder.load(std.Options.debug_io, "macro.zcm") catch |err| { std.debug.print("Load failed: {}\n", .{err}); }; status_message = "Loaded from macro.zcm"; @@ -169,7 +169,8 @@ pub fn main() !void { backend.present(&fb); // Cap at ~60 FPS - std.Thread.sleep(16 * std.time.ns_per_ms); + const ts = std.posix.timespec{ .sec = 0, .nsec = 16 * std.time.ns_per_ms }; + _ = std.posix.system.nanosleep(&ts, null); } std.debug.print("Final status: {s}\n", .{status_message}); diff --git a/examples/table_demo.zig b/examples/table_demo.zig index 77955ba..09566dd 100644 --- a/examples/table_demo.zig +++ b/examples/table_demo.zig @@ -334,7 +334,8 @@ pub fn main() !void { frame += 1; // Cap at ~60 FPS - std.Thread.sleep(16 * std.time.ns_per_ms); + const ts = std.posix.timespec{ .sec = 0, .nsec = 16 * std.time.ns_per_ms }; + _ = std.posix.system.nanosleep(&ts, null); } print("\n=== Demo Complete ===\n", .{}); diff --git a/src/core/accessibility.zig b/src/core/accessibility.zig index 8ae101c..e0eda2e 100644 --- a/src/core/accessibility.zig +++ b/src/core/accessibility.zig @@ -310,8 +310,7 @@ pub const Info = struct { /// Format as accessible text announcement pub fn announce(self: Self, buf: []u8) []const u8 { - var stream = std.io.fixedBufferStream(buf); - const writer = stream.writer(); + var writer = std.Io.Writer.fixed(buf); // Label if (self.label.len > 0) { @@ -319,40 +318,44 @@ pub const Info = struct { } // Role - writer.print(", {s}", .{self.role.name()}) catch return buf[0..stream.pos]; + writer.print(", {s}", .{self.role.name()}) catch return buf[0..writer.end]; // Value if (self.value) |v| { - writer.print(", {s}", .{v}) catch return buf[0..stream.pos]; + writer.print(", {s}", .{v}) catch return buf[0..writer.end]; } else if (self.value_now) |v| { if (self.value_min != null and self.value_max != null) { - writer.print(", {d:.0} of {d:.0}", .{ v, self.value_max.? }) catch return buf[0..stream.pos]; + writer.print(", {d:.0} of {d:.0}", .{ v, self.value_max.? }) catch return buf[0..writer.end]; + } else { + writer.print(", {d:.0}", .{v}) catch return buf[0..writer.end]; } } // State - if (self.state.disabled) { - writer.writeAll(", disabled") catch return buf[0..stream.pos]; - } - if (self.state.checked) { - writer.writeAll(", checked") catch return buf[0..stream.pos]; - } - if (self.state.expanded) { - writer.writeAll(", expanded") catch return buf[0..stream.pos]; + if (self.state.focused) { + writer.writeAll(", focused") catch return buf[0..writer.end]; } if (self.state.selected) { - writer.writeAll(", selected") catch return buf[0..stream.pos]; + writer.writeAll(", selected") catch return buf[0..writer.end]; } - if (self.state.invalid) { - writer.writeAll(", invalid") catch return buf[0..stream.pos]; + if (self.state.disabled) { + writer.writeAll(", disabled") catch return buf[0..writer.end]; + } + if (self.state.checked) { + writer.writeAll(", checked") catch return buf[0..writer.end]; + } + if (self.state.expanded) { + writer.writeAll(", expanded") catch return buf[0..writer.end]; + } else if (self.role == .menu or self.role == .treeitem) { + writer.writeAll(", collapsed") catch return buf[0..writer.end]; } - // Position - if (self.pos_in_set != null and self.set_size != null) { - writer.print(", {d} of {d}", .{ self.pos_in_set.?, self.set_size.? }) catch return buf[0..stream.pos]; + // Shortcut + if (self.shortcut.len > 0) { + writer.print(", shortcut {s}", .{self.shortcut}) catch return buf[0..writer.end]; } - return buf[0..stream.pos]; + return buf[0..writer.end]; } }; diff --git a/src/core/mainloop.zig b/src/core/mainloop.zig index 5b0c5d2..558dc52 100644 --- a/src/core/mainloop.zig +++ b/src/core/mainloop.zig @@ -257,7 +257,7 @@ pub const MainLoop = struct { .resize => |size| { self.handleResize(size.width, size.height) catch {}; }, - .text_input => |_| { + .text_input => { self.needs_redraw = true; }, } diff --git a/src/core/shortcuts.zig b/src/core/shortcuts.zig index f0e2b94..d65cd89 100644 --- a/src/core/shortcuts.zig +++ b/src/core/shortcuts.zig @@ -249,8 +249,7 @@ pub const ShortcutManager = struct { /// Format a shortcut as human-readable text pub fn formatShortcut(buf: []u8, shortcut: Shortcut) []const u8 { - var stream = std.io.fixedBufferStream(buf); - const writer = stream.writer(); + var writer = std.Io.Writer.fixed(buf); if (shortcut.modifiers.ctrl) { writer.writeAll("Ctrl+") catch return ""; @@ -268,7 +267,7 @@ pub fn formatShortcut(buf: []u8, shortcut: Shortcut) []const u8 { const key_name = keyName(shortcut.key); writer.writeAll(key_name) catch return ""; - return buf[0..stream.pos]; + return buf[0..writer.end]; } /// Get human-readable name for a key diff --git a/src/macro/macro.zig b/src/macro/macro.zig index 7bb4b8e..389dcfd 100644 --- a/src/macro/macro.zig +++ b/src/macro/macro.zig @@ -115,12 +115,12 @@ pub const MacroRecorder = struct { } /// Save recorded events to a file - pub fn save(self: *Self, path: []const u8) !void { - const file = try std.fs.cwd().createFile(path, .{}); - defer file.close(); + pub fn save(self: *Self, io: std.Io, path: []const u8) !void { + const file = try std.Io.Dir.cwd().createFile(io, path, .{}); + defer file.close(io); // Write header directly - _ = try file.write("ZCATGUI_MACRO_V1\n"); + _ = try file.writeStreamingAll(io, "ZCATGUI_MACRO_V1\n"); // Write events var line_buf: [128]u8 = undefined; @@ -132,20 +132,20 @@ pub const MacroRecorder = struct { event.char orelse 0, @as(u8, if (event.pressed) 1 else 0), }) catch continue; - _ = try file.write(line); + _ = try file.writeStreamingAll(io, line); } } /// Load events from a file - pub fn load(self: *Self, path: []const u8) !void { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); + pub fn load(self: *Self, io: std.Io, path: []const u8) !void { + const file = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer file.close(io); // Read file content into buffer - const stat = try file.stat(); - const content = try self.allocator.alloc(u8, stat.size); + const stat = try file.stat(io); + const content = try self.allocator.alloc(u8, @intCast(stat.size)); defer self.allocator.free(content); - _ = try file.readAll(content); + _ = try file.readPositional(io, &.{content}, 0); // Parse content line by line var lines = std.mem.splitScalar(u8, content, '\n'); @@ -203,7 +203,11 @@ pub const MacroPlayer = struct { for (events) |event| { inject_fn(event); if (delay_ms > 0) { - std.Thread.sleep(delay_ms * std.time.ns_per_ms); + const ts = std.posix.timespec{ + .sec = @intCast(@divTrunc(delay_ms, 1000)), + .nsec = @intCast((delay_ms % 1000) * std.time.ns_per_ms), + }; + _ = std.posix.system.nanosleep(&ts, null); } } } diff --git a/src/panels/detail/base.zig b/src/panels/detail/base.zig index 91bb6cc..b9bedd9 100644 --- a/src/panels/detail/base.zig +++ b/src/panels/detail/base.zig @@ -126,7 +126,7 @@ pub const DetailPanelBase = struct { /// Transiciona automaticamente de saved -> viewing despues de 2 segundos. pub fn updateSavedTimer(self: *Self) void { if (self.state == .saved) { - const elapsed = std.time.milliTimestamp() - self.saved_timer; + const elapsed = zcatgui.utils.milliTimestamp() - self.saved_timer; if (elapsed >= 2000) { self.state = .viewing; } @@ -141,7 +141,7 @@ pub const DetailPanelBase = struct { /// Inicia el timer para volver a viewing. pub fn markSaved(self: *Self) void { self.state = .saved; - self.saved_timer = std.time.milliTimestamp(); + self.saved_timer = zcatgui.utils.milliTimestamp(); self.error_message = null; } diff --git a/src/render/ttf.zig b/src/render/ttf.zig index 49c9b9c..f23f5bb 100644 --- a/src/render/ttf.zig +++ b/src/render/ttf.zig @@ -101,14 +101,14 @@ pub const TtfFont = struct { const Self = @This(); /// Load font from file - pub fn loadFromFile(allocator: Allocator, path: []const u8) !Self { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); + pub fn loadFromFile(io: std.Io, allocator: Allocator, path: []const u8) !Self { + const file = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer file.close(io); - const stat = try file.stat(); - const data = try allocator.alloc(u8, stat.size); + const stat = try file.stat(io); + const data = try allocator.alloc(u8, @intCast(stat.size)); - const bytes_read = try file.readAll(data); + const bytes_read = try file.readPositional(io, &.{data}, 0); if (bytes_read != stat.size) { allocator.free(data); return error.IncompleteRead; @@ -565,9 +565,10 @@ test "decodeUtf8" { test "TtfFont with zcatttf" { const allocator = std.testing.allocator; + const io = std.testing.io; // Try to load DroidSans from system - var font = TtfFont.loadFromFile(allocator, "/usr/share/fonts/google-droid-sans-fonts/DroidSans.ttf") catch { + var font = TtfFont.loadFromFile(io, allocator, "/usr/share/fonts/google-droid-sans-fonts/DroidSans.ttf") catch { // Font not available on this system, skip test return; }; diff --git a/src/utils/benchmark.zig b/src/utils/benchmark.zig index 5043dfb..aaeea42 100644 --- a/src/utils/benchmark.zig +++ b/src/utils/benchmark.zig @@ -17,6 +17,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const time = @import("time.zig"); /// High-resolution timer for benchmarking pub const Timer = struct { @@ -35,13 +36,13 @@ pub const Timer = struct { } pub fn start(self: *Self) void { - self.start_time = std.time.nanoTimestamp(); + self.start_time = time.nanoTimestamp(); self.running = true; } pub fn stop(self: *Self) void { if (self.running) { - self.elapsed_ns = std.time.nanoTimestamp() - self.start_time; + self.elapsed_ns = time.nanoTimestamp() - self.start_time; self.running = false; } } @@ -54,7 +55,7 @@ pub const Timer = struct { pub fn elapsedNs(self: Self) i128 { if (self.running) { - return std.time.nanoTimestamp() - self.start_time; + return time.nanoTimestamp() - self.start_time; } return self.elapsed_ns; } @@ -318,11 +319,11 @@ pub const FrameTimer = struct { } pub fn beginFrame(self: *Self) void { - self.frame_start = std.time.nanoTimestamp(); + self.frame_start = time.nanoTimestamp(); } pub fn endFrame(self: *Self) void { - self.last_frame_ns = std.time.nanoTimestamp() - self.frame_start; + self.last_frame_ns = time.nanoTimestamp() - self.frame_start; self.frame_count += 1; self.total_time_ns += self.last_frame_ns; self.min_frame_ns = @min(self.min_frame_ns, self.last_frame_ns); @@ -505,7 +506,8 @@ pub fn benchmarkCommands(allocator: Allocator) !void { test "Timer basic" { var timer = Timer.init(); timer.start(); - std.Thread.sleep(1_000_000); // 1ms + const ts = std.posix.timespec{ .sec = 0, .nsec = 1_000_000 }; + _ = std.posix.system.nanosleep(&ts, null); timer.stop(); try std.testing.expect(timer.elapsedNs() > 0); @@ -517,7 +519,8 @@ test "Benchmark basic" { for (0..10) |_| { bench.startIteration(); - std.Thread.sleep(100_000); // 0.1ms + const ts = std.posix.timespec{ .sec = 0, .nsec = 100_000 }; + _ = std.posix.system.nanosleep(&ts, null); bench.endIteration(); } @@ -544,7 +547,8 @@ test "FrameTimer" { for (0..5) |_| { timer.beginFrame(); - std.Thread.sleep(1_000_000); // 1ms + const ts = std.posix.timespec{ .sec = 0, .nsec = 1_000_000 }; + _ = std.posix.system.nanosleep(&ts, null); timer.endFrame(); } diff --git a/src/utils/time.zig b/src/utils/time.zig new file mode 100644 index 0000000..2262492 --- /dev/null +++ b/src/utils/time.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +/// Returns the current Unix timestamp in seconds +pub fn timestamp() i64 { + const ts = std.posix.clock_gettime(std.posix.CLOCK.REALTIME) catch return 0; + return ts.sec; +} + +/// Returns the current Unix timestamp in milliseconds +pub fn milliTimestamp() i64 { + const ts = std.posix.clock_gettime(std.posix.CLOCK.REALTIME) catch return 0; + return ts.sec * 1000 + @divTrunc(ts.nsec, 1_000_000); +} + +/// Returns the current Unix timestamp in nanoseconds +pub fn nanoTimestamp() i128 { + const ts = std.posix.clock_gettime(std.posix.CLOCK.REALTIME) catch return 0; + return @as(i128, ts.sec) * 1_000_000_000 + ts.nsec; +} diff --git a/src/utils/utils.zig b/src/utils/utils.zig index 14384d6..7ebd1c5 100644 --- a/src/utils/utils.zig +++ b/src/utils/utils.zig @@ -13,6 +13,7 @@ pub const arena = @import("arena.zig"); pub const pool = @import("pool.zig"); pub const benchmark = @import("benchmark.zig"); pub const testing_utils = @import("testing.zig"); +pub const time = @import("time.zig"); // Re-exports pub const FrameArena = arena.FrameArena; @@ -27,6 +28,10 @@ pub const Timer = benchmark.Timer; pub const FrameTimer = benchmark.FrameTimer; pub const AllocationTracker = benchmark.AllocationTracker; +pub const timestamp = time.timestamp; +pub const milliTimestamp = time.milliTimestamp; +pub const nanoTimestamp = time.nanoTimestamp; + // Testing utilities pub const TestRunner = testing_utils.TestRunner; pub const SnapshotTester = testing_utils.SnapshotTester; diff --git a/src/widgets/datepicker.zig b/src/widgets/datepicker.zig index a85d01b..5b50391 100644 --- a/src/widgets/datepicker.zig +++ b/src/widgets/datepicker.zig @@ -9,6 +9,7 @@ const Command = @import("../core/command.zig"); const Layout = @import("../core/layout.zig"); const Style = @import("../core/style.zig"); const Input = @import("../core/input.zig"); +const utils = @import("../utils/utils.zig"); /// Date structure pub const Date = struct { @@ -29,7 +30,7 @@ pub const Date = struct { /// Get today's date (simplified - uses epoch calculation) pub fn today() Self { - const ts = std.time.timestamp(); + const ts = utils.timestamp(); return fromTimestamp(ts); } diff --git a/src/widgets/image.zig b/src/widgets/image.zig index f4a4401..19866f6 100644 --- a/src/widgets/image.zig +++ b/src/widgets/image.zig @@ -9,6 +9,7 @@ const Context = @import("../core/context.zig").Context; const Command = @import("../core/command.zig"); const Layout = @import("../core/layout.zig"); const Style = @import("../core/style.zig"); +const utils = @import("../utils/utils.zig"); /// Image pixel format pub const Format = enum { @@ -166,7 +167,7 @@ pub const ImageCache = struct { /// Get image from cache pub fn get(self: *Self, id: u64) ?*CachedImage { if (self.entries.getPtr(id)) |entry| { - entry.last_used = std.time.milliTimestamp(); + entry.last_used = utils.milliTimestamp(); return entry; } return null; @@ -184,7 +185,7 @@ pub const ImageCache = struct { // Add entry self.entries.put(id, .{ .data = data, - .last_used = std.time.milliTimestamp(), + .last_used = utils.milliTimestamp(), .size_bytes = size, }) catch return; diff --git a/src/widgets/progress/spinner.zig b/src/widgets/progress/spinner.zig index 4104c69..99de0ec 100644 --- a/src/widgets/progress/spinner.zig +++ b/src/widgets/progress/spinner.zig @@ -8,6 +8,7 @@ const Layout = @import("../../core/layout.zig"); const Style = @import("../../core/style.zig"); const Rect = Layout.Rect; const Color = Style.Color; +const utils = @import("../../utils/utils.zig"); const render = @import("render.zig"); @@ -51,7 +52,7 @@ pub const SpinnerState = struct { last_update: i64 = 0, pub fn update(self: *SpinnerState, speed: f32) void { - const now = std.time.milliTimestamp(); + const now = utils.milliTimestamp(); if (self.last_update == 0) { self.last_update = now; return; diff --git a/src/widgets/toast.zig b/src/widgets/toast.zig index 003b9de..4308ad9 100644 --- a/src/widgets/toast.zig +++ b/src/widgets/toast.zig @@ -29,6 +29,7 @@ const Layout = @import("../core/layout.zig"); const Style = @import("../core/style.zig"); const Rect = Layout.Rect; const Color = Style.Color; +const utils = @import("../utils/utils.zig"); // ============================================================================= // Types @@ -146,7 +147,7 @@ pub const Toast = struct { .toast_type = toast_type, .message = undefined, .message_len = @min(message.len, 256), - .created_ms = std.time.milliTimestamp(), + .created_ms = utils.milliTimestamp(), .duration_ms = duration_ms, .dismissing = false, .animation = 0, @@ -174,13 +175,13 @@ pub const Toast = struct { pub fn shouldDismiss(self: *const Self) bool { if (self.duration_ms == 0) return false; - const elapsed = std.time.milliTimestamp() - self.created_ms; + const elapsed = utils.milliTimestamp() - self.created_ms; return elapsed >= self.duration_ms; } pub fn getRemainingMs(self: *const Self) i64 { if (self.duration_ms == 0) return -1; - const elapsed = std.time.milliTimestamp() - self.created_ms; + const elapsed = utils.milliTimestamp() - self.created_ms; return @max(0, @as(i64, self.duration_ms) - elapsed); } }; diff --git a/src/widgets/tooltip.zig b/src/widgets/tooltip.zig index c79aecf..cc73c11 100644 --- a/src/widgets/tooltip.zig +++ b/src/widgets/tooltip.zig @@ -28,6 +28,7 @@ const Layout = @import("../core/layout.zig"); const Style = @import("../core/style.zig"); const Rect = Layout.Rect; const Color = Style.Color; +const utils = @import("../utils/utils.zig"); // ============================================================================= // Configuration @@ -123,7 +124,7 @@ pub const State = struct { /// Update tooltip visibility based on hover pub fn update(self: *Self, target: Rect, mouse_x: i32, mouse_y: i32, delay_ms: u32) void { const hovering = self.isHovering(target, mouse_x, mouse_y); - const now = std.time.milliTimestamp(); + const now = utils.milliTimestamp(); if (hovering) { if (self.hover_start_ms == 0) {