a modern tui library written in zig
at main 4.7 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: 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}