This commit is contained in:
2026-04-18 15:23:29 -05:00
parent 1de39a5824
commit e18ba9ab79
10 changed files with 219 additions and 42 deletions
+15 -1
View File
@@ -1,17 +1,31 @@
const std = @import("std");
const mem = std.mem;
const view = @import("root.zig");
const style = view.style;
const Writer = std.Io.Writer;
const Style = style.Style;
// Cell is a single cell in the [DoubleBuffer].
pub const Cell = struct {
char: [8]u8,
width: usize,
style: ?Style,
/// eql returns
pub fn eql(self: *const Cell, b: *const Cell) bool {
return mem.eql(u8, &self.char, &b.char) and self.width == b.width;
}
pub fn render(self: *const @This(), w: *std.Io.Writer) !void {
pub fn render(self: *const @This(), w: *Writer) !void {
if (self.style) |*s| {
try s.render(w);
}
_ = try w.write(self.char[0..self.width]);
if (self.style) |_| {
try style.Reset.render(w);
}
}
};
+19 -7
View File
@@ -23,10 +23,18 @@ pub const DoubleBuffer = struct {
back: Matrix(Cell),
pub fn init(gpa: Allocator, size: Rect) !DoubleBuffer {
const a = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size);
const a = try Matrix(Cell).init(gpa, .{
.char = [_]u8{' '} ** 8,
.width = 1,
.style = null,
}, size);
errdefer a.deinit(gpa);
const b = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size);
const b = try Matrix(Cell).init(gpa, .{
.char = [_]u8{' '} ** 8,
.width = 1,
.style = null,
}, size);
return .{
.front = a,
@@ -60,18 +68,19 @@ pub const DoubleBuffer = struct {
for (back_row) |cell| {
_ = try cell.render(w);
}
_ = try w.write("\n");
_ = try w.write("\x1b[1E");
}
_ = try w.write("\x1b[H");
self.swap();
}
pub fn refresh(self: *@This(), w: *std.Io.Writer) !void {
_ = try w.write("\x1b[H");
var back_iter = self.back.row_iterator();
var front_iter = self.front.row_iterator();
var skip_lines: usize = 0;
var rendered: bool = false;
while (true) {
var back_row = back_iter.next() orelse break;
@@ -88,15 +97,18 @@ pub const DoubleBuffer = struct {
for (back_row[x..]) |*cell| {
try cell.render(w);
}
_ = try w.write("\n");
_ = try w.write("\x1b[1E");
skip_lines = 0;
rendered = true;
break;
}
skip_lines += 1;
}
_ = try w.write("\x1b[?25l");
if (rendered) {
_ = try w.write("\x1b[H");
}
self.swap();
}
};
+7
View File
@@ -1,5 +1,12 @@
const widget = @import("widget.zig");
pub const style = @import("style.zig");
pub const DoubleBuffer = @import("double_buffer.zig").DoubleBuffer;
pub const Cell = @import("cell.zig").Cell;
pub const Widget = widget.Widget;
pub const WidgetError = widget.WidgetError;
pub const Viewport = @import("viewport.zig").Viewport;
test {
@import("std").testing.refAllDecls(@This());
+16
View File
@@ -0,0 +1,16 @@
const std = @import("std");
const Writer = std.Io.Writer;
/// Style is a specific style which will write out to the writer.
pub const Style = struct {
escape: []const u8,
pub fn render(self: @This(), w: *Writer) !void {
_ = try w.write(self.escape);
}
};
/// Reset is a special case that will reset all styles
pub const Reset = Style{ .escape = "\x1b[0m" };
pub const Red = Style{ .escape = "\x1b[31m" };
+49 -1
View File
@@ -1,8 +1,56 @@
const std = @import("std");
const grome = @import("../root.zig");
const view = @import("root.zig");
const DoubleBuffer = grome.draw.DoubleBuffer;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const Writer = std.Io.Writer;
const DoubleBuffer = view.DoubleBuffer;
const Widget = view.Widget;
const Rect = grome.container.Rect;
pub const Viewport = struct {
/// buffer holds the double buffer which will actually write things to
/// the screen.
buffer: DoubleBuffer,
/// widgets define the widgets which will be rendered to the double
/// buffer
widgets: ArrayList(*Widget),
pub fn init(gpa: Allocator, size: Rect) !@This() {
return .{
.buffer = try DoubleBuffer.init(gpa, size),
.widgets = .empty,
};
}
pub fn hard_refresh(self: *@This(), w: *Writer) !void {
try self.render();
try self.buffer.hard_refresh(w);
}
pub fn deinit(self: *@This(), gpa: Allocator) void {
self.buffer.deinit(gpa);
self.widgets.deinit(gpa);
}
pub fn render(self: *@This()) !void {
// loop through all the widgets and render them
for (self.widgets.items) |widget| {
const b = widget.geometry();
var frame = self.buffer.frame(b);
try widget.render(&frame);
}
}
pub fn refresh(self: *@This(), w: *Writer) !void {
try self.render();
try self.buffer.refresh(w);
}
pub fn add_widget(self: *@This(), gpa: Allocator, widget: *Widget) !void {
return self.widgets.append(gpa, widget);
}
};
+32 -1
View File
@@ -2,7 +2,38 @@ const std = @import("std");
const mem = std.mem;
const grome = @import("../root.zig");
const view = @import("root.zig");
const Block = grome.container.Block;
const SubMatrix = grome.container.SubMatrix;
const Cell = view.Cell;
/// WidgetError is an error returned when doing things
/// and stuff
pub const WidgetError = error{
UnknownError,
};
/// A widget is an intrusive interface that allows the implementor to draw
/// a specific item to a frame.
pub const Widget = struct {};
pub const Widget = struct {
/// geometryFn returns a Block, which specifies the location and size of the
/// submatrix which will be passed into the render function
geometryFn: *const fn (*const @This()) Block,
/// renderFn passes in the SubMatrix which can be written to...
/// neat
renderFn: *const fn (*@This(), frame: *SubMatrix(Cell)) WidgetError!void,
/// geometry is the driver for geometryFn for the Widget interface.
/// you must create the geometryFn.
pub fn geometry(self: *const @This()) Block {
return self.geometryFn(self);
}
/// render is the driver for renderFn for the Widget interface.
/// you must create the renderFn.
pub fn render(self: *@This(), frame: *SubMatrix(Cell)) WidgetError!void {
return self.renderFn(self, frame);
}
};