feat(layouts): add WIP layout implementations
This commit is contained in:
@@ -21,6 +21,14 @@ delay for each frame in each line of the output.
|
|||||||
- [ ] Have clickable/navigatable links inside of the tui application
|
- [ ] Have clickable/navigatable links inside of the tui application
|
||||||
- [ ] Launch simple http server alongside 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`
|
## Branch: `own-tty-visuals`
|
||||||
|
|||||||
@@ -77,5 +77,9 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
|
|
||||||
// import and export of `Layout` implementations
|
// import and export of `Layout` implementations
|
||||||
pub const Pane = @import("layout/Pane.zig").Layout(Event);
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
92
src/layout/Framing.zig
Normal file
92
src/layout/Framing.zig
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
109
src/layout/HStack.zig
Normal file
109
src/layout/HStack.zig
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
92
src/layout/Padding.zig
Normal file
92
src/layout/Padding.zig
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO: remove this `Layout`
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const terminal = @import("../terminal.zig");
|
const terminal = @import("../terminal.zig");
|
||||||
const isTaggedUnion = @import("../event.zig").isTaggedUnion;
|
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 Widget = @import("../widget.zig").Widget(Event);
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
|
const Contents = std.ArrayList(u8);
|
||||||
return struct {
|
return struct {
|
||||||
widget: Widget = undefined,
|
widget: Widget = undefined,
|
||||||
events: Events = undefined,
|
events: Events = undefined,
|
||||||
c: std.ArrayList(u8) = undefined,
|
contents: Contents = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, widget: Widget) @This() {
|
pub fn init(allocator: std.mem.Allocator, widget: Widget) @This() {
|
||||||
return .{
|
return .{
|
||||||
.widget = widget,
|
.widget = widget,
|
||||||
.events = Events.init(allocator),
|
.events = Events.init(allocator),
|
||||||
.c = std.ArrayList(u8).init(allocator),
|
.contents = Contents.init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn deinit(this: *@This()) void {
|
||||||
this.widget.deinit();
|
this.widget.deinit();
|
||||||
this.events.deinit();
|
this.events.deinit();
|
||||||
this.c.deinit();
|
this.contents.deinit();
|
||||||
this.* = undefined;
|
this.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,10 +56,10 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) !*std.ArrayList(u8) {
|
pub fn content(this: *@This()) !*std.ArrayList(u8) {
|
||||||
this.c.clearRetainingCapacity();
|
this.contents.clearRetainingCapacity();
|
||||||
try this.c.appendSlice("\n");
|
try this.contents.appendSlice("\n");
|
||||||
try this.c.appendSlice(try this.widget.content());
|
try this.contents.appendSlice(try this.widget.content());
|
||||||
try this.c.appendSlice("\n");
|
try this.contents.appendSlice("\n");
|
||||||
return &this.c;
|
return &this.c;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
109
src/layout/VStack.zig
Normal file
109
src/layout/VStack.zig
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user