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