diff --git a/src/app.zig b/src/app.zig index 39e3c20..c64dd61 100644 --- a/src/app.zig +++ b/src/app.zig @@ -47,7 +47,7 @@ pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fulls pub const Event = mergeTaggedUnions(event.SystemEvent, E); pub const Renderer = R(fullscreen); pub const Layout = @import("layout.zig").Layout(Event, Renderer); - pub const Widget = @import("widget.zig").Widget(Event); + pub const Widget = @import("widget.zig").Widget(Event, Renderer); queue: Queue(Event, 256) = .{}, thread: ?std.Thread = null, diff --git a/src/layout/Framing.zig b/src/layout/Framing.zig index 70a9718..ce17fd9 100644 --- a/src/layout/Framing.zig +++ b/src/layout/Framing.zig @@ -17,7 +17,7 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { } const Element = union(enum) { layout: @import("../layout.zig").Layout(Event, Renderer), - widget: @import("../widget.zig").Widget(Event), + widget: @import("../widget.zig").Widget(Event, Renderer), }; const Events = std.ArrayList(Event); return struct { diff --git a/src/layout/HStack.zig b/src/layout/HStack.zig index e3a0856..2c1ea46 100644 --- a/src/layout/HStack.zig +++ b/src/layout/HStack.zig @@ -15,7 +15,7 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { if (!isTaggedUnion(Event)) { @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, Renderer); const Lay = @import("../layout.zig").Layout(Event, Renderer); const Element = union(enum) { layout: Lay, diff --git a/src/layout/Padding.zig b/src/layout/Padding.zig index 388af0a..bb8e702 100644 --- a/src/layout/Padding.zig +++ b/src/layout/Padding.zig @@ -17,7 +17,7 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { } const Element = union(enum) { layout: @import("../layout.zig").Layout(Event, Renderer), - widget: @import("../widget.zig").Widget(Event), + widget: @import("../widget.zig").Widget(Event, Renderer), }; const Events = std.ArrayList(Event); return struct { diff --git a/src/layout/VStack.zig b/src/layout/VStack.zig index 0d04ab9..88e532e 100644 --- a/src/layout/VStack.zig +++ b/src/layout/VStack.zig @@ -15,7 +15,7 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { if (!isTaggedUnion(Event)) { @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, Renderer); const Lay = @import("../layout.zig").Layout(Event, Renderer); const Element = union(enum) { layout: Lay, @@ -80,10 +80,16 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { switch (event) { .resize => |size| { this.size = size; - log.debug("Using size: {{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows }); + log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{ + size.anchor.col, + size.anchor.row, + size.cols, + size.rows, + }); const len: u16 = @truncate(this.elements.items.len); this.element_rows = @divTrunc(size.rows, len); var overflow = this.size.rows % len; + var offset: u16 = 0; // adjust size according to the containing elements for (this.elements.items) |*element| { var rows = this.element_rows; @@ -93,10 +99,15 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { } const sub_event: Event = .{ .resize = .{ + .anchor = .{ + .col = size.anchor.col, + .row = size.anchor.row + offset, + }, .cols = size.cols, .rows = rows, }, }; + offset += rows; switch (element.*) { .layout => |*layout| { const events = try layout.handle(sub_event); @@ -140,8 +151,8 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { overflow -|= 1; row += 1; } + // TODO: that's the anchor of each component (only necessary for each widget rendering) 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.*) { @@ -150,8 +161,7 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { }, .widget => |*widget| { // TODO: clear per widget if necesary (i.e. can I query that?) - const content = try widget.content(); - _ = try terminal.write(content); + try widget.render(renderer); }, } } diff --git a/src/terminal.zig b/src/terminal.zig index fdff6c1..c1061f9 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -5,6 +5,7 @@ pub const code_point = @import("code_point"); const log = std.log.scoped(.terminal); pub const Size = struct { + anchor: Position = .{ .col = 1, .row = 1 }, // top left corner by default cols: u16, rows: u16, }; diff --git a/src/widget.zig b/src/widget.zig index f8aa50d..514d294 100644 --- a/src/widget.zig +++ b/src/widget.zig @@ -1,7 +1,7 @@ //! Dynamic dispatch for widget implementations. //! Each widget should at last implement these functions: //! - handle(this: *@This(), event: Event) ?Event {} -//! - content(this: *@This()) ![]u8 {} +//! - render(this: *@This(), renderer: Renderer) !void {} //! - deinit(this: *@This()) void {} //! //! Create a `Widget` using `createFrom(object: anytype)` and use them through @@ -10,12 +10,14 @@ //! //! Each `Widget` may cache its content and should if the contents will not //! change for a long time. -const std = @import("std"); +//! +//! When `Widget.render` is called the provided `Renderer` type is expected +//! which handles how contents are rendered for a given widget. const isTaggedUnion = @import("event.zig").isTaggedUnion; -const log = std.log.scoped(.widget); +const log = @import("std").log.scoped(.widget); -pub fn Widget(comptime Event: type) type { +pub fn Widget(comptime Event: type, comptime Renderer: type) type { if (!isTaggedUnion(Event)) { @compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`."); } @@ -25,7 +27,7 @@ pub fn Widget(comptime Event: type) type { const VTable = struct { handle: *const fn (this: *WidgetType, event: Event) ?Event, - content: *const fn (this: *WidgetType) anyerror![]u8, + render: *const fn (this: *WidgetType, renderer: Renderer) anyerror!void, deinit: *const fn (this: *WidgetType) void, }; @@ -36,16 +38,21 @@ pub fn Widget(comptime Event: type) type { pub fn handle(this: *WidgetType, event: Event) ?Event { switch (event) { .resize => |size| { - log.debug("received size: .{{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows }); + log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{ + size.anchor.col, + size.anchor.row, + size.cols, + size.rows, + }); }, else => {}, } return this.vtable.handle(this, event); } - // Return the entire content of this `Widget`. - pub fn content(this: *WidgetType) ![]u8 { - return try this.vtable.content(this); + // Render the content of this `Widget` given the `Size` of the widget (.resize System`Event`). + pub fn render(this: *WidgetType, renderer: Renderer) !void { + try this.vtable.render(this, renderer); } pub fn deinit(this: *WidgetType) void { @@ -64,13 +71,13 @@ pub fn Widget(comptime Event: type) type { return widget.handle(event); } }.handle, - .content = struct { + .render = struct { // Return the entire content of this `Widget`. - fn content(this: *WidgetType) ![]u8 { + fn render(this: *WidgetType, renderer: Renderer) !void { const widget: @TypeOf(object) = @ptrFromInt(this.object); - return try widget.content(); + try widget.render(renderer); } - }.content, + }.render, .deinit = struct { fn deinit(this: *WidgetType) void { const widget: @TypeOf(object) = @ptrFromInt(this.object); @@ -82,7 +89,7 @@ pub fn Widget(comptime Event: type) type { } // TODO: import and export of `Widget` implementations (with corresponding initialization using `Event`) - pub const RawText = @import("widget/RawText.zig").Widget(Event); - pub const Spacer = @import("widget/Spacer.zig").Widget(Event); + pub const RawText = @import("widget/RawText.zig").Widget(Event, Renderer); + pub const Spacer = @import("widget/Spacer.zig").Widget(Event, Renderer); }; } diff --git a/src/widget/RawText.zig b/src/widget/RawText.zig index dcc714f..36f3ec2 100644 --- a/src/widget/RawText.zig +++ b/src/widget/RawText.zig @@ -8,7 +8,7 @@ const Style = terminal.Style; const log = std.log.scoped(.widget_rawtext); -pub fn Widget(comptime Event: type) type { +pub fn Widget(comptime Event: type, comptime Renderer: type) type { if (!isTaggedUnion(Event)) { @compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`."); } @@ -75,20 +75,23 @@ pub fn Widget(comptime Event: type) type { return null; } - pub fn content(this: *@This()) ![]u8 { + pub fn render(this: *@This(), renderer: Renderer) !void { + try terminal.setCursorPosition(this.size.anchor); + // TODO: render `this.contents` + _ = renderer; if (this.size.rows >= this.line_index.items.len) { - return this.contents.items; + _ = try terminal.write(this.contents.items); } else { // more rows than we can display const i = this.line_index.items[this.line]; const e = this.size.rows + this.line + 1; if (e >= this.line_index.items.len) { - return this.contents.items[i..]; + _ = try terminal.write(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; - return this.contents.items[i..x]; + _ = try terminal.write(this.contents.items[i..x]); } } }; diff --git a/src/widget/Spacer.zig b/src/widget/Spacer.zig index 888eaca..b347a24 100644 --- a/src/widget/Spacer.zig +++ b/src/widget/Spacer.zig @@ -6,7 +6,7 @@ const Error = @import("../event.zig").Error; const log = std.log.scoped(.widget_spacer); -pub fn Widget(comptime Event: type) type { +pub fn Widget(comptime Event: type, comptime Renderer: type) type { if (!isTaggedUnion(Event)) { @compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`."); } @@ -25,9 +25,10 @@ pub fn Widget(comptime Event: type) type { return null; } - pub fn content(this: *@This()) ![]u8 { + pub fn render(this: *@This(), renderer: Renderer) !void { + // FIX: this should rather clear the `terminal.Size` provided through the event system using the _renderer_. _ = this; - return &[0]u8{}; + _ = renderer; } }; }