mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
renderer/opengl: use new preedit format
This commit is contained in:
@ -894,14 +894,7 @@ fn keyEvent(
|
|||||||
// we need to set our proper preedit state.
|
// we need to set our proper preedit state.
|
||||||
if (self.im_composing) preedit: {
|
if (self.im_composing) preedit: {
|
||||||
const text = self.im_buf[0..self.im_len];
|
const text = self.im_buf[0..self.im_len];
|
||||||
const view = std.unicode.Utf8View.init(text) catch |err| {
|
self.core_surface.preeditCallback(text) catch |err| {
|
||||||
log.warn("cannot build utf8 view over input: {}", .{err});
|
|
||||||
break :preedit;
|
|
||||||
};
|
|
||||||
var it = view.iterator();
|
|
||||||
|
|
||||||
const cp: u21 = it.nextCodepoint() orelse 0;
|
|
||||||
self.core_surface.preeditCallback(cp) catch |err| {
|
|
||||||
log.err("error in preedit callback err={}", .{err});
|
log.err("error in preedit callback err={}", .{err});
|
||||||
break :preedit;
|
break :preedit;
|
||||||
};
|
};
|
||||||
|
@ -1555,7 +1555,7 @@ fn addPreeditCell(
|
|||||||
.mode = .bg,
|
.mode = .bg,
|
||||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||||
.cell_width = if (cp.wide) 2 else 1,
|
.cell_width = if (cp.wide) 2 else 1,
|
||||||
.color = .{ bg.r, bg.g, bg.b, 1 },
|
.color = .{ bg.r, bg.g, bg.b, 255 },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add our text
|
// Add our text
|
||||||
|
@ -718,6 +718,18 @@ pub fn rebuildCells(
|
|||||||
// We've written no data to the GPU, refresh it all
|
// We've written no data to the GPU, refresh it all
|
||||||
self.gl_cells_written = 0;
|
self.gl_cells_written = 0;
|
||||||
|
|
||||||
|
// Determine our x/y range for preedit. We don't want to render anything
|
||||||
|
// here because we will render the preedit separately.
|
||||||
|
const preedit_range: ?struct {
|
||||||
|
y: usize,
|
||||||
|
x: [2]usize,
|
||||||
|
} = if (preedit) |preedit_v| preedit: {
|
||||||
|
break :preedit .{
|
||||||
|
.y = screen.cursor.y,
|
||||||
|
.x = preedit_v.range(screen.cursor.x, screen.cols - 1),
|
||||||
|
};
|
||||||
|
} else null;
|
||||||
|
|
||||||
// This is the cell that has [mode == .fg] and is underneath our cursor.
|
// This is the cell that has [mode == .fg] and is underneath our cursor.
|
||||||
// We keep track of it so that we can invert the colors so the character
|
// We keep track of it so that we can invert the colors so the character
|
||||||
// remains visible.
|
// remains visible.
|
||||||
@ -789,6 +801,18 @@ pub fn rebuildCells(
|
|||||||
);
|
);
|
||||||
while (try iter.next(self.alloc)) |run| {
|
while (try iter.next(self.alloc)) |run| {
|
||||||
for (try self.font_shaper.shape(run)) |shaper_cell| {
|
for (try self.font_shaper.shape(run)) |shaper_cell| {
|
||||||
|
// If this cell falls within our preedit range then we skip it.
|
||||||
|
// We do this so we don't have conflicting data on the same
|
||||||
|
// cell.
|
||||||
|
if (preedit_range) |range| {
|
||||||
|
if (range.y == y and
|
||||||
|
shaper_cell.x >= range.x[0] and
|
||||||
|
shaper_cell.x <= range.x[1])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (self.updateCell(
|
if (self.updateCell(
|
||||||
term_selection,
|
term_selection,
|
||||||
screen,
|
screen,
|
||||||
@ -816,33 +840,29 @@ pub fn rebuildCells(
|
|||||||
// Add the cursor at the end so that it overlays everything. If we have
|
// 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
|
// a cursor cell then we invert the colors on that and add it in so
|
||||||
// that we can always see it.
|
// that we can always see it.
|
||||||
if (cursor_style_) |cursor_style| {
|
if (cursor_style_) |cursor_style| cursor_style: {
|
||||||
const real_cursor_cell = self.addCursor(screen, cursor_style, preedit);
|
|
||||||
|
|
||||||
// If we have a preedit, we try to render the preedit text on top
|
// If we have a preedit, we try to render the preedit text on top
|
||||||
// of the cursor.
|
// of the cursor.
|
||||||
if (preedit) |preedit_v| preedit: {
|
if (preedit) |preedit_v| {
|
||||||
if (preedit_v.codepoint > 0) {
|
const range = preedit_range.?;
|
||||||
// We try to base on the cursor cell but if its not there
|
var x = range.x[0];
|
||||||
// we use the actual cursor and if thats not there we give
|
for (preedit_v.codepoints[0..preedit_v.len]) |cp| {
|
||||||
// up on preedit rendering.
|
self.addPreeditCell(cp, x, range.y) catch |err| {
|
||||||
var cell: GPUCell = cursor_cell orelse
|
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
|
||||||
(real_cursor_cell orelse break :preedit).*;
|
x,
|
||||||
cell.fg_r = 0;
|
range.y,
|
||||||
cell.fg_g = 0;
|
err,
|
||||||
cell.fg_b = 0;
|
});
|
||||||
cell.fg_a = 255;
|
};
|
||||||
cell.grid_width = if (preedit_v.wide) 2 else 1;
|
|
||||||
|
|
||||||
// If preedit rendering succeeded then we don't want to
|
x += if (cp.wide) 2 else 1;
|
||||||
// re-render the underlying cell fg
|
|
||||||
if (self.updateCellChar(&cell, preedit_v.codepoint)) {
|
|
||||||
cursor_cell = null;
|
|
||||||
self.cells.appendAssumeCapacity(cell);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Preedit hides the cursor
|
||||||
|
break :cursor_style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = self.addCursor(screen, cursor_style);
|
||||||
if (cursor_cell) |*cell| {
|
if (cursor_cell) |*cell| {
|
||||||
if (cell.mode == .fg) {
|
if (cell.mode == .fg) {
|
||||||
if (self.config.cursor_text) |txt| {
|
if (self.config.cursor_text) |txt| {
|
||||||
@ -868,22 +888,97 @@ pub fn rebuildCells(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn addPreeditCell(
|
||||||
|
self: *OpenGL,
|
||||||
|
cp: renderer.State.Preedit.Codepoint,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
) !void {
|
||||||
|
// Preedit is rendered inverted
|
||||||
|
const bg = self.foreground_color;
|
||||||
|
const fg = self.background_color;
|
||||||
|
|
||||||
|
// Get the font for this codepoint.
|
||||||
|
const font_index = if (self.font_group.indexForCodepoint(
|
||||||
|
self.alloc,
|
||||||
|
@intCast(cp.codepoint),
|
||||||
|
.regular,
|
||||||
|
.text,
|
||||||
|
)) |index| index orelse return else |_| return;
|
||||||
|
|
||||||
|
// Get the font face so we can get the glyph
|
||||||
|
const face = self.font_group.group.faceFromIndex(font_index) catch |err| {
|
||||||
|
log.warn("error getting face for font_index={} err={}", .{ font_index, err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the face to now get the glyph index
|
||||||
|
const glyph_index = face.glyphIndex(@intCast(cp.codepoint)) orelse return;
|
||||||
|
|
||||||
|
// Render the glyph for our preedit text
|
||||||
|
const glyph = self.font_group.renderGlyph(
|
||||||
|
self.alloc,
|
||||||
|
font_index,
|
||||||
|
glyph_index,
|
||||||
|
.{},
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error rendering preedit glyph err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add our opaque background cell
|
||||||
|
self.cells_bg.appendAssumeCapacity(.{
|
||||||
|
.mode = .bg,
|
||||||
|
.grid_col = @intCast(x),
|
||||||
|
.grid_row = @intCast(y),
|
||||||
|
.grid_width = if (cp.wide) 2 else 1,
|
||||||
|
.glyph_x = 0,
|
||||||
|
.glyph_y = 0,
|
||||||
|
.glyph_width = 0,
|
||||||
|
.glyph_height = 0,
|
||||||
|
.glyph_offset_x = 0,
|
||||||
|
.glyph_offset_y = 0,
|
||||||
|
.fg_r = 0,
|
||||||
|
.fg_g = 0,
|
||||||
|
.fg_b = 0,
|
||||||
|
.fg_a = 0,
|
||||||
|
.bg_r = bg.r,
|
||||||
|
.bg_g = bg.g,
|
||||||
|
.bg_b = bg.b,
|
||||||
|
.bg_a = 255,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add our text
|
||||||
|
self.cells.appendAssumeCapacity(.{
|
||||||
|
.mode = .fg,
|
||||||
|
.grid_col = @intCast(x),
|
||||||
|
.grid_row = @intCast(y),
|
||||||
|
.grid_width = if (cp.wide) 2 else 1,
|
||||||
|
.glyph_x = glyph.atlas_x,
|
||||||
|
.glyph_y = glyph.atlas_y,
|
||||||
|
.glyph_width = glyph.width,
|
||||||
|
.glyph_height = glyph.height,
|
||||||
|
.glyph_offset_x = glyph.offset_x,
|
||||||
|
.glyph_offset_y = glyph.offset_y,
|
||||||
|
.fg_r = fg.r,
|
||||||
|
.fg_g = fg.g,
|
||||||
|
.fg_b = fg.b,
|
||||||
|
.fg_a = 255,
|
||||||
|
.bg_r = 0,
|
||||||
|
.bg_g = 0,
|
||||||
|
.bg_b = 0,
|
||||||
|
.bg_a = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn addCursor(
|
fn addCursor(
|
||||||
self: *OpenGL,
|
self: *OpenGL,
|
||||||
screen: *terminal.Screen,
|
screen: *terminal.Screen,
|
||||||
cursor_style: renderer.CursorStyle,
|
cursor_style: renderer.CursorStyle,
|
||||||
preedit: ?renderer.State.Preedit,
|
|
||||||
) ?*const GPUCell {
|
) ?*const GPUCell {
|
||||||
// Add the cursor. We render the cursor over the wide character if
|
// Add the cursor. We render the cursor over the wide character if
|
||||||
// we're on the wide characer tail.
|
// we're on the wide characer tail.
|
||||||
const wide, const x = cell: {
|
const wide, const x = cell: {
|
||||||
// If we have preedit text, our width is based on that.
|
|
||||||
if (preedit) |p| {
|
|
||||||
if (p.codepoint > 0) {
|
|
||||||
break :cell .{ p.wide, screen.cursor.x };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The cursor goes over the screen cursor position.
|
// The cursor goes over the screen cursor position.
|
||||||
const cell = screen.getCell(
|
const cell = screen.getCell(
|
||||||
.active,
|
.active,
|
||||||
@ -949,47 +1044,6 @@ fn addCursor(
|
|||||||
return &self.cells.items[self.cells.items.len - 1];
|
return &self.cells.items[self.cells.items.len - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates cell with the the given character. This returns true if the
|
|
||||||
/// cell was successfully updated.
|
|
||||||
fn updateCellChar(self: *OpenGL, cell: *GPUCell, cp: u21) bool {
|
|
||||||
// Get the font index for this codepoint
|
|
||||||
const font_index = if (self.font_group.indexForCodepoint(
|
|
||||||
self.alloc,
|
|
||||||
@intCast(cp),
|
|
||||||
.regular,
|
|
||||||
.text,
|
|
||||||
)) |index| index orelse return false else |_| return false;
|
|
||||||
|
|
||||||
// Get the font face so we can get the glyph
|
|
||||||
const face = self.font_group.group.faceFromIndex(font_index) catch |err| {
|
|
||||||
log.warn("error getting face for font_index={} err={}", .{ font_index, err });
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use the face to now get the glyph index
|
|
||||||
const glyph_index = face.glyphIndex(@intCast(cp)) orelse return false;
|
|
||||||
|
|
||||||
// Render the glyph for our preedit text
|
|
||||||
const glyph = self.font_group.renderGlyph(
|
|
||||||
self.alloc,
|
|
||||||
font_index,
|
|
||||||
glyph_index,
|
|
||||||
.{},
|
|
||||||
) catch |err| {
|
|
||||||
log.warn("error rendering preedit glyph err={}", .{err});
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update the cell glyph
|
|
||||||
cell.glyph_x = glyph.atlas_x;
|
|
||||||
cell.glyph_y = glyph.atlas_y;
|
|
||||||
cell.glyph_width = glyph.width;
|
|
||||||
cell.glyph_height = glyph.height;
|
|
||||||
cell.glyph_offset_x = glyph.offset_x;
|
|
||||||
cell.glyph_offset_y = glyph.offset_y;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a single cell. The bool returns whether the cell was updated
|
/// Update a single cell. The bool returns whether the cell was updated
|
||||||
/// or not. If the cell wasn't updated, a full refreshCells call is
|
/// or not. If the cell wasn't updated, a full refreshCells call is
|
||||||
/// needed.
|
/// needed.
|
||||||
|
Reference in New Issue
Block a user