diff --git a/src/container/sub_matrix.zig b/src/container/sub_matrix.zig index 06e33cb..da9a9c0 100644 --- a/src/container/sub_matrix.zig +++ b/src/container/sub_matrix.zig @@ -4,6 +4,7 @@ const container = @import("root.zig"); const Block = container.Block; const Point = container.Point; +const Rect = container.Rect; const Rowable = container.Rowable; const Matrix = container.Matrix; const Error = container.Error; @@ -52,6 +53,11 @@ pub fn SubMatrix(comptime T: type) type { return r[self._mask.loc.x .. self._mask.loc.x + self._mask.size.width]; } + // size returns the size of the submask + pub fn size(self: *@This()) Rect { + return self._mask.size; + } + pub fn row_iterator(self: *@This()) RowIterator(T) { return .init(&self.rowable); } diff --git a/src/game/rect.zig b/src/game/rect.zig new file mode 100644 index 0000000..2961cf8 --- /dev/null +++ b/src/game/rect.zig @@ -0,0 +1,90 @@ +const std = @import("std"); +const grome = @import("../root.zig"); + +const Point = grome.container.Point; +const Block = grome.container.Block; +const SubMatrix = grome.container.SubMatrix; +const Widget = grome.view.Widget; +const Rect = grome.view.Rect; +const WidgetError = grome.view.WidgetError; +const Cell = grome.view.Cell; +const Style = grome.view.style.Style; + +pub const Rectangle = struct { + style: ?Style, + block: Block, + widget: Widget, + + pub fn init( + block: Block, + style: ?Style, + ) Rectangle { + return .{ + .style = style, + .block = block, + .widget = .{ + .geometryFn = Rectangle.geometry, + .renderFn = Rectangle.render, + }, + }; + } + + pub fn geometry(widget: *Widget) Block { + const self: *@This() = @fieldParentPtr("widget", widget); + + return self.block; + } + + pub fn render(widget: *Widget, frame: *SubMatrix(Cell)) WidgetError!void { + const self: *@This() = @fieldParentPtr("widget", widget); + const size = frame.size(); + + var first = frame.rowable.row(0) catch return WidgetError.UnknownError; + first[0] = Cell{ + .char = ("┌ " ** 2).*, + .style = self.style, + .width = 3, + }; + @memset(first[1 .. first.len - 1], Cell{ + .char = ("─ " ** 2).*, + .style = self.style, + .width = 3, + }); + first[first.len - 1] = Cell{ + .char = ("┐ " ** 2).*, + .style = self.style, + .width = 3, + }; + + for (1..size.height - 1) |row_num| { + var row = frame.rowable.row(row_num) catch return WidgetError.UnknownError; + row[0] = Cell{ + .char = ("│ " ** 2).*, + .style = self.style, + .width = 3, + }; + row[row.len - 1] = Cell{ + .char = ("│ " ** 2).*, + .style = self.style, + .width = 3, + }; + } + + var last = frame.rowable.row(size.height - 1) catch return WidgetError.UnknownError; + last[0] = Cell{ + .char = ("└ " ** 2).*, + .style = self.style, + .width = 3, + }; + @memset(last[1 .. last.len - 1], Cell{ + .char = ("─ " ** 2).*, + .style = self.style, + .width = 3, + }); + last[last.len - 1] = Cell{ + .char = ("┘ " ** 2).*, + .style = self.style, + .width = 3, + }; + } +}; diff --git a/src/game/root.zig b/src/game/root.zig index d6b6854..ebc02da 100644 --- a/src/game/root.zig +++ b/src/game/root.zig @@ -1,2 +1,3 @@ pub const Text = @import("text.zig").Text; pub const Nucleus = @import("nucleus.zig").Nucleus; +pub const Rectangle = @import("rect.zig").Rectangle; diff --git a/src/game/text.zig b/src/game/text.zig index 3232673..5814ea4 100644 --- a/src/game/text.zig +++ b/src/game/text.zig @@ -8,17 +8,19 @@ const Widget = grome.view.Widget; const WidgetError = grome.view.WidgetError; const Cell = grome.view.Cell; const Block = grome.container.Block; +const Point = grome.container.Point; 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) + location: Point, text: [1024]Cell, text_width: usize, widget: Widget, - pub fn init(text: []const u8, s: ?Style) @This() { + pub fn init(text: []const u8, location: Point, s: ?Style) @This() { // TODO: this is bad, but I want to see it work. var cells: [1024]Cell = undefined; @@ -33,6 +35,7 @@ pub const Text = struct { return .{ .text = cells, .text_width = text.len, + .location = location, .widget = .{ .geometryFn = @This().geometry, .renderFn = @This().render, @@ -43,7 +46,7 @@ pub const Text = struct { pub fn geometry(widget: *Widget) Block { const self: *@This() = @fieldParentPtr("widget", widget); return Block{ - .loc = .{ .x = 0, .y = 0 }, + .loc = self.location, .size = .{ .height = 1, .width = self.text_width }, }; } diff --git a/src/main.zig b/src/main.zig index 1e80877..ea28f0b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const grome = @import("root.zig"); +const term = grome.view.term; const style = grome.view.style; const Duration = std.Io.Duration; @@ -9,6 +10,7 @@ const Rect = grome.container.Rect; const Text = grome.game.Text; const Nucleus = grome.game.Nucleus; const Viewport = grome.view.Viewport; +const Rectangle = grome.game.Rectangle; pub fn main(init: std.process.Init) !void { // This is appropriate for anything that lives as long as the process. @@ -20,18 +22,18 @@ pub fn main(init: std.process.Init) !void { var w = &stdout_file_writer.interface; // alright lets play with the matrix - const size = Rect{ - .height = 70, - .width = 120, - }; + const size = try term.term_size(init.io); var viewport = try Viewport.init(arena, size); defer viewport.deinit(arena); - var text = Text.init("Hello Olive", style.Blue); + var rect = Rectangle.init(.{ .loc = .{ .x = 0, .y = 0 }, .size = size }, null); + try viewport.add_widget(arena, &rect.widget); + + var text = Text.init("Hello Olive", .{ .x = 1, .y = 1 }, style.Blue); try viewport.add_widget(arena, &text.widget); - var nucleus = Nucleus.init(.{ .x = 1, .y = 1 }); + var nucleus = Nucleus.init(.{ .x = 3, .y = 3 }); try viewport.add_widget(arena, &nucleus.widget); try viewport.hard_refresh(w); diff --git a/src/view/term.zig b/src/view/term.zig index 0455c58..fb6bb9d 100644 --- a/src/view/term.zig +++ b/src/view/term.zig @@ -1,6 +1,14 @@ const std = @import("std"); +const builtin = @import("builtin"); +const grome = @import("../root.zig"); const Writer = std.Io.Writer; +const Rect = grome.container.Rect; + +pub const Error = error{ + PlatformNotSupported, + UnknownError, +}; /// clear clears the terminal screen entierly pub fn clear(w: *Writer) !void { @@ -33,3 +41,33 @@ pub fn clear_remaining_line(w: *Writer) !void { pub fn hide_cursor(w: *Writer) !void { _ = try w.write("\x1b[?25l"); } + +/// term_size defineds the terminal size when run. I hope +pub fn term_size(io: std.Io) !Rect { + return switch (builtin.os.tag) { + .linux => { + var winsize: std.posix.winsize = .{ + .row = 0, + .col = 0, + .xpixel = 0, + .ypixel = 0, + }; + + const err = (try std.Io.operate(io, .{ .device_io_control = .{ + .file = .stdout(), + .code = std.posix.T.IOCGWINSZ, + .arg = &winsize, + } })).device_io_control; + + if (err >= 0) { + return Rect{ + .height = winsize.row, + .width = winsize.col, + }; + } else { + return Error.UnknownError; + } + }, + else => Error.PlatformNotSupported, + }; +}