mod(renderer): initial version of double buffer intermediate renderer
This branch will implement the necessary changes for the widgets and their implementations to use the new renderer correctly.
This commit is contained in:
@@ -3,7 +3,7 @@ const zterm = @import("zterm");
|
|||||||
|
|
||||||
const App = zterm.App(
|
const App = zterm.App(
|
||||||
union(enum) {},
|
union(enum) {},
|
||||||
zterm.Renderer.Direct,
|
zterm.Renderer.Buffered,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
const Key = zterm.Key;
|
const Key = zterm.Key;
|
||||||
@@ -26,7 +26,8 @@ pub fn main() !void {
|
|||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .{};
|
var app: App = .{};
|
||||||
var renderer: App.Renderer = .{};
|
var renderer = App.Renderer.init(allocator);
|
||||||
|
defer renderer.deinit();
|
||||||
// TODO: when not running fullscreen, the application needs to screen down accordingly to display the contents
|
// TODO: when not running fullscreen, the application needs to screen down accordingly to display the contents
|
||||||
// -> size hint how much should it use?
|
// -> size hint how much should it use?
|
||||||
|
|
||||||
@@ -75,17 +76,19 @@ pub fn main() !void {
|
|||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.layout = Layout.createFrom(Layout.VStack.init(allocator, .{
|
.layout = Layout.createFrom(Layout.VStack.init(allocator, .{
|
||||||
Widget.createFrom(blk: {
|
// Widget.createFrom(blk: {
|
||||||
const file = try std.fs.cwd().openFile("./examples/stack.zig", .{});
|
// const file = try std.fs.cwd().openFile("./examples/stack.zig", .{});
|
||||||
defer file.close();
|
// defer file.close();
|
||||||
break :blk Widget.RawText.init(allocator, file);
|
// break :blk Widget.RawText.init(allocator, file);
|
||||||
}),
|
// }),
|
||||||
Widget.createFrom(Widget.Spacer.init(allocator)),
|
Widget.createFrom(Widget.Spacer.init(allocator)),
|
||||||
Widget.createFrom(blk: {
|
Widget.createFrom(Widget.Spacer.init(allocator)),
|
||||||
const file = try std.fs.cwd().openFile("./examples/stack.zig", .{});
|
Widget.createFrom(Widget.Spacer.init(allocator)),
|
||||||
defer file.close();
|
// Widget.createFrom(blk: {
|
||||||
break :blk Widget.RawText.init(allocator, file);
|
// const file = try std.fs.cwd().openFile("./examples/stack.zig", .{});
|
||||||
}),
|
// defer file.close();
|
||||||
|
// break :blk Widget.RawText.init(allocator, file);
|
||||||
|
// }),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const App = zterm.App(
|
|||||||
tui, // view instance to the corresponding view for 'tui'
|
tui, // view instance to the corresponding view for 'tui'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
zterm.Renderer.Direct,
|
zterm.Renderer.Buffered,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
const Cell = zterm.Cell;
|
const Cell = zterm.Cell;
|
||||||
@@ -24,49 +24,26 @@ const Tui = struct {
|
|||||||
pub fn init(allocator: std.mem.Allocator) *Tui {
|
pub fn init(allocator: std.mem.Allocator) *Tui {
|
||||||
var tui = allocator.create(Tui) catch @panic("Out of memory: tui.zig");
|
var tui = allocator.create(Tui) catch @panic("Out of memory: tui.zig");
|
||||||
tui.allocator = allocator;
|
tui.allocator = allocator;
|
||||||
// FIXME: the layout creates an 'incorrect alignment'?
|
tui.layout = Layout.createFrom(Layout.VStack.init(allocator, .{
|
||||||
tui.layout = Layout.createFrom(Layout.VContainer.init(allocator, .{
|
Layout.createFrom(Layout.HStack.init(allocator, .{
|
||||||
.{
|
Widget.createFrom(Widget.Text.init(allocator, .left, &[1]Cell{
|
||||||
Layout.createFrom(Layout.Framing.init(allocator, .{
|
.{ .rune = 'Y', .style = .{ .fg = .{ .index = 6 }, .bold = true } },
|
||||||
.title = .{
|
|
||||||
.str = "Welcome to my terminal website",
|
|
||||||
.style = .{
|
|
||||||
.ul = .{ .index = 6 },
|
|
||||||
.ul_style = .single,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, .{
|
|
||||||
.layout = Layout.createFrom(Layout.HContainer.init(allocator, .{
|
|
||||||
.{
|
|
||||||
Widget.createFrom(Widget.Text.init(allocator, .left, &[1]Cell{
|
|
||||||
.{ .content = "Yves Biener", .style = .{ .bold = true } },
|
|
||||||
})),
|
|
||||||
25,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
Widget.createFrom(Widget.Text.init(allocator, .left, &[1]Cell{
|
|
||||||
.{ .content = "File name", .style = .{ .bold = true } },
|
|
||||||
})),
|
|
||||||
50,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
Widget.createFrom(Widget.Text.init(allocator, .left, &[1]Cell{
|
|
||||||
.{ .content = "Contacts", .style = .{ .bold = true } },
|
|
||||||
})),
|
|
||||||
25,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
})),
|
})),
|
||||||
10,
|
Widget.createFrom(Widget.Text.init(allocator, .left, &[1]Cell{
|
||||||
},
|
.{ .rune = 'F', .style = .{ .fg = .{ .index = 6 }, .bold = true } },
|
||||||
.{
|
|
||||||
Layout.createFrom(Layout.Margin.init(allocator, .{ .left = 15, .right = 15 }, .{
|
|
||||||
.widget = Widget.createFrom(Widget.Text.init(allocator, .default, &[1]Cell{
|
|
||||||
.{ .content = "Does this change anything", .style = .{ .ul = .default, .ul_style = .single } },
|
|
||||||
})),
|
|
||||||
})),
|
})),
|
||||||
90,
|
Widget.createFrom(Widget.Text.init(allocator, .left, &[1]Cell{
|
||||||
},
|
.{ .rune = 'C', .style = .{ .fg = .{ .index = 6 }, .bold = true } },
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
// .{
|
||||||
|
// Layout.createFrom(Layout.Margin.init(allocator, .{ .left = 15, .right = 15 }, .{
|
||||||
|
// .widget = Widget.createFrom(Widget.Text.init(allocator, .default, &[1]Cell{
|
||||||
|
// .{ .rune = 'D', .style = .{ .ul = .default, .ul_style = .single } },
|
||||||
|
// })),
|
||||||
|
// })),
|
||||||
|
// 90,
|
||||||
|
// },
|
||||||
}));
|
}));
|
||||||
return tui;
|
return tui;
|
||||||
}
|
}
|
||||||
@@ -76,6 +53,14 @@ const Tui = struct {
|
|||||||
this.allocator.destroy(this);
|
this.allocator.destroy(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn enable(this: *Tui) void {
|
||||||
|
_ = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable(this: *Tui) void {
|
||||||
|
_ = this;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle(this: *Tui, event: App.Event) !*Events {
|
pub fn handle(this: *Tui, event: App.Event) !*Events {
|
||||||
return try this.layout.handle(event);
|
return try this.layout.handle(event);
|
||||||
}
|
}
|
||||||
@@ -99,7 +84,9 @@ pub fn main() !void {
|
|||||||
const allocator = arena.allocator();
|
const allocator = arena.allocator();
|
||||||
|
|
||||||
var app: App = .{};
|
var app: App = .{};
|
||||||
var renderer: App.Renderer = .{};
|
var renderer = App.Renderer.init(allocator);
|
||||||
|
defer renderer.deinit();
|
||||||
|
|
||||||
var view: View = undefined;
|
var view: View = undefined;
|
||||||
|
|
||||||
var tui_view = View.createFrom(Tui.init(allocator));
|
var tui_view = View.createFrom(Tui.init(allocator));
|
||||||
@@ -147,5 +134,6 @@ pub fn main() !void {
|
|||||||
app.postEvent(e);
|
app.postEvent(e);
|
||||||
}
|
}
|
||||||
try view.render(&renderer);
|
try view.render(&renderer);
|
||||||
|
try renderer.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: t
|
|||||||
return &this.events;
|
return &this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
const round_frame = .{ "╭", "─", "╮", "│", "╰", "╯" };
|
const round_frame: [6][]const u8 = .{ "╭", "─", "╮", "│", "╰", "╯" };
|
||||||
const square_frame = .{ "┌", "─", "┐", "│", "└", "┘" };
|
const square_frame: [6][]const u8 = .{ "┌", "─", "┐", "│", "└", "┘" };
|
||||||
|
|
||||||
fn renderFrame(this: *@This(), renderer: *Renderer) !void {
|
fn renderFrame(this: *@This(), renderer: *Renderer) !void {
|
||||||
// FIXME: use renderer instead!
|
// FIXME: use renderer instead!
|
||||||
@@ -140,14 +140,14 @@ pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: t
|
|||||||
// render top: +---+
|
// render top: +---+
|
||||||
try terminal.setCursorPosition(this.size.anchor);
|
try terminal.setCursorPosition(this.size.anchor);
|
||||||
const writer = terminal.writer();
|
const writer = terminal.writer();
|
||||||
try this.config.style.value(writer, frame[0]);
|
// try this.config.style.value(writer, frame[0]);
|
||||||
if (this.config.title.str.len > 0) {
|
for (0..this.config.title.str.len) |i| {
|
||||||
try this.config.title.style.value(writer, this.config.title.str);
|
try this.config.title.style.value(writer, this.config.title.str[i]);
|
||||||
}
|
}
|
||||||
for (0..this.size.cols -| 2 -| this.config.title.str.len) |_| {
|
for (0..this.size.cols -| 2 -| this.config.title.str.len) |_| {
|
||||||
try this.config.style.value(writer, frame[1]);
|
// try this.config.style.value(writer, frame[1]);
|
||||||
}
|
}
|
||||||
try this.config.style.value(writer, frame[2]);
|
// try this.config.style.value(writer, frame[2]);
|
||||||
// render left: |
|
// render left: |
|
||||||
for (1..this.size.rows -| 1) |r| {
|
for (1..this.size.rows -| 1) |r| {
|
||||||
const row: u16 = @truncate(r);
|
const row: u16 = @truncate(r);
|
||||||
@@ -155,7 +155,7 @@ pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: t
|
|||||||
.col = this.size.anchor.col,
|
.col = this.size.anchor.col,
|
||||||
.row = this.size.anchor.row + row,
|
.row = this.size.anchor.row + row,
|
||||||
});
|
});
|
||||||
try this.config.style.value(writer, frame[3]);
|
// try this.config.style.value(writer, frame[3]);
|
||||||
}
|
}
|
||||||
// render right: |
|
// render right: |
|
||||||
for (1..this.size.rows -| 1) |r| {
|
for (1..this.size.rows -| 1) |r| {
|
||||||
@@ -164,18 +164,18 @@ pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: t
|
|||||||
.col = this.size.anchor.col + this.size.cols -| 1,
|
.col = this.size.anchor.col + this.size.cols -| 1,
|
||||||
.row = this.size.anchor.row + row,
|
.row = this.size.anchor.row + row,
|
||||||
});
|
});
|
||||||
try this.config.style.value(writer, frame[3]);
|
// try this.config.style.value(writer, frame[3]);
|
||||||
}
|
}
|
||||||
// render bottom: +---+
|
// render bottom: +---+
|
||||||
try terminal.setCursorPosition(.{
|
try terminal.setCursorPosition(.{
|
||||||
.col = this.size.anchor.col,
|
.col = this.size.anchor.col,
|
||||||
.row = this.size.anchor.row + this.size.rows - 1,
|
.row = this.size.anchor.row + this.size.rows - 1,
|
||||||
});
|
});
|
||||||
try this.config.style.value(writer, frame[4]);
|
// try this.config.style.value(writer, frame[4]);
|
||||||
for (0..this.size.cols -| 2) |_| {
|
for (0..this.size.cols -| 2) |_| {
|
||||||
try this.config.style.value(writer, frame[1]);
|
// try this.config.style.value(writer, frame[1]);
|
||||||
}
|
}
|
||||||
try this.config.style.value(writer, frame[5]);
|
// try this.config.style.value(writer, frame[5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(this: *@This(), renderer: *Renderer) !void {
|
pub fn render(this: *@This(), renderer: *Renderer) !void {
|
||||||
|
|||||||
168
src/render.zig
168
src/render.zig
@@ -10,97 +10,107 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const terminal = @import("terminal.zig");
|
const terminal = @import("terminal.zig");
|
||||||
|
|
||||||
const Cells = []const terminal.Cell;
|
const Cell = terminal.Cell;
|
||||||
const Position = terminal.Position;
|
const Position = terminal.Position;
|
||||||
const Size = terminal.Size;
|
const Size = terminal.Size;
|
||||||
|
|
||||||
pub fn Direct(comptime fullscreen: bool) type {
|
/// Double-buffered intermediate rendering pipeline
|
||||||
const log = std.log.scoped(.renderer_direct);
|
pub fn Buffered(comptime fullscreen: bool) type {
|
||||||
_ = log;
|
const log = std.log.scoped(.renderer_buffered);
|
||||||
|
// _ = log;
|
||||||
_ = fullscreen;
|
_ = fullscreen;
|
||||||
return struct {
|
return struct {
|
||||||
size: Size = undefined,
|
allocator: std.mem.Allocator,
|
||||||
|
created: bool,
|
||||||
|
size: Size,
|
||||||
|
screen: []Cell,
|
||||||
|
virtual_screen: []Cell,
|
||||||
|
|
||||||
pub fn resize(this: *@This(), size: Size) void {
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
this.size = size;
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.created = false,
|
||||||
|
.size = undefined,
|
||||||
|
.screen = undefined,
|
||||||
|
.virtual_screen = undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(this: *@This(), size: Size) !void {
|
pub fn deinit(this: *@This()) void {
|
||||||
_ = this;
|
if (this.created) {
|
||||||
// NOTE: clear on the entire screen may introduce too much overhead and could instead clear the entire screen instead.
|
this.allocator.free(this.screen);
|
||||||
// - it could also then try to optimize for further *clear* calls, that result in pretty much a nop? -> how to identify those clear calls?
|
this.allocator.free(this.virtual_screen);
|
||||||
// TODO: this should instead by dynamic and correct of size (terminal could be too large currently)
|
|
||||||
std.debug.assert(1028 > size.cols);
|
|
||||||
var buf: [1028]u8 = undefined;
|
|
||||||
@memset(buf[0..], ' ');
|
|
||||||
for (0..size.rows) |r| {
|
|
||||||
const row: u16 = @truncate(r);
|
|
||||||
try terminal.setCursorPosition(.{
|
|
||||||
.col = size.anchor.col,
|
|
||||||
.row = size.anchor.row + row,
|
|
||||||
});
|
|
||||||
_ = try terminal.write(buf[0..size.cols]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(this: *@This(), size: Size, cells: Cells) !void {
|
pub fn resize(this: *@This(), size: Size) void {
|
||||||
_ = this;
|
this.size = size;
|
||||||
try terminal.setCursorPosition(size.anchor);
|
if (!this.created) {
|
||||||
var row: u16 = 0;
|
this.screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
||||||
var remaining_cols = size.cols;
|
this.virtual_screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
||||||
|
this.created = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.allocator.resize(this.screen, size.cols * size.rows)) {
|
||||||
|
@panic("render.zig: Could not resize `screen` buffer");
|
||||||
|
}
|
||||||
|
if (this.allocator.resize(this.virtual_screen, size.cols * size.rows)) {
|
||||||
|
@panic("render.zig: Could not resize `virtual screen` buffer.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(this: *@This(), size: Size) !void {
|
||||||
|
log.debug("renderer::clear", .{});
|
||||||
|
var vs = this.virtual_screen;
|
||||||
|
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
||||||
|
for (0..size.rows) |row| {
|
||||||
|
for (0..size.cols) |col| {
|
||||||
|
vs[anchor + (row * this.size.cols) + col].reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
||||||
|
pub fn render(this: *@This(), size: Size, cells: []const Cell) !void {
|
||||||
|
log.debug("renderer:render: cells: {any}", .{cells});
|
||||||
|
std.debug.assert(cells.len > 0);
|
||||||
|
|
||||||
|
var idx: usize = 0;
|
||||||
|
var vs = this.virtual_screen;
|
||||||
|
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
||||||
|
|
||||||
|
for (0..size.rows) |row| {
|
||||||
|
for (0..size.cols) |col| {
|
||||||
|
const cell = cells[idx];
|
||||||
|
idx += 1;
|
||||||
|
|
||||||
|
vs[anchor + (row * this.size.cols) + col].style = cell.style;
|
||||||
|
vs[anchor + (row * this.size.cols) + col].rune = cell.rune;
|
||||||
|
|
||||||
|
if (cells.len == idx) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write *virtual screen* to alternate screen (should be called once and last during each render loop iteration in the main loop).
|
||||||
|
pub fn flush(this: *@This()) !void {
|
||||||
|
log.debug("renderer::flush", .{});
|
||||||
const writer = terminal.writer();
|
const writer = terminal.writer();
|
||||||
for (cells) |cell| {
|
const s = this.screen;
|
||||||
var idx: usize = 0;
|
const vs = this.virtual_screen;
|
||||||
print_cell: while (true) {
|
for (0..this.size.rows) |row| {
|
||||||
const cell_len = cell.len(idx);
|
for (0..this.size.cols) |col| {
|
||||||
if (cell_len > remaining_cols) {
|
const idx = (row * this.size.cols) + col;
|
||||||
const result = try cell.writeUpToNewline(writer, idx, idx + remaining_cols);
|
const cs = s[idx];
|
||||||
row += 1;
|
const cvs = vs[idx];
|
||||||
if (row >= size.rows) {
|
if (cs.eql(cvs))
|
||||||
return; // we are done
|
continue;
|
||||||
}
|
// render differences found in virtual screen
|
||||||
try terminal.setCursorPosition(.{
|
// TODO: improve the writing speed (many unecessary writes (i.e. the style for every character..))
|
||||||
.col = size.anchor.col,
|
try terminal.setCursorPosition(.{ .row = @truncate(row), .col = @truncate(col) });
|
||||||
.row = size.anchor.row + row,
|
try cvs.value(writer);
|
||||||
});
|
// update screen to be the virtual screen for the next frame
|
||||||
remaining_cols = size.cols;
|
s[idx] = vs[idx];
|
||||||
idx = result.idx;
|
|
||||||
if (result.newline) {
|
|
||||||
idx += 1; // skip over newline
|
|
||||||
} else {
|
|
||||||
// there is still content to the newline (which will not be printed)
|
|
||||||
for (idx..cell.content.len) |i| {
|
|
||||||
if (cell.content[i] == '\n') {
|
|
||||||
idx = i + 1;
|
|
||||||
continue :print_cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break; // go to next cell (as we went to the end of the cell and do not print on the next line)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// print rest of cell
|
|
||||||
const result = try cell.writeUpToNewline(writer, idx, idx + cell_len);
|
|
||||||
if (result.newline) {
|
|
||||||
row += 1;
|
|
||||||
if (row >= size.rows) {
|
|
||||||
return; // we are done
|
|
||||||
}
|
|
||||||
try terminal.setCursorPosition(.{
|
|
||||||
.col = size.anchor.col,
|
|
||||||
.row = size.anchor.row + row,
|
|
||||||
});
|
|
||||||
remaining_cols = size.cols;
|
|
||||||
idx = result.idx + 1; // skip over newline
|
|
||||||
} else {
|
|
||||||
remaining_cols -= @truncate(cell_len -| idx);
|
|
||||||
idx = 0;
|
|
||||||
break; // go to next cell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// written all cell contents
|
|
||||||
if (idx >= cell.content.len) {
|
|
||||||
break; // go to next cell
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub const ReportMode = enum {
|
|||||||
pub fn getTerminalSize() Size {
|
pub fn getTerminalSize() Size {
|
||||||
var ws: std.posix.winsize = undefined;
|
var ws: std.posix.winsize = undefined;
|
||||||
_ = std.posix.system.ioctl(std.posix.STDIN_FILENO, std.posix.T.IOCGWINSZ, @intFromPtr(&ws));
|
_ = std.posix.system.ioctl(std.posix.STDIN_FILENO, std.posix.T.IOCGWINSZ, @intFromPtr(&ws));
|
||||||
return .{ .cols = ws.col, .rows = ws.row };
|
return .{ .cols = ws.col - 1, .rows = ws.row - 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn saveScreen() !void {
|
pub fn saveScreen() !void {
|
||||||
@@ -126,8 +126,8 @@ pub fn getCursorPosition() !Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.row = try std.fmt.parseInt(u16, row[0..ridx], 10),
|
.row = try std.fmt.parseInt(u16, row[0..ridx], 10) - 1,
|
||||||
.col = try std.fmt.parseInt(u16, col[0..cidx], 10),
|
.col = try std.fmt.parseInt(u16, col[0..cidx], 10) - 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,60 +2,24 @@ const std = @import("std");
|
|||||||
pub const Style = @import("Style.zig");
|
pub const Style = @import("Style.zig");
|
||||||
|
|
||||||
style: Style = .{},
|
style: Style = .{},
|
||||||
content: []const u8 = undefined,
|
rune: u8 = ' ',
|
||||||
|
|
||||||
pub const Result = struct {
|
pub const Character = struct {
|
||||||
idx: usize,
|
grapheme: []const u8,
|
||||||
newline: bool,
|
width: u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn len(this: @This(), start: usize) usize {
|
pub fn eql(this: @This(), other: @This()) bool {
|
||||||
std.debug.assert(this.content.len > start);
|
return this.rune == other.rune and this.style.eql(other.style);
|
||||||
return this.content[start..].len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(this: @This(), writer: anytype, start: usize, end: usize) !void {
|
pub fn reset(this: *@This()) void {
|
||||||
std.debug.assert(this.content.len > start);
|
this.style = .{ .fg = .default, .bg = .default, .ul = .default, .ul_style = .off };
|
||||||
std.debug.assert(this.content.len >= end);
|
this.rune = ' ';
|
||||||
std.debug.assert(start < end);
|
|
||||||
|
|
||||||
try this.style.value(writer, this.content[start..end]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeUpToNewline(this: @This(), writer: anytype, start: usize, end: usize) !Result {
|
pub fn value(this: @This(), writer: anytype) !void {
|
||||||
std.debug.assert(this.content.len > start);
|
try this.style.value(writer, this.rune);
|
||||||
std.debug.assert(this.content.len >= end);
|
|
||||||
std.debug.assert(start < end);
|
|
||||||
|
|
||||||
for (start..end) |i| {
|
|
||||||
if (this.content[i] == '\n') {
|
|
||||||
if (start < i) {
|
|
||||||
// this is just an empty line with a newline
|
|
||||||
try this.value(writer, start, i);
|
|
||||||
}
|
|
||||||
return .{
|
|
||||||
.idx = i,
|
|
||||||
.newline = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try this.write(writer, start, end);
|
|
||||||
return .{
|
|
||||||
.idx = end,
|
|
||||||
.newline = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value(this: @This(), writer: anytype, start: usize, end: usize) !void {
|
|
||||||
std.debug.assert(start < this.content.len);
|
|
||||||
std.debug.assert(this.content.len >= end);
|
|
||||||
std.debug.assert(start < end);
|
|
||||||
try this.style.value(writer, this.content[start..end]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// not really supported
|
|
||||||
pub fn format(this: @This(), writer: anytype, comptime fmt: []const u8, args: anytype) !void {
|
|
||||||
try this.style.format(writer, fmt, args); // NOTE: args should contain this.content[start..end] or this.content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const Position = @import("Position.zig");
|
const Position = @import("Position.zig");
|
||||||
|
|
||||||
anchor: Position = .{ .col = 1, .row = 1 }, // top left corner by default
|
anchor: Position = .{ .col = 0, .row = 0 }, // top left corner by default
|
||||||
cols: u16,
|
cols: u16,
|
||||||
rows: u16,
|
rows: u16,
|
||||||
|
|||||||
@@ -113,6 +113,20 @@ reverse: bool = false,
|
|||||||
invisible: bool = false,
|
invisible: bool = false,
|
||||||
strikethrough: bool = false,
|
strikethrough: bool = false,
|
||||||
|
|
||||||
|
pub fn eql(this: @This(), other: @This()) bool {
|
||||||
|
return this.fg.eql(other.fg) and
|
||||||
|
this.bg.eql(other.bg) and
|
||||||
|
this.ul.eql(other.ul) and
|
||||||
|
other.ul_style == this.ul_style and
|
||||||
|
other.bold == this.bold and
|
||||||
|
other.dim == this.dim and
|
||||||
|
other.italic == this.italic and
|
||||||
|
other.blink == this.blink and
|
||||||
|
other.reverse == this.reverse and
|
||||||
|
other.invisible == this.invisible and
|
||||||
|
other.strikethrough == this.strikethrough;
|
||||||
|
}
|
||||||
|
|
||||||
/// Merge _other_ `Style` to _this_ style and overwrite _this_ `Style`'s value
|
/// Merge _other_ `Style` to _this_ style and overwrite _this_ `Style`'s value
|
||||||
/// if the _other_ value differs from the default value.
|
/// if the _other_ value differs from the default value.
|
||||||
pub fn merge(this: *@This(), other: @This()) void {
|
pub fn merge(this: *@This(), other: @This()) void {
|
||||||
@@ -284,15 +298,9 @@ fn end(this: @This(), writer: anytype) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(this: @This(), writer: anytype, comptime content: []const u8, args: anytype) !void {
|
pub fn value(this: @This(), writer: anytype, content: u8) !void {
|
||||||
try this.start(writer);
|
try this.start(writer);
|
||||||
try std.fmt.format(writer, content, args);
|
_ = try writer.write(&[_]u8{content});
|
||||||
try this.end(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value(this: @This(), writer: anytype, content: []const u8) !void {
|
|
||||||
try this.start(writer);
|
|
||||||
_ = try writer.write(content);
|
|
||||||
try this.end(writer);
|
try this.end(writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,10 +62,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
switch (this.alignment) {
|
switch (this.alignment) {
|
||||||
.default => break :blk this.size,
|
.default => break :blk this.size,
|
||||||
.center => {
|
.center => {
|
||||||
var length_usize: usize = 0;
|
const length_usize = this.contents.len;
|
||||||
for (this.contents) |content| {
|
|
||||||
length_usize += content.content.len;
|
|
||||||
}
|
|
||||||
const length: u16 = @truncate(length_usize);
|
const length: u16 = @truncate(length_usize);
|
||||||
const cols = @min(length, this.size.cols);
|
const cols = @min(length, this.size.cols);
|
||||||
const rows = cols / length;
|
const rows = cols / length;
|
||||||
@@ -79,10 +76,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
.top => {
|
.top => {
|
||||||
var length_usize: usize = 0;
|
const length_usize = this.contents.len;
|
||||||
for (this.contents) |content| {
|
|
||||||
length_usize += content.content.len;
|
|
||||||
}
|
|
||||||
const length: u16 = @truncate(length_usize);
|
const length: u16 = @truncate(length_usize);
|
||||||
const cols = @min(length, this.size.cols);
|
const cols = @min(length, this.size.cols);
|
||||||
const rows = cols / length;
|
const rows = cols / length;
|
||||||
@@ -96,10 +90,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
.bottom => {
|
.bottom => {
|
||||||
var length_usize: usize = 0;
|
const length_usize = this.contents.len;
|
||||||
for (this.contents) |content| {
|
|
||||||
length_usize += content.content.len;
|
|
||||||
}
|
|
||||||
const length: u16 = @truncate(length_usize);
|
const length: u16 = @truncate(length_usize);
|
||||||
const cols = @min(length, this.size.cols);
|
const cols = @min(length, this.size.cols);
|
||||||
const rows = cols / length;
|
const rows = cols / length;
|
||||||
@@ -113,10 +104,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
.left => {
|
.left => {
|
||||||
var length_usize: usize = 0;
|
const length_usize = this.contents.len;
|
||||||
for (this.contents) |content| {
|
|
||||||
length_usize += content.content.len;
|
|
||||||
}
|
|
||||||
const length: u16 = @truncate(length_usize);
|
const length: u16 = @truncate(length_usize);
|
||||||
const cols = @min(length, this.size.cols);
|
const cols = @min(length, this.size.cols);
|
||||||
const rows = cols / length;
|
const rows = cols / length;
|
||||||
@@ -130,10 +118,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
.right => {
|
.right => {
|
||||||
var length_usize: usize = 0;
|
const length_usize = this.contents.len;
|
||||||
for (this.contents) |content| {
|
|
||||||
length_usize += content.content.len;
|
|
||||||
}
|
|
||||||
const length: u16 = @truncate(length_usize);
|
const length: u16 = @truncate(length_usize);
|
||||||
const cols = @min(length, this.size.cols);
|
const cols = @min(length, this.size.cols);
|
||||||
const rows = cols / length;
|
const rows = cols / length;
|
||||||
@@ -148,6 +133,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
log.debug("Text.contents: {any}", .{this.contents});
|
||||||
try renderer.render(size, this.contents);
|
try renderer.render(size, this.contents);
|
||||||
this.require_render = false;
|
this.require_render = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user