screen: implement initial screen data structure

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
Tim Culverhouse 2024-01-18 23:17:57 -06:00
parent a9c97d051b
commit 266c5ec224
6 changed files with 102 additions and 17 deletions

View file

@ -3,29 +3,41 @@ const odditui = @import("odditui");
const log = std.log.scoped(.main); const log = std.log.scoped(.main);
pub fn main() !void { 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(.{}); var app: odditui.App(Event) = try odditui.App(Event).init(.{});
defer app.deinit(); defer app.deinit(alloc);
try app.start(); try app.start();
defer app.stop(); defer app.stop();
outer: while (true) { outer: while (true) {
const event = app.nextEvent(); const event = app.nextEvent();
log.debug("event: {}\r\n", .{event});
switch (event) { switch (event) {
.key_press => |key| { .key_press => |key| {
if (key.codepoint == 'c' and key.mods.ctrl) { if (key.codepoint == 'c' and key.mods.ctrl) {
break :outer; break :outer;
} }
}, },
.winsize => {}, .winsize => |ws| {
try app.resize(alloc, ws.rows, ws.cols);
},
else => {}, else => {},
} }
log.debug("event: {}\r\n", .{event});
} }
} }
const Event = union(enum) { const Event = union(enum) {
key_press: odditui.Key, key_press: odditui.Key,
winsize: std.os.system.winsize, winsize: odditui.Winsize,
mouse: u8, mouse: u8,
}; };

43
src/Screen.zig Normal file
View file

@ -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;
}

View file

@ -218,7 +218,14 @@ fn ior(inout: u32, group: usize, num: usize, len: usize) usize {
return (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)); 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{ var winsize = os.system.winsize{
.ws_row = 0, .ws_row = 0,
.ws_col = 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)); const err = os.system.ioctl(fd, TIOCGWINSZ, @intFromPtr(&winsize));
if (os.errno(err) == .SUCCESS) 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; return error.IoctlError;
} }

View file

@ -3,11 +3,17 @@ const std = @import("std");
const queue = @import("queue.zig"); const queue = @import("queue.zig");
const Tty = @import("Tty.zig"); const Tty = @import("Tty.zig");
const Key = @import("Key.zig"); const Key = @import("Key.zig");
const Screen = @import("Screen.zig");
/// App is the entrypoint for an odditui application. The provided type T should /// 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 /// 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 /// handle. Odditui will look for the following fields on the union and, if
/// found, emit them via the "nextEvent" method /// 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 { pub fn App(comptime T: type) type {
return struct { return struct {
const Self = @This(); const Self = @This();
@ -23,6 +29,8 @@ pub fn App(comptime T: type) type {
tty: ?Tty, tty: ?Tty,
screen: Screen,
/// Runtime options /// Runtime options
const Options = struct {}; const Options = struct {};
@ -31,14 +39,16 @@ pub fn App(comptime T: type) type {
return Self{ return Self{
.queue = .{}, .queue = .{},
.tty = null, .tty = null,
.screen = Screen.init(),
}; };
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
if (self.tty) |_| { if (self.tty) |_| {
var tty = &self.tty.?; var tty = &self.tty.?;
tty.deinit(); tty.deinit();
} }
self.screen.deinit(alloc);
} }
/// spawns the input thread to start listening to the tty for input /// 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 { pub fn postEvent(self: *Self, event: T) void {
self.queue.push(event); 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);
}
}; };
} }

View file

@ -9,10 +9,19 @@ pub const Character = struct {
}; };
pub const Style = struct { pub const Style = struct {
pub const Underline = enum {
off,
single,
double,
curly,
dotted,
dashed,
};
fg: Color = .default, fg: Color = .default,
bg: Color = .default, bg: Color = .default,
ul: Color = .default, ul: Color = .default,
ul_style: UnderlineStyle = .off, ul_style: Underline = .off,
url: ?[]const u8 = null, url: ?[]const u8 = null,
url_params: ?[]const u8 = null, url_params: ?[]const u8 = null,
}; };
@ -22,12 +31,3 @@ pub const Color = union(enum) {
index: u8, index: u8,
rgb: [3]u8, rgb: [3]u8,
}; };
pub const UnderlineStyle = enum {
off,
single,
double,
curly,
dotted,
dashed,
};

View file

@ -1,5 +1,6 @@
pub const App = @import("app.zig").App; pub const App = @import("app.zig").App;
pub const Key = @import("Key.zig"); pub const Key = @import("Key.zig");
pub const Winsize = @import("Tty.zig").Winsize;
test { test {
_ = @import("Key.zig"); _ = @import("Key.zig");