diff --git a/README.md b/README.md index 8b1baf4..558a8a3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,14 @@ 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` + --- ## Branch: `own-tty-visuals` diff --git a/src/layout.zig b/src/layout.zig index f45c07a..478b856 100644 --- a/src/layout.zig +++ b/src/layout.zig @@ -77,5 +77,9 @@ pub fn Layout(comptime Event: type) type { // import and export of `Layout` implementations pub const Pane = @import("layout/Pane.zig").Layout(Event); + pub const HStack = @import("layout/HStack.zig").Layout(Event); + pub const VStack = @import("layout/VStack.zig").Layout(Event); + pub const Padding = @import("layout/Padding.zig").Layout(Event); + pub const Framing = @import("layout/Framing.zig").Layout(Event); }; } diff --git a/src/layout/Framing.zig b/src/layout/Framing.zig new file mode 100644 index 0000000..7397337 --- /dev/null +++ b/src/layout/Framing.zig @@ -0,0 +1,92 @@ +//! Framing layout for a nested `Layout`s or `Widget`s. +//! +//! # Example +//! ... +const std = @import("std"); +const terminal = @import("../terminal.zig"); +const isTaggedUnion = @import("../event.zig").isTaggedUnion; +const Error = @import("../event.zig").Error; +const Key = @import("../key.zig"); + +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)`."); + } + const Element = union { + layout: @import("../layout.zig").Layout(Event), + widget: @import("../widget.zig").Widget(Event), + }; + const Events = std.ArrayList(Event); + const Contents = std.ArrayList(u8); + return struct { + size: terminal.Size = undefined, + contents: Contents = undefined, + element: Element = undefined, + events: Events = undefined, + + pub fn init(allocator: std.mem.Allocator, element: Element) @This() { + return .{ + .contents = Contents.init(allocator), + .element = element, + .events = Events.init(allocator), + }; + } + + pub fn deinit(this: *@This()) void { + this.contents.deinit(); + this.element.deinit(); + this.events.deinit(); + } + + pub fn handle(this: *@This(), event: Event) !*Event { + this.events.clearRetainingCapacity(); + // order is important + switch (event) { + .resize => |size| { + this.size = size; + // adjust size according to the containing elements + const sub_event = event; + switch (this.element) { + .layout => |layout| { + this.events.appendSlice(layout.handle(sub_event).items); + }, + .widget => |widget| { + if (widget.handle(sub_event)) |e| { + this.events.append(e); + } + }, + } + }, + else => { + for (this.elements.items) |element| { + switch (element) { + .layout => |layout| { + this.events.appendSlice(layout.handle(event).items); + }, + .widget => |widget| { + if (widget.handle(event)) |e| { + this.events.append(e); + } + }, + } + } + }, + } + } + + pub fn content(this: *@This()) !*Contents { + this.contents.clearRetainingCapacity(); + // TODO: frame contents + switch (this.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()); + }, + } + return &this.c; + } + }; +} diff --git a/src/layout/HStack.zig b/src/layout/HStack.zig new file mode 100644 index 0000000..6fdfc26 --- /dev/null +++ b/src/layout/HStack.zig @@ -0,0 +1,109 @@ +//! Horizontal Stacking layout for nested `Layout`s and/or `Widget`s. +//! +//! # Example +//! ... +const std = @import("std"); +const terminal = @import("../terminal.zig"); +const isTaggedUnion = @import("../event.zig").isTaggedUnion; +const Error = @import("../event.zig").Error; +const Key = @import("../key.zig"); + +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)`."); + } + const Element = union { + layout: @import("../layout.zig").Layout(Event), + widget: @import("../widget.zig").Widget(Event), + }; + const Elements = std.ArrayList(Element); + const Events = std.ArrayList(Event); + const Contents = std.ArrayList(u8); + return struct { + // TODO: current focused `Element`? + size: terminal.Size = undefined, + contents: Contents = undefined, + elements: Elements = undefined, + events: Events = undefined, + + // TODO: optional verdic argument for `Element`? + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .contents = Contents.init(allocator), + .elements = Elements.init(allocator), + .events = Events.init(allocator), + }; + } + + pub fn deinit(this: *@This()) void { + this.events.deinit(); + this.contents.deinit(); + if (this.elements.items) |element| { + switch (element) { + .layout => |l| { + l.deinit(); + }, + .widget => |w| { + w.deinit(); + }, + } + } + this.elements.deinit(); + } + + pub fn handle(this: *@This(), event: Event) !*Event { + this.events.clearRetainingCapacity(); + // order is important + switch (event) { + .resize => |size| { + this.size = size; + // adjust size according to the containing elements + for (this.elements.items) |element| { + const sub_event = event; + switch (element) { + .layout => |l| { + this.events.appendSlice(l.handle(sub_event).items); + }, + .widget => |w| { + if (w.handle(sub_event)) |e| { + this.events.append(e); + } + }, + } + } + }, + else => { + for (this.elements.items) |element| { + switch (element) { + .layout => |layout| { + this.events.appendSlice(layout.handle(event).items); + }, + .widget => |widget| { + if (widget.handle(event)) |e| { + this.events.append(e); + } + }, + } + } + }, + } + } + + pub fn content(this: *@This()) !*Contents { + this.contents.clearRetainingCapacity(); + // TODO: concat contents accordingly to create a vertical stack + for (this.elements.items) |element| { + 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()); + }, + } + } + return &this.c; + } + }; +} diff --git a/src/layout/Padding.zig b/src/layout/Padding.zig new file mode 100644 index 0000000..c8aec73 --- /dev/null +++ b/src/layout/Padding.zig @@ -0,0 +1,92 @@ +//! Padding layout for a nested `Layout`s or `Widget`s. +//! +//! # Example +//! ... +const std = @import("std"); +const terminal = @import("../terminal.zig"); +const isTaggedUnion = @import("../event.zig").isTaggedUnion; +const Error = @import("../event.zig").Error; +const Key = @import("../key.zig"); + +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)`."); + } + const Element = union { + layout: @import("../layout.zig").Layout(Event), + widget: @import("../widget.zig").Widget(Event), + }; + const Events = std.ArrayList(Event); + const Contents = std.ArrayList(u8); + return struct { + size: terminal.Size = undefined, + contents: Contents = undefined, + element: Element = undefined, + events: Events = undefined, + + pub fn init(allocator: std.mem.Allocator, element: Element) @This() { + return .{ + .contents = Contents.init(allocator), + .element = element, + .events = Events.init(allocator), + }; + } + + pub fn deinit(this: *@This()) void { + this.contents.deinit(); + this.element.deinit(); + this.events.deinit(); + } + + pub fn handle(this: *@This(), event: Event) !*Event { + this.events.clearRetainingCapacity(); + // order is important + switch (event) { + .resize => |size| { + this.size = size; + // adjust size according to the containing elements + const sub_event = event; + switch (this.element) { + .layout => |layout| { + this.events.appendSlice(layout.handle(sub_event).items); + }, + .widget => |widget| { + if (widget.handle(sub_event)) |e| { + this.events.append(e); + } + }, + } + }, + else => { + for (this.elements.items) |element| { + switch (element) { + .layout => |layout| { + this.events.appendSlice(layout.handle(event).items); + }, + .widget => |widget| { + if (widget.handle(event)) |e| { + this.events.append(e); + } + }, + } + } + }, + } + } + + pub fn content(this: *@This()) !*Contents { + this.contents.clearRetainingCapacity(); + // TODO: padding contents accordingly + switch (this.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()); + }, + } + return &this.c; + } + }; +} diff --git a/src/layout/Pane.zig b/src/layout/Pane.zig index ceaf0ef..1d3678c 100644 --- a/src/layout/Pane.zig +++ b/src/layout/Pane.zig @@ -1,3 +1,4 @@ +// TODO: remove this `Layout` const std = @import("std"); const terminal = @import("../terminal.zig"); const isTaggedUnion = @import("../event.zig").isTaggedUnion; @@ -10,23 +11,24 @@ pub fn Layout(comptime Event: type) type { } const Widget = @import("../widget.zig").Widget(Event); const Events = std.ArrayList(Event); + const Contents = std.ArrayList(u8); return struct { widget: Widget = undefined, events: Events = undefined, - c: std.ArrayList(u8) = undefined, + contents: Contents = undefined, pub fn init(allocator: std.mem.Allocator, widget: Widget) @This() { return .{ .widget = widget, .events = Events.init(allocator), - .c = std.ArrayList(u8).init(allocator), + .contents = Contents.init(allocator), }; } pub fn deinit(this: *@This()) void { this.widget.deinit(); this.events.deinit(); - this.c.deinit(); + this.contents.deinit(); this.* = undefined; } @@ -54,10 +56,10 @@ pub fn Layout(comptime Event: type) type { } pub fn content(this: *@This()) !*std.ArrayList(u8) { - this.c.clearRetainingCapacity(); - try this.c.appendSlice("\n"); - try this.c.appendSlice(try this.widget.content()); - try this.c.appendSlice("\n"); + this.contents.clearRetainingCapacity(); + try this.contents.appendSlice("\n"); + try this.contents.appendSlice(try this.widget.content()); + try this.contents.appendSlice("\n"); return &this.c; } }; diff --git a/src/layout/VStack.zig b/src/layout/VStack.zig new file mode 100644 index 0000000..25f481c --- /dev/null +++ b/src/layout/VStack.zig @@ -0,0 +1,109 @@ +//! Vertical Stacking layout for nested `Layout`s and/or `Widget`s. +//! +//! # Example +//! ... +const std = @import("std"); +const terminal = @import("../terminal.zig"); +const isTaggedUnion = @import("../event.zig").isTaggedUnion; +const Error = @import("../event.zig").Error; +const Key = @import("../key.zig"); + +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)`."); + } + const Element = union { + layout: @import("../layout.zig").Layout(Event), + widget: @import("../widget.zig").Widget(Event), + }; + const Elements = std.ArrayList(Element); + const Events = std.ArrayList(Event); + const Contents = std.ArrayList(u8); + return struct { + // TODO: current focused `Element`? + size: terminal.Size = undefined, + contents: Contents = undefined, + elements: Elements = undefined, + events: Events = undefined, + + // TODO: optional verdic argument for `Element`? + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .contents = Contents.init(allocator), + .elements = Elements.init(allocator), + .events = Events.init(allocator), + }; + } + + pub fn deinit(this: *@This()) void { + this.events.deinit(); + this.contents.deinit(); + if (this.elements.items) |element| { + switch (element) { + .layout => |l| { + l.deinit(); + }, + .widget => |w| { + w.deinit(); + }, + } + } + this.elements.deinit(); + } + + pub fn handle(this: *@This(), event: Event) !*Event { + this.events.clearRetainingCapacity(); + // order is important + switch (event) { + .resize => |size| { + this.size = size; + // adjust size according to the containing elements + for (this.elements.items) |element| { + const sub_event = event; + switch (element) { + .layout => |l| { + this.events.appendSlice(l.handle(sub_event).items); + }, + .widget => |w| { + if (w.handle(sub_event)) |e| { + this.events.append(e); + } + }, + } + } + }, + else => { + for (this.elements.items) |element| { + switch (element) { + .layout => |layout| { + this.events.appendSlice(layout.handle(event).items); + }, + .widget => |widget| { + if (widget.handle(event)) |e| { + this.events.append(e); + } + }, + } + } + }, + } + } + + pub fn content(this: *@This()) !*Contents { + this.contents.clearRetainingCapacity(); + // TODO: concat contents accordingly to create a vertical stack + for (this.elements.items) |element| { + 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()); + }, + } + } + return &this.c; + } + }; +}