//! Terminal abstraction for zcatui. //! //! The Terminal struct provides the main entry point for TUI applications. //! It handles initialization, drawing, and cleanup of the terminal state. //! //! ## Example //! //! ```zig //! var term = try Terminal.init(allocator); //! defer term.deinit(); //! //! try term.draw(renderFn); //! ``` const std = @import("std"); const buffer = @import("buffer.zig"); const Buffer = buffer.Buffer; const Rect = buffer.Rect; const backend_mod = @import("backend/backend.zig"); const AnsiBackend = backend_mod.AnsiBackend; /// Terminal provides the main interface for TUI applications. pub const Terminal = struct { allocator: std.mem.Allocator, backend: AnsiBackend, current_buffer: Buffer, previous_buffer: Buffer, /// Initializes the terminal for TUI mode. /// /// Enables raw mode, hides cursor, and clears screen. pub fn init(allocator: std.mem.Allocator) !Terminal { var backend = AnsiBackend.init(); // Get terminal size const size = backend.getSize(); const rect = Rect.init(0, 0, size.width, size.height); var current_buffer = try Buffer.init(allocator, rect); var previous_buffer = try Buffer.init(allocator, rect); // Enter alternate screen, hide cursor, enable raw mode try backend.enterAlternateScreen(); try backend.hideCursor(); try backend.enableRawMode(); try backend.clear(); // Mark all cells as needing redraw current_buffer.markDirty(); previous_buffer.markClean(); return .{ .allocator = allocator, .backend = backend, .current_buffer = current_buffer, .previous_buffer = previous_buffer, }; } /// Cleans up terminal state. /// /// Shows cursor, exits alternate screen, and restores terminal mode. pub fn deinit(self: *Terminal) void { self.backend.disableRawMode() catch {}; self.backend.showCursor() catch {}; self.backend.leaveAlternateScreen() catch {}; self.current_buffer.deinit(); self.previous_buffer.deinit(); } /// Returns the current terminal area. pub fn area(self: *const Terminal) Rect { return self.current_buffer.area; } /// Returns a pointer to the current buffer for rendering. pub fn buffer(self: *Terminal) *Buffer { return &self.current_buffer; } /// Draws the UI by calling the provided render function. /// /// The render function receives the terminal area and buffer, /// and should render all widgets to the buffer. pub fn draw(self: *Terminal, comptime render_fn: fn (Rect, *Buffer) void) !void { // Clear buffer self.current_buffer.clear(); // Call user's render function render_fn(self.area(), &self.current_buffer); // Flush changes to terminal try self.flush(); } /// Draws using a context-aware render function. pub fn drawWithContext( self: *Terminal, context: anytype, comptime render_fn: fn (@TypeOf(context), Rect, *Buffer) void, ) !void { self.current_buffer.clear(); render_fn(context, self.area(), &self.current_buffer); try self.flush(); } /// Flushes buffer changes to the terminal. /// /// Compares current and previous buffers, only outputting differences. fn flush(self: *Terminal) !void { const rect = self.current_buffer.area; var y: u16 = rect.y; while (y < rect.bottom()) : (y += 1) { var x: u16 = rect.x; while (x < rect.right()) : (x += 1) { const current = self.current_buffer.get(x, y) orelse continue; const previous = self.previous_buffer.get(x, y); // Only update if changed const needs_update = if (previous) |prev| current.char != prev.char or !colorEqual(current.fg, prev.fg) or !colorEqual(current.bg, prev.bg) or !modifierEqual(current.modifiers, prev.modifiers) else true; if (needs_update) { try self.backend.moveCursor(x, y); try self.backend.setStyle(current.fg, current.bg, current.modifiers); try self.backend.writeChar(current.char); // Update previous buffer if (self.previous_buffer.getPtr(x, y)) |prev| { prev.* = current; } } } } try self.backend.flush(); } /// Resizes the terminal buffers. pub fn resize(self: *Terminal, width: u16, height: u16) !void { const new_rect = Rect.init(0, 0, width, height); self.current_buffer.deinit(); self.previous_buffer.deinit(); self.current_buffer = try Buffer.init(self.allocator, new_rect); self.previous_buffer = try Buffer.init(self.allocator, new_rect); self.current_buffer.markDirty(); } /// Clears the terminal screen. pub fn clear(self: *Terminal) !void { try self.backend.clear(); self.current_buffer.clear(); self.previous_buffer.clear(); self.current_buffer.markDirty(); } }; // Helper functions for comparison fn colorEqual(a: @import("style.zig").Color, b: @import("style.zig").Color) bool { return std.meta.eql(a, b); } fn modifierEqual(a: @import("style.zig").Modifier, b: @import("style.zig").Modifier) bool { return std.meta.eql(a, b); } // ============================================================================ // Tests // ============================================================================ test "Terminal type exists" { // Basic compilation test - actual terminal tests require real terminal _ = Terminal; }