WIP: add Container type with corresponding Properties configuration
The configuration of the `Container` types is very much inspired by [clay](https://github.com/nicbarker/clay).
This commit is contained in:
16
build.zig
16
build.zig
@@ -18,14 +18,14 @@ pub fn build(b: *std.Build) void {
|
|||||||
lib.addImport("code_point", zg.module("code_point"));
|
lib.addImport("code_point", zg.module("code_point"));
|
||||||
|
|
||||||
// TODO: examples (not yet available)
|
// TODO: examples (not yet available)
|
||||||
// const stack_example = b.addExecutable(.{
|
const container = b.addExecutable(.{
|
||||||
// .name = "stack",
|
.name = "container",
|
||||||
// .root_source_file = b.path("examples/stack.zig"),
|
.root_source_file = b.path("examples/container.zig"),
|
||||||
// .target = target,
|
.target = target,
|
||||||
// .optimize = optimize,
|
.optimize = optimize,
|
||||||
// });
|
});
|
||||||
// stack_example.root_module.addImport("zterm", lib);
|
container.root_module.addImport("zterm", lib);
|
||||||
// b.installArtifact(stack_example);
|
b.installArtifact(container);
|
||||||
|
|
||||||
// testing
|
// testing
|
||||||
const lib_unit_tests = b.addTest(.{
|
const lib_unit_tests = b.addTest(.{
|
||||||
|
|||||||
59
examples/container.zig
Normal file
59
examples/container.zig
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const zterm = @import("zterm");
|
||||||
|
|
||||||
|
const App = zterm.App(union(enum) {});
|
||||||
|
const Key = zterm.Key;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.example);
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
errdefer |err| log.err("Application Error: {any}", .{err});
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer {
|
||||||
|
const deinit_status = gpa.deinit();
|
||||||
|
if (deinit_status == .leak) {
|
||||||
|
log.err("memory lead", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
var app: App = .{};
|
||||||
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
var container = try App.Container.init(allocator, .{});
|
||||||
|
defer container.deinit();
|
||||||
|
|
||||||
|
// NOTE: should the min-size here be required?
|
||||||
|
try app.start(null);
|
||||||
|
defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err});
|
||||||
|
|
||||||
|
// event loop
|
||||||
|
while (true) {
|
||||||
|
const event = app.nextEvent();
|
||||||
|
log.debug("received event: {s}", .{@tagName(event)});
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
.init => {
|
||||||
|
if (container.handle(event)) |e| app.postEvent(e);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
.quit => break,
|
||||||
|
.resize => |size| try renderer.resize(size),
|
||||||
|
.key => |key| {
|
||||||
|
if (key.matches(.{ .cp = 'q' }))
|
||||||
|
app.quit();
|
||||||
|
},
|
||||||
|
.err => |err| {
|
||||||
|
log.err("Received {any} with message: {s}", .{ @errorName(err.err), err.msg });
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should instead use tryPost because it may block the main loop from actually removing events from the queue, deadlocking itself
|
||||||
|
if (container.handle(event)) |e| app.postEvent(e);
|
||||||
|
renderer.render(@TypeOf(container), &container);
|
||||||
|
try renderer.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/app.zig
59
src/app.zig
@@ -7,6 +7,7 @@ const mergeTaggedUnions = event.mergeTaggedUnions;
|
|||||||
const isTaggedUnion = event.isTaggedUnion;
|
const isTaggedUnion = event.isTaggedUnion;
|
||||||
|
|
||||||
const Key = @import("key.zig");
|
const Key = @import("key.zig");
|
||||||
|
const Size = @import("size.zig");
|
||||||
const Queue = @import("queue.zig").Queue;
|
const Queue = @import("queue.zig").Queue;
|
||||||
|
|
||||||
const log = std.log.scoped(.app);
|
const log = std.log.scoped(.app);
|
||||||
@@ -15,14 +16,6 @@ const log = std.log.scoped(.app);
|
|||||||
/// an tagged union for all the user events that can be send through the
|
/// an tagged union for all the user events that can be send through the
|
||||||
/// applications event loop.
|
/// applications event loop.
|
||||||
///
|
///
|
||||||
/// _R_ is the type function for the `Renderer` to use. The parameter boolean
|
|
||||||
/// will be set to the _fullscreen_ value at compile time. The corresponding
|
|
||||||
/// `Renderer` type is accessible through the generated type of this function.
|
|
||||||
///
|
|
||||||
/// _fullscreen_ will be used to configure the `App` and the `Renderer` to
|
|
||||||
/// respect the corresponding configuration whether to render a fullscreen tui
|
|
||||||
/// or an inline tui.
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// Create an `App` which renders using the `PlainRenderer` in fullscreen with
|
/// Create an `App` which renders using the `PlainRenderer` in fullscreen with
|
||||||
@@ -32,41 +25,35 @@ const log = std.log.scoped(.app);
|
|||||||
/// const zterm = @import("zterm");
|
/// const zterm = @import("zterm");
|
||||||
/// const App = zterm.App(
|
/// const App = zterm.App(
|
||||||
/// union(enum) {},
|
/// union(enum) {},
|
||||||
/// zterm.Renderer.Direct,
|
|
||||||
/// true,
|
|
||||||
/// );
|
/// );
|
||||||
/// // later on use
|
/// // later on create an `App` instance and start the event loop
|
||||||
/// var app: App = .{};
|
/// var app: App = .{};
|
||||||
/// var renderer: App.Renderer = .{};
|
/// try app.start(null);
|
||||||
|
/// defer app.stop() catch unreachable;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fullscreen: bool) type {
|
pub fn App(comptime E: type) type {
|
||||||
if (!isTaggedUnion(E)) {
|
if (!isTaggedUnion(E)) {
|
||||||
@compileError("Provided user event `E` for `App(comptime E: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `E` for `App(comptime E: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
return struct {
|
return struct {
|
||||||
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
|
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
|
||||||
pub const Renderer = R(fullscreen);
|
pub const Container = @import("container.zig").Container(Event);
|
||||||
pub const Layout = @import("layout.zig").Layout(Event, Renderer);
|
|
||||||
pub const Widget = @import("widget.zig").Widget(Event, Renderer);
|
|
||||||
pub const View = @import("view.zig").View(Event, Renderer);
|
|
||||||
|
|
||||||
queue: Queue(Event, 256) = .{},
|
queue: Queue(Event, 256) = .{},
|
||||||
thread: ?std.Thread = null,
|
thread: ?std.Thread = null,
|
||||||
quit_event: std.Thread.ResetEvent = .{},
|
quit_event: std.Thread.ResetEvent = .{},
|
||||||
termios: ?std.posix.termios = null,
|
termios: ?std.posix.termios = null,
|
||||||
attached_handler: bool = false,
|
attached_handler: bool = false,
|
||||||
min_size: ?terminal.Size = null,
|
min_size: ?Size = null,
|
||||||
prev_size: terminal.Size = .{ .cols = 0, .rows = 0 },
|
prev_size: Size = .{ .cols = 0, .rows = 0 },
|
||||||
|
|
||||||
pub const SignalHandler = struct {
|
pub const SignalHandler = struct {
|
||||||
context: *anyopaque,
|
context: *anyopaque,
|
||||||
callback: *const fn (context: *anyopaque) void,
|
callback: *const fn (context: *anyopaque) void,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn start(this: *@This(), min_size: ?terminal.Size) !void {
|
pub fn start(this: *@This(), min_size: ?Size) !void {
|
||||||
if (fullscreen) { // a minimal size only really makes sense if the application is rendered fullscreen
|
this.min_size = min_size;
|
||||||
this.min_size = min_size;
|
|
||||||
}
|
|
||||||
if (this.thread) |_| return;
|
if (this.thread) |_| return;
|
||||||
|
|
||||||
if (!this.attached_handler) {
|
if (!this.attached_handler) {
|
||||||
@@ -92,19 +79,17 @@ pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fulls
|
|||||||
if (this.termios) |_| {} else {
|
if (this.termios) |_| {} else {
|
||||||
this.termios = termios;
|
this.termios = termios;
|
||||||
}
|
}
|
||||||
if (fullscreen) {
|
try terminal.saveScreen();
|
||||||
try terminal.saveScreen();
|
try terminal.enterAltScreen();
|
||||||
try terminal.enterAltScreen();
|
try terminal.hideCursor();
|
||||||
try terminal.hideCursor();
|
// post init event (as the very first element to be in the queue - event loop)
|
||||||
}
|
this.postEvent(.init);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interrupt(this: *@This()) !void {
|
pub fn interrupt(this: *@This()) !void {
|
||||||
this.quit_event.set();
|
this.quit_event.set();
|
||||||
if (fullscreen) {
|
try terminal.exitAltScreen();
|
||||||
try terminal.exitAltScreen();
|
try terminal.restoreScreen();
|
||||||
try terminal.restoreScreen();
|
|
||||||
}
|
|
||||||
if (this.thread) |thread| {
|
if (this.thread) |thread| {
|
||||||
thread.join();
|
thread.join();
|
||||||
this.thread = null;
|
this.thread = null;
|
||||||
@@ -115,11 +100,9 @@ pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fulls
|
|||||||
try this.interrupt();
|
try this.interrupt();
|
||||||
if (this.termios) |*termios| {
|
if (this.termios) |*termios| {
|
||||||
try terminal.disableRawMode(termios);
|
try terminal.disableRawMode(termios);
|
||||||
if (fullscreen) {
|
try terminal.showCursor();
|
||||||
try terminal.showCursor();
|
try terminal.exitAltScreen();
|
||||||
try terminal.exitAltScreen();
|
try terminal.restoreScreen();
|
||||||
try terminal.restoreScreen();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.termios = null;
|
this.termios = null;
|
||||||
}
|
}
|
||||||
@@ -322,7 +305,7 @@ pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fulls
|
|||||||
|
|
||||||
// TODO: only post the event if the size has changed?
|
// TODO: only post the event if the size has changed?
|
||||||
// because there might be too many resize events (which force a re-draw of the entire screen)
|
// because there might be too many resize events (which force a re-draw of the entire screen)
|
||||||
const size: terminal.Size = .{
|
const size: Size = .{
|
||||||
.rows = std.fmt.parseUnsigned(u16, height_char, 10) catch break,
|
.rows = std.fmt.parseUnsigned(u16, height_char, 10) catch break,
|
||||||
.cols = std.fmt.parseUnsigned(u16, width_char, 10) catch break,
|
.cols = std.fmt.parseUnsigned(u16, width_char, 10) catch break,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Style = @import("Style.zig");
|
const Style = @import("style.zig");
|
||||||
|
|
||||||
pub const Cell = @This();
|
pub const Cell = @This();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
||||||
|
|
||||||
|
const Cell = @import("cell.zig");
|
||||||
|
const Color = @import("color.zig").Color;
|
||||||
|
const Size = @import("size.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.container);
|
||||||
|
|
||||||
|
/// Border configuration struct
|
||||||
|
pub const Border = struct {
|
||||||
|
/// Color to use for the border
|
||||||
|
color: Color = .default,
|
||||||
|
/// Configure the corner type to be used for the border
|
||||||
|
corners: enum(u1) {
|
||||||
|
squared,
|
||||||
|
rounded,
|
||||||
|
} = .squared,
|
||||||
|
/// Configure the sides where the borders shall be rendered
|
||||||
|
sides: packed struct {
|
||||||
|
top: bool = true,
|
||||||
|
bottom: bool = true,
|
||||||
|
left: bool = true,
|
||||||
|
right: bool = true,
|
||||||
|
} = .{},
|
||||||
|
/// Configure separator borders between child element to added to the layout
|
||||||
|
separator: struct {
|
||||||
|
enabled: bool = false,
|
||||||
|
color: Color = .default,
|
||||||
|
line: enum {
|
||||||
|
line,
|
||||||
|
dotted,
|
||||||
|
// TODO: add more variations which could be used for the separator
|
||||||
|
} = .line,
|
||||||
|
} = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Rectangle configuration struct
|
||||||
|
pub const Rectangle = struct {
|
||||||
|
/// `Color` to use to fill the `Rectangle` with
|
||||||
|
/// NOTE: used as background color when rendering! such that it renders the
|
||||||
|
/// children accordingly without removing the coloring of the `Rectangle`
|
||||||
|
fill: Color = .default,
|
||||||
|
/// Configure the corners of the `Rectangle`
|
||||||
|
corners: enum(u1) {
|
||||||
|
squared,
|
||||||
|
rounded,
|
||||||
|
} = .squared,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Scroll configuration struct
|
||||||
|
pub const Scroll = packed struct {
|
||||||
|
/// Enable horizontal scrolling for this element
|
||||||
|
horizontal: bool = false,
|
||||||
|
/// Enable vertical scrolling for this element
|
||||||
|
vertical: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Layout configuration struct
|
||||||
|
pub const Layout = struct {
|
||||||
|
/// control the direction in which child elements are laid out
|
||||||
|
direction: enum(u1) { horizontal, vertical } = .horizontal,
|
||||||
|
/// Padding outside of the child elements
|
||||||
|
padding: packed struct {
|
||||||
|
top: u16 = 0,
|
||||||
|
bottom: u16 = 0,
|
||||||
|
left: u16 = 0,
|
||||||
|
right: u16 = 0,
|
||||||
|
|
||||||
|
/// Create a padding with equivalent padding in all four directions.
|
||||||
|
pub fn all(padding: u16) @This() {
|
||||||
|
return .{ .top = padding, .bottom = padding, .left = padding, .right = padding };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a padding with equivalent padding in the left and right directions; others directions remain the default value.
|
||||||
|
pub fn horizontal(padding: u16) @This() {
|
||||||
|
return .{ .left = padding, .right = padding };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a padding with equivalent padding in the top and bottom directions; others directions remain the default value.
|
||||||
|
pub fn vertical(padding: u16) @This() {
|
||||||
|
return .{ .top = padding, .bottom = padding };
|
||||||
|
}
|
||||||
|
} = .{},
|
||||||
|
/// Padding used in between child elements as gaps when laid out
|
||||||
|
gap: u16 = 0,
|
||||||
|
// TODO: is there a way to make x / y type copied by the compiler at comptime instead? such that this only has to be defined once?
|
||||||
|
/// Alignment of where the child elements are positioned relative to the parent container when laid out
|
||||||
|
alignment: packed struct {
|
||||||
|
x: enum(u2) { center, left, right } = .center,
|
||||||
|
y: enum(u2) { center, left, right } = .center,
|
||||||
|
} = .{},
|
||||||
|
// TODO: is there a way to make width / height type copied by the compiler at comptime instead? such that this only has to be defined once?
|
||||||
|
// NOTE: `sizing` cannot be *packed* because of the tagged unions? is this necessary -> I would need to measure the size differences
|
||||||
|
/// Sizing to be used for the width and height of this element to use
|
||||||
|
sizing: struct {
|
||||||
|
width: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
|
||||||
|
height: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
|
||||||
|
} = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn Container(comptime Event: type) type {
|
||||||
|
if (!isTaggedUnion(Event)) {
|
||||||
|
@compileError("Provided user event `Event` for `Container(comptime Event: type)`");
|
||||||
|
}
|
||||||
|
return struct {
|
||||||
|
size: Size,
|
||||||
|
properties: Properties,
|
||||||
|
elements: std.ArrayList(@This()),
|
||||||
|
|
||||||
|
/// Properties for each `Container` to configure their layout,
|
||||||
|
/// border, styling, etc. For details see the corresponding individual
|
||||||
|
/// documentation of the members of this struct accordingly.
|
||||||
|
pub const Properties = struct {
|
||||||
|
border: Border = .{},
|
||||||
|
rectangle: Rectangle = .{},
|
||||||
|
scroll: Scroll = .{},
|
||||||
|
layout: Layout = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, properties: Properties) !@This() {
|
||||||
|
return .{
|
||||||
|
.size = .{ .cols = 0, .rows = 0 },
|
||||||
|
.properties = properties,
|
||||||
|
.elements = std.ArrayList(@This()).init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(this: *@This()) void {
|
||||||
|
for (this.elements.items) |*element| {
|
||||||
|
element.deinit();
|
||||||
|
}
|
||||||
|
this.elements.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(this: *@This(), element: @This()) !void {
|
||||||
|
try this.elements.append(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(this: *@This(), event: Event) ?Event {
|
||||||
|
switch (event) {
|
||||||
|
.init => log.debug(".init event", .{}),
|
||||||
|
.resize => |size| {
|
||||||
|
this.size = size;
|
||||||
|
for (this.elements.items) |*element| {
|
||||||
|
const element_size: Size = size;
|
||||||
|
// TODO; adjust size according to the layout of the `Container`
|
||||||
|
if (element.handle(.{ .resize = element_size })) |e| {
|
||||||
|
_ = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
for (this.elements.items) |*element| {
|
||||||
|
if (element.handle(event)) |e| {
|
||||||
|
// TODO: if only the top level container returns a single
|
||||||
|
// event (i.e. as a reaction to a certain other event) what
|
||||||
|
// should happen to potential other events?
|
||||||
|
_ = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contents(this: *const @This()) []const Cell {
|
||||||
|
// TODO: use the size and the corresponding contents to determine what should be show in form of a `Cell` array
|
||||||
|
_ = this;
|
||||||
|
return &[0]Cell{};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ const terminal = @import("terminal.zig");
|
|||||||
const Size = @import("size.zig");
|
const Size = @import("size.zig");
|
||||||
const Key = @import("key.zig");
|
const Key = @import("key.zig");
|
||||||
|
|
||||||
// System events available to every application.
|
/// System events available to every `zterm.App`
|
||||||
pub const SystemEvent = union(enum) {
|
pub const SystemEvent = union(enum) {
|
||||||
/// Initialize event, which is send once at the beginning of the event loop and before the first render loop
|
/// Initialize event, which is send once at the beginning of the event loop and before the first render loop
|
||||||
|
/// TODO: not sure if this is necessary or if there is an actual usecase for this - for now it will remain
|
||||||
init,
|
init,
|
||||||
/// Quit event to signify the end of the event loop (rendering should stop afterwards)
|
/// Quit event to signify the end of the event loop (rendering should stop afterwards)
|
||||||
quit,
|
quit,
|
||||||
|
|||||||
184
src/render.zig
184
src/render.zig
@@ -10,109 +10,111 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const terminal = @import("terminal.zig");
|
const terminal = @import("terminal.zig");
|
||||||
|
|
||||||
const Cell = terminal.Cell;
|
const Cell = @import("cell.zig");
|
||||||
const Position = terminal.Position;
|
const Size = @import("size.zig");
|
||||||
const Size = terminal.Size;
|
const Position = Size.Position;
|
||||||
|
|
||||||
/// Double-buffered intermediate rendering pipeline
|
/// Double-buffered intermediate rendering pipeline
|
||||||
pub fn Buffered(comptime fullscreen: bool) type {
|
pub const Buffered = struct {
|
||||||
const log = std.log.scoped(.renderer_buffered);
|
const log = std.log.scoped(.renderer_buffered);
|
||||||
// _ = log;
|
// _ = log;
|
||||||
_ = fullscreen;
|
allocator: std.mem.Allocator,
|
||||||
return struct {
|
created: bool,
|
||||||
allocator: std.mem.Allocator,
|
size: Size,
|
||||||
created: bool,
|
screen: []Cell,
|
||||||
size: Size,
|
virtual_screen: []Cell,
|
||||||
screen: []Cell,
|
|
||||||
virtual_screen: []Cell,
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.created = false,
|
.created = false,
|
||||||
.size = undefined,
|
.size = undefined,
|
||||||
.screen = undefined,
|
.screen = undefined,
|
||||||
.virtual_screen = undefined,
|
.virtual_screen = undefined,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(this: *@This()) void {
|
||||||
|
if (this.created) {
|
||||||
|
this.allocator.free(this.screen);
|
||||||
|
this.allocator.free(this.virtual_screen);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn resize(this: *@This(), size: Size) !void {
|
||||||
if (this.created) {
|
this.size = size;
|
||||||
this.allocator.free(this.screen);
|
if (!this.created) {
|
||||||
this.allocator.free(this.virtual_screen);
|
this.screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
||||||
|
@memset(this.screen, Cell{});
|
||||||
|
this.virtual_screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
||||||
|
@memset(this.virtual_screen, Cell{});
|
||||||
|
this.created = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.allocator.resize(this.screen, size.cols * size.rows)) {
|
||||||
|
@panic("render.zig: Could not resize `screen` buffer");
|
||||||
|
}
|
||||||
|
if (this.allocator.resize(this.virtual_screen, size.cols * size.rows)) {
|
||||||
|
@panic("render.zig: Could not resize `virtual screen` buffer.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(this: *@This(), size: Size) void {
|
||||||
|
log.debug("renderer::clear", .{});
|
||||||
|
var vs = this.virtual_screen;
|
||||||
|
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
||||||
|
for (0..size.rows) |row| {
|
||||||
|
for (0..size.cols) |col| {
|
||||||
|
vs[anchor + (row * this.size.cols) + col].reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resize(this: *@This(), size: Size) void {
|
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
||||||
this.size = size;
|
pub fn render(this: *@This(), comptime T: type, container: *T) void {
|
||||||
if (!this.created) {
|
const cells: []const Cell = container.contents();
|
||||||
this.screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
const size: Size = container.size;
|
||||||
this.virtual_screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
log.debug("renderer:render: cells: {any} @ {any}", .{ cells, size });
|
||||||
this.created = true;
|
|
||||||
return;
|
if (cells.len == 0) return;
|
||||||
}
|
|
||||||
if (this.allocator.resize(this.screen, size.cols * size.rows)) {
|
var idx: usize = 0;
|
||||||
@panic("render.zig: Could not resize `screen` buffer");
|
var vs = this.virtual_screen;
|
||||||
}
|
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
||||||
if (this.allocator.resize(this.virtual_screen, size.cols * size.rows)) {
|
|
||||||
@panic("render.zig: Could not resize `virtual screen` buffer.");
|
for (0..size.rows) |row| {
|
||||||
|
for (0..size.cols) |col| {
|
||||||
|
const cell = cells[idx];
|
||||||
|
idx += 1;
|
||||||
|
|
||||||
|
vs[anchor + (row * this.size.cols) + col].style = cell.style;
|
||||||
|
vs[anchor + (row * this.size.cols) + col].rune = cell.rune;
|
||||||
|
|
||||||
|
if (cells.len == idx) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(this: *@This(), size: Size) !void {
|
/// Write *virtual screen* to alternate screen (should be called once and last during each render loop iteration in the main loop).
|
||||||
log.debug("renderer::clear", .{});
|
pub fn flush(this: *@This()) !void {
|
||||||
var vs = this.virtual_screen;
|
log.debug("renderer::flush", .{});
|
||||||
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
const writer = terminal.writer();
|
||||||
for (0..size.rows) |row| {
|
const s = this.screen;
|
||||||
for (0..size.cols) |col| {
|
const vs = this.virtual_screen;
|
||||||
vs[anchor + (row * this.size.cols) + col].reset();
|
for (0..this.size.rows) |row| {
|
||||||
}
|
for (0..this.size.cols) |col| {
|
||||||
|
const idx = (row * this.size.cols) + col;
|
||||||
|
const cs = s[idx];
|
||||||
|
const cvs = vs[idx];
|
||||||
|
if (cs.eql(cvs))
|
||||||
|
continue;
|
||||||
|
// render differences found in virtual screen
|
||||||
|
// TODO: improve the writing speed (many unnecessary writes (i.e. the style for every character..))
|
||||||
|
try terminal.setCursorPosition(.{ .row = @truncate(row), .col = @truncate(col) });
|
||||||
|
try cvs.value(writer);
|
||||||
|
// update screen to be the virtual screen for the next frame
|
||||||
|
s[idx] = vs[idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
};
|
||||||
pub fn render(this: *@This(), size: Size, cells: []const Cell) !void {
|
|
||||||
log.debug("renderer:render: cells: {any}", .{cells});
|
|
||||||
std.debug.assert(cells.len > 0);
|
|
||||||
|
|
||||||
var idx: usize = 0;
|
|
||||||
var vs = this.virtual_screen;
|
|
||||||
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
|
||||||
|
|
||||||
for (0..size.rows) |row| {
|
|
||||||
for (0..size.cols) |col| {
|
|
||||||
const cell = cells[idx];
|
|
||||||
idx += 1;
|
|
||||||
|
|
||||||
vs[anchor + (row * this.size.cols) + col].style = cell.style;
|
|
||||||
vs[anchor + (row * this.size.cols) + col].rune = cell.rune;
|
|
||||||
|
|
||||||
if (cells.len == idx) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write *virtual screen* to alternate screen (should be called once and last during each render loop iteration in the main loop).
|
|
||||||
pub fn flush(this: *@This()) !void {
|
|
||||||
log.debug("renderer::flush", .{});
|
|
||||||
const writer = terminal.writer();
|
|
||||||
const s = this.screen;
|
|
||||||
const vs = this.virtual_screen;
|
|
||||||
for (0..this.size.rows) |row| {
|
|
||||||
for (0..this.size.cols) |col| {
|
|
||||||
const idx = (row * this.size.cols) + col;
|
|
||||||
const cs = s[idx];
|
|
||||||
const cvs = vs[idx];
|
|
||||||
if (cs.eql(cvs))
|
|
||||||
continue;
|
|
||||||
// render differences found in virtual screen
|
|
||||||
// TODO: improve the writing speed (many unnecessary writes (i.e. the style for every character..))
|
|
||||||
try terminal.setCursorPosition(.{ .row = @truncate(row), .col = @truncate(col) });
|
|
||||||
try cvs.value(writer);
|
|
||||||
// update screen to be the virtual screen for the next frame
|
|
||||||
s[idx] = vs[idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
pub const Size = @This();
|
pub const Size = @This();
|
||||||
|
|
||||||
pub const Position = struct {
|
pub const Position = struct {
|
||||||
col: u16,
|
col: u16 = 0,
|
||||||
row: u16,
|
row: u16 = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
anchor: Position = .{},
|
anchor: Position = .{},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const code_point = @import("code_point");
|
pub const code_point = @import("code_point");
|
||||||
|
|
||||||
const Key = @import("key.zig");
|
const Key = @import("key.zig");
|
||||||
const Size = @import("size.zig");
|
const Size = @import("size.zig");
|
||||||
|
|||||||
Reference in New Issue
Block a user