add(layout/vstack): intial implementation of vstack layout

This commit is contained in:
2024-11-09 16:57:05 +01:00
parent b5c5f4e3e2
commit b418e4d3a7
4 changed files with 67 additions and 32 deletions

View File

@@ -21,6 +21,9 @@ delay for each frame in each line of the output.
- [ ] Have clickable/navigatable links inside of the tui application
- [ ] Launch simple http server alongside tui application
---
- [ ] Split into own repository
- [ ] Create other layouts
- [ ] horizontal stack
- [ ] vertical stack
@@ -28,13 +31,15 @@ delay for each frame in each line of the output.
- [ ] Building Block `Layout`s
- [ ] Framing `Layout`
- [ ] Padding `Layout`
- [ ] Create demo gifs using [vhs](https://github.com/charmbracelet/vhs)
- [ ] Move documentation into new repository
- [ ] add dependency to new repository into this project
---
## Branch: `own-tty-visuals`
- [ ] How can I support to run a sub-process inside of a given pane / layout?
- [ ] Create demo gifs using [vhs](https://github.com/charmbracelet/vhs)
- [x] Could I simulate a corresponding event loop?
- emmit as many as possible through another thread (until the event queue is full?) [1023]
@@ -43,3 +48,5 @@ delay for each frame in each line of the output.
-> Or buffered writer to the `std.posix.STDOUT_FILENO`?
-> I could use this to see if it makes sense to implement a buffered version using a screen buffer (to only render the differences?)
- seems pretty good (with some exceptions)
- [ ] styling could be tricky with a given layout (which introduces corresponding line breaks ...)

View File

@@ -8,6 +8,8 @@ const isTaggedUnion = @import("../event.zig").isTaggedUnion;
const Error = @import("../event.zig").Error;
const Key = @import("../key.zig");
const log = std.log.scoped(.layout_vstack);
pub fn Layout(comptime Event: type) type {
if (!isTaggedUnion(Event)) {
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
@@ -78,9 +80,17 @@ pub fn Layout(comptime Event: type) type {
switch (event) {
.resize => |size| {
this.size = size;
log.debug("Using size: {{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows });
const len: u16 = @truncate(this.elements.items.len);
const rows = size.rows / len;
// adjust size according to the containing elements
for (this.elements.items) |*element| {
const sub_event = event;
const sub_event: Event = .{
.resize = .{
.cols = size.cols,
.rows = rows,
},
};
switch (element.*) {
.layout => |*layout| {
const events = try layout.handle(sub_event);
@@ -115,17 +125,22 @@ pub fn Layout(comptime Event: type) type {
pub fn content(this: *@This()) !*Contents {
this.contents.clearRetainingCapacity();
// TODO: concat contents accordingly to create a vertical stack
for (this.elements.items) |*element| {
// TODO: concat contents accordingly to create a horizontal stack
for (this.elements.items, 1..) |*element, i| {
switch (element.*) {
.layout => |*layout| {
const layout_content = try layout.content();
try this.contents.appendSlice(layout_content.items);
},
.widget => |*widget| {
try this.contents.appendSlice(try widget.content());
const widget_content = try widget.content();
try this.contents.appendSlice(widget_content);
},
}
// TODO: support clear positioning of content on the tui screen
if (i != this.elements.items.len) {
try this.contents.appendSlice("\n"); // NOTE: this assumes that the previous content fills all the provided size.rows accordingly with content, such that a newline introduces the start of the next content
}
}
return &this.contents;
}

View File

@@ -33,17 +33,21 @@ pub fn main() !void {
var rawText = App.Widget.RawText.init(allocator, file);
file.close();
const doc = try std.fs.cwd().openFile("./doc/test.md", .{});
var docText = App.Widget.RawText.init(allocator, doc);
doc.close();
var framing = App.Layout.Framing.init(allocator, .{
.widget = App.Widget.createFrom(&rawText),
});
var vstack = App.Layout.VStack.init(allocator, .{
var hstack = App.Layout.HStack.init(allocator, .{
App.Layout.createFrom(&framing),
});
var hstack = App.Layout.HStack.init(allocator, .{
App.Layout.createFrom(&vstack),
var vstack = App.Layout.VStack.init(allocator, .{
App.Widget.createFrom(&docText),
App.Layout.createFrom(&hstack),
});
var layout = App.Layout.createFrom(&hstack);
var layout = App.Layout.createFrom(&vstack);
defer layout.deinit();
try app.start();

View File

@@ -6,30 +6,37 @@ const isTaggedUnion = @import("../event.zig").isTaggedUnion;
const Error = @import("../event.zig").Error;
const Key = @import("../key.zig");
const log = std.log.scoped(.widget_rawtext);
pub fn Widget(comptime Event: type) type {
if (!isTaggedUnion(Event)) {
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
}
const Contents = std.ArrayList(u8);
return struct {
c: std.ArrayList(u8) = undefined,
contents: Contents = undefined,
line_index: std.ArrayList(usize) = undefined,
line: usize = 0,
size: terminal.Size = undefined,
pub fn init(allocator: std.mem.Allocator, file: std.fs.File) @This() {
var c = std.ArrayList(u8).init(allocator);
var contents = Contents.init(allocator);
var line_index = std.ArrayList(usize).init(allocator);
file.reader().readAllArrayList(&c, 4192) catch {};
for (c.items, 0..) |item, i| {
file.reader().readAllArrayList(&contents, std.math.maxInt(usize)) catch {};
line_index.append(0) catch {};
for (contents.items, 0..) |item, i| {
if (item == '\n') {
line_index.append(i) catch {};
line_index.append(i + 1) catch {};
}
}
return .{ .c = c, .line_index = line_index };
return .{
.contents = contents,
.line_index = line_index,
};
}
pub fn deinit(this: *@This()) void {
this.c.deinit();
this.contents.deinit();
this.line_index.deinit();
this.* = undefined;
}
@@ -39,8 +46,9 @@ pub fn Widget(comptime Event: type) type {
// store the received size
.resize => |size| {
this.size = size;
if (this.line > this.line_index.items.len - 1 - size.rows) {
this.line = this.line_index.items.len - 1 - size.rows;
log.debug("Using size: {{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows });
if (this.line > this.line_index.items.len -| 1 -| size.rows) {
this.line = this.line_index.items.len -| 1 -| size.rows;
}
},
.key => |key| {
@@ -50,11 +58,11 @@ pub fn Widget(comptime Event: type) type {
}
if (key.matches(.{ .cp = 'G' })) {
// bottom
this.line = this.line_index.items.len - 1 - this.size.rows;
this.line = this.line_index.items.len -| 1 -| this.size.rows;
}
if (key.matches(.{ .cp = 'j' })) {
// down
if (this.line < this.line_index.items.len - 1 - this.size.rows) {
if (this.line < this.line_index.items.len -| 1 -| this.size.rows) {
this.line +|= 1;
}
}
@@ -70,16 +78,17 @@ pub fn Widget(comptime Event: type) type {
pub fn content(this: *@This()) ![]u8 {
if (this.size.rows >= this.line_index.items.len) {
return this.c.items;
return this.contents.items;
} else {
// more rows than we can display
const i = this.line_index.items[this.line];
log.debug("i := {d} this.line := {d}", .{ i, this.line });
const e = this.size.rows + this.line;
if (e >= this.line_index.items.len) {
return this.c.items[i..];
return this.contents.items[i..];
}
const x = this.line_index.items[e];
return this.c.items[i..x];
const x = this.line_index.items[e] - 1;
return this.contents.items[i..x];
}
}
};