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
+1
View File
@@ -0,0 +1 @@
pub const Text = @import("text.zig").Text;
+57
View File
@@ -0,0 +1,57 @@
const std = @import("std");
const grome = @import("../root.zig");
const style = grome.view.style;
const Writer = std.Io.Writer;
const Widget = grome.view.Widget;
const WidgetError = grome.view.WidgetError;
const Cell = grome.view.Cell;
const Block = grome.container.Block;
const SubMatrix = grome.container.SubMatrix;
const Style = style.Style;
pub const Text = struct {
// TODO: This should be a matrix instead and we should add the ability
// to turn raw text into a Matrix(Cell)
text: [1024]Cell,
text_width: usize,
widget: Widget,
pub fn init(text: []const u8, s: ?Style) @This() {
// TODO: this is bad, but I want to see it work.
var cells: [1024]Cell = undefined;
for (0..text.len) |x| {
cells[x] = Cell{
.char = [_]u8{text[x]} ** 8,
.width = 1,
.style = s,
};
}
return .{
.text = cells,
.text_width = text.len,
.widget = .{
.geometryFn = @This().geometry,
.renderFn = @This().render,
},
};
}
pub fn geometry(widget: *const Widget) Block {
const self: *const @This() = @fieldParentPtr("widget", widget);
return Block{
.loc = .{ .x = 0, .y = 0 },
.size = .{ .height = 1, .width = self.text_width },
};
}
pub fn render(widget: *Widget, frame: *SubMatrix(Cell)) WidgetError!void {
const self: *@This() = @fieldParentPtr("widget", widget);
var row = frame.rowable.row(0) catch return WidgetError.UnknownError;
const ending = @min(self.text_width, row.len);
@memcpy(row[0..ending], self.text[0..ending]);
}
};
+22 -32
View File
@@ -1,11 +1,13 @@
const std = @import("std");
const grome = @import("root.zig");
const style = grome.view.style;
const Io = std.Io;
const Duration = std.Io.Duration;
const Clock = std.Io.Clock;
const Writer = std.Io.File.Writer;
const Rect = grome.container.Rect;
const DoubleBuffer = grome.view.DoubleBuffer;
const Duration = Io.Duration;
const Clock = Io.Clock;
const Text = grome.game.Text;
const Viewport = grome.view.Viewport;
pub fn main(init: std.process.Init) !void {
// This is appropriate for anything that lives as long as the process.
@@ -13,39 +15,27 @@ pub fn main(init: std.process.Init) !void {
// Initialize the stdout writer
var stdout_buffer: [1024]u8 = undefined;
var stdout_file_writer: Io.File.Writer = .init(.stdout(), init.io, &stdout_buffer);
var stdout_file_writer: Writer = .init(.stdout(), init.io, &stdout_buffer);
var w = &stdout_file_writer.interface;
// alright lets play with the matrix
const size = Rect{ .height = 70, .width = 120 };
var double_buf = try DoubleBuffer.init(arena, size);
defer double_buf.deinit(arena);
try double_buf.hard_refresh(w);
const size = Rect{
.height = 70,
.width = 120,
};
var viewport = try Viewport.init(arena, size);
defer viewport.deinit(arena);
var text = Text.init("hello Grome", style.Red);
try viewport.add_widget(arena, &text.widget);
try viewport.hard_refresh(w);
try w.flush();
while (true) {
var frame = double_buf.frame(.{ .loc = .{ .x = 0, .y = 10 }, .size = .{ .height = 1, .width = 120 } });
const row = try frame.rowable.row(0);
var fill_next: bool = false;
var unfilled: bool = true;
for (row) |*cell| {
if (cell.char[0] == '>') {
fill_next = true;
cell.char[0] = ' ';
} else {
if (fill_next) {
fill_next = false;
cell.char[0] = '>';
unfilled = false;
}
}
}
if (unfilled) {
row[0].char[0] = '>';
}
try double_buf.refresh(w);
try viewport.refresh(w);
try w.flush();
try init.io.sleep(Duration.fromMilliseconds(10), Clock.real);
try init.io.sleep(Duration.fromMilliseconds(30), Clock.real);
}
}
+1
View File
@@ -1,6 +1,7 @@
const std = @import("std");
pub const container = @import("container/root.zig");
pub const view = @import("view/root.zig");
pub const game = @import("game/root.zig");
test {
std.testing.refAllDecls(@This());
+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);
}
};