Configuration to enable scrollbar rendering for scrollable `Element`s. Currently only the fg `Color` of the scrollbar can be configured while the background uses the same fg `Color` but adds the emphasis `.dim` to make it obvious what the is the actual scrollbar. In the future it might be necessary to provide the user with more options to configure the representation of the scrollbar. Tests have been added to test the scrollbar rendering and placement accordingly.
198 lines
6.0 KiB
Zig
198 lines
6.0 KiB
Zig
const QuitText = struct {
|
|
const text = "Press ctrl+c to quit.";
|
|
|
|
pub fn element(this: *@This()) App.Element {
|
|
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
|
}
|
|
|
|
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
|
|
_ = ctx;
|
|
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
|
|
|
const row = 2;
|
|
const col = size.x / 2 -| (text.len / 2);
|
|
const anchor = (row * size.x) + col;
|
|
|
|
for (text, 0..) |cp, idx| {
|
|
cells[anchor + idx].style.fg = .white;
|
|
cells[anchor + idx].style.bg = .black;
|
|
cells[anchor + idx].cp = cp;
|
|
|
|
// NOTE do not write over the contents of this `Container`'s `Size`
|
|
if (anchor + idx == cells.len - 1) break;
|
|
}
|
|
}
|
|
};
|
|
|
|
const HelloWorldText = packed struct {
|
|
const text = "Hello World";
|
|
|
|
pub fn element(this: *@This()) App.Element {
|
|
return .{
|
|
.ptr = this,
|
|
.vtable = &.{ .content = content },
|
|
};
|
|
}
|
|
|
|
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
|
|
_ = ctx;
|
|
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
|
|
|
const row = size.y / 2;
|
|
const col = size.x / 2 -| (text.len / 2);
|
|
const anchor = (row * size.x) + col;
|
|
|
|
for (text, 0..) |cp, idx| {
|
|
cells[anchor + idx].style.fg = .white;
|
|
cells[anchor + idx].style.bg = .black;
|
|
cells[anchor + idx].cp = cp;
|
|
|
|
// NOTE do not write over the contents of this `Container`'s `Size`
|
|
if (anchor + idx == cells.len - 1) break;
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn main() !void {
|
|
errdefer |err| log.err("Application Error: {any}", .{err});
|
|
|
|
// TODO maybe create own allocator as some sort of arena allocator to have consistent memory usage
|
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
|
defer {
|
|
const deinit_status = gpa.deinit();
|
|
if (deinit_status == .leak) {
|
|
log.err("memory leak", .{});
|
|
}
|
|
}
|
|
const allocator = gpa.allocator();
|
|
|
|
var app: App = .init;
|
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
|
defer renderer.deinit();
|
|
|
|
var element_wrapper: HelloWorldText = .{};
|
|
const element = element_wrapper.element();
|
|
|
|
var quit_text: QuitText = .{};
|
|
|
|
var top_box = try App.Container.init(allocator, .{
|
|
.rectangle = .{ .fill = .blue },
|
|
.layout = .{
|
|
.gap = 2,
|
|
.separator = .{
|
|
.enabled = true,
|
|
},
|
|
.direction = .vertical,
|
|
.padding = .vertical(1),
|
|
},
|
|
}, .{});
|
|
try top_box.append(try App.Container.init(allocator, .{
|
|
.rectangle = .{ .fill = .light_green },
|
|
.size = .{
|
|
.dim = .{ .y = 30 },
|
|
},
|
|
}, .{}));
|
|
try top_box.append(try App.Container.init(allocator, .{
|
|
.rectangle = .{ .fill = .light_green },
|
|
.size = .{
|
|
.dim = .{ .y = 5 },
|
|
},
|
|
}, element));
|
|
try top_box.append(try App.Container.init(allocator, .{
|
|
.rectangle = .{ .fill = .light_green },
|
|
.size = .{
|
|
.dim = .{ .y = 2 },
|
|
},
|
|
}, .{}));
|
|
defer top_box.deinit();
|
|
|
|
var bottom_box = try App.Container.init(allocator, .{
|
|
.border = .{
|
|
.sides = .all,
|
|
.color = .blue,
|
|
},
|
|
.layout = .{
|
|
.separator = .{
|
|
.enabled = true,
|
|
.color = .red,
|
|
},
|
|
.direction = .vertical,
|
|
.padding = .vertical(1),
|
|
},
|
|
.size = .{
|
|
.dim = .{ .y = 30 },
|
|
},
|
|
}, .{});
|
|
try bottom_box.append(try App.Container.init(allocator, .{
|
|
.rectangle = .{ .fill = .grey },
|
|
}, .{}));
|
|
try bottom_box.append(try App.Container.init(allocator, .{
|
|
.rectangle = .{ .fill = .grey },
|
|
}, element));
|
|
try bottom_box.append(try App.Container.init(allocator, .{
|
|
.rectangle = .{ .fill = .grey },
|
|
}, .{}));
|
|
defer bottom_box.deinit();
|
|
|
|
var container = try App.Container.init(allocator, .{
|
|
.layout = .{
|
|
.gap = 2,
|
|
.separator = .{
|
|
.enabled = true,
|
|
.line = .double,
|
|
},
|
|
.padding = .{ .top = 5, .bottom = 3, .left = 3, .right = 3 },
|
|
.direction = .vertical,
|
|
},
|
|
}, quit_text.element());
|
|
defer container.deinit();
|
|
|
|
// place empty container containing the element of the scrollable Container.
|
|
var scrollable_top: App.Scrollable = .init(top_box, .enabled(.grey));
|
|
try container.append(try App.Container.init(allocator, .{}, scrollable_top.element()));
|
|
|
|
var scrollable_bottom: App.Scrollable = .init(bottom_box, .enabled(.white));
|
|
try container.append(try App.Container.init(allocator, .{}, scrollable_bottom.element()));
|
|
|
|
try app.start();
|
|
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)});
|
|
|
|
// pre event handling
|
|
switch (event) {
|
|
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
|
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
|
else => {},
|
|
}
|
|
|
|
container.handle(event) catch |err| app.postEvent(.{
|
|
.err = .{
|
|
.err = err,
|
|
.msg = "Container Event handling failed",
|
|
},
|
|
});
|
|
|
|
// post event handling
|
|
switch (event) {
|
|
.quit => break,
|
|
else => {},
|
|
}
|
|
|
|
container.resize(try renderer.resize());
|
|
container.reposition(.{});
|
|
try renderer.render(@TypeOf(container), &container);
|
|
try renderer.flush();
|
|
}
|
|
}
|
|
|
|
const log = std.log.scoped(.default);
|
|
|
|
const std = @import("std");
|
|
const zterm = @import("zterm");
|
|
const input = zterm.input;
|
|
const App = zterm.App(union(enum) {});
|