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