screen: implement initial screen data structure
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
a9c97d051b
commit
266c5ec224
6 changed files with 102 additions and 17 deletions
|
@ -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
43
src/Screen.zig
Normal 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;
|
||||||
|
}
|
16
src/Tty.zig
16
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));
|
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;
|
||||||
}
|
}
|
||||||
|
|
19
src/app.zig
19
src/app.zig
|
@ -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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
src/cell.zig
20
src/cell.zig
|
@ -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,
|
|
||||||
};
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in a new issue