a modern tui library written in zig
at v0.5.0 4.8 kB view raw
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}