diff --git a/examples/main.zig b/examples/main.zig index 8b53451..340415d 100644 --- a/examples/main.zig +++ b/examples/main.zig @@ -3,29 +3,41 @@ const odditui = @import("odditui"); const log = std.log.scoped(.main); pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + const deinit_status = gpa.deinit(); + //fail test; can't try in defer as defer is executed after we return + if (deinit_status == .leak) { + log.err("memory leak", .{}); + } + } + const alloc = gpa.allocator(); + var app: odditui.App(Event) = try odditui.App(Event).init(.{}); - defer app.deinit(); + defer app.deinit(alloc); try app.start(); defer app.stop(); outer: while (true) { const event = app.nextEvent(); + log.debug("event: {}\r\n", .{event}); switch (event) { .key_press => |key| { if (key.codepoint == 'c' and key.mods.ctrl) { break :outer; } }, - .winsize => {}, + .winsize => |ws| { + try app.resize(alloc, ws.rows, ws.cols); + }, else => {}, } - log.debug("event: {}\r\n", .{event}); } } const Event = union(enum) { key_press: odditui.Key, - winsize: std.os.system.winsize, + winsize: odditui.Winsize, mouse: u8, }; diff --git a/src/Screen.zig b/src/Screen.zig new file mode 100644 index 0000000..1a6641d --- /dev/null +++ b/src/Screen.zig @@ -0,0 +1,43 @@ +const std = @import("std"); + +const Cell = @import("cell.zig").Cell; + +const Screen = @This(); + +width: usize, +height: usize, + +buf: []Cell = undefined, + +pub fn init() Screen { + return Screen{ + .width = 0, + .height = 0, + }; +} + +pub fn deinit(self: *Screen, alloc: std.mem.Allocator) void { + alloc.free(self.buf); +} + +pub fn resize(self: *Screen, alloc: std.mem.Allocator, w: usize, h: usize) !void { + alloc.free(self.buf); + self.buf = try alloc.alloc(Cell, w * h); + self.width = w; + self.height = h; +} + +/// writes a cell to a location. 0 indexed +pub fn writeCell(self: *Screen, cell: Cell, row: usize, col: usize) void { + if (self.width < col) { + // column out of bounds + return; + } + if (self.height < row) { + // height out of bounds + return; + } + const i = (col * self.width) + row; + std.debug.assert(i < self.buf.len); + self.buf[i] = cell; +} diff --git a/src/Tty.zig b/src/Tty.zig index 62f9e56..c1ac0cb 100644 --- a/src/Tty.zig +++ b/src/Tty.zig @@ -218,7 +218,14 @@ fn ior(inout: u32, group: usize, num: usize, len: usize) usize { return (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)); } -fn getWinsize(fd: os.fd_t) !os.system.winsize { +pub const Winsize = struct { + rows: usize, + cols: usize, + x_pixel: usize, + y_pixel: usize, +}; + +fn getWinsize(fd: os.fd_t) !Winsize { var winsize = os.system.winsize{ .ws_row = 0, .ws_col = 0, @@ -228,6 +235,11 @@ fn getWinsize(fd: os.fd_t) !os.system.winsize { const err = os.system.ioctl(fd, TIOCGWINSZ, @intFromPtr(&winsize)); if (os.errno(err) == .SUCCESS) - return winsize; + return Winsize{ + .rows = winsize.ws_row, + .cols = winsize.ws_col, + .x_pixel = winsize.ws_xpixel, + .y_pixel = winsize.ws_ypixel, + }; return error.IoctlError; } diff --git a/src/app.zig b/src/app.zig index a6b9eac..43c2c68 100644 --- a/src/app.zig +++ b/src/app.zig @@ -3,11 +3,17 @@ const std = @import("std"); const queue = @import("queue.zig"); const Tty = @import("Tty.zig"); const Key = @import("Key.zig"); +const Screen = @import("Screen.zig"); /// App is the entrypoint for an odditui application. The provided type T should /// be a tagged union which contains all of the events the application will /// handle. Odditui will look for the following fields on the union and, if /// found, emit them via the "nextEvent" method +/// +/// The following events *must* be in your enum +/// - `key_press: Key`, for key press events +/// - `winsize: std.os.system.winsize`, for resize events. Must call app.resize +/// when receiving this event pub fn App(comptime T: type) type { return struct { const Self = @This(); @@ -23,6 +29,8 @@ pub fn App(comptime T: type) type { tty: ?Tty, + screen: Screen, + /// Runtime options const Options = struct {}; @@ -31,14 +39,16 @@ pub fn App(comptime T: type) type { return Self{ .queue = .{}, .tty = null, + .screen = Screen.init(), }; } - pub fn deinit(self: *Self) void { + pub fn deinit(self: *Self, alloc: std.mem.Allocator) void { if (self.tty) |_| { var tty = &self.tty.?; tty.deinit(); } + self.screen.deinit(alloc); } /// spawns the input thread to start listening to the tty for input @@ -66,6 +76,13 @@ pub fn App(comptime T: type) type { pub fn postEvent(self: *Self, event: T) void { self.queue.push(event); } + + /// resize allocates a slice of cellsequal to the number of cells + /// required to display the screen (ie width x height). Any previous screen is + /// freed when resizing + pub fn resize(self: *Self, alloc: std.mem.Allocator, w: usize, h: usize) !void { + try self.screen.resize(alloc, w, h); + } }; } diff --git a/src/cell.zig b/src/cell.zig index db94358..51ab55f 100644 --- a/src/cell.zig +++ b/src/cell.zig @@ -9,10 +9,19 @@ pub const Character = struct { }; pub const Style = struct { + pub const Underline = enum { + off, + single, + double, + curly, + dotted, + dashed, + }; + fg: Color = .default, bg: Color = .default, ul: Color = .default, - ul_style: UnderlineStyle = .off, + ul_style: Underline = .off, url: ?[]const u8 = null, url_params: ?[]const u8 = null, }; @@ -22,12 +31,3 @@ pub const Color = union(enum) { index: u8, rgb: [3]u8, }; - -pub const UnderlineStyle = enum { - off, - single, - double, - curly, - dotted, - dashed, -}; diff --git a/src/main.zig b/src/main.zig index c73fa03..15eae6b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,6 @@ pub const App = @import("app.zig").App; pub const Key = @import("Key.zig"); +pub const Winsize = @import("Tty.zig").Winsize; test { _ = @import("Key.zig");