a modern tui library written in zig
1//! A View is effectively an "oversized" Window that can be written to and rendered in pieces.
2
3const std = @import("std");
4const mem = std.mem;
5
6const View = @This();
7
8const gw = @import("../gwidth.zig");
9
10const Screen = @import("../Screen.zig");
11const Window = @import("../Window.zig");
12const Unicode = @import("../Unicode.zig");
13const Cell = @import("../Cell.zig");
14
15/// View Allocator
16alloc: mem.Allocator,
17
18/// Underlying Screen
19screen: Screen,
20
21/// View Initialization Config
22pub const Config = struct {
23 width: usize,
24 height: usize,
25};
26
27/// Initialize a new View
28pub fn init(alloc: mem.Allocator, unicode: *const Unicode, config: Config) mem.Allocator.Error!View {
29 const screen = try Screen.init(
30 alloc,
31 .{
32 .cols = config.width,
33 .rows = config.height,
34 .x_pixel = 0,
35 .y_pixel = 0,
36 },
37 unicode,
38 );
39 return .{
40 .alloc = alloc,
41 .screen = screen,
42 };
43}
44
45pub fn window(self: *View) Window {
46 return .{
47 .x_off = 0,
48 .y_off = 0,
49 .width = self.screen.width,
50 .height = self.screen.height,
51 .screen = &self.screen,
52 };
53}
54
55/// Deinitialize this View
56pub fn deinit(self: *View) void {
57 self.screen.deinit(self.alloc);
58}
59
60pub const DrawOptions = struct {
61 x_off: usize = 0,
62 y_off: usize = 0,
63};
64
65pub fn draw(self: *View, win: Window, opts: DrawOptions) void {
66 if (opts.x_off >= self.screen.width) return;
67 if (opts.y_off >= self.screen.height) return;
68
69 const width = @min(win.width, self.screen.width - opts.x_off);
70 const height = @min(win.height, self.screen.height - opts.y_off);
71
72 for (0..height) |row| {
73 const src_start = opts.x_off + ((row + opts.y_off) * self.screen.width);
74 const src_end = src_start + width;
75 const dst_start = win.x_off + ((row + win.y_off) * win.screen.width);
76 const dst_end = dst_start + width;
77 @memcpy(win.screen.buf[dst_start..dst_end], self.screen.buf[src_start..src_end]);
78 }
79}
80
81/// Render Config for `toWin()`
82pub const RenderConfig = struct {
83 x: usize = 0,
84 y: usize = 0,
85 width: Extent = .fit,
86 height: Extent = .fit,
87
88 pub const Extent = union(enum) {
89 fit,
90 max: usize,
91 };
92};
93
94/// Render a portion of this View to the provided Window (`win`).
95/// This will return the bounded X (col), Y (row) coordinates based on the rendering.
96pub fn toWin(self: *View, win: Window, config: RenderConfig) !struct { usize, usize } {
97 var x = @min(self.screen.width - 1, config.x);
98 var y = @min(self.screen.height - 1, config.y);
99 const width = width: {
100 var width = switch (config.width) {
101 .fit => win.width,
102 .max => |w| @min(win.width, w),
103 };
104 width = @min(width, self.screen.width);
105 break :width @min(width, self.screen.width -| 1 -| x +| win.width);
106 };
107 const height = height: {
108 var height = switch (config.height) {
109 .fit => win.height,
110 .max => |h| @min(win.height, h),
111 };
112 height = @min(height, self.screen.height);
113 break :height @min(height, self.screen.height -| 1 -| y +| win.height);
114 };
115 x = @min(x, self.screen.width -| width);
116 y = @min(y, self.screen.height -| height);
117 const child = win.child(.{
118 .width = .{ .limit = width },
119 .height = .{ .limit = height },
120 });
121 self.draw(child, .{ .x_off = x, .y_off = y });
122 return .{ x, y };
123}
124
125/// Writes a cell to the location in the View
126pub fn writeCell(self: *View, col: usize, row: usize, cell: Cell) void {
127 self.screen.writeCell(col, row, cell);
128}
129
130/// Reads a cell at the location in the View
131pub fn readCell(self: *const View, col: usize, row: usize) ?Cell {
132 return self.screen.readCell(col, row);
133}
134
135/// Fills the View with the default cell
136pub fn clear(self: View) void {
137 self.fill(.{ .default = true });
138}
139
140/// Returns the width of the grapheme. This depends on the terminal capabilities
141pub fn gwidth(self: View, str: []const u8) usize {
142 return gw.gwidth(str, self.screen.width_method, &self.screen.unicode.width_data);
143}
144
145/// Fills the View with the provided cell
146pub fn fill(self: View, cell: Cell) void {
147 @memset(self.screen.buf, cell);
148}
149
150/// Prints segments to the View. Returns true if the text overflowed with the
151/// given wrap strategy and size.
152pub fn print(self: *View, segments: []const Cell.Segment, opts: Window.PrintOptions) !Window.PrintResult {
153 return self.window().print(segments, opts);
154}
155
156/// Print a single segment. This is just a shortcut for print(&.{segment}, opts)
157pub fn printSegment(self: *View, segment: Cell.Segment, opts: Window.PrintOptions) !Window.PrintResult {
158 return self.print(&.{segment}, opts);
159}