mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
renderer/metal: render the cursor
This commit is contained in:
@ -543,6 +543,9 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
};
|
||||
};
|
||||
|
||||
const cells = try mtl_cell.Contents.init(alloc);
|
||||
errdefer cells.deinit(alloc);
|
||||
|
||||
return Metal{
|
||||
.alloc = alloc,
|
||||
.config = options.config,
|
||||
@ -557,7 +560,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
.current_background_color = options.config.background,
|
||||
|
||||
// Render state
|
||||
.cells = .{},
|
||||
.cells = cells,
|
||||
.uniforms = .{
|
||||
.projection_matrix = undefined,
|
||||
.cell_size = undefined,
|
||||
@ -829,9 +832,11 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
// log.debug("drawing frame index={}", .{self.gpu_state.frame_index});
|
||||
|
||||
// Setup our frame data
|
||||
const cells_bg = self.cells.bgCells();
|
||||
const cells_fg = self.cells.fgCells();
|
||||
try frame.uniforms.sync(self.gpu_state.device, &.{self.uniforms});
|
||||
try frame.cells_bg.sync(self.gpu_state.device, self.cells.bgs.items);
|
||||
try frame.cells.sync(self.gpu_state.device, self.cells.text.items);
|
||||
try frame.cells_bg.sync(self.gpu_state.device, cells_bg);
|
||||
try frame.cells.sync(self.gpu_state.device, cells_fg);
|
||||
|
||||
// If we have custom shaders, update the animation time.
|
||||
if (self.custom_shader_state) |*state| {
|
||||
@ -930,13 +935,13 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
|
||||
|
||||
// Then draw background cells
|
||||
try self.drawCellBgs(encoder, frame, self.cells.bgs.items.len);
|
||||
try self.drawCellBgs(encoder, frame, cells_bg.len);
|
||||
|
||||
// Then draw images under text
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_bg_end..self.image_text_end]);
|
||||
|
||||
// Then draw fg cells
|
||||
try self.drawCellFgs(encoder, frame, self.cells.text.items.len);
|
||||
try self.drawCellFgs(encoder, frame, cells_fg.len);
|
||||
|
||||
// Then draw remaining images
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
|
||||
@ -1864,7 +1869,6 @@ fn rebuildCells2(
|
||||
) !void {
|
||||
// TODO: cursor_cell
|
||||
// TODO: cursor_Row
|
||||
_ = cursor_style_;
|
||||
|
||||
// Create an arena for all our temporary allocations while rebuilding
|
||||
var arena = ArenaAllocator.init(self.alloc);
|
||||
@ -1978,12 +1982,20 @@ fn rebuildCells2(
|
||||
}
|
||||
}
|
||||
|
||||
// Add the cursor at the end so that it overlays everything. If we have
|
||||
// a cursor cell then we invert the colors on that and add it in so
|
||||
// that we can always see it.
|
||||
// if (cursor_style_) |cursor_style| cursor_style: {
|
||||
// // If we have a preedit, we try to render the preedit text on top
|
||||
// // of the cursor.
|
||||
// Setup our cursor rendering information.
|
||||
cursor: {
|
||||
// If we have no cursor style then we don't render the cursor.
|
||||
const style = cursor_style_ orelse {
|
||||
self.cells.setCursor(null);
|
||||
break :cursor;
|
||||
};
|
||||
|
||||
// Prepare the cursor cell contents.
|
||||
self.addCursor2(screen, style);
|
||||
}
|
||||
|
||||
// If we have a preedit, we try to render the preedit text on top
|
||||
// of the cursor.
|
||||
// if (preedit) |preedit_v| {
|
||||
// const range = preedit_range.?;
|
||||
// var x = range.x[0];
|
||||
@ -2002,18 +2014,16 @@ fn rebuildCells2(
|
||||
// // Preedit hides the cursor
|
||||
// break :cursor_style;
|
||||
// }
|
||||
|
||||
// if (cursor_cell) |*cell| {
|
||||
// if (cell.mode == .fg) {
|
||||
// cell.color = if (self.config.cursor_text) |txt|
|
||||
// .{ txt.r, txt.g, txt.b, 255 }
|
||||
// else
|
||||
// .{ self.background_color.r, self.background_color.g, self.background_color.b, 255 };
|
||||
// }
|
||||
//
|
||||
// _ = self.addCursor(screen, cursor_style);
|
||||
// // if (cursor_cell) |*cell| {
|
||||
// // if (cell.mode == .fg) {
|
||||
// // cell.color = if (self.config.cursor_text) |txt|
|
||||
// // .{ txt.r, txt.g, txt.b, 255 }
|
||||
// // else
|
||||
// // .{ self.background_color.r, self.background_color.g, self.background_color.b, 255 };
|
||||
// // }
|
||||
// //
|
||||
// // self.cells_text.appendAssumeCapacity(cell.*);
|
||||
// // }
|
||||
// self.cells_text.appendAssumeCapacity(cell.*);
|
||||
// }
|
||||
}
|
||||
|
||||
@ -2456,6 +2466,63 @@ fn updateCell(
|
||||
return true;
|
||||
}
|
||||
|
||||
fn addCursor2(
|
||||
self: *Metal,
|
||||
screen: *terminal.Screen,
|
||||
cursor_style: renderer.CursorStyle,
|
||||
) void {
|
||||
// Add the cursor. We render the cursor over the wide character if
|
||||
// we're on the wide characer tail.
|
||||
const wide, const x = cell: {
|
||||
// The cursor goes over the screen cursor position.
|
||||
const cell = screen.cursor.page_cell;
|
||||
if (cell.wide != .spacer_tail or screen.cursor.x == 0)
|
||||
break :cell .{ cell.wide == .wide, screen.cursor.x };
|
||||
|
||||
// If we're part of a wide character, we move the cursor back to
|
||||
// the actual character.
|
||||
const prev_cell = screen.cursorCellLeft(1);
|
||||
break :cell .{ prev_cell.wide == .wide, screen.cursor.x - 1 };
|
||||
};
|
||||
|
||||
const color = self.cursor_color orelse self.foreground_color;
|
||||
const alpha: u8 = if (!self.focused) 255 else alpha: {
|
||||
const alpha = 255 * self.config.cursor_opacity;
|
||||
break :alpha @intFromFloat(@ceil(alpha));
|
||||
};
|
||||
|
||||
const sprite: font.Sprite = switch (cursor_style) {
|
||||
.block => .cursor_rect,
|
||||
.block_hollow => .cursor_hollow_rect,
|
||||
.bar => .cursor_bar,
|
||||
.underline => .underline,
|
||||
};
|
||||
|
||||
const render = self.font_grid.renderGlyph(
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@intFromEnum(sprite),
|
||||
.{
|
||||
.cell_width = if (wide) 2 else 1,
|
||||
.grid_metrics = self.grid_metrics,
|
||||
},
|
||||
) catch |err| {
|
||||
log.warn("error rendering cursor glyph err={}", .{err});
|
||||
return;
|
||||
};
|
||||
|
||||
self.cells.setCursor(.{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ x, screen.cursor.y },
|
||||
.cell_width = if (wide) 2 else 1,
|
||||
.color = .{ color.r, color.g, color.b, alpha },
|
||||
.bg_color = .{ 0, 0, 0, 0 },
|
||||
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
|
||||
.glyph_size = .{ render.glyph.width, render.glyph.height },
|
||||
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
|
||||
});
|
||||
}
|
||||
|
||||
fn addCursor(
|
||||
self: *Metal,
|
||||
screen: *terminal.Screen,
|
||||
|
@ -35,19 +35,47 @@ pub const Contents = struct {
|
||||
///
|
||||
/// Before any operation, this must be initialized by calling resize
|
||||
/// on the contents.
|
||||
map: []Map = undefined,
|
||||
map: []Map,
|
||||
|
||||
/// The grid size of the terminal. This is used to determine the
|
||||
/// map array index from a coordinate.
|
||||
cols: usize = 0,
|
||||
cols: usize,
|
||||
|
||||
/// The actual GPU data (on the CPU) for all the cells in the terminal.
|
||||
/// This only contains the cells that have content set. To determine
|
||||
/// if a cell has content set, we check the map.
|
||||
///
|
||||
/// This data is synced to a buffer on every frame.
|
||||
bgs: std.ArrayListUnmanaged(mtl_shaders.CellBg) = .{},
|
||||
text: std.ArrayListUnmanaged(mtl_shaders.CellText) = .{},
|
||||
bgs: std.ArrayListUnmanaged(mtl_shaders.CellBg),
|
||||
text: std.ArrayListUnmanaged(mtl_shaders.CellText),
|
||||
|
||||
/// True when the cursor should be rendered.
|
||||
cursor: bool,
|
||||
|
||||
/// The amount of text elements we reserve at the beginning for
|
||||
/// special elements like the cursor.
|
||||
const text_reserved_len = 1;
|
||||
|
||||
pub fn init(alloc: Allocator) !Contents {
|
||||
const map = try alloc.alloc(Map, 0);
|
||||
errdefer alloc.free(map);
|
||||
|
||||
var result: Contents = .{
|
||||
.map = map,
|
||||
.cols = 0,
|
||||
.bgs = .{},
|
||||
.text = .{},
|
||||
.cursor = false,
|
||||
};
|
||||
|
||||
// We preallocate some amount of space for cell contents
|
||||
// we always have as a prefix. For now the current prefix
|
||||
// is length 1: the cursor.
|
||||
try result.text.ensureTotalCapacity(alloc, text_reserved_len);
|
||||
result.text.items.len = text_reserved_len;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Contents, alloc: Allocator) void {
|
||||
alloc.free(self.map);
|
||||
@ -70,7 +98,30 @@ pub const Contents = struct {
|
||||
self.map = map;
|
||||
self.cols = size.columns;
|
||||
self.bgs.clearAndFree(alloc);
|
||||
self.text.clearAndFree(alloc);
|
||||
self.text.shrinkAndFree(alloc, text_reserved_len);
|
||||
}
|
||||
|
||||
/// Returns the slice of fg cell contents to sync with the GPU.
|
||||
pub fn fgCells(self: *const Contents) []const mtl_shaders.CellText {
|
||||
const start: usize = if (self.cursor) 0 else 1;
|
||||
return self.text.items[start..];
|
||||
}
|
||||
|
||||
/// Returns the slice of bg cell contents to sync with the GPU.
|
||||
pub fn bgCells(self: *const Contents) []const mtl_shaders.CellBg {
|
||||
return self.bgs.items;
|
||||
}
|
||||
|
||||
/// Set the cursor value. If the value is null then the cursor
|
||||
/// is hidden.
|
||||
pub fn setCursor(self: *Contents, v: ?mtl_shaders.CellText) void {
|
||||
const cell = v orelse {
|
||||
self.cursor = false;
|
||||
return;
|
||||
};
|
||||
|
||||
self.cursor = true;
|
||||
self.text.items[0] = cell;
|
||||
}
|
||||
|
||||
/// Get the cell contents for the given type and coordinate.
|
||||
@ -244,7 +295,7 @@ test Contents {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
|
||||
var c: Contents = .{};
|
||||
var c = try Contents.init(alloc);
|
||||
try c.resize(alloc, .{ .rows = rows, .columns = cols });
|
||||
defer c.deinit(alloc);
|
||||
|
||||
@ -287,7 +338,7 @@ test "Contents clear retains other content" {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
|
||||
var c: Contents = .{};
|
||||
var c = try Contents.init(alloc);
|
||||
try c.resize(alloc, .{ .rows = rows, .columns = cols });
|
||||
defer c.deinit(alloc);
|
||||
|
||||
@ -319,7 +370,7 @@ test "Contents clear last added content" {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
|
||||
var c: Contents = .{};
|
||||
var c = try Contents.init(alloc);
|
||||
try c.resize(alloc, .{ .rows = rows, .columns = cols });
|
||||
defer c.deinit(alloc);
|
||||
|
||||
|
Reference in New Issue
Block a user