mod: replace Layout.content with Layout.render
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 30s
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 30s
The App.Renderer is used for the new `Layout.render` method. Each layout renders itself now with corresponding renderers which might only update parts of the screen, etc.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.zig-cache/
|
.zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
log
|
log
|
||||||
|
TODO.md
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fulls
|
|||||||
}
|
}
|
||||||
return struct {
|
return struct {
|
||||||
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
|
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
|
||||||
pub const Layout = @import("layout.zig").Layout(Event);
|
|
||||||
pub const Widget = @import("widget.zig").Widget(Event);
|
|
||||||
pub const Renderer = R(fullscreen);
|
pub const Renderer = R(fullscreen);
|
||||||
|
pub const Layout = @import("layout.zig").Layout(Event, Renderer);
|
||||||
|
pub const Widget = @import("widget.zig").Widget(Event);
|
||||||
|
|
||||||
queue: Queue(Event, 256) = .{},
|
queue: Queue(Event, 256) = .{},
|
||||||
thread: ?std.Thread = null,
|
thread: ?std.Thread = null,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Dynamic dispatch for layout implementations.
|
//! Dynamic dispatch for layout implementations.
|
||||||
//! Each layout should at last implement these functions:
|
//! Each layout should at last implement these functions:
|
||||||
//! - handle(this: *@This(), event: Event) anyerror!*std.ArrayList(Event) {}
|
//! - handle(this: *@This(), event: Event) anyerror!*std.ArrayList(Event) {}
|
||||||
//! - content(this: *@This()) anyerror!*std.ArrayList(u8) {}
|
//! - render(this: *@This(), renderer: Renderer) anyerror!void {}
|
||||||
//! - deinit(this: *@This()) void {}
|
//! - deinit(this: *@This()) void {}
|
||||||
//!
|
//!
|
||||||
//! Create a `Layout` using `createFrom(object: anytype)` and use them through
|
//! Create a `Layout` using `createFrom(object: anytype)` and use them through
|
||||||
@@ -11,10 +11,13 @@
|
|||||||
//! Each `Layout` is responsible for clearing the allocated memory of the used
|
//! Each `Layout` is responsible for clearing the allocated memory of the used
|
||||||
//! widgets when deallocated. This means that `deinit()` will also deallocate
|
//! widgets when deallocated. This means that `deinit()` will also deallocate
|
||||||
//! every used widget too.
|
//! every used widget too.
|
||||||
|
//!
|
||||||
|
//! When `Layout.render` is called the provided `Renderer` type is expected
|
||||||
|
//! which handles how contents are rendered for a given layout.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type) type {
|
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
@@ -25,7 +28,7 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
|
|
||||||
const VTable = struct {
|
const VTable = struct {
|
||||||
handle: *const fn (this: *LayoutType, event: Event) anyerror!*Events,
|
handle: *const fn (this: *LayoutType, event: Event) anyerror!*Events,
|
||||||
content: *const fn (this: *LayoutType) anyerror!*std.ArrayList(u8),
|
render: *const fn (this: *LayoutType, renderer: Renderer) anyerror!void,
|
||||||
deinit: *const fn (this: *LayoutType) void,
|
deinit: *const fn (this: *LayoutType) void,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,9 +40,9 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
return try this.vtable.handle(this, event);
|
return try this.vtable.handle(this, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the entire content of this `Layout`.
|
// Render this `Layout` completely. This will render contained sub-elements too.
|
||||||
pub fn content(this: *LayoutType) !*std.ArrayList(u8) {
|
pub fn render(this: *LayoutType, renderer: Renderer) !void {
|
||||||
return try this.vtable.content(this);
|
return try this.vtable.render(this, renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *LayoutType) void {
|
pub fn deinit(this: *LayoutType) void {
|
||||||
@@ -58,13 +61,13 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
return try layout.handle(event);
|
return try layout.handle(event);
|
||||||
}
|
}
|
||||||
}.handle,
|
}.handle,
|
||||||
.content = struct {
|
.render = struct {
|
||||||
// Return the entire content of this `Layout`.
|
// Render the contents of this `Layout`.
|
||||||
fn content(this: *LayoutType) !*std.ArrayList(u8) {
|
fn render(this: *LayoutType, renderer: Renderer) !void {
|
||||||
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
||||||
return try layout.content();
|
try layout.render(renderer);
|
||||||
}
|
}
|
||||||
}.content,
|
}.render,
|
||||||
.deinit = struct {
|
.deinit = struct {
|
||||||
fn deinit(this: *LayoutType) void {
|
fn deinit(this: *LayoutType) void {
|
||||||
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
||||||
@@ -76,9 +79,9 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// import and export of `Layout` implementations
|
// import and export of `Layout` implementations
|
||||||
pub const HStack = @import("layout/HStack.zig").Layout(Event);
|
pub const HStack = @import("layout/HStack.zig").Layout(Event, Renderer);
|
||||||
pub const VStack = @import("layout/VStack.zig").Layout(Event);
|
pub const VStack = @import("layout/VStack.zig").Layout(Event, Renderer);
|
||||||
pub const Padding = @import("layout/Padding.zig").Layout(Event);
|
pub const Padding = @import("layout/Padding.zig").Layout(Event, Renderer);
|
||||||
pub const Framing = @import("layout/Framing.zig").Layout(Event);
|
pub const Framing = @import("layout/Framing.zig").Layout(Event, Renderer);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,32 +11,28 @@ const Key = terminal.Key;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_framing);
|
const log = std.log.scoped(.layout_framing);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type) type {
|
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Element = union(enum) {
|
const Element = union(enum) {
|
||||||
layout: @import("../layout.zig").Layout(Event),
|
layout: @import("../layout.zig").Layout(Event, Renderer),
|
||||||
widget: @import("../widget.zig").Widget(Event),
|
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 {
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
contents: Contents = undefined,
|
|
||||||
element: Element = undefined,
|
element: Element = undefined,
|
||||||
events: Events = undefined,
|
events: Events = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, element: Element) @This() {
|
pub fn init(allocator: std.mem.Allocator, element: Element) @This() {
|
||||||
return .{
|
return .{
|
||||||
.contents = Contents.init(allocator),
|
|
||||||
.element = element,
|
.element = element,
|
||||||
.events = Events.init(allocator),
|
.events = Events.init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn deinit(this: *@This()) void {
|
||||||
this.contents.deinit();
|
|
||||||
this.events.deinit();
|
this.events.deinit();
|
||||||
switch ((&this.element).*) {
|
switch ((&this.element).*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
@@ -85,19 +81,18 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
return &this.events;
|
return &this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) !*Contents {
|
pub fn render(this: *@This(), renderer: Renderer) !void {
|
||||||
this.contents.clearRetainingCapacity();
|
|
||||||
// TODO: padding contents accordingly
|
// TODO: padding contents accordingly
|
||||||
switch ((&this.element).*) {
|
switch ((&this.element).*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
const layout_content = try layout.content();
|
try layout.render(renderer);
|
||||||
try this.contents.appendSlice(layout_content.items);
|
|
||||||
},
|
},
|
||||||
.widget => |*widget| {
|
.widget => |*widget| {
|
||||||
try this.contents.appendSlice(try widget.content());
|
const content = try widget.content();
|
||||||
|
// TODO: use renderer
|
||||||
|
_ = try terminal.write(content);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return &this.contents;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,23 +11,21 @@ const Key = terminal.Key;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_hstack);
|
const log = std.log.scoped(.layout_hstack);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type) type {
|
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Widget = @import("../widget.zig").Widget(Event);
|
const Widget = @import("../widget.zig").Widget(Event);
|
||||||
const Lay = @import("../layout.zig").Layout(Event);
|
const Lay = @import("../layout.zig").Layout(Event, Renderer);
|
||||||
const Element = union(enum) {
|
const Element = union(enum) {
|
||||||
layout: Lay,
|
layout: Lay,
|
||||||
widget: Widget,
|
widget: Widget,
|
||||||
};
|
};
|
||||||
const Elements = std.ArrayList(Element);
|
const Elements = std.ArrayList(Element);
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
const Contents = std.ArrayList(u8);
|
|
||||||
return struct {
|
return struct {
|
||||||
// TODO: current focused `Element`?
|
// TODO: current focused `Element`?
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
contents: Contents = undefined,
|
|
||||||
elements: Elements = undefined,
|
elements: Elements = undefined,
|
||||||
events: Events = undefined,
|
events: Events = undefined,
|
||||||
|
|
||||||
@@ -53,7 +51,6 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(Lay) ++ " or " ++ @typeName(Widget) ++ " but " ++ @typeName(ChildType));
|
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(Lay) ++ " or " ++ @typeName(Widget) ++ " but " ++ @typeName(ChildType));
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.contents = Contents.init(allocator),
|
|
||||||
.elements = elements,
|
.elements = elements,
|
||||||
.events = Events.init(allocator),
|
.events = Events.init(allocator),
|
||||||
};
|
};
|
||||||
@@ -61,7 +58,6 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn deinit(this: *@This()) void {
|
||||||
this.events.deinit();
|
this.events.deinit();
|
||||||
this.contents.deinit();
|
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
switch (element.*) {
|
switch (element.*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
@@ -116,21 +112,21 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
return &this.events;
|
return &this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) !*Contents {
|
pub fn render(this: *@This(), renderer: Renderer) !void {
|
||||||
this.contents.clearRetainingCapacity();
|
// TODO: concat contents accordingly to create a horizontal stack
|
||||||
// TODO: concat contents accordingly to create a vertical stack
|
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
switch (element.*) {
|
switch (element.*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
const layout_content = try layout.content();
|
try layout.render(renderer);
|
||||||
try this.contents.appendSlice(layout_content.items);
|
|
||||||
},
|
},
|
||||||
.widget => |*widget| {
|
.widget => |*widget| {
|
||||||
try this.contents.appendSlice(try widget.content());
|
// TODO: clear per widget if necessary (i.e. can I query that?)
|
||||||
|
// TODO: render using `renderer`
|
||||||
|
const content = try widget.content();
|
||||||
|
_ = try terminal.write(content);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &this.contents;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,32 +11,28 @@ const Key = terminal.Key;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_padding);
|
const log = std.log.scoped(.layout_padding);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type) type {
|
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Element = union(enum) {
|
const Element = union(enum) {
|
||||||
layout: @import("../layout.zig").Layout(Event),
|
layout: @import("../layout.zig").Layout(Event, Renderer),
|
||||||
widget: @import("../widget.zig").Widget(Event),
|
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 {
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
contents: Contents = undefined,
|
|
||||||
element: Element = undefined,
|
element: Element = undefined,
|
||||||
events: Events = undefined,
|
events: Events = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, element: Element) @This() {
|
pub fn init(allocator: std.mem.Allocator, element: Element) @This() {
|
||||||
return .{
|
return .{
|
||||||
.contents = Contents.init(allocator),
|
|
||||||
.element = element,
|
.element = element,
|
||||||
.events = Events.init(allocator),
|
.events = Events.init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn deinit(this: *@This()) void {
|
||||||
this.contents.deinit();
|
|
||||||
this.events.deinit();
|
this.events.deinit();
|
||||||
switch ((&this.element).*) {
|
switch ((&this.element).*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
@@ -85,19 +81,18 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
return &this.events;
|
return &this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) !*Contents {
|
pub fn render(this: *@This(), renderer: Renderer) !void {
|
||||||
this.contents.clearRetainingCapacity();
|
|
||||||
// TODO: padding contents accordingly
|
// TODO: padding contents accordingly
|
||||||
switch ((&this.element).*) {
|
switch ((&this.element).*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
const layout_content = try layout.content();
|
try layout.render(renderer);
|
||||||
try this.contents.appendSlice(layout_content.items);
|
|
||||||
},
|
},
|
||||||
.widget => |*widget| {
|
.widget => |*widget| {
|
||||||
try this.contents.appendSlice(try widget.content());
|
const content = try widget.content();
|
||||||
|
// TODO: use renderer
|
||||||
|
_ = try terminal.write(content);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return &this.contents;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,23 +11,24 @@ const Key = terminal.Key;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_vstack);
|
const log = std.log.scoped(.layout_vstack);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type) type {
|
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Widget = @import("../widget.zig").Widget(Event);
|
const Widget = @import("../widget.zig").Widget(Event);
|
||||||
const Lay = @import("../layout.zig").Layout(Event);
|
const Lay = @import("../layout.zig").Layout(Event, Renderer);
|
||||||
const Element = union(enum) {
|
const Element = union(enum) {
|
||||||
layout: Lay,
|
layout: Lay,
|
||||||
widget: Widget,
|
widget: Widget,
|
||||||
};
|
};
|
||||||
const Elements = std.ArrayList(Element);
|
const Elements = std.ArrayList(Element);
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
const Contents = std.ArrayList(u8);
|
|
||||||
return struct {
|
return struct {
|
||||||
// TODO: current focused `Element`?
|
// TODO: current focused `Element`?
|
||||||
|
// FIX: this should not be 'hardcoded' but dynamically be calculated and updated (i.e. through the event system)
|
||||||
|
anchor: terminal.Position = .{ .col = 1, .row = 1 },
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
contents: Contents = undefined,
|
element_rows: u16 = undefined,
|
||||||
elements: Elements = undefined,
|
elements: Elements = undefined,
|
||||||
events: Events = undefined,
|
events: Events = undefined,
|
||||||
|
|
||||||
@@ -53,7 +54,6 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(Lay) ++ " or " ++ @typeName(Widget) ++ " but " ++ @typeName(ChildType));
|
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(Lay) ++ " or " ++ @typeName(Widget) ++ " but " ++ @typeName(ChildType));
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.contents = Contents.init(allocator),
|
|
||||||
.elements = elements,
|
.elements = elements,
|
||||||
.events = Events.init(allocator),
|
.events = Events.init(allocator),
|
||||||
};
|
};
|
||||||
@@ -61,7 +61,6 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn deinit(this: *@This()) void {
|
||||||
this.events.deinit();
|
this.events.deinit();
|
||||||
this.contents.deinit();
|
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
switch (element.*) {
|
switch (element.*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
@@ -83,9 +82,15 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
this.size = size;
|
this.size = size;
|
||||||
log.debug("Using size: {{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows });
|
log.debug("Using size: {{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows });
|
||||||
const len: u16 = @truncate(this.elements.items.len);
|
const len: u16 = @truncate(this.elements.items.len);
|
||||||
const rows = size.rows / len;
|
this.element_rows = @divTrunc(size.rows, len);
|
||||||
|
var overflow = this.size.rows % len;
|
||||||
// adjust size according to the containing elements
|
// adjust size according to the containing elements
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
|
var rows = this.element_rows;
|
||||||
|
if (overflow > 0) {
|
||||||
|
overflow -|= 1;
|
||||||
|
rows += 1;
|
||||||
|
}
|
||||||
const sub_event: Event = .{
|
const sub_event: Event = .{
|
||||||
.resize = .{
|
.resize = .{
|
||||||
.cols = size.cols,
|
.cols = size.cols,
|
||||||
@@ -124,26 +129,32 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
return &this.events;
|
return &this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) !*Contents {
|
pub fn render(this: *@This(), renderer: Renderer) !void {
|
||||||
this.contents.clearRetainingCapacity();
|
// FIX: renderer should clear only what is going to change! (i.e. the 'active' widget / layout)
|
||||||
// TODO: concat contents accordingly to create a horizontal stack
|
try renderer.clear(this.anchor, this.size);
|
||||||
for (this.elements.items, 1..) |*element, i| {
|
var overflow = this.size.rows % this.elements.items.len;
|
||||||
|
for (this.elements.items, 0..) |*element, i| {
|
||||||
|
const row_mul: u16 = @truncate(i);
|
||||||
|
var row = row_mul * this.element_rows + 1;
|
||||||
|
if (i > 0 and overflow > 0) {
|
||||||
|
overflow -|= 1;
|
||||||
|
row += 1;
|
||||||
|
}
|
||||||
|
const pos: terminal.Position = .{ .col = 1, .row = row };
|
||||||
|
log.debug("using position: .{{ .cols = {d}, .rows = {d} }}", .{ pos.col, pos.row });
|
||||||
|
// TODO: do this using the renderer
|
||||||
|
try terminal.setCursorPosition(pos);
|
||||||
switch (element.*) {
|
switch (element.*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
const layout_content = try layout.content();
|
try layout.render(renderer);
|
||||||
try this.contents.appendSlice(layout_content.items);
|
|
||||||
},
|
},
|
||||||
.widget => |*widget| {
|
.widget => |*widget| {
|
||||||
const widget_content = try widget.content();
|
// TODO: clear per widget if necesary (i.e. can I query that?)
|
||||||
try this.contents.appendSlice(widget_content);
|
const content = try widget.content();
|
||||||
|
_ = try terminal.write(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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/main.zig
14
src/main.zig
@@ -24,7 +24,9 @@ pub fn main() !void {
|
|||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .{};
|
var app: App = .{};
|
||||||
var renderer: App.Renderer = .{};
|
const renderer: App.Renderer = .{};
|
||||||
|
// FIX: when not running fullscreen, the application needs to screen down accordingly to display the contents
|
||||||
|
// -> size hint how much should it use?
|
||||||
|
|
||||||
const mainFile = try std.fs.cwd().openFile("./src/main.zig", .{});
|
const mainFile = try std.fs.cwd().openFile("./src/main.zig", .{});
|
||||||
var mainFileText = App.Widget.RawText.init(allocator, mainFile);
|
var mainFileText = App.Widget.RawText.init(allocator, mainFile);
|
||||||
@@ -35,17 +37,11 @@ pub fn main() !void {
|
|||||||
appFile.close();
|
appFile.close();
|
||||||
|
|
||||||
var spacer = App.Widget.Spacer.init();
|
var spacer = App.Widget.Spacer.init();
|
||||||
var framing = App.Layout.Framing.init(allocator, .{
|
|
||||||
.widget = App.Widget.createFrom(&mainFileText),
|
|
||||||
});
|
|
||||||
var hstack = App.Layout.HStack.init(allocator, .{
|
|
||||||
App.Layout.createFrom(&framing),
|
|
||||||
});
|
|
||||||
// TODO: corresponding contents need to be filled out by the layout accordingly!
|
// TODO: corresponding contents need to be filled out by the layout accordingly!
|
||||||
var vstack = App.Layout.VStack.init(allocator, .{
|
var vstack = App.Layout.VStack.init(allocator, .{
|
||||||
App.Widget.createFrom(&appFileText),
|
App.Widget.createFrom(&appFileText),
|
||||||
App.Layout.createFrom(&hstack),
|
|
||||||
App.Widget.createFrom(&spacer),
|
App.Widget.createFrom(&spacer),
|
||||||
|
App.Widget.createFrom(&mainFileText),
|
||||||
});
|
});
|
||||||
var layout = App.Layout.createFrom(&vstack);
|
var layout = App.Layout.createFrom(&vstack);
|
||||||
defer layout.deinit();
|
defer layout.deinit();
|
||||||
@@ -88,6 +84,6 @@ pub fn main() !void {
|
|||||||
for (events.items) |e| {
|
for (events.items) |e| {
|
||||||
app.postEvent(e);
|
app.postEvent(e);
|
||||||
}
|
}
|
||||||
try renderer.render(try layout.content());
|
try layout.render(renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const std = @import("std");
|
|||||||
const terminal = @import("terminal.zig");
|
const terminal = @import("terminal.zig");
|
||||||
|
|
||||||
const Contents = std.ArrayList(u8);
|
const Contents = std.ArrayList(u8);
|
||||||
|
const Position = terminal.Position;
|
||||||
const Size = terminal.Size;
|
const Size = terminal.Size;
|
||||||
|
|
||||||
pub fn Buffered(comptime fullscreen: bool) type {
|
pub fn Buffered(comptime fullscreen: bool) type {
|
||||||
@@ -40,16 +41,23 @@ pub fn Buffered(comptime fullscreen: bool) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Plain(comptime fullscreen: bool) type {
|
pub fn Plain(comptime _: bool) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn render(this: *@This(), content: *Contents) !void {
|
pub fn clear(this: @This(), anchor: Position, size: Size) !void {
|
||||||
_ = this;
|
_ = this;
|
||||||
if (fullscreen) {
|
_ = size;
|
||||||
|
// NOTE: clear entire screen for the first content (derived from the anchor being at the very left-top)
|
||||||
|
if (anchor.col == 1 and anchor.row == 1) {
|
||||||
try terminal.clearScreen();
|
try terminal.clearScreen();
|
||||||
try terminal.setCursorPositionHome();
|
|
||||||
}
|
}
|
||||||
// TODO: how would I clear the screen in case of a non fullscreen application (i.e. to clear to the start of the command)
|
}
|
||||||
_ = try terminal.write(content.items);
|
|
||||||
|
pub fn render(this: @This(), anchor: Position, size: Size, contents: *Contents) !void {
|
||||||
|
_ = this;
|
||||||
|
_ = size;
|
||||||
|
// FIXME: this should respect the given `size`
|
||||||
|
try terminal.setCursorPosition(anchor);
|
||||||
|
_ = try terminal.write(contents.items);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ pub fn write(buf: []const u8) !usize {
|
|||||||
return try std.posix.write(std.posix.STDIN_FILENO, buf);
|
return try std.posix.write(std.posix.STDIN_FILENO, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setCursorPosition(pos: Position) !void {
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.row, pos.col });
|
||||||
|
_ = try std.posix.write(std.posix.STDIN_FILENO, value);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getCursorPosition() !Position {
|
pub fn getCursorPosition() !Position {
|
||||||
// Needs Raw mode (no wait for \n) to work properly cause
|
// Needs Raw mode (no wait for \n) to work properly cause
|
||||||
// control sequence will not be written without it.
|
// control sequence will not be written without it.
|
||||||
|
|||||||
@@ -10,8 +10,11 @@
|
|||||||
//!
|
//!
|
||||||
//! Each `Widget` may cache its content and should if the contents will not
|
//! Each `Widget` may cache its content and should if the contents will not
|
||||||
//! change for a long time.
|
//! change for a long time.
|
||||||
|
const std = @import("std");
|
||||||
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.widget);
|
||||||
|
|
||||||
pub fn Widget(comptime Event: type) type {
|
pub fn Widget(comptime Event: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
@@ -31,6 +34,12 @@ pub fn Widget(comptime Event: type) type {
|
|||||||
|
|
||||||
// Handle the provided `Event` for this `Widget`.
|
// Handle the provided `Event` for this `Widget`.
|
||||||
pub fn handle(this: *WidgetType, event: Event) ?Event {
|
pub fn handle(this: *WidgetType, event: Event) ?Event {
|
||||||
|
switch (event) {
|
||||||
|
.resize => |size| {
|
||||||
|
log.debug("received size: .{{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows });
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
return this.vtable.handle(this, event);
|
return this.vtable.handle(this, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ pub fn Widget(comptime Event: type) type {
|
|||||||
// store the received size
|
// store the received size
|
||||||
.resize => |size| {
|
.resize => |size| {
|
||||||
this.size = size;
|
this.size = size;
|
||||||
log.debug("Using size: {{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows });
|
|
||||||
if (this.line > this.line_index.items.len -| 1 -| size.rows) {
|
if (this.line > this.line_index.items.len -| 1 -| size.rows) {
|
||||||
this.line = this.line_index.items.len -| 1 -| size.rows;
|
this.line = this.line_index.items.len -| 1 -| size.rows;
|
||||||
}
|
}
|
||||||
@@ -82,11 +81,12 @@ pub fn Widget(comptime Event: type) type {
|
|||||||
} else {
|
} else {
|
||||||
// more rows than we can display
|
// more rows than we can display
|
||||||
const i = this.line_index.items[this.line];
|
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 + 1;
|
||||||
const e = this.size.rows + this.line;
|
|
||||||
if (e >= this.line_index.items.len) {
|
if (e >= this.line_index.items.len) {
|
||||||
return this.contents.items[i..];
|
return this.contents.items[i..];
|
||||||
}
|
}
|
||||||
|
// last line should not end with the last character (likely a newline character)
|
||||||
|
// FIX: what about files which do not end with a newline?
|
||||||
const x = this.line_index.items[e] - 1;
|
const x = this.line_index.items[e] - 1;
|
||||||
return this.contents.items[i..x];
|
return this.contents.items[i..x];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user