Merge pull request #2676 from ghostty-org/coordinate-space

Refactor how we pass around sizes
This commit is contained in:
Mitchell Hashimoto
2024-11-15 07:43:59 -08:00
committed by GitHub
18 changed files with 471 additions and 497 deletions

View File

@ -113,13 +113,8 @@ io_thr: std.Thread,
/// Terminal inspector /// Terminal inspector
inspector: ?*inspector.Inspector = null, inspector: ?*inspector.Inspector = null,
/// All the cached sizes since we need them at various times. /// All our sizing information.
screen_size: renderer.ScreenSize, size: renderer.Size,
grid_size: renderer.GridSize,
cell_size: renderer.CellSize,
/// Explicit padding due to configuration
padding: renderer.Padding,
/// The configuration derived from the main config. We "derive" it so that /// The configuration derived from the main config. We "derive" it so that
/// we don't have a shared pointer hanging around that we need to worry about /// we don't have a shared pointer hanging around that we need to worry about
@ -329,6 +324,32 @@ const DerivedConfig = struct {
for (self.links) |*link| link.regex.deinit(); for (self.links) |*link| link.regex.deinit();
self.arena.deinit(); self.arena.deinit();
} }
fn scaledPadding(self: *const DerivedConfig, x_dpi: f32, y_dpi: f32) renderer.Padding {
const padding_top: u32 = padding_top: {
const padding_top: f32 = @floatFromInt(self.window_padding_top);
break :padding_top @intFromFloat(@floor(padding_top * y_dpi / 72));
};
const padding_bottom: u32 = padding_bottom: {
const padding_bottom: f32 = @floatFromInt(self.window_padding_bottom);
break :padding_bottom @intFromFloat(@floor(padding_bottom * y_dpi / 72));
};
const padding_left: u32 = padding_left: {
const padding_left: f32 = @floatFromInt(self.window_padding_left);
break :padding_left @intFromFloat(@floor(padding_left * x_dpi / 72));
};
const padding_right: u32 = padding_right: {
const padding_right: f32 = @floatFromInt(self.window_padding_right);
break :padding_right @intFromFloat(@floor(padding_right * x_dpi / 72));
};
return .{
.top = padding_top,
.bottom = padding_bottom,
.left = padding_left,
.right = padding_right,
};
}
}; };
/// Create a new surface. This must be called from the main thread. The /// Create a new surface. This must be called from the main thread. The
@ -378,28 +399,26 @@ pub fn init(
// Pre-calculate our initial cell size ourselves. // Pre-calculate our initial cell size ourselves.
const cell_size = font_grid.cellSize(); const cell_size = font_grid.cellSize();
// Convert our padding from points to pixels // Build our size struct which has all the sizes we need.
const padding_top: u32 = padding_top: { const size: renderer.Size = size: {
const padding_top: f32 = @floatFromInt(derived_config.window_padding_top); var size: renderer.Size = .{
break :padding_top @intFromFloat(@floor(padding_top * y_dpi / 72)); .screen = screen: {
}; const surface_size = try rt_surface.getSize();
const padding_bottom: u32 = padding_bottom: { break :screen .{
const padding_bottom: f32 = @floatFromInt(derived_config.window_padding_bottom); .width = surface_size.width,
break :padding_bottom @intFromFloat(@floor(padding_bottom * y_dpi / 72)); .height = surface_size.height,
}; };
const padding_left: u32 = padding_left: { },
const padding_left: f32 = @floatFromInt(derived_config.window_padding_left);
break :padding_left @intFromFloat(@floor(padding_left * x_dpi / 72)); .cell = font_grid.cellSize(),
}; .padding = derived_config.scaledPadding(x_dpi, y_dpi),
const padding_right: u32 = padding_right: { };
const padding_right: f32 = @floatFromInt(derived_config.window_padding_right);
break :padding_right @intFromFloat(@floor(padding_right * x_dpi / 72)); if (derived_config.window_padding_balance) {
}; size.balancePadding();
const padding: renderer.Padding = .{ }
.top = padding_top,
.bottom = padding_bottom, break :size size;
.left = padding_left,
.right = padding_right,
}; };
// Create our terminal grid with the initial size // Create our terminal grid with the initial size
@ -407,26 +426,12 @@ pub fn init(
var renderer_impl = try Renderer.init(alloc, .{ var renderer_impl = try Renderer.init(alloc, .{
.config = try Renderer.DerivedConfig.init(alloc, config), .config = try Renderer.DerivedConfig.init(alloc, config),
.font_grid = font_grid, .font_grid = font_grid,
.padding = .{ .size = size,
.explicit = padding,
.balance = config.@"window-padding-balance",
},
.surface_mailbox = .{ .surface = self, .app = app_mailbox }, .surface_mailbox = .{ .surface = self, .app = app_mailbox },
.rt_surface = rt_surface, .rt_surface = rt_surface,
}); });
errdefer renderer_impl.deinit(); errdefer renderer_impl.deinit();
// Calculate our grid size based on known dimensions.
const surface_size = try rt_surface.getSize();
const screen_size: renderer.ScreenSize = .{
.width = surface_size.width,
.height = surface_size.height,
};
const grid_size = renderer.GridSize.init(
screen_size.subPadding(padding),
cell_size,
);
// The mutex used to protect our renderer state. // The mutex used to protect our renderer state.
const mutex = try alloc.create(std.Thread.Mutex); const mutex = try alloc.create(std.Thread.Mutex);
mutex.* = .{}; mutex.* = .{};
@ -467,10 +472,7 @@ pub fn init(
.io = undefined, .io = undefined,
.io_thread = io_thread, .io_thread = io_thread,
.io_thr = undefined, .io_thr = undefined,
.screen_size = .{ .width = 0, .height = 0 }, .size = size,
.grid_size = .{},
.cell_size = cell_size,
.padding = padding,
.config = derived_config, .config = derived_config,
}; };
@ -510,10 +512,7 @@ pub fn init(
errdefer io_mailbox.deinit(alloc); errdefer io_mailbox.deinit(alloc);
try termio.Termio.init(&self.io, alloc, .{ try termio.Termio.init(&self.io, alloc, .{
.grid_size = grid_size, .size = size,
.cell_size = cell_size,
.screen_size = screen_size,
.padding = padding,
.full_config = config, .full_config = config,
.config = try termio.Termio.DerivedConfig.init(alloc, config), .config = try termio.Termio.DerivedConfig.init(alloc, config),
.backend = .{ .exec = io_exec }, .backend = .{ .exec = io_exec },
@ -532,7 +531,7 @@ pub fn init(
try rt_app.performAction( try rt_app.performAction(
.{ .surface = self }, .{ .surface = self },
.cell_size, .cell_size,
.{ .width = cell_size.width, .height = cell_size.height }, .{ .width = size.cell.width, .height = size.cell.height },
); );
// Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app // Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app
@ -541,8 +540,8 @@ pub fn init(
.{ .surface = self }, .{ .surface = self },
.size_limit, .size_limit,
.{ .{
.min_width = cell_size.width * 10, .min_width = size.cell.width * 10,
.min_height = cell_size.height * 4, .min_height = size.cell.height * 4,
// No max: // No max:
.max_width = 0, .max_width = 0,
.max_height = 0, .max_height = 0,
@ -554,7 +553,7 @@ pub fn init(
// init stuff we should get rid of this. But this is required because // init stuff we should get rid of this. But this is required because
// sizeCallback does retina-aware stuff we don't do here and don't want // sizeCallback does retina-aware stuff we don't do here and don't want
// to duplicate. // to duplicate.
try self.sizeCallback(surface_size); try self.resize(self.size.screen);
// Give the renderer one more opportunity to finalize any surface // Give the renderer one more opportunity to finalize any surface
// setup on the main thread prior to spinning up the rendering thread. // setup on the main thread prior to spinning up the rendering thread.
@ -594,12 +593,12 @@ pub fn init(
// account for the padding so we get the exact correct grid size. // account for the padding so we get the exact correct grid size.
const final_width: u32 = const final_width: u32 =
@as(u32, @intFromFloat(@ceil(width_f32 / scale.x))) + @as(u32, @intFromFloat(@ceil(width_f32 / scale.x))) +
padding.left + size.padding.left +
padding.right; size.padding.right;
const final_height: u32 = const final_height: u32 =
@as(u32, @intFromFloat(@ceil(height_f32 / scale.y))) + @as(u32, @intFromFloat(@ceil(height_f32 / scale.y))) +
padding.top + size.padding.top +
padding.bottom; size.padding.bottom;
rt_app.performAction( rt_app.performAction(
.{ .surface = self }, .{ .surface = self },
@ -1152,18 +1151,12 @@ pub fn selectionInfo(self: *const Surface) ?apprt.Selection {
// Our sizes are all scaled so we need to send the unscaled values back. // Our sizes are all scaled so we need to send the unscaled values back.
const content_scale = self.rt_surface.getContentScale() catch .{ .x = 1, .y = 1 }; const content_scale = self.rt_surface.getContentScale() catch .{ .x = 1, .y = 1 };
// We need to account for padding as well.
const pad = if (self.config.window_padding_balance)
renderer.Padding.balanced(self.screen_size, self.grid_size, self.cell_size)
else
self.padding;
const x: f64 = x: { const x: f64 = x: {
// Simple x * cell width gives the left // Simple x * cell width gives the left
var x: f64 = @floatFromInt(tl_coord.x * self.cell_size.width); var x: f64 = @floatFromInt(tl_coord.x * self.size.cell.width);
// Add padding // Add padding
x += @floatFromInt(pad.left); x += @floatFromInt(self.size.padding.left);
// Scale // Scale
x /= content_scale.x; x /= content_scale.x;
@ -1173,14 +1166,14 @@ pub fn selectionInfo(self: *const Surface) ?apprt.Selection {
const y: f64 = y: { const y: f64 = y: {
// Simple y * cell height gives the top // Simple y * cell height gives the top
var y: f64 = @floatFromInt(tl_coord.y * self.cell_size.height); var y: f64 = @floatFromInt(tl_coord.y * self.size.cell.height);
// We want the text baseline // We want the text baseline
y += @floatFromInt(self.cell_size.height); y += @floatFromInt(self.size.cell.height);
y -= @floatFromInt(self.font_metrics.cell_baseline); y -= @floatFromInt(self.font_metrics.cell_baseline);
// Add padding // Add padding
y += @floatFromInt(pad.top); y += @floatFromInt(self.size.padding.top);
// Scale // Scale
y /= content_scale.y; y /= content_scale.y;
@ -1221,10 +1214,10 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
const x: f64 = x: { const x: f64 = x: {
// Simple x * cell width gives the top-left corner // Simple x * cell width gives the top-left corner
var x: f64 = @floatFromInt(cursor.x * self.cell_size.width); var x: f64 = @floatFromInt(cursor.x * self.size.cell.width);
// We want the midpoint // We want the midpoint
x += @as(f64, @floatFromInt(self.cell_size.width)) / 2; x += @as(f64, @floatFromInt(self.size.cell.width)) / 2;
// And scale it // And scale it
x /= content_scale.x; x /= content_scale.x;
@ -1234,10 +1227,10 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
const y: f64 = y: { const y: f64 = y: {
// Simple x * cell width gives the top-left corner // Simple x * cell width gives the top-left corner
var y: f64 = @floatFromInt(cursor.y * self.cell_size.height); var y: f64 = @floatFromInt(cursor.y * self.size.cell.height);
// We want the bottom // We want the bottom
y += @floatFromInt(self.cell_size.height); y += @floatFromInt(self.size.cell.height);
// And scale it // And scale it
y /= content_scale.y; y /= content_scale.y;
@ -1363,24 +1356,12 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
/// Change the cell size for the terminal grid. This can happen as /// Change the cell size for the terminal grid. This can happen as
/// a result of changing the font size at runtime. /// a result of changing the font size at runtime.
fn setCellSize(self: *Surface, size: renderer.CellSize) !void { fn setCellSize(self: *Surface, size: renderer.CellSize) !void {
// Update our new cell size for future calcs // Update our cell size within our size struct
self.cell_size = size; self.size.cell = size;
if (self.config.window_padding_balance) self.size.balancePadding();
// Update our grid_size
self.grid_size = renderer.GridSize.init(
self.screen_size.subPadding(self.padding),
self.cell_size,
);
// Notify the terminal // Notify the terminal
self.io.queueMessage(.{ self.io.queueMessage(.{ .resize = self.size }, .unlocked);
.resize = .{
.grid_size = self.grid_size,
.cell_size = self.cell_size,
.screen_size = self.screen_size,
.padding = self.padding,
},
}, .unlocked);
// Notify the window // Notify the window
try self.rt_app.performAction( try self.rt_app.performAction(
@ -1451,41 +1432,32 @@ pub fn sizeCallback(self: *Surface, size: apprt.SurfaceSize) !void {
// Update our screen size, but only if it actually changed. And if // Update our screen size, but only if it actually changed. And if
// the screen size didn't change, then our grid size could not have // the screen size didn't change, then our grid size could not have
// changed, so we just return. // changed, so we just return.
if (self.screen_size.equals(new_screen_size)) return; if (self.size.screen.equals(new_screen_size)) return;
try self.resize(new_screen_size); try self.resize(new_screen_size);
} }
fn resize(self: *Surface, size: renderer.ScreenSize) !void { fn resize(self: *Surface, size: renderer.ScreenSize) !void {
// Save our screen size // Save our screen size
self.screen_size = size; self.size.screen = size;
if (self.config.window_padding_balance) self.size.balancePadding();
// Recalculate our grid size. Because Ghostty supports fluid resizing, // Recalculate our grid size. Because Ghostty supports fluid resizing,
// its possible the grid doesn't change at all even if the screen size changes. // its possible the grid doesn't change at all even if the screen size changes.
// We have to update the IO thread no matter what because we send // We have to update the IO thread no matter what because we send
// pixel-level sizing to the subprocess. // pixel-level sizing to the subprocess.
self.grid_size = renderer.GridSize.init( const grid_size = self.size.grid();
self.screen_size.subPadding(self.padding), if (grid_size.columns < 5 and (self.size.padding.left > 0 or self.size.padding.right > 0)) {
self.cell_size,
);
if (self.grid_size.columns < 5 and (self.padding.left > 0 or self.padding.right > 0)) {
log.warn("WARNING: very small terminal grid detected with padding " ++ log.warn("WARNING: very small terminal grid detected with padding " ++
"set. Is your padding reasonable?", .{}); "set. Is your padding reasonable?", .{});
} }
if (self.grid_size.rows < 2 and (self.padding.top > 0 or self.padding.bottom > 0)) { if (grid_size.rows < 2 and (self.size.padding.top > 0 or self.size.padding.bottom > 0)) {
log.warn("WARNING: very small terminal grid detected with padding " ++ log.warn("WARNING: very small terminal grid detected with padding " ++
"set. Is your padding reasonable?", .{}); "set. Is your padding reasonable?", .{});
} }
// Mail the IO thread // Mail the IO thread
self.io.queueMessage(.{ self.io.queueMessage(.{ .resize = self.size }, .unlocked);
.resize = .{
.grid_size = self.grid_size,
.cell_size = self.cell_size,
.screen_size = self.screen_size,
.padding = self.padding,
},
}, .unlocked);
} }
/// Called to set the preedit state for character input. Preedit is used /// Called to set the preedit state for character input. Preedit is used
@ -2144,7 +2116,8 @@ pub fn scrollCallback(
if (!scroll_mods.precision) { if (!scroll_mods.precision) {
// Calculate our magnitude of scroll. This is constant (not // Calculate our magnitude of scroll. This is constant (not
// dependent on yoff). // dependent on yoff).
const grid_rows_f64: f64 = @floatFromInt(self.grid_size.rows); const grid_size = self.size.grid();
const grid_rows_f64: f64 = @floatFromInt(grid_size.rows);
const y_delta_f64: f64 = @round((grid_rows_f64 * self.config.mouse_scroll_multiplier) / 15.0); const y_delta_f64: f64 = @round((grid_rows_f64 * self.config.mouse_scroll_multiplier) / 15.0);
const y_delta_usize: usize = @max(1, @as(usize, @intFromFloat(y_delta_f64))); const y_delta_usize: usize = @max(1, @as(usize, @intFromFloat(y_delta_f64)));
@ -2171,7 +2144,7 @@ pub fn scrollCallback(
// If the new offset is less than a single unit of scroll, we save // If the new offset is less than a single unit of scroll, we save
// the new pending value and do not scroll yet. // the new pending value and do not scroll yet.
const cell_size: f64 = @floatFromInt(self.cell_size.height); const cell_size: f64 = @floatFromInt(self.size.cell.height);
if (@abs(poff) < cell_size) { if (@abs(poff) < cell_size) {
self.mouse.pending_scroll_y = poff; self.mouse.pending_scroll_y = poff;
break :y .{}; break :y .{};
@ -2201,7 +2174,7 @@ pub fn scrollCallback(
const xoff_adjusted: f64 = xoff * self.config.mouse_scroll_multiplier; const xoff_adjusted: f64 = xoff * self.config.mouse_scroll_multiplier;
const poff: f64 = self.mouse.pending_scroll_x + xoff_adjusted; const poff: f64 = self.mouse.pending_scroll_x + xoff_adjusted;
const cell_size: f64 = @floatFromInt(self.cell_size.width); const cell_size: f64 = @floatFromInt(self.size.cell.width);
if (@abs(poff) < cell_size) { if (@abs(poff) < cell_size) {
self.mouse.pending_scroll_x = poff; self.mouse.pending_scroll_x = poff;
break :x .{}; break :x .{};
@ -2326,36 +2299,15 @@ pub fn contentScaleCallback(self: *Surface, content_scale: apprt.ContentScale) !
try self.setFontSize(size); try self.setFontSize(size);
// Update our padding which is dependent on DPI. // Update our padding which is dependent on DPI. We only do this for
self.padding = padding: { // unbalanced padding since balanced padding is not dependent on DPI.
const padding_top: u32 = padding_top: { if (!self.config.window_padding_balance) {
const padding_top: f32 = @floatFromInt(self.config.window_padding_top); self.size.padding = self.config.scaledPadding(x_dpi, y_dpi);
break :padding_top @intFromFloat(@floor(padding_top * y_dpi / 72)); }
};
const padding_bottom: u32 = padding_bottom: {
const padding_bottom: f32 = @floatFromInt(self.config.window_padding_bottom);
break :padding_bottom @intFromFloat(@floor(padding_bottom * y_dpi / 72));
};
const padding_left: u32 = padding_left: {
const padding_left: f32 = @floatFromInt(self.config.window_padding_left);
break :padding_left @intFromFloat(@floor(padding_left * x_dpi / 72));
};
const padding_right: u32 = padding_right: {
const padding_right: f32 = @floatFromInt(self.config.window_padding_right);
break :padding_right @intFromFloat(@floor(padding_right * x_dpi / 72));
};
break :padding .{
.top = padding_top,
.bottom = padding_bottom,
.left = padding_left,
.right = padding_right,
};
};
// Force a resize event because the change in padding will affect // Force a resize event because the change in padding will affect
// pixel-level changes to the renderer and viewport. // pixel-level changes to the renderer and viewport.
try self.resize(self.screen_size); try self.resize(self.size.screen);
} }
/// The type of action to report for a mouse event. /// The type of action to report for a mouse event.
@ -2394,8 +2346,8 @@ fn mouseReport(
// We always report release events no matter where they happen. // We always report release events no matter where they happen.
if (action != .release) { if (action != .release) {
const pos_out_viewport = pos_out_viewport: { const pos_out_viewport = pos_out_viewport: {
const max_x: f32 = @floatFromInt(self.screen_size.width); const max_x: f32 = @floatFromInt(self.size.screen.width);
const max_y: f32 = @floatFromInt(self.screen_size.height); const max_y: f32 = @floatFromInt(self.size.screen.height);
break :pos_out_viewport pos.x < 0 or pos.y < 0 or break :pos_out_viewport pos.x < 0 or pos.y < 0 or
pos.x > max_x or pos.y > max_y; pos.x > max_x or pos.y > max_y;
}; };
@ -2554,15 +2506,22 @@ fn mouseReport(
.sgr_pixels => { .sgr_pixels => {
// Final character to send in the CSI // Final character to send in the CSI
const final: u8 = if (action == .release) 'm' else 'M'; const final: u8 = if (action == .release) 'm' else 'M';
const adjusted = self.posAdjusted(pos.x, pos.y);
// The position has to be adjusted to the terminal space.
const coord: renderer.Coordinate.Terminal = (renderer.Coordinate{
.surface = .{
.x = pos.x,
.y = pos.y,
},
}).convert(.terminal, self.size).terminal;
// Response always is at least 4 chars, so this leaves the // Response always is at least 4 chars, so this leaves the
// remainder for numbers which are very large... // remainder for numbers which are very large...
var data: termio.Message.WriteReq.Small.Array = undefined; var data: termio.Message.WriteReq.Small.Array = undefined;
const resp = try std.fmt.bufPrint(&data, "\x1B[<{d};{d};{d}{c}", .{ const resp = try std.fmt.bufPrint(&data, "\x1B[<{d};{d};{d}{c}", .{
button_code, button_code,
@as(i32, @intFromFloat(@round(adjusted.x))), @as(i32, @intFromFloat(@round(coord.x))),
@as(i32, @intFromFloat(@round(adjusted.y))), @as(i32, @intFromFloat(@round(coord.y))),
final, final,
}); });
@ -2817,7 +2776,7 @@ pub fn mouseButtonCallback(
// If we move our cursor too much between clicks then we reset // If we move our cursor too much between clicks then we reset
// the multi-click state. // the multi-click state.
if (self.mouse.left_click_count > 0) { if (self.mouse.left_click_count > 0) {
const max_distance: f64 = @floatFromInt(self.cell_size.width); const max_distance: f64 = @floatFromInt(self.size.cell.width);
const distance = @sqrt( const distance = @sqrt(
std.math.pow(f64, pos.x - self.mouse.left_click_xpos, 2) + std.math.pow(f64, pos.x - self.mouse.left_click_xpos, 2) +
std.math.pow(f64, pos.y - self.mouse.left_click_ypos, 2), std.math.pow(f64, pos.y - self.mouse.left_click_ypos, 2),
@ -3317,8 +3276,8 @@ pub fn cursorPosCallback(
// We allow for a 1 pixel buffer at the top and bottom to detect // We allow for a 1 pixel buffer at the top and bottom to detect
// scroll even in full screen windows. // scroll even in full screen windows.
// Note: one day, we can change this from distance to time based if we want. // Note: one day, we can change this from distance to time based if we want.
//log.warn("CURSOR POS: {} {}", .{ pos, self.screen_size }); //log.warn("CURSOR POS: {} {}", .{ pos, self.size.screen });
const max_y: f32 = @floatFromInt(self.screen_size.height); const max_y: f32 = @floatFromInt(self.size.screen.height);
if (pos.y <= 1 or pos.y > max_y - 1) { if (pos.y <= 1 or pos.y > max_y - 1) {
const delta: isize = if (pos.y < 0) -1 else 1; const delta: isize = if (pos.y < 0) -1 else 1;
try self.io.terminal.scrollViewport(.{ .delta = delta }); try self.io.terminal.scrollViewport(.{ .delta = delta });
@ -3477,11 +3436,11 @@ fn dragLeftClickSingle(
const click_pin = self.mouse.left_click_pin.?.*; const click_pin = self.mouse.left_click_pin.?.*;
// the boundary point at which we consider selection or non-selection // the boundary point at which we consider selection or non-selection
const cell_width_f64: f64 = @floatFromInt(self.cell_size.width); const cell_width_f64: f64 = @floatFromInt(self.size.cell.width);
const cell_xboundary = cell_width_f64 * 0.6; const cell_xboundary = cell_width_f64 * 0.6;
// first xpos of the clicked cell adjusted for padding // first xpos of the clicked cell adjusted for padding
const left_padding_f64: f64 = @as(f64, @floatFromInt(self.padding.left)); const left_padding_f64: f64 = @as(f64, @floatFromInt(self.size.padding.left));
const cell_xstart = @as(f64, @floatFromInt(click_pin.x)) * cell_width_f64; const cell_xstart = @as(f64, @floatFromInt(click_pin.x)) * cell_width_f64;
const cell_start_xpos = self.mouse.left_click_xpos - cell_xstart - left_padding_f64; const cell_start_xpos = self.mouse.left_click_xpos - cell_xstart - left_padding_f64;
@ -3631,43 +3590,11 @@ pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
if (report) try self.reportColorScheme(); if (report) try self.reportColorScheme();
} }
pub fn posAdjusted(self: Surface, xpos: f64, ypos: f64) struct { x: f64, y: f64 } {
const pad = if (self.config.window_padding_balance)
renderer.Padding.balanced(self.screen_size, self.grid_size, self.cell_size)
else
self.padding;
return .{
.x = xpos - @as(f64, @floatFromInt(pad.left)),
.y = ypos - @as(f64, @floatFromInt(pad.top)),
};
}
pub fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Coordinate { pub fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Coordinate {
// xpos/ypos need to be adjusted for window padding // Get our grid cell
// (i.e. "window-padding-*" settings. const coord: renderer.Coordinate = .{ .surface = .{ .x = xpos, .y = ypos } };
const adjusted = self.posAdjusted(xpos, ypos); const grid = coord.convert(.grid, self.size).grid;
return .{ .x = grid.x, .y = grid.y };
// adjusted.x and adjusted.y can be negative if while dragging, the user moves the
// mouse off the surface. Likewise, they can be larger than our surface
// width if the user drags out of the surface positively.
return .{
.x = if (adjusted.x < 0) 0 else x: {
// Our cell is the mouse divided by cell width
const cell_width: f64 = @floatFromInt(self.cell_size.width);
const x: usize = @intFromFloat(adjusted.x / cell_width);
// Can be off the screen if the user drags it out, so max
// it out on our available columns
break :x @min(x, self.grid_size.columns - 1);
},
.y = if (adjusted.y < 0) 0 else y: {
const cell_height: f64 = @floatFromInt(self.cell_size.height);
const y: usize = @intFromFloat(adjusted.y / cell_height);
break :y @min(y, self.grid_size.rows - 1);
},
};
} }
/// Scroll to the bottom of the viewport. /// Scroll to the bottom of the viewport.
@ -3905,21 +3832,21 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
}, },
.scroll_page_up => { .scroll_page_up => {
const rows: isize = @intCast(self.grid_size.rows); const rows: isize = @intCast(self.size.grid().rows);
self.io.queueMessage(.{ self.io.queueMessage(.{
.scroll_viewport = .{ .delta = -1 * rows }, .scroll_viewport = .{ .delta = -1 * rows },
}, .unlocked); }, .unlocked);
}, },
.scroll_page_down => { .scroll_page_down => {
const rows: isize = @intCast(self.grid_size.rows); const rows: isize = @intCast(self.size.grid().rows);
self.io.queueMessage(.{ self.io.queueMessage(.{
.scroll_viewport = .{ .delta = rows }, .scroll_viewport = .{ .delta = rows },
}, .unlocked); }, .unlocked);
}, },
.scroll_page_fractional => |fraction| { .scroll_page_fractional => |fraction| {
const rows: f32 = @floatFromInt(self.grid_size.rows); const rows: f32 = @floatFromInt(self.size.grid().rows);
const delta: isize = @intFromFloat(@trunc(fraction * rows)); const delta: isize = @intFromFloat(@trunc(fraction * rows));
self.io.queueMessage(.{ self.io.queueMessage(.{
.scroll_viewport = .{ .delta = delta }, .scroll_viewport = .{ .delta = delta },
@ -3989,7 +3916,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.left => .left, .left => .left,
.down => .down, .down => .down,
.up => .up, .up => .up,
.auto => if (self.screen_size.width > self.screen_size.height) .auto => if (self.size.screen.width > self.size.screen.height)
.right .right
else else
.down, .down,

View File

@ -1447,13 +1447,14 @@ pub const CAPI = struct {
/// Return the size information a surface has. /// Return the size information a surface has.
export fn ghostty_surface_size(surface: *Surface) SurfaceSize { export fn ghostty_surface_size(surface: *Surface) SurfaceSize {
const grid_size = surface.core_surface.size.grid();
return .{ return .{
.columns = surface.core_surface.grid_size.columns, .columns = grid_size.columns,
.rows = surface.core_surface.grid_size.rows, .rows = grid_size.rows,
.width_px = surface.core_surface.screen_size.width, .width_px = surface.core_surface.size.screen.width,
.height_px = surface.core_surface.screen_size.height, .height_px = surface.core_surface.size.screen.height,
.cell_width_px = surface.core_surface.cell_size.width, .cell_width_px = surface.core_surface.size.cell.width,
.cell_height_px = surface.core_surface.cell_size.height, .cell_height_px = surface.core_surface.size.cell.height,
}; };
} }

View File

@ -94,13 +94,14 @@ fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c.gboolean {
return c.FALSE; return c.FALSE;
}; };
const grid_size = surface.core_surface.size.grid();
var buf: [32]u8 = undefined; var buf: [32]u8 = undefined;
const text = std.fmt.bufPrintZ( const text = std.fmt.bufPrintZ(
&buf, &buf,
"{d}c {d}r", "{d}c {d}r",
.{ .{
surface.core_surface.grid_size.columns, grid_size.columns,
surface.core_surface.grid_size.rows, grid_size.rows,
}, },
) catch |err| { ) catch |err| {
log.err("unable to format text: {}", .{err}); log.err("unable to format text: {}", .{err});

View File

@ -177,29 +177,30 @@ fn beforeSend(
const obj = sentry.Value.initObject(); const obj = sentry.Value.initObject();
errdefer obj.decref(); errdefer obj.decref();
const surface = thr_state.surface; const surface = thr_state.surface;
const grid_size = surface.size.grid();
obj.set( obj.set(
"screen-width", "screen-width",
sentry.Value.initInt32(std.math.cast(i32, surface.screen_size.width) orelse -1), sentry.Value.initInt32(std.math.cast(i32, surface.size.screen.width) orelse -1),
); );
obj.set( obj.set(
"screen-height", "screen-height",
sentry.Value.initInt32(std.math.cast(i32, surface.screen_size.height) orelse -1), sentry.Value.initInt32(std.math.cast(i32, surface.size.screen.height) orelse -1),
); );
obj.set( obj.set(
"grid-columns", "grid-columns",
sentry.Value.initInt32(std.math.cast(i32, surface.grid_size.columns) orelse -1), sentry.Value.initInt32(std.math.cast(i32, grid_size.columns) orelse -1),
); );
obj.set( obj.set(
"grid-rows", "grid-rows",
sentry.Value.initInt32(std.math.cast(i32, surface.grid_size.rows) orelse -1), sentry.Value.initInt32(std.math.cast(i32, grid_size.rows) orelse -1),
); );
obj.set( obj.set(
"cell-width", "cell-width",
sentry.Value.initInt32(std.math.cast(i32, surface.cell_size.width) orelse -1), sentry.Value.initInt32(std.math.cast(i32, surface.size.cell.width) orelse -1),
); );
obj.set( obj.set(
"cell-height", "cell-height",
sentry.Value.initInt32(std.math.cast(i32, surface.cell_size.height) orelse -1), sentry.Value.initInt32(std.math.cast(i32, surface.size.cell.height) orelse -1),
); );
contexts.set("Dimensions", obj); contexts.set("Dimensions", obj);

View File

@ -11,6 +11,7 @@ const cimgui = @import("cimgui");
const Surface = @import("../Surface.zig"); const Surface = @import("../Surface.zig");
const font = @import("../font/main.zig"); const font = @import("../font/main.zig");
const input = @import("../input.zig"); const input = @import("../input.zig");
const renderer = @import("../renderer.zig");
const terminal = @import("../terminal/main.zig"); const terminal = @import("../terminal/main.zig");
const inspector = @import("main.zig"); const inspector = @import("main.zig");
@ -641,8 +642,8 @@ fn renderSizeWindow(self: *Inspector) void {
_ = cimgui.c.igTableSetColumnIndex(1); _ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText( cimgui.c.igText(
"%dpx x %dpx", "%dpx x %dpx",
self.surface.screen_size.width, self.surface.size.screen.width,
self.surface.screen_size.height, self.surface.size.screen.height,
); );
} }
} }
@ -656,10 +657,11 @@ fn renderSizeWindow(self: *Inspector) void {
} }
{ {
_ = cimgui.c.igTableSetColumnIndex(1); _ = cimgui.c.igTableSetColumnIndex(1);
const grid_size = self.surface.size.grid();
cimgui.c.igText( cimgui.c.igText(
"%dc x %dr", "%dc x %dr",
self.surface.grid_size.columns, grid_size.columns,
self.surface.grid_size.rows, grid_size.rows,
); );
} }
} }
@ -675,8 +677,8 @@ fn renderSizeWindow(self: *Inspector) void {
_ = cimgui.c.igTableSetColumnIndex(1); _ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText( cimgui.c.igText(
"%dpx x %dpx", "%dpx x %dpx",
self.surface.cell_size.width, self.surface.size.cell.width,
self.surface.cell_size.height, self.surface.size.cell.height,
); );
} }
} }
@ -692,10 +694,10 @@ fn renderSizeWindow(self: *Inspector) void {
_ = cimgui.c.igTableSetColumnIndex(1); _ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText( cimgui.c.igText(
"T=%d B=%d L=%d R=%d px", "T=%d B=%d L=%d R=%d px",
self.surface.padding.top, self.surface.size.padding.top,
self.surface.padding.bottom, self.surface.size.padding.bottom,
self.surface.padding.left, self.surface.size.padding.left,
self.surface.padding.right, self.surface.size.padding.right,
); );
} }
} }
@ -785,7 +787,13 @@ fn renderSizeWindow(self: *Inspector) void {
} }
{ {
const adjusted = self.surface.posAdjusted(self.mouse.last_xpos, self.mouse.last_ypos); const coord: renderer.Coordinate.Terminal = (renderer.Coordinate{
.surface = .{
.x = self.mouse.last_xpos,
.y = self.mouse.last_ypos,
},
}).convert(.terminal, self.surface.size).terminal;
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0); cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
{ {
_ = cimgui.c.igTableSetColumnIndex(0); _ = cimgui.c.igTableSetColumnIndex(0);
@ -795,8 +803,8 @@ fn renderSizeWindow(self: *Inspector) void {
_ = cimgui.c.igTableSetColumnIndex(1); _ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText( cimgui.c.igText(
"(%dpx, %dpx)", "(%dpx, %dpx)",
@as(i64, @intFromFloat(adjusted.x)), @as(i64, @intFromFloat(coord.x)),
@as(i64, @intFromFloat(adjusted.y)), @as(i64, @intFromFloat(coord.y)),
); );
} }
} }

View File

@ -24,6 +24,8 @@ pub const Thread = @import("renderer/Thread.zig");
pub const State = @import("renderer/State.zig"); pub const State = @import("renderer/State.zig");
pub const CursorStyle = cursor.Style; pub const CursorStyle = cursor.Style;
pub const Message = message.Message; pub const Message = message.Message;
pub const Size = size.Size;
pub const Coordinate = size.Coordinate;
pub const CellSize = size.CellSize; pub const CellSize = size.CellSize;
pub const ScreenSize = size.ScreenSize; pub const ScreenSize = size.ScreenSize;
pub const GridSize = size.GridSize; pub const GridSize = size.GridSize;

View File

@ -70,12 +70,8 @@ surface_mailbox: apprt.surface.Mailbox,
/// Current font metrics defining our grid. /// Current font metrics defining our grid.
grid_metrics: font.face.Metrics, grid_metrics: font.face.Metrics,
/// Current screen size dimensions for this grid. This is set on the first /// The size of everything.
/// resize event, and is not immediately available. size: renderer.Size,
screen_size: ?renderer.ScreenSize,
/// Explicit padding.
padding: renderer.Options.Padding,
/// True if the window is focused /// True if the window is focused
focused: bool, focused: bool,
@ -626,13 +622,12 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
}; };
errdefer if (display_link) |v| v.release(); errdefer if (display_link) |v| v.release();
return Metal{ var result: Metal = .{
.alloc = alloc, .alloc = alloc,
.config = options.config, .config = options.config,
.surface_mailbox = options.surface_mailbox, .surface_mailbox = options.surface_mailbox,
.grid_metrics = font_critical.metrics, .grid_metrics = font_critical.metrics,
.screen_size = null, .size = options.size,
.padding = options.padding,
.focused = true, .focused = true,
.foreground_color = options.config.foreground, .foreground_color = options.config.foreground,
.background_color = options.config.background, .background_color = options.config.background,
@ -668,6 +663,12 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
.custom_shader_state = custom_shader_state, .custom_shader_state = custom_shader_state,
.gpu_state = gpu_state, .gpu_state = gpu_state,
}; };
// Do an initialize screen size setup to ensure our undefined values
// above are initialized.
try result.setScreenSize(result.size);
return result;
} }
pub fn deinit(self: *Metal) void { pub fn deinit(self: *Metal) void {
@ -796,19 +797,6 @@ pub fn hasVsync(self: *const Metal) bool {
return display_link.isRunning(); return display_link.isRunning();
} }
/// Returns the grid size for a given screen size. This is safe to call
/// on any thread.
fn gridSize(self: *Metal) ?renderer.GridSize {
const screen_size = self.screen_size orelse return null;
return renderer.GridSize.init(
screen_size.subPadding(self.padding.explicit),
.{
.width = self.grid_metrics.cell_width,
.height = self.grid_metrics.cell_height,
},
);
}
/// Callback when the focus changes for the terminal this is rendering. /// Callback when the focus changes for the terminal this is rendering.
/// ///
/// Must be called on the render thread. /// Must be called on the render thread.
@ -878,15 +866,13 @@ pub fn setFontGrid(self: *Metal, grid: *font.SharedGrid) void {
// //
// If the screen size isn't set, it will be eventually so that'll call // If the screen size isn't set, it will be eventually so that'll call
// the setScreenSize automatically. // the setScreenSize automatically.
if (self.screen_size) |size| { self.setScreenSize(self.size) catch |err| {
self.setScreenSize(size, self.padding.explicit) catch |err| { // The setFontGrid function can't fail but resizing our cell
// The setFontGrid function can't fail but resizing our cell // buffer definitely can fail. If it does, our renderer is probably
// buffer definitely can fail. If it does, our renderer is probably // screwed but let's just log it and continue until we can figure
// screwed but let's just log it and continue until we can figure // out a better way to handle this.
// out a better way to handle this. log.err("error resizing cells buffer err={}", .{err});
log.err("error resizing cells buffer err={}", .{err}); };
};
}
} }
/// Update the frame data. /// Update the frame data.
@ -898,9 +884,6 @@ pub fn updateFrame(
) !void { ) !void {
_ = surface; _ = surface;
// If we don't have a screen size yet then we can't render anything.
if (self.screen_size == null) return;
// Data we extract out of the critical area. // Data we extract out of the critical area.
const Critical = struct { const Critical = struct {
bg: terminal.color.RGB, bg: terminal.color.RGB,
@ -1116,9 +1099,6 @@ pub fn updateFrame(
pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void { pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
_ = surface; _ = surface;
// If we don't have a screen size yet then we can't render anything.
if (self.screen_size == null) return;
// If we have no cells rebuilt we can usually skip drawing since there // If we have no cells rebuilt we can usually skip drawing since there
// is no changed data. However, if we have active animations we still // is no changed data. However, if we have active animations we still
// need to draw so that we can update the time uniform and render the // need to draw so that we can update the time uniform and render the
@ -1972,38 +1952,19 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
/// Resize the screen. /// Resize the screen.
pub fn setScreenSize( pub fn setScreenSize(
self: *Metal, self: *Metal,
dim: renderer.ScreenSize, size: renderer.Size,
pad: renderer.Padding,
) !void { ) !void {
// Store our sizes // Store our sizes
self.screen_size = dim; self.size = size;
self.padding.explicit = pad; const grid_size = size.grid();
const terminal_size = size.terminal();
// Recalculate the rows/columns. This can't fail since we just set
// the screen size above.
const grid_size = self.gridSize().?;
// Determine if we need to pad the window. For "auto" padding, we take
// the leftover amounts on the right/bottom that don't fit a full grid cell
// and we split them equal across all boundaries.
const padding = if (self.padding.balance)
renderer.Padding.balanced(
dim,
grid_size,
.{
.width = self.grid_metrics.cell_width,
.height = self.grid_metrics.cell_height,
},
)
else
self.padding.explicit;
const padded_dim = dim.subPadding(padding);
// Blank space around the grid. // Blank space around the grid.
const blank: renderer.Padding = dim.blankPadding(padding, grid_size, .{ const blank: renderer.Padding = size.screen.blankPadding(
.width = self.grid_metrics.cell_width, size.padding,
.height = self.grid_metrics.cell_height, grid_size,
}).add(padding); size.cell,
).add(size.padding);
var padding_extend = self.uniforms.padding_extend; var padding_extend = self.uniforms.padding_extend;
switch (self.config.padding_color) { switch (self.config.padding_color) {
@ -2030,18 +1991,18 @@ pub fn setScreenSize(
// Set the size of the drawable surface to the bounds // Set the size of the drawable surface to the bounds
self.layer.setProperty("drawableSize", macos.graphics.Size{ self.layer.setProperty("drawableSize", macos.graphics.Size{
.width = @floatFromInt(dim.width), .width = @floatFromInt(size.screen.width),
.height = @floatFromInt(dim.height), .height = @floatFromInt(size.screen.height),
}); });
// Setup our uniforms // Setup our uniforms
const old = self.uniforms; const old = self.uniforms;
self.uniforms = .{ self.uniforms = .{
.projection_matrix = math.ortho2d( .projection_matrix = math.ortho2d(
-1 * @as(f32, @floatFromInt(padding.left)), -1 * @as(f32, @floatFromInt(size.padding.left)),
@floatFromInt(padded_dim.width + padding.right), @floatFromInt(terminal_size.width + size.padding.right),
@floatFromInt(padded_dim.height + padding.bottom), @floatFromInt(terminal_size.height + size.padding.bottom),
-1 * @as(f32, @floatFromInt(padding.top)), -1 * @as(f32, @floatFromInt(size.padding.top)),
), ),
.cell_size = .{ .cell_size = .{
@floatFromInt(self.grid_metrics.cell_width), @floatFromInt(self.grid_metrics.cell_width),
@ -2082,8 +2043,8 @@ pub fn setScreenSize(
} }
state.uniforms.resolution = .{ state.uniforms.resolution = .{
@floatFromInt(dim.width), @floatFromInt(size.screen.width),
@floatFromInt(dim.height), @floatFromInt(size.screen.height),
1, 1,
}; };
@ -2097,8 +2058,8 @@ pub fn setScreenSize(
break :init id_init; break :init id_init;
}; };
desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm)); desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm));
desc.setProperty("width", @as(c_ulong, @intCast(dim.width))); desc.setProperty("width", @as(c_ulong, @intCast(size.screen.width)));
desc.setProperty("height", @as(c_ulong, @intCast(dim.height))); desc.setProperty("height", @as(c_ulong, @intCast(size.screen.height)));
desc.setProperty( desc.setProperty(
"usage", "usage",
@intFromEnum(mtl.MTLTextureUsage.render_target) | @intFromEnum(mtl.MTLTextureUsage.render_target) |
@ -2127,8 +2088,8 @@ pub fn setScreenSize(
break :init id_init; break :init id_init;
}; };
desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm)); desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm));
desc.setProperty("width", @as(c_ulong, @intCast(dim.width))); desc.setProperty("width", @as(c_ulong, @intCast(size.screen.width)));
desc.setProperty("height", @as(c_ulong, @intCast(dim.height))); desc.setProperty("height", @as(c_ulong, @intCast(size.screen.height)));
desc.setProperty( desc.setProperty(
"usage", "usage",
@intFromEnum(mtl.MTLTextureUsage.render_target) | @intFromEnum(mtl.MTLTextureUsage.render_target) |
@ -2148,7 +2109,7 @@ pub fn setScreenSize(
}; };
} }
log.debug("screen size screen={} grid={}, cell_width={} cell_height={}", .{ dim, grid_size, self.grid_metrics.cell_width, self.grid_metrics.cell_height }); log.debug("screen size size={}", .{size});
} }
/// Convert the terminal state to GPU cells stored in CPU memory. These /// Convert the terminal state to GPU cells stored in CPU memory. These

View File

@ -51,10 +51,8 @@ config: DerivedConfig,
/// Current font metrics defining our grid. /// Current font metrics defining our grid.
grid_metrics: font.face.Metrics, grid_metrics: font.face.Metrics,
/// Current screen size dimensions for this grid. This is set on the first /// The size of everything.
/// resize event, and is not immediately available. size: renderer.Size,
screen_size: ?renderer.ScreenSize,
grid_size: renderer.GridSize,
/// The current set of cells to render. Each set of cells goes into /// The current set of cells to render. Each set of cells goes into
/// a separate shader call. /// a separate shader call.
@ -108,9 +106,6 @@ cursor_color: ?terminal.color.RGB,
/// foreground color as the cursor color. /// foreground color as the cursor color.
cursor_invert: bool, cursor_invert: bool,
/// Padding options
padding: renderer.Options.Padding,
/// The mailbox for communicating with the window. /// The mailbox for communicating with the window.
surface_mailbox: apprt.surface.Mailbox, surface_mailbox: apprt.surface.Mailbox,
@ -141,7 +136,7 @@ image_virtual: bool = false,
/// Defererred OpenGL operation to update the screen size. /// Defererred OpenGL operation to update the screen size.
const SetScreenSize = struct { const SetScreenSize = struct {
size: renderer.ScreenSize, size: renderer.Size,
fn apply(self: SetScreenSize, r: *OpenGL) !void { fn apply(self: SetScreenSize, r: *OpenGL) !void {
const gl_state: *GLState = if (r.gl_state) |*v| const gl_state: *GLState = if (r.gl_state) |*v|
@ -150,19 +145,8 @@ const SetScreenSize = struct {
return error.OpenGLUninitialized; return error.OpenGLUninitialized;
// Apply our padding // Apply our padding
const grid_size = r.gridSize(self.size); const grid_size = self.size.grid();
const padding = if (r.padding.balance) const terminal_size = self.size.terminal();
renderer.Padding.balanced(
self.size,
grid_size,
.{
.width = r.grid_metrics.cell_width,
.height = r.grid_metrics.cell_height,
},
)
else
r.padding.explicit;
const padded_size = self.size.subPadding(padding);
// Blank space around the grid. // Blank space around the grid.
const blank: renderer.Padding = switch (r.config.padding_color) { const blank: renderer.Padding = switch (r.config.padding_color) {
@ -170,30 +154,20 @@ const SetScreenSize = struct {
// clear color. // clear color.
.background => .{}, .background => .{},
.extend, .@"extend-always" => self.size.blankPadding(padding, grid_size, .{ .extend, .@"extend-always" => self.size.screen.blankPadding(
.width = r.grid_metrics.cell_width, self.size.padding,
.height = r.grid_metrics.cell_height, grid_size,
}).add(padding), self.size.cell,
).add(self.size.padding),
}; };
log.debug("GL api: screen size padded={} screen={} grid={} cell={} padding={}", .{
padded_size,
self.size,
r.gridSize(self.size),
renderer.CellSize{
.width = r.grid_metrics.cell_width,
.height = r.grid_metrics.cell_height,
},
r.padding.explicit,
});
// Update our viewport for this context to be the entire window. // Update our viewport for this context to be the entire window.
// OpenGL works in pixels, so we have to use the pixel size. // OpenGL works in pixels, so we have to use the pixel size.
try gl.viewport( try gl.viewport(
0, 0,
0, 0,
@intCast(self.size.width), @intCast(self.size.screen.width),
@intCast(self.size.height), @intCast(self.size.screen.height),
); );
// Update the projection uniform within our shader // Update the projection uniform within our shader
@ -206,10 +180,10 @@ const SetScreenSize = struct {
// 2D orthographic projection with the full w/h // 2D orthographic projection with the full w/h
math.ortho2d( math.ortho2d(
-1 * @as(f32, @floatFromInt(padding.left)), -1 * @as(f32, @floatFromInt(self.size.padding.left)),
@floatFromInt(padded_size.width + padding.right), @floatFromInt(terminal_size.width + self.size.padding.right),
@floatFromInt(padded_size.height + padding.bottom), @floatFromInt(terminal_size.height + self.size.padding.bottom),
-1 * @as(f32, @floatFromInt(padding.top)), -1 * @as(f32, @floatFromInt(self.size.padding.top)),
), ),
); );
} }
@ -405,8 +379,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
.cells_bg = .{}, .cells_bg = .{},
.cells = .{}, .cells = .{},
.grid_metrics = grid.metrics, .grid_metrics = grid.metrics,
.screen_size = null, .size = options.size,
.grid_size = .{},
.gl_state = gl_state, .gl_state = gl_state,
.font_grid = grid, .font_grid = grid,
.font_shaper = shaper, .font_shaper = shaper,
@ -417,7 +390,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
.background_color = options.config.background, .background_color = options.config.background,
.cursor_color = options.config.cursor_color, .cursor_color = options.config.cursor_color,
.cursor_invert = options.config.cursor_invert, .cursor_invert = options.config.cursor_invert,
.padding = options.padding,
.surface_mailbox = options.surface_mailbox, .surface_mailbox = options.surface_mailbox,
.deferred_font_size = .{ .metrics = grid.metrics }, .deferred_font_size = .{ .metrics = grid.metrics },
.deferred_config = .{}, .deferred_config = .{},
@ -558,9 +530,7 @@ pub fn displayRealize(self: *OpenGL) !void {
self.texture_color_resized = 0; self.texture_color_resized = 0;
// We need to reset our uniforms // We need to reset our uniforms
if (self.screen_size) |size| { self.deferred_screen_size = .{ .size = self.size };
self.deferred_screen_size = .{ .size = size };
}
self.deferred_font_size = .{ .metrics = self.grid_metrics }; self.deferred_font_size = .{ .metrics = self.grid_metrics };
self.deferred_config = .{}; self.deferred_config = .{};
} }
@ -688,15 +658,9 @@ pub fn setFontGrid(self: *OpenGL, grid: *font.SharedGrid) void {
self.font_shaper_cache.deinit(self.alloc); self.font_shaper_cache.deinit(self.alloc);
self.font_shaper_cache = font_shaper_cache; self.font_shaper_cache = font_shaper_cache;
if (self.screen_size) |size| { // Update our screen size because the font grid can affect grid
// Update our grid size if we have a screen size. If we don't, its okay // metrics which update uniforms.
// because this will get set when we get the screen size set. self.deferred_screen_size = .{ .size = self.size };
self.grid_size = self.gridSize(size);
// Update our screen size because the font grid can affect grid
// metrics which update uniforms.
self.deferred_screen_size = .{ .size = size };
}
// Defer our GPU updates // Defer our GPU updates
self.deferred_font_size = .{ .metrics = grid.metrics }; self.deferred_font_size = .{ .metrics = grid.metrics };
@ -725,6 +689,8 @@ pub fn updateFrame(
// Update all our data as tightly as possible within the mutex. // Update all our data as tightly as possible within the mutex.
var critical: Critical = critical: { var critical: Critical = critical: {
const grid_size = self.size.grid();
state.mutex.lock(); state.mutex.lock();
defer state.mutex.unlock(); defer state.mutex.unlock();
@ -753,8 +719,8 @@ pub fn updateFrame(
// //
// For some reason this doesn't seem to cause any significant issues // For some reason this doesn't seem to cause any significant issues
// with flickering while resizing. '\_('-')_/' // with flickering while resizing. '\_('-')_/'
if (self.grid_size.rows != state.terminal.rows or if (grid_size.rows != state.terminal.rows or
self.grid_size.columns != state.terminal.cols) grid_size.columns != state.terminal.cols)
{ {
return; return;
} }
@ -1314,7 +1280,7 @@ pub fn rebuildCells(
color_palette, color_palette,
self.background_color, self.background_color,
); );
} else if (y == self.grid_size.rows - 1) { } else if (y == self.size.grid().rows - 1) {
self.padding_extend_bottom = !row.neverExtendBg( self.padding_extend_bottom = !row.neverExtendBg(
color_palette, color_palette,
self.background_color, self.background_color,
@ -2115,18 +2081,6 @@ fn addGlyph(
}); });
} }
/// Returns the grid size for a given screen size. This is safe to call
/// on any thread.
fn gridSize(self: *const OpenGL, screen_size: renderer.ScreenSize) renderer.GridSize {
return renderer.GridSize.init(
screen_size.subPadding(self.padding.explicit),
.{
.width = self.grid_metrics.cell_width,
.height = self.grid_metrics.cell_height,
},
);
}
/// Update the configuration. /// Update the configuration.
pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void { pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
// We always redo the font shaper in case font features changed. We // We always redo the font shaper in case font features changed. We
@ -2164,8 +2118,7 @@ pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
/// used for the shader so that the scaling of the grid is correct. /// used for the shader so that the scaling of the grid is correct.
pub fn setScreenSize( pub fn setScreenSize(
self: *OpenGL, self: *OpenGL,
dim: renderer.ScreenSize, size: renderer.Size,
pad: renderer.Padding,
) !void { ) !void {
if (single_threaded_draw) self.draw_mutex.lock(); if (single_threaded_draw) self.draw_mutex.lock();
defer if (single_threaded_draw) self.draw_mutex.unlock(); defer if (single_threaded_draw) self.draw_mutex.unlock();
@ -2177,22 +2130,12 @@ pub fn setScreenSize(
self.cells_bg.clearAndFree(self.alloc); self.cells_bg.clearAndFree(self.alloc);
// Store our screen size // Store our screen size
self.screen_size = dim; self.size = size;
self.padding.explicit = pad;
self.grid_size = self.gridSize(dim);
log.debug("screen size screen={} grid={} cell={} padding={}", .{
dim,
self.grid_size,
renderer.CellSize{
.width = self.grid_metrics.cell_width,
.height = self.grid_metrics.cell_height,
},
self.padding.explicit,
});
// Defer our OpenGL updates // Defer our OpenGL updates
self.deferred_screen_size = .{ .size = dim }; self.deferred_screen_size = .{ .size = size };
log.debug("screen size size={}", .{size});
} }
/// Updates the font texture atlas if it is dirty. /// Updates the font texture atlas if it is dirty.

View File

@ -11,8 +11,8 @@ config: renderer.Renderer.DerivedConfig,
/// The font grid that should be used along with the key for deref-ing. /// The font grid that should be used along with the key for deref-ing.
font_grid: *font.SharedGrid, font_grid: *font.SharedGrid,
/// Padding options for the viewport. /// The size of everything.
padding: Padding, size: renderer.Size,
/// The mailbox for sending the surface messages. This is only valid /// The mailbox for sending the surface messages. This is only valid
/// once the thread has started and should not be used outside of the thread. /// once the thread has started and should not be used outside of the thread.
@ -20,12 +20,3 @@ surface_mailbox: apprt.surface.Mailbox,
/// The apprt surface. /// The apprt surface.
rt_surface: *apprt.Surface, rt_surface: *apprt.Surface,
pub const Padding = struct {
// Explicit padding options, in pixels. The surface thread is
// expected to convert points to pixels for a given DPI.
explicit: renderer.Padding,
// Balance options
balance: bool = false,
};

View File

@ -371,9 +371,7 @@ fn drainMailbox(self: *Thread) !void {
self.renderer.markDirty(); self.renderer.markDirty();
}, },
.resize => |v| { .resize => |v| try self.renderer.setScreenSize(v),
try self.renderer.setScreenSize(v.screen_size, v.padding);
},
.change_config => |config| { .change_config => |config| {
defer config.alloc.destroy(config.thread); defer config.alloc.destroy(config.thread);

View File

@ -54,14 +54,8 @@ pub const Message = union(enum) {
/// config file in response to an OSC 12 command. /// config file in response to an OSC 12 command.
cursor_color: ?terminal.color.RGB, cursor_color: ?terminal.color.RGB,
/// Changes the screen size. /// Changes the size. The screen size might change, padding, grid, etc.
resize: struct { resize: renderer.Size,
/// The full screen (drawable) size. This does NOT include padding.
screen_size: renderer.ScreenSize,
/// The explicit padding values.
padding: renderer.Padding,
},
/// The derived configuration to update the renderer with. /// The derived configuration to update the renderer with.
change_config: struct { change_config: struct {

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const gl = @import("opengl"); const gl = @import("opengl");
const ScreenSize = @import("../size.zig").ScreenSize; const Size = @import("../size.zig").Size;
const log = std.log.scoped(.opengl_custom); const log = std.log.scoped(.opengl_custom);
@ -154,11 +154,11 @@ pub const State = struct {
self.fbo.destroy(); self.fbo.destroy();
} }
pub fn setScreenSize(self: *State, size: ScreenSize) !void { pub fn setScreenSize(self: *State, size: Size) !void {
// Update our uniforms // Update our uniforms
self.uniforms.resolution = .{ self.uniforms.resolution = .{
@floatFromInt(size.width), @floatFromInt(size.screen.width),
@floatFromInt(size.height), @floatFromInt(size.screen.height),
1, 1,
}; };
try self.syncUniforms(); try self.syncUniforms();
@ -168,8 +168,8 @@ pub const State = struct {
try texbind.image2D( try texbind.image2D(
0, 0,
.rgb, .rgb,
@intCast(size.width), @intCast(size.screen.width),
@intCast(size.height), @intCast(size.screen.height),
0, 0,
.rgb, .rgb,
.UnsignedByte, .UnsignedByte,

View File

@ -5,6 +5,139 @@ const terminal = @import("../terminal/main.zig");
const log = std.log.scoped(.renderer_size); const log = std.log.scoped(.renderer_size);
/// All relevant sizes for a rendered terminal. These are all the sizes that
/// any functionality should need to know about the terminal in order to
/// convert between any coordinate systems.
///
/// See the individual field type documentation for more information on each
/// field. One important note is that any pixel values should already be scaled
/// to the current DPI of the screen. If the DPI changes, the sizes should be
/// recalculated and we expect this to be done by the caller.
pub const Size = struct {
screen: ScreenSize,
cell: CellSize,
padding: Padding,
/// Return the grid size for this size. The grid size is calculated by
/// taking the screen size, removing padding, and dividing by the cell
/// dimensions.
pub fn grid(self: Size) GridSize {
return GridSize.init(self.screen.subPadding(self.padding), self.cell);
}
/// The size of the terminal. This is the same as the screen without
/// padding.
pub fn terminal(self: Size) ScreenSize {
return self.screen.subPadding(self.padding);
}
/// Set the padding to be balanced around the grid. Overwrites the current
/// padding.
pub fn balancePadding(self: *Size) void {
self.padding = Padding.balanced(
self.screen,
self.grid(),
self.cell,
);
}
};
/// A coordinate. This is defined as a tagged union to allow for different
/// coordinate systems to be represented.
///
/// A coordinate is only valid within the context of a stable Size value.
/// If any of the sizes in the Size struct change, the coordinate is no
/// longer valid and must be recalculated. A conversion function is provided
/// to migrate to a new Size (which may result in failure).
///
/// The coordinate systems are:
///
/// * surface: (0, 0) is the top-left of the surface (with padding). Negative
/// values are allowed and are off the surface. Likewise, values greater
/// than the surface size are off the surface. Units are pixels.
///
/// * terminal: (0, 0) is the top-left of the terminal grid. This is the
/// same as the surface but with the padding removed. Negative values and
/// values greater than the grid size are allowed and are off the terminal.
/// Units are pixels.
///
/// * grid: (0, 0) is the top-left of the grid. Units are in cells. Negative
/// values are not allowed but values greater than the grid size are
/// possible and are off the grid.
///
pub const Coordinate = union(enum) {
surface: Surface,
terminal: Terminal,
grid: Grid,
pub const Tag = @typeInfo(Coordinate).Union.tag_type.?;
pub const Surface = struct { x: f64, y: f64 };
pub const Terminal = struct { x: f64, y: f64 };
pub const Grid = struct { x: GridSize.Unit, y: GridSize.Unit };
/// Convert a coordinate to a different space within the same Size.
pub fn convert(self: Coordinate, to: Tag, size: Size) Coordinate {
// Unlikely fast-path but avoid work.
if (@as(Tag, self) == to) return self;
// To avoid the combinatorial explosion of conversion functions, we
// convert to the surface system first and then reconvert from there.
const surface = self.convertToSurface(size);
return switch (to) {
.surface => .{ .surface = surface },
.terminal => .{ .terminal = .{
.x = surface.x - @as(f64, @floatFromInt(size.padding.left)),
.y = surface.y - @as(f64, @floatFromInt(size.padding.top)),
} },
.grid => grid: {
// Get rid of the padding.
const term = (Coordinate{ .surface = surface }).convert(
.terminal,
size,
).terminal;
// We need our grid to clamp
const grid = size.grid();
// Calculate the grid position.
const cell_width: f64 = @as(f64, @floatFromInt(size.cell.width));
const cell_height: f64 = @as(f64, @floatFromInt(size.cell.height));
const clamped_x: f64 = @max(0, term.x);
const clamped_y: f64 = @max(0, term.y);
const col: GridSize.Unit = @intFromFloat(clamped_x / cell_width);
const row: GridSize.Unit = @intFromFloat(clamped_y / cell_height);
const clamped_col: GridSize.Unit = @min(col, grid.columns - 1);
const clamped_row: GridSize.Unit = @min(row, grid.rows - 1);
break :grid .{ .grid = .{ .x = clamped_col, .y = clamped_row } };
},
};
}
/// Convert a coordinate to the surface coordinate system.
fn convertToSurface(self: Coordinate, size: Size) Surface {
return switch (self) {
.surface => |v| v,
.terminal => |v| .{
.x = v.x + @as(f64, @floatFromInt(size.padding.left)),
.y = v.y + @as(f64, @floatFromInt(size.padding.top)),
},
.grid => |v| grid: {
const col: f64 = @floatFromInt(v.x);
const row: f64 = @floatFromInt(v.y);
const cell_width: f64 = @floatFromInt(size.cell.width);
const cell_height: f64 = @floatFromInt(size.cell.height);
const padding_left: f64 = @floatFromInt(size.padding.left);
const padding_top: f64 = @floatFromInt(size.padding.top);
break :grid .{
.x = col * cell_width + padding_left,
.y = row * cell_height + padding_top,
};
},
};
}
};
/// The dimensions of a single "cell" in the terminal grid. /// The dimensions of a single "cell" in the terminal grid.
/// ///
/// The dimensions are dependent on the current loaded set of font glyphs. /// The dimensions are dependent on the current loaded set of font glyphs.
@ -67,7 +200,7 @@ pub const ScreenSize = struct {
/// The dimensions of the grid itself, in rows/columns units. /// The dimensions of the grid itself, in rows/columns units.
pub const GridSize = struct { pub const GridSize = struct {
const Unit = terminal.size.CellCountInt; pub const Unit = terminal.size.CellCountInt;
columns: Unit = 0, columns: Unit = 0,
rows: Unit = 0, rows: Unit = 0,
@ -201,3 +334,54 @@ test "GridSize update rounding" {
try testing.expectEqual(@as(GridSize.Unit, 3), grid.columns); try testing.expectEqual(@as(GridSize.Unit, 3), grid.columns);
try testing.expectEqual(@as(GridSize.Unit, 2), grid.rows); try testing.expectEqual(@as(GridSize.Unit, 2), grid.rows);
} }
test "coordinate conversion" {
const testing = std.testing;
// A size for testing purposes. Purposely easy to calculate numbers.
const test_size: Size = .{
.screen = .{
.width = 100,
.height = 100,
},
.cell = .{
.width = 5,
.height = 10,
},
.padding = .{},
};
// Each pair is a test case of [expected, actual]. We only test
// one-way conversion because conversion can be lossy due to clamping
// and so on.
const table: []const [2]Coordinate = &.{
.{
.{ .grid = .{ .x = 0, .y = 0 } },
.{ .surface = .{ .x = 0, .y = 0 } },
},
.{
.{ .grid = .{ .x = 1, .y = 0 } },
.{ .surface = .{ .x = 6, .y = 0 } },
},
.{
.{ .grid = .{ .x = 1, .y = 1 } },
.{ .surface = .{ .x = 6, .y = 10 } },
},
.{
.{ .grid = .{ .x = 0, .y = 0 } },
.{ .surface = .{ .x = -10, .y = -10 } },
},
.{
.{ .grid = .{ .x = test_size.grid().columns - 1, .y = test_size.grid().rows - 1 } },
.{ .surface = .{ .x = 100_000, .y = 100_000 } },
},
};
for (table) |pair| {
const expected = pair[0];
const actual = pair[1].convert(@as(Coordinate.Tag, expected), test_size);
try testing.expectEqual(expected, actual);
}
}

View File

@ -8,17 +8,8 @@ const Command = @import("../Command.zig");
const Config = @import("../config.zig").Config; const Config = @import("../config.zig").Config;
const termio = @import("../termio.zig"); const termio = @import("../termio.zig");
/// The size of the terminal grid. /// All size metrics for the terminal.
grid_size: renderer.GridSize, size: renderer.Size,
/// The size of a single cell, in pixels.
cell_size: renderer.CellSize,
/// The size of the viewport in pixels.
screen_size: renderer.ScreenSize,
/// The padding of the viewport.
padding: renderer.Padding,
/// The full app configuration. This is only available during initialization. /// The full app configuration. This is only available during initialization.
/// The memory it points to is NOT stable after the init call so any values /// The memory it points to is NOT stable after the init call so any values

View File

@ -56,11 +56,8 @@ renderer_mailbox: *renderer.Thread.Mailbox,
/// The mailbox for communicating with the surface. /// The mailbox for communicating with the surface.
surface_mailbox: apprt.surface.Mailbox, surface_mailbox: apprt.surface.Mailbox,
/// The cached grid size whenever a resize is called. /// The cached size info
grid_size: renderer.GridSize, size: renderer.Size,
/// The size of a single cell. Used for size reports.
cell_size: renderer.CellSize,
/// The mailbox implementation to use. /// The mailbox implementation to use.
mailbox: termio.Mailbox, mailbox: termio.Mailbox,
@ -131,10 +128,13 @@ pub const DerivedConfig = struct {
/// to run a child process. /// to run a child process.
pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
// Create our terminal // Create our terminal
var term = try terminal.Terminal.init(alloc, .{ var term = try terminal.Terminal.init(alloc, opts: {
.cols = opts.grid_size.columns, const grid_size = opts.size.grid();
.rows = opts.grid_size.rows, break :opts .{
.max_scrollback = opts.full_config.@"scrollback-limit", .cols = grid_size.columns,
.rows = grid_size.rows,
.max_scrollback = opts.full_config.@"scrollback-limit",
};
}); });
errdefer term.deinit(alloc); errdefer term.deinit(alloc);
term.default_palette = opts.config.palette; term.default_palette = opts.config.palette;
@ -168,14 +168,14 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
// Set our default cursor style // Set our default cursor style
term.screen.cursor.cursor_style = opts.config.cursor_style; term.screen.cursor.cursor_style = opts.config.cursor_style;
// Setup our terminal size in pixels for certain requests.
term.width_px = term.cols * opts.size.cell.width;
term.height_px = term.rows * opts.size.cell.height;
// Setup our backend. // Setup our backend.
var backend = opts.backend; var backend = opts.backend;
backend.initTerminal(&term); backend.initTerminal(&term);
// Setup our terminal size in pixels for certain requests.
term.width_px = opts.grid_size.columns * opts.cell_size.width;
term.height_px = opts.grid_size.rows * opts.cell_size.height;
// Create our stream handler. This points to memory in self so it // Create our stream handler. This points to memory in self so it
// isn't safe to use until self.* is set. // isn't safe to use until self.* is set.
const handler: StreamHandler = handler: { const handler: StreamHandler = handler: {
@ -191,7 +191,7 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
.renderer_state = opts.renderer_state, .renderer_state = opts.renderer_state,
.renderer_wakeup = opts.renderer_wakeup, .renderer_wakeup = opts.renderer_wakeup,
.renderer_mailbox = opts.renderer_mailbox, .renderer_mailbox = opts.renderer_mailbox,
.grid_size = &self.grid_size, .size = &self.size,
.terminal = &self.terminal, .terminal = &self.terminal,
.osc_color_report_format = opts.config.osc_color_report_format, .osc_color_report_format = opts.config.osc_color_report_format,
.enquiry_response = opts.config.enquiry_response, .enquiry_response = opts.config.enquiry_response,
@ -214,8 +214,7 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
.renderer_wakeup = opts.renderer_wakeup, .renderer_wakeup = opts.renderer_wakeup,
.renderer_mailbox = opts.renderer_mailbox, .renderer_mailbox = opts.renderer_mailbox,
.surface_mailbox = opts.surface_mailbox, .surface_mailbox = opts.surface_mailbox,
.grid_size = opts.grid_size, .size = opts.size,
.cell_size = opts.cell_size,
.backend = opts.backend, .backend = opts.backend,
.mailbox = opts.mailbox, .mailbox = opts.mailbox,
.terminal_stream = .{ .terminal_stream = .{
@ -349,18 +348,12 @@ pub fn changeConfig(self: *Termio, td: *ThreadData, config: *DerivedConfig) !voi
pub fn resize( pub fn resize(
self: *Termio, self: *Termio,
td: *ThreadData, td: *ThreadData,
grid_size: renderer.GridSize, size: renderer.Size,
cell_size: renderer.CellSize,
screen_size: renderer.ScreenSize,
padding: renderer.Padding,
) !void { ) !void {
// Update the size of our pty. const grid_size = size.grid();
const padded_size = screen_size.subPadding(padding);
try self.backend.resize(grid_size, padded_size);
// Update our cached grid size // Update the size of our pty.
self.grid_size = grid_size; try self.backend.resize(grid_size, size.terminal());
self.cell_size = cell_size;
// Enter the critical area that we want to keep small // Enter the critical area that we want to keep small
{ {
@ -375,8 +368,8 @@ pub fn resize(
); );
// Update our pixel sizes // Update our pixel sizes
self.terminal.width_px = self.grid_size.columns * self.cell_size.width; self.terminal.width_px = grid_size.columns * self.size.cell.width;
self.terminal.height_px = self.grid_size.rows * self.cell_size.height; self.terminal.height_px = grid_size.rows * self.size.cell.height;
// Disable synchronized output mode so that we show changes // Disable synchronized output mode so that we show changes
// immediately for a resize. This is allowed by the spec. // immediately for a resize. This is allowed by the spec.
@ -389,12 +382,7 @@ pub fn resize(
} }
// Mail the renderer so that it can update the GPU and re-render // Mail the renderer so that it can update the GPU and re-render
_ = self.renderer_mailbox.push(.{ _ = self.renderer_mailbox.push(.{ .resize = size }, .{ .forever = {} });
.resize = .{
.screen_size = screen_size,
.padding = padding,
},
}, .{ .forever = {} });
self.renderer_wakeup.notify() catch {}; self.renderer_wakeup.notify() catch {};
} }
@ -406,6 +394,8 @@ pub fn sizeReport(self: *Termio, td: *ThreadData, style: termio.Message.SizeRepo
} }
fn sizeReportLocked(self: *Termio, td: *ThreadData, style: termio.Message.SizeReport) !void { fn sizeReportLocked(self: *Termio, td: *ThreadData, style: termio.Message.SizeReport) !void {
const grid_size = self.size.grid();
// 1024 bytes should be enough for size report since report // 1024 bytes should be enough for size report since report
// in columns and pixels. // in columns and pixels.
var buf: [1024]u8 = undefined; var buf: [1024]u8 = undefined;
@ -414,34 +404,34 @@ fn sizeReportLocked(self: *Termio, td: *ThreadData, style: termio.Message.SizeRe
&buf, &buf,
"\x1B[48;{};{};{};{}t", "\x1B[48;{};{};{};{}t",
.{ .{
self.grid_size.rows, grid_size.rows,
self.grid_size.columns, grid_size.columns,
self.grid_size.rows * self.cell_size.height, grid_size.rows * self.size.cell.height,
self.grid_size.columns * self.cell_size.width, grid_size.columns * self.size.cell.width,
}, },
), ),
.csi_14_t => try std.fmt.bufPrint( .csi_14_t => try std.fmt.bufPrint(
&buf, &buf,
"\x1b[4;{};{}t", "\x1b[4;{};{}t",
.{ .{
self.grid_size.rows * self.cell_size.height, grid_size.rows * self.size.cell.height,
self.grid_size.columns * self.cell_size.width, grid_size.columns * self.size.cell.width,
}, },
), ),
.csi_16_t => try std.fmt.bufPrint( .csi_16_t => try std.fmt.bufPrint(
&buf, &buf,
"\x1b[6;{};{}t", "\x1b[6;{};{}t",
.{ .{
self.cell_size.height, self.size.cell.height,
self.cell_size.width, self.size.cell.width,
}, },
), ),
.csi_18_t => try std.fmt.bufPrint( .csi_18_t => try std.fmt.bufPrint(
&buf, &buf,
"\x1b[8;{};{}t", "\x1b[8;{};{}t",
.{ .{
self.grid_size.rows, grid_size.rows,
self.grid_size.columns, grid_size.columns,
}, },
), ),
}; };

View File

@ -17,6 +17,7 @@ const builtin = @import("builtin");
const xev = @import("xev"); const xev = @import("xev");
const crash = @import("../crash/main.zig"); const crash = @import("../crash/main.zig");
const termio = @import("../termio.zig"); const termio = @import("../termio.zig");
const renderer = @import("../renderer.zig");
const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue; const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@ -28,7 +29,7 @@ const Coalesce = struct {
/// Not all message types are coalesced. /// Not all message types are coalesced.
const min_ms = 25; const min_ms = 25;
resize: ?termio.Message.Resize = null, resize: ?renderer.Size = null,
}; };
/// The number of milliseconds before we reset the synchronized output flag /// The number of milliseconds before we reset the synchronized output flag
@ -324,7 +325,7 @@ fn startSynchronizedOutput(self: *Thread, cb: *CallbackData) void {
); );
} }
fn handleResize(self: *Thread, cb: *CallbackData, resize: termio.Message.Resize) void { fn handleResize(self: *Thread, cb: *CallbackData, resize: renderer.Size) void {
self.coalesce_data.resize = resize; self.coalesce_data.resize = resize;
// If the timer is already active we just return. In the future we want // If the timer is already active we just return. In the future we want
@ -380,13 +381,7 @@ fn coalesceCallback(
if (cb.self.coalesce_data.resize) |v| { if (cb.self.coalesce_data.resize) |v| {
cb.self.coalesce_data.resize = null; cb.self.coalesce_data.resize = null;
cb.io.resize( cb.io.resize(&cb.data, v) catch |err| {
&cb.data,
v.grid_size,
v.cell_size,
v.screen_size,
v.padding,
) catch |err| {
log.warn("error during resize err={}", .{err}); log.warn("error during resize err={}", .{err});
}; };
} }

View File

@ -16,22 +16,6 @@ pub const Message = union(enum) {
/// in the future. /// in the future.
pub const WriteReq = MessageData(u8, 38); pub const WriteReq = MessageData(u8, 38);
pub const Resize = struct {
/// The grid size for the given screen size with padding applied.
grid_size: renderer.GridSize,
/// The updated cell size.
cell_size: renderer.CellSize,
/// The full screen (drawable) size. This does NOT include padding.
/// This should be sent on to the renderer.
screen_size: renderer.ScreenSize,
/// The padding, so that the terminal implementation can subtract
/// this to send to the pty.
padding: renderer.Padding,
};
/// Purposely crash the renderer. This is used for testing and debugging. /// Purposely crash the renderer. This is used for testing and debugging.
/// See the "crash" binding action. /// See the "crash" binding action.
crash: void, crash: void,
@ -47,7 +31,7 @@ pub const Message = union(enum) {
inspector: bool, inspector: bool,
/// Resize the window. /// Resize the window.
resize: Resize, resize: renderer.Size,
/// Request a size report is sent to the pty ([in-band /// Request a size report is sent to the pty ([in-band
/// size report, mode 2048](https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83) and /// size report, mode 2048](https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83) and

View File

@ -26,7 +26,7 @@ const disable_kitty_keyboard_protocol = apprt.runtime == apprt.glfw;
/// unless all of the member fields are copied. /// unless all of the member fields are copied.
pub const StreamHandler = struct { pub const StreamHandler = struct {
alloc: Allocator, alloc: Allocator,
grid_size: *renderer.GridSize, size: *renderer.Size,
terminal: *terminal.Terminal, terminal: *terminal.Terminal,
/// Mailbox for data to the termio thread. /// Mailbox for data to the termio thread.
@ -611,12 +611,15 @@ pub const StreamHandler = struct {
}, },
// Force resize back to the window size // Force resize back to the window size
.enable_mode_3 => self.terminal.resize( .enable_mode_3 => {
self.alloc, const grid_size = self.size.grid();
self.grid_size.columns, self.terminal.resize(
self.grid_size.rows, self.alloc,
) catch |err| { grid_size.columns,
log.err("error updating terminal size: {}", .{err}); grid_size.rows,
) catch |err| {
log.err("error updating terminal size: {}", .{err});
};
}, },
.@"132_column" => try self.terminal.deccolm( .@"132_column" => try self.terminal.deccolm(