styles
This commit is contained in:
@@ -0,0 +1 @@
|
||||
pub const Text = @import("text.zig").Text;
|
||||
@@ -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
@@ -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,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
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user