mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
apprt/gtk, opengl: render preedit
This commit is contained in:
@ -1191,6 +1191,11 @@ pub const Surface = struct {
|
||||
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec_key));
|
||||
_ = c.gtk_im_context_filter_keypress(self.im_context, event) != 0;
|
||||
|
||||
// If we aren't composing, then we set our preedit to empty no matter what.
|
||||
if (!self.im_composing) {
|
||||
self.core_surface.preeditCallback(null) catch {};
|
||||
}
|
||||
|
||||
// If we're not in a dead key state, we want to translate our text
|
||||
// to some input.Key.
|
||||
const key = if (!self.im_composing) key: {
|
||||
@ -1214,6 +1219,7 @@ pub const Surface = struct {
|
||||
// If we consume the key then we want to reset the dead key state.
|
||||
if (consumed) {
|
||||
c.gtk_im_context_reset(self.im_context);
|
||||
self.core_surface.preeditCallback(null) catch {};
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -1221,10 +1227,19 @@ pub const Surface = struct {
|
||||
// If this is a dead key, then we're composing a character and
|
||||
// we end processing here. We don't process keybinds for dead keys.
|
||||
if (self.im_composing) {
|
||||
// TODO: we ultimately want to update some surface state so that
|
||||
// we can show the user that we're in dead key mode and the
|
||||
// precomposed character. For now, we can just ignore and that
|
||||
// is not incorrect behavior.
|
||||
const text = self.im_buf[0..self.im_len];
|
||||
const view = std.unicode.Utf8View.init(text) catch |err| {
|
||||
log.warn("cannot build utf8 view over input: {}", .{err});
|
||||
return 0;
|
||||
};
|
||||
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});
|
||||
return 0;
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1294,9 +1309,11 @@ pub const Surface = struct {
|
||||
_ = c.gtk_im_context_get_preedit_string(ctx, &buf, null, null);
|
||||
defer c.g_free(buf);
|
||||
const str = std.mem.sliceTo(buf, 0);
|
||||
log.debug("preedit str={s}", .{str});
|
||||
|
||||
// TODO: actually use this string.
|
||||
// Copy the preedit string into the im_buf. This is safe because
|
||||
// commit will always overwrite this.
|
||||
self.im_len = @intCast(@min(self.im_buf.len, str.len));
|
||||
@memcpy(self.im_buf[0..self.im_len], str);
|
||||
}
|
||||
|
||||
fn gtkInputPreeditEnd(
|
||||
@ -1307,6 +1324,7 @@ pub const Surface = struct {
|
||||
const self = userdataSelf(ud.?);
|
||||
if (!self.in_keypress) return;
|
||||
self.im_composing = false;
|
||||
self.im_len = 0;
|
||||
}
|
||||
|
||||
fn gtkInputCommit(
|
||||
|
@ -1275,7 +1275,6 @@ fn updateCellChar(self: *Metal, cell: *GPUCell, cp: u21) bool {
|
||||
cell.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y };
|
||||
cell.glyph_size = .{ glyph.width, glyph.height };
|
||||
cell.glyph_offset = .{ glyph.offset_x, glyph.offset_y };
|
||||
cell.cell_width = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -721,6 +721,7 @@ pub fn render(
|
||||
selection: ?terminal.Selection,
|
||||
screen: terminal.Screen,
|
||||
draw_cursor: bool,
|
||||
preedit: ?renderer.State.Preedit,
|
||||
};
|
||||
|
||||
// Update all our data as tightly as possible within the mutex.
|
||||
@ -733,6 +734,9 @@ pub fn render(
|
||||
// then it is not visible.
|
||||
if (!state.cursor.visible) break :visible false;
|
||||
|
||||
// If we are in preedit, then we always show the cursor
|
||||
if (state.preedit != null) break :visible true;
|
||||
|
||||
// If the cursor isn't a blinking style, then never blink.
|
||||
if (!state.cursor.style.blinking()) break :visible true;
|
||||
|
||||
@ -740,10 +744,17 @@ pub fn render(
|
||||
break :visible self.cursor_visible;
|
||||
};
|
||||
|
||||
if (self.focused) {
|
||||
self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
|
||||
} else {
|
||||
self.cursor_style = .box_hollow;
|
||||
// The cursor style only needs to be set if its visible.
|
||||
if (self.cursor_visible) {
|
||||
self.cursor_style = cursor_style: {
|
||||
// If we have a dead key preedit then we always use a box style
|
||||
if (state.preedit != null) break :cursor_style .box;
|
||||
|
||||
// If we aren't focused, we use a hollow box
|
||||
if (!self.focused) break :cursor_style .box_hollow;
|
||||
|
||||
break :cursor_style renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
|
||||
};
|
||||
}
|
||||
|
||||
// Swap bg/fg if the terminal is reversed
|
||||
@ -796,13 +807,17 @@ pub fn render(
|
||||
else
|
||||
null;
|
||||
|
||||
// Whether to draw our cursor or not.
|
||||
const draw_cursor = self.cursor_visible and state.terminal.screen.viewportIsBottom();
|
||||
|
||||
break :critical .{
|
||||
.gl_bg = self.config.background,
|
||||
.devmode_data = devmode_data,
|
||||
.active_screen = state.terminal.active_screen,
|
||||
.selection = selection,
|
||||
.screen = screen_copy,
|
||||
.draw_cursor = self.cursor_visible and state.terminal.screen.viewportIsBottom(),
|
||||
.draw_cursor = draw_cursor,
|
||||
.preedit = if (draw_cursor) state.preedit else null,
|
||||
};
|
||||
};
|
||||
defer critical.screen.deinit();
|
||||
@ -821,6 +836,7 @@ pub fn render(
|
||||
critical.selection,
|
||||
&critical.screen,
|
||||
critical.draw_cursor,
|
||||
critical.preedit,
|
||||
);
|
||||
}
|
||||
|
||||
@ -858,6 +874,7 @@ pub fn rebuildCells(
|
||||
term_selection: ?terminal.Selection,
|
||||
screen: *terminal.Screen,
|
||||
draw_cursor: bool,
|
||||
preedit: ?renderer.State.Preedit,
|
||||
) !void {
|
||||
const t = trace(@src());
|
||||
defer t.end();
|
||||
@ -1006,7 +1023,31 @@ pub fn rebuildCells(
|
||||
// a cursor cell then we invert the colors on that and add it in so
|
||||
// that we can always see it.
|
||||
if (draw_cursor) {
|
||||
self.addCursor(screen);
|
||||
const real_cursor_cell = self.addCursor(screen);
|
||||
|
||||
// If we have a preedit, we try to render the preedit text on top
|
||||
// of the cursor.
|
||||
if (preedit) |preedit_v| preedit: {
|
||||
if (preedit_v.codepoint > 0) {
|
||||
// We try to base on the cursor cell but if its not there
|
||||
// we use the actual cursor and if thats not there we give
|
||||
// up on preedit rendering.
|
||||
var cell: GPUCell = cursor_cell orelse
|
||||
(real_cursor_cell orelse break :preedit).*;
|
||||
cell.fg_r = 0;
|
||||
cell.fg_g = 0;
|
||||
cell.fg_b = 0;
|
||||
cell.fg_a = 255;
|
||||
|
||||
// If preedit rendering succeeded then we don't want to
|
||||
// re-render the underlying cell fg
|
||||
if (self.updateCellChar(&cell, preedit_v.codepoint)) {
|
||||
cursor_cell = null;
|
||||
self.cells.appendAssumeCapacity(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor_cell) |*cell| {
|
||||
cell.fg_r = 0;
|
||||
cell.fg_g = 0;
|
||||
@ -1023,7 +1064,7 @@ pub fn rebuildCells(
|
||||
}
|
||||
}
|
||||
|
||||
fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
|
||||
fn addCursor(self: *OpenGL, screen: *terminal.Screen) ?*const GPUCell {
|
||||
// Add the cursor
|
||||
const cell = screen.getCell(
|
||||
.active,
|
||||
@ -1050,7 +1091,7 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
|
||||
.{},
|
||||
) catch |err| {
|
||||
log.warn("error rendering cursor glyph err={}", .{err});
|
||||
return;
|
||||
return null;
|
||||
};
|
||||
|
||||
self.cells.appendAssumeCapacity(.{
|
||||
@ -1073,6 +1114,49 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
|
||||
.glyph_offset_x = glyph.offset_x,
|
||||
.glyph_offset_y = glyph.offset_y,
|
||||
});
|
||||
|
||||
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
|
||||
|
Reference in New Issue
Block a user