From b418e4d3a7a668a682a8c52736ab2c902683f00f Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Sat, 9 Nov 2024 16:57:05 +0100 Subject: [PATCH] add(layout/vstack): intial implementation of vstack layout --- README.md | 23 +++++++++++++++-------- src/layout/VStack.zig | 23 +++++++++++++++++++---- src/main.zig | 14 +++++++++----- src/widget/RawText.zig | 39 ++++++++++++++++++++++++--------------- 4 files changed, 67 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 558a8a3..562854c 100644 --- a/README.md +++ b/README.md @@ -21,20 +21,25 @@ 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 -- [ ] Create other layouts - - [ ] horizontal stack - - [ ] vertical stack - - [ ] `Layout` in `Layout`? -> interfaces are very similar anyway - - [ ] Building Block `Layout`s - - [ ] Framing `Layout` - - [ ] Padding `Layout` +--- + +- [ ] Split into own repository + - [ ] Create other layouts + - [ ] horizontal stack + - [ ] vertical stack + - [ ] `Layout` in `Layout`? -> interfaces are very similar anyway + - [ ] 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 ...) diff --git a/src/layout/VStack.zig b/src/layout/VStack.zig index 135dfb4..4332e24 100644 --- a/src/layout/VStack.zig +++ b/src/layout/VStack.zig @@ -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; } diff --git a/src/main.zig b/src/main.zig index 71197b3..f668fc9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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(); diff --git a/src/widget/RawText.zig b/src/widget/RawText.zig index 90f07c3..ff2144e 100644 --- a/src/widget/RawText.zig +++ b/src/widget/RawText.zig @@ -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]; } } };