a modern tui library written in zig
at v0.4.0 5.7 kB view raw
1const std = @import("std"); 2const fmt = std.fmt; 3const math = std.math; 4const base64 = std.base64.standard.Encoder; 5const zigimg = @import("zigimg"); 6 7const Window = @import("Window.zig"); 8 9const Image = @This(); 10 11const transmit_opener = "\x1b_Gf=32,i={d},s={d},v={d},m={d};"; 12 13pub const Source = union(enum) { 14 path: []const u8, 15 mem: []const u8, 16}; 17 18pub const TransmitFormat = enum { 19 rgb, 20 rgba, 21 png, 22}; 23 24pub const Placement = struct { 25 img_id: u32, 26 options: Image.DrawOptions, 27}; 28 29pub const CellSize = struct { 30 rows: usize, 31 cols: usize, 32}; 33 34pub const DrawOptions = struct { 35 /// an offset into the top left cell, in pixels, with where to place the 36 /// origin of the image. These must be less than the pixel size of a single 37 /// cell 38 pixel_offset: ?struct { 39 x: usize, 40 y: usize, 41 } = null, 42 /// the vertical stacking order 43 /// < 0: Drawn beneath text 44 /// < -1_073_741_824: Drawn beneath "default" background cells 45 z_index: ?i32 = null, 46 /// A clip region of the source image to draw. 47 clip_region: ?struct { 48 x: ?usize = null, 49 y: ?usize = null, 50 width: ?usize = null, 51 height: ?usize = null, 52 } = null, 53 /// Scaling to apply to the Image 54 scale: enum { 55 /// no scaling applied. the image may extend beyond the window 56 none, 57 /// Stretch / shrink the image to fill the window 58 fill, 59 /// Scale the image to fit the window, maintaining aspect ratio 60 fit, 61 /// Scale the image to fit the window, only if needed. 62 contain, 63 } = .none, 64 /// the size to render the image. Generally you will not need to use this 65 /// field, and should prefer to use scale. `draw` will fill in this field with 66 /// the correct values if a scale method is applied. 67 size: ?struct { 68 rows: ?usize = null, 69 cols: ?usize = null, 70 } = null, 71}; 72 73/// unique identifier for this image. This will be managed by the screen. 74id: u32, 75 76/// width in pixels 77width: usize, 78/// height in pixels 79height: usize, 80 81pub fn draw(self: Image, win: Window, opts: DrawOptions) !void { 82 var p_opts = opts; 83 switch (opts.scale) { 84 .none => {}, 85 .fill => { 86 p_opts.size = .{ 87 .rows = win.height, 88 .cols = win.width, 89 }; 90 }, 91 .fit, 92 .contain, 93 => contain: { 94 // cell geometry 95 const x_pix = win.screen.width_pix; 96 const y_pix = win.screen.height_pix; 97 const w = win.screen.width; 98 const h = win.screen.height; 99 100 const pix_per_col = try std.math.divCeil(usize, x_pix, w); 101 const pix_per_row = try std.math.divCeil(usize, y_pix, h); 102 103 const win_width_pix = pix_per_col * win.width; 104 const win_height_pix = pix_per_row * win.height; 105 106 const fit_x: bool = if (win_width_pix >= self.width) true else false; 107 const fit_y: bool = if (win_height_pix >= self.height) true else false; 108 109 // Does the image fit with no scaling? 110 if (opts.scale == .contain and fit_x and fit_y) break :contain; 111 112 // Does the image require vertical scaling? 113 if (fit_x and !fit_y) 114 p_opts.size = .{ 115 .rows = win.height, 116 } 117 118 // Does the image require horizontal scaling? 119 else if (!fit_x and fit_y) 120 p_opts.size = .{ 121 .cols = win.width, 122 } 123 else if (!fit_x and !fit_y) { 124 const diff_x = self.width - win_width_pix; 125 const diff_y = self.height - win_height_pix; 126 // The width difference is larger than the height difference. 127 // Scale by width 128 if (diff_x > diff_y) 129 p_opts.size = .{ 130 .cols = win.width, 131 } 132 else 133 // The height difference is larger than the width difference. 134 // Scale by height 135 p_opts.size = .{ 136 .rows = win.height, 137 }; 138 } else { 139 std.debug.assert(opts.scale == .fit); 140 std.debug.assert(win_width_pix >= self.width); 141 std.debug.assert(win_height_pix >= self.height); 142 143 // Fits in both directions. Find the closer direction 144 const diff_x = win_width_pix - self.width; 145 const diff_y = win_height_pix - self.height; 146 // The width is closer in dimension. Scale by that 147 if (diff_x < diff_y) 148 p_opts.size = .{ 149 .cols = win.width, 150 } 151 else 152 p_opts.size = .{ 153 .rows = win.height, 154 }; 155 } 156 }, 157 } 158 const p = Placement{ 159 .img_id = self.id, 160 .options = p_opts, 161 }; 162 win.writeCell(0, 0, .{ .image = p }); 163} 164 165/// the size of the image, in cells 166pub fn cellSize(self: Image, win: Window) !CellSize { 167 // cell geometry 168 const x_pix = win.screen.width_pix; 169 const y_pix = win.screen.height_pix; 170 const w = win.screen.width; 171 const h = win.screen.height; 172 173 const pix_per_col = try std.math.divCeil(usize, x_pix, w); 174 const pix_per_row = try std.math.divCeil(usize, y_pix, h); 175 176 const cell_width = std.math.divCeil(usize, self.width, pix_per_col) catch 0; 177 const cell_height = std.math.divCeil(usize, self.height, pix_per_row) catch 0; 178 return .{ 179 .rows = cell_height, 180 .cols = cell_width, 181 }; 182}