52554a17
Render terminal cursor
a73x 2026-04-08 11:44
Commit message
src/font.zig
| Old | New | ||
|---|---|---|---|
| @@ -166,14 +166,15 @@ pub const Atlas = struct { | |||
| 166 | pub fn init(alloc: std.mem.Allocator, width: u32, height: u32) !Atlas { | 166 | pub fn init(alloc: std.mem.Allocator, width: u32, height: u32) !Atlas { |
| 167 | const pixels = try alloc.alloc(u8, @as(usize, width) * @as(usize, height)); | 167 | const pixels = try alloc.alloc(u8, @as(usize, width) * @as(usize, height)); |
| 168 | @memset(pixels, 0); | 168 | @memset(pixels, 0); |
| 169 | pixels[0] = 255; | ||
| 169 | return .{ | 170 | return .{ |
| 170 | .alloc = alloc, | 171 | .alloc = alloc, |
| 171 | .width = width, | 172 | .width = width, |
| 172 | .height = height, | 173 | .height = height, |
| 173 | .pixels = pixels, | 174 | .pixels = pixels, |
| 174 | .cursor_x = 0, | 175 | .cursor_x = 1, |
| 175 | .cursor_y = 0, | 176 | .cursor_y = 0, |
| 176 | .row_height = 0, | 177 | .row_height = 1, |
| 177 | .cache = std.AutoHashMap(u21, GlyphUV).init(alloc), | 178 | .cache = std.AutoHashMap(u21, GlyphUV).init(alloc), |
| 178 | .dirty = true, | 179 | .dirty = true, |
| 179 | }; | 180 | }; |
| @@ -184,6 +185,20 @@ pub const Atlas = struct { | |||
| 184 | self.cache.deinit(); | 185 | self.cache.deinit(); |
| 185 | } | 186 | } |
| 186 | 187 | ||
| 188 | pub fn cursorUV(self: *const Atlas) GlyphUV { | ||
| 189 | return .{ | ||
| 190 | .u0 = 0, | ||
| 191 | .v0 = 0, | ||
| 192 | .u1 = 1.0 / @as(f32, @floatFromInt(self.width)), | ||
| 193 | .v1 = 1.0 / @as(f32, @floatFromInt(self.height)), | ||
| 194 | .width = 1, | ||
| 195 | .height = 1, | ||
| 196 | .bearing_x = 0, | ||
| 197 | .bearing_y = 0, | ||
| 198 | .advance_x = 1, | ||
| 199 | }; | ||
| 200 | } | ||
| 201 | |||
| 187 | pub fn getOrInsert(self: *Atlas, face: *Face, codepoint: u21) !GlyphUV { | 202 | pub fn getOrInsert(self: *Atlas, face: *Face, codepoint: u21) !GlyphUV { |
| 188 | if (self.cache.get(codepoint)) |uv| return uv; | 203 | if (self.cache.get(codepoint)) |uv| return uv; |
| 189 | 204 | ||
| @@ -270,3 +285,13 @@ test "Atlas packs multiple glyphs and returns UVs" { | |||
| 270 | try std.testing.expectEqual(uv_m.u0, uv_m2.u0); | 285 | try std.testing.expectEqual(uv_m.u0, uv_m2.u0); |
| 271 | try std.testing.expectEqual(uv_m.v0, uv_m2.v0); | 286 | try std.testing.expectEqual(uv_m.v0, uv_m2.v0); |
| 272 | } | 287 | } |
| 288 | |||
| 289 | test "Atlas reserves a white pixel for cursor rendering" { | ||
| 290 | var atlas = try Atlas.init(std.testing.allocator, 16, 16); | ||
| 291 | defer atlas.deinit(); | ||
| 292 | |||
| 293 | const uv = atlas.cursorUV(); | ||
| 294 | try std.testing.expectEqual(@as(u8, 255), atlas.pixels[0]); | ||
| 295 | try std.testing.expectEqual(@as(f32, 0), uv.u0); | ||
| 296 | try std.testing.expectEqual(@as(f32, 0), uv.v0); | ||
| 297 | } | ||
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -222,6 +222,31 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 222 | } | 222 | } |
| 223 | } | 223 | } |
| 224 | 224 | ||
| 225 | if (term.render_state.cursor.visible) { | ||
| 226 | if (term.render_state.cursor.viewport) |cursor| { | ||
| 227 | const cursor_uv = atlas.cursorUV(); | ||
| 228 | try instances.append(alloc, .{ | ||
| 229 | .cell_pos = .{ | ||
| 230 | @floatFromInt(cursor.x), | ||
| 231 | @floatFromInt(cursor.y), | ||
| 232 | }, | ||
| 233 | .glyph_size = .{ | ||
| 234 | @floatFromInt(cell_w), | ||
| 235 | @floatFromInt(cell_h), | ||
| 236 | }, | ||
| 237 | .glyph_bearing = .{ 0, 0 }, | ||
| 238 | .uv_rect = .{ | ||
| 239 | cursor_uv.u0, | ||
| 240 | cursor_uv.v0, | ||
| 241 | cursor_uv.u1, | ||
| 242 | cursor_uv.v1, | ||
| 243 | }, | ||
| 244 | .fg = .{ 1.0, 1.0, 1.0, 0.5 }, | ||
| 245 | .bg = .{ 0, 0, 0, 0 }, | ||
| 246 | }); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 225 | // Re-upload atlas if new glyphs were added | 250 | // Re-upload atlas if new glyphs were added |
| 226 | if (atlas.dirty) { | 251 | if (atlas.dirty) { |
| 227 | try ctx.uploadAtlas(atlas.pixels); | 252 | try ctx.uploadAtlas(atlas.pixels); |