mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #2676 from ghostty-org/coordinate-space
Refactor how we pass around sizes
This commit is contained in:
297
src/Surface.zig
297
src/Surface.zig
@ -113,13 +113,8 @@ io_thr: std.Thread,
|
||||
/// Terminal inspector
|
||||
inspector: ?*inspector.Inspector = null,
|
||||
|
||||
/// All the cached sizes since we need them at various times.
|
||||
screen_size: renderer.ScreenSize,
|
||||
grid_size: renderer.GridSize,
|
||||
cell_size: renderer.CellSize,
|
||||
|
||||
/// Explicit padding due to configuration
|
||||
padding: renderer.Padding,
|
||||
/// All our sizing information.
|
||||
size: renderer.Size,
|
||||
|
||||
/// 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
|
||||
@ -329,6 +324,32 @@ const DerivedConfig = struct {
|
||||
for (self.links) |*link| link.regex.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
|
||||
@ -378,28 +399,26 @@ pub fn init(
|
||||
// Pre-calculate our initial cell size ourselves.
|
||||
const cell_size = font_grid.cellSize();
|
||||
|
||||
// Convert our padding from points to pixels
|
||||
const padding_top: u32 = padding_top: {
|
||||
const padding_top: f32 = @floatFromInt(derived_config.window_padding_top);
|
||||
break :padding_top @intFromFloat(@floor(padding_top * y_dpi / 72));
|
||||
// Build our size struct which has all the sizes we need.
|
||||
const size: renderer.Size = size: {
|
||||
var size: renderer.Size = .{
|
||||
.screen = screen: {
|
||||
const surface_size = try rt_surface.getSize();
|
||||
break :screen .{
|
||||
.width = surface_size.width,
|
||||
.height = surface_size.height,
|
||||
};
|
||||
const padding_bottom: u32 = padding_bottom: {
|
||||
const padding_bottom: f32 = @floatFromInt(derived_config.window_padding_bottom);
|
||||
break :padding_bottom @intFromFloat(@floor(padding_bottom * y_dpi / 72));
|
||||
},
|
||||
|
||||
.cell = font_grid.cellSize(),
|
||||
.padding = derived_config.scaledPadding(x_dpi, y_dpi),
|
||||
};
|
||||
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));
|
||||
};
|
||||
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));
|
||||
};
|
||||
const padding: renderer.Padding = .{
|
||||
.top = padding_top,
|
||||
.bottom = padding_bottom,
|
||||
.left = padding_left,
|
||||
.right = padding_right,
|
||||
|
||||
if (derived_config.window_padding_balance) {
|
||||
size.balancePadding();
|
||||
}
|
||||
|
||||
break :size size;
|
||||
};
|
||||
|
||||
// Create our terminal grid with the initial size
|
||||
@ -407,26 +426,12 @@ pub fn init(
|
||||
var renderer_impl = try Renderer.init(alloc, .{
|
||||
.config = try Renderer.DerivedConfig.init(alloc, config),
|
||||
.font_grid = font_grid,
|
||||
.padding = .{
|
||||
.explicit = padding,
|
||||
.balance = config.@"window-padding-balance",
|
||||
},
|
||||
.size = size,
|
||||
.surface_mailbox = .{ .surface = self, .app = app_mailbox },
|
||||
.rt_surface = rt_surface,
|
||||
});
|
||||
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.
|
||||
const mutex = try alloc.create(std.Thread.Mutex);
|
||||
mutex.* = .{};
|
||||
@ -467,10 +472,7 @@ pub fn init(
|
||||
.io = undefined,
|
||||
.io_thread = io_thread,
|
||||
.io_thr = undefined,
|
||||
.screen_size = .{ .width = 0, .height = 0 },
|
||||
.grid_size = .{},
|
||||
.cell_size = cell_size,
|
||||
.padding = padding,
|
||||
.size = size,
|
||||
.config = derived_config,
|
||||
};
|
||||
|
||||
@ -510,10 +512,7 @@ pub fn init(
|
||||
errdefer io_mailbox.deinit(alloc);
|
||||
|
||||
try termio.Termio.init(&self.io, alloc, .{
|
||||
.grid_size = grid_size,
|
||||
.cell_size = cell_size,
|
||||
.screen_size = screen_size,
|
||||
.padding = padding,
|
||||
.size = size,
|
||||
.full_config = config,
|
||||
.config = try termio.Termio.DerivedConfig.init(alloc, config),
|
||||
.backend = .{ .exec = io_exec },
|
||||
@ -532,7 +531,7 @@ pub fn init(
|
||||
try rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.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
|
||||
@ -541,8 +540,8 @@ pub fn init(
|
||||
.{ .surface = self },
|
||||
.size_limit,
|
||||
.{
|
||||
.min_width = cell_size.width * 10,
|
||||
.min_height = cell_size.height * 4,
|
||||
.min_width = size.cell.width * 10,
|
||||
.min_height = size.cell.height * 4,
|
||||
// No max:
|
||||
.max_width = 0,
|
||||
.max_height = 0,
|
||||
@ -554,7 +553,7 @@ pub fn init(
|
||||
// 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
|
||||
// to duplicate.
|
||||
try self.sizeCallback(surface_size);
|
||||
try self.resize(self.size.screen);
|
||||
|
||||
// Give the renderer one more opportunity to finalize any surface
|
||||
// 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.
|
||||
const final_width: u32 =
|
||||
@as(u32, @intFromFloat(@ceil(width_f32 / scale.x))) +
|
||||
padding.left +
|
||||
padding.right;
|
||||
size.padding.left +
|
||||
size.padding.right;
|
||||
const final_height: u32 =
|
||||
@as(u32, @intFromFloat(@ceil(height_f32 / scale.y))) +
|
||||
padding.top +
|
||||
padding.bottom;
|
||||
size.padding.top +
|
||||
size.padding.bottom;
|
||||
|
||||
rt_app.performAction(
|
||||
.{ .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.
|
||||
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: {
|
||||
// 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
|
||||
x += @floatFromInt(pad.left);
|
||||
x += @floatFromInt(self.size.padding.left);
|
||||
|
||||
// Scale
|
||||
x /= content_scale.x;
|
||||
@ -1173,14 +1166,14 @@ pub fn selectionInfo(self: *const Surface) ?apprt.Selection {
|
||||
|
||||
const y: f64 = y: {
|
||||
// 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
|
||||
y += @floatFromInt(self.cell_size.height);
|
||||
y += @floatFromInt(self.size.cell.height);
|
||||
y -= @floatFromInt(self.font_metrics.cell_baseline);
|
||||
|
||||
// Add padding
|
||||
y += @floatFromInt(pad.top);
|
||||
y += @floatFromInt(self.size.padding.top);
|
||||
|
||||
// Scale
|
||||
y /= content_scale.y;
|
||||
@ -1221,10 +1214,10 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
|
||||
const x: f64 = x: {
|
||||
// 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
|
||||
x += @as(f64, @floatFromInt(self.cell_size.width)) / 2;
|
||||
x += @as(f64, @floatFromInt(self.size.cell.width)) / 2;
|
||||
|
||||
// And scale it
|
||||
x /= content_scale.x;
|
||||
@ -1234,10 +1227,10 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
|
||||
const y: f64 = y: {
|
||||
// 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
|
||||
y += @floatFromInt(self.cell_size.height);
|
||||
y += @floatFromInt(self.size.cell.height);
|
||||
|
||||
// And scale it
|
||||
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
|
||||
/// a result of changing the font size at runtime.
|
||||
fn setCellSize(self: *Surface, size: renderer.CellSize) !void {
|
||||
// Update our new cell size for future calcs
|
||||
self.cell_size = size;
|
||||
|
||||
// Update our grid_size
|
||||
self.grid_size = renderer.GridSize.init(
|
||||
self.screen_size.subPadding(self.padding),
|
||||
self.cell_size,
|
||||
);
|
||||
// Update our cell size within our size struct
|
||||
self.size.cell = size;
|
||||
if (self.config.window_padding_balance) self.size.balancePadding();
|
||||
|
||||
// Notify the terminal
|
||||
self.io.queueMessage(.{
|
||||
.resize = .{
|
||||
.grid_size = self.grid_size,
|
||||
.cell_size = self.cell_size,
|
||||
.screen_size = self.screen_size,
|
||||
.padding = self.padding,
|
||||
},
|
||||
}, .unlocked);
|
||||
self.io.queueMessage(.{ .resize = self.size }, .unlocked);
|
||||
|
||||
// Notify the window
|
||||
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
|
||||
// the screen size didn't change, then our grid size could not have
|
||||
// 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);
|
||||
}
|
||||
|
||||
fn resize(self: *Surface, size: renderer.ScreenSize) !void {
|
||||
// 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,
|
||||
// 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
|
||||
// pixel-level sizing to the subprocess.
|
||||
self.grid_size = renderer.GridSize.init(
|
||||
self.screen_size.subPadding(self.padding),
|
||||
self.cell_size,
|
||||
);
|
||||
if (self.grid_size.columns < 5 and (self.padding.left > 0 or self.padding.right > 0)) {
|
||||
const grid_size = self.size.grid();
|
||||
if (grid_size.columns < 5 and (self.size.padding.left > 0 or self.size.padding.right > 0)) {
|
||||
log.warn("WARNING: very small terminal grid detected with padding " ++
|
||||
"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 " ++
|
||||
"set. Is your padding reasonable?", .{});
|
||||
}
|
||||
|
||||
// Mail the IO thread
|
||||
self.io.queueMessage(.{
|
||||
.resize = .{
|
||||
.grid_size = self.grid_size,
|
||||
.cell_size = self.cell_size,
|
||||
.screen_size = self.screen_size,
|
||||
.padding = self.padding,
|
||||
},
|
||||
}, .unlocked);
|
||||
self.io.queueMessage(.{ .resize = self.size }, .unlocked);
|
||||
}
|
||||
|
||||
/// Called to set the preedit state for character input. Preedit is used
|
||||
@ -2144,7 +2116,8 @@ pub fn scrollCallback(
|
||||
if (!scroll_mods.precision) {
|
||||
// Calculate our magnitude of scroll. This is constant (not
|
||||
// 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_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
|
||||
// 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) {
|
||||
self.mouse.pending_scroll_y = poff;
|
||||
break :y .{};
|
||||
@ -2201,7 +2174,7 @@ pub fn scrollCallback(
|
||||
|
||||
const xoff_adjusted: f64 = xoff * self.config.mouse_scroll_multiplier;
|
||||
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) {
|
||||
self.mouse.pending_scroll_x = poff;
|
||||
break :x .{};
|
||||
@ -2326,36 +2299,15 @@ pub fn contentScaleCallback(self: *Surface, content_scale: apprt.ContentScale) !
|
||||
|
||||
try self.setFontSize(size);
|
||||
|
||||
// Update our padding which is dependent on DPI.
|
||||
self.padding = padding: {
|
||||
const padding_top: u32 = padding_top: {
|
||||
const padding_top: f32 = @floatFromInt(self.config.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.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,
|
||||
};
|
||||
};
|
||||
// Update our padding which is dependent on DPI. We only do this for
|
||||
// unbalanced padding since balanced padding is not dependent on DPI.
|
||||
if (!self.config.window_padding_balance) {
|
||||
self.size.padding = self.config.scaledPadding(x_dpi, y_dpi);
|
||||
}
|
||||
|
||||
// Force a resize event because the change in padding will affect
|
||||
// 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.
|
||||
@ -2394,8 +2346,8 @@ fn mouseReport(
|
||||
// We always report release events no matter where they happen.
|
||||
if (action != .release) {
|
||||
const pos_out_viewport = pos_out_viewport: {
|
||||
const max_x: f32 = @floatFromInt(self.screen_size.width);
|
||||
const max_y: f32 = @floatFromInt(self.screen_size.height);
|
||||
const max_x: f32 = @floatFromInt(self.size.screen.width);
|
||||
const max_y: f32 = @floatFromInt(self.size.screen.height);
|
||||
break :pos_out_viewport pos.x < 0 or pos.y < 0 or
|
||||
pos.x > max_x or pos.y > max_y;
|
||||
};
|
||||
@ -2554,15 +2506,22 @@ fn mouseReport(
|
||||
.sgr_pixels => {
|
||||
// Final character to send in the CSI
|
||||
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
|
||||
// remainder for numbers which are very large...
|
||||
var data: termio.Message.WriteReq.Small.Array = undefined;
|
||||
const resp = try std.fmt.bufPrint(&data, "\x1B[<{d};{d};{d}{c}", .{
|
||||
button_code,
|
||||
@as(i32, @intFromFloat(@round(adjusted.x))),
|
||||
@as(i32, @intFromFloat(@round(adjusted.y))),
|
||||
@as(i32, @intFromFloat(@round(coord.x))),
|
||||
@as(i32, @intFromFloat(@round(coord.y))),
|
||||
final,
|
||||
});
|
||||
|
||||
@ -2817,7 +2776,7 @@ pub fn mouseButtonCallback(
|
||||
// If we move our cursor too much between clicks then we reset
|
||||
// the multi-click state.
|
||||
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(
|
||||
std.math.pow(f64, pos.x - self.mouse.left_click_xpos, 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
|
||||
// scroll even in full screen windows.
|
||||
// Note: one day, we can change this from distance to time based if we want.
|
||||
//log.warn("CURSOR POS: {} {}", .{ pos, self.screen_size });
|
||||
const max_y: f32 = @floatFromInt(self.screen_size.height);
|
||||
//log.warn("CURSOR POS: {} {}", .{ pos, self.size.screen });
|
||||
const max_y: f32 = @floatFromInt(self.size.screen.height);
|
||||
if (pos.y <= 1 or pos.y > max_y - 1) {
|
||||
const delta: isize = if (pos.y < 0) -1 else 1;
|
||||
try self.io.terminal.scrollViewport(.{ .delta = delta });
|
||||
@ -3477,11 +3436,11 @@ fn dragLeftClickSingle(
|
||||
const click_pin = self.mouse.left_click_pin.?.*;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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_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();
|
||||
}
|
||||
|
||||
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 {
|
||||
// xpos/ypos need to be adjusted for window padding
|
||||
// (i.e. "window-padding-*" settings.
|
||||
const adjusted = self.posAdjusted(xpos, ypos);
|
||||
|
||||
// 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);
|
||||
},
|
||||
};
|
||||
// Get our grid cell
|
||||
const coord: renderer.Coordinate = .{ .surface = .{ .x = xpos, .y = ypos } };
|
||||
const grid = coord.convert(.grid, self.size).grid;
|
||||
return .{ .x = grid.x, .y = grid.y };
|
||||
}
|
||||
|
||||
/// Scroll to the bottom of the viewport.
|
||||
@ -3905,21 +3832,21 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
},
|
||||
|
||||
.scroll_page_up => {
|
||||
const rows: isize = @intCast(self.grid_size.rows);
|
||||
const rows: isize = @intCast(self.size.grid().rows);
|
||||
self.io.queueMessage(.{
|
||||
.scroll_viewport = .{ .delta = -1 * rows },
|
||||
}, .unlocked);
|
||||
},
|
||||
|
||||
.scroll_page_down => {
|
||||
const rows: isize = @intCast(self.grid_size.rows);
|
||||
const rows: isize = @intCast(self.size.grid().rows);
|
||||
self.io.queueMessage(.{
|
||||
.scroll_viewport = .{ .delta = rows },
|
||||
}, .unlocked);
|
||||
},
|
||||
|
||||
.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));
|
||||
self.io.queueMessage(.{
|
||||
.scroll_viewport = .{ .delta = delta },
|
||||
@ -3989,7 +3916,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
.left => .left,
|
||||
.down => .down,
|
||||
.up => .up,
|
||||
.auto => if (self.screen_size.width > self.screen_size.height)
|
||||
.auto => if (self.size.screen.width > self.size.screen.height)
|
||||
.right
|
||||
else
|
||||
.down,
|
||||
|
@ -1447,13 +1447,14 @@ pub const CAPI = struct {
|
||||
|
||||
/// Return the size information a surface has.
|
||||
export fn ghostty_surface_size(surface: *Surface) SurfaceSize {
|
||||
const grid_size = surface.core_surface.size.grid();
|
||||
return .{
|
||||
.columns = surface.core_surface.grid_size.columns,
|
||||
.rows = surface.core_surface.grid_size.rows,
|
||||
.width_px = surface.core_surface.screen_size.width,
|
||||
.height_px = surface.core_surface.screen_size.height,
|
||||
.cell_width_px = surface.core_surface.cell_size.width,
|
||||
.cell_height_px = surface.core_surface.cell_size.height,
|
||||
.columns = grid_size.columns,
|
||||
.rows = grid_size.rows,
|
||||
.width_px = surface.core_surface.size.screen.width,
|
||||
.height_px = surface.core_surface.size.screen.height,
|
||||
.cell_width_px = surface.core_surface.size.cell.width,
|
||||
.cell_height_px = surface.core_surface.size.cell.height,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -94,13 +94,14 @@ fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c.gboolean {
|
||||
return c.FALSE;
|
||||
};
|
||||
|
||||
const grid_size = surface.core_surface.size.grid();
|
||||
var buf: [32]u8 = undefined;
|
||||
const text = std.fmt.bufPrintZ(
|
||||
&buf,
|
||||
"{d}c ⨯ {d}r",
|
||||
.{
|
||||
surface.core_surface.grid_size.columns,
|
||||
surface.core_surface.grid_size.rows,
|
||||
grid_size.columns,
|
||||
grid_size.rows,
|
||||
},
|
||||
) catch |err| {
|
||||
log.err("unable to format text: {}", .{err});
|
||||
|
@ -177,29 +177,30 @@ fn beforeSend(
|
||||
const obj = sentry.Value.initObject();
|
||||
errdefer obj.decref();
|
||||
const surface = thr_state.surface;
|
||||
const grid_size = surface.size.grid();
|
||||
obj.set(
|
||||
"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(
|
||||
"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(
|
||||
"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(
|
||||
"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(
|
||||
"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(
|
||||
"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);
|
||||
|
@ -11,6 +11,7 @@ const cimgui = @import("cimgui");
|
||||
const Surface = @import("../Surface.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
const input = @import("../input.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const inspector = @import("main.zig");
|
||||
|
||||
@ -641,8 +642,8 @@ fn renderSizeWindow(self: *Inspector) void {
|
||||
_ = cimgui.c.igTableSetColumnIndex(1);
|
||||
cimgui.c.igText(
|
||||
"%dpx x %dpx",
|
||||
self.surface.screen_size.width,
|
||||
self.surface.screen_size.height,
|
||||
self.surface.size.screen.width,
|
||||
self.surface.size.screen.height,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -656,10 +657,11 @@ fn renderSizeWindow(self: *Inspector) void {
|
||||
}
|
||||
{
|
||||
_ = cimgui.c.igTableSetColumnIndex(1);
|
||||
const grid_size = self.surface.size.grid();
|
||||
cimgui.c.igText(
|
||||
"%dc x %dr",
|
||||
self.surface.grid_size.columns,
|
||||
self.surface.grid_size.rows,
|
||||
grid_size.columns,
|
||||
grid_size.rows,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -675,8 +677,8 @@ fn renderSizeWindow(self: *Inspector) void {
|
||||
_ = cimgui.c.igTableSetColumnIndex(1);
|
||||
cimgui.c.igText(
|
||||
"%dpx x %dpx",
|
||||
self.surface.cell_size.width,
|
||||
self.surface.cell_size.height,
|
||||
self.surface.size.cell.width,
|
||||
self.surface.size.cell.height,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -692,10 +694,10 @@ fn renderSizeWindow(self: *Inspector) void {
|
||||
_ = cimgui.c.igTableSetColumnIndex(1);
|
||||
cimgui.c.igText(
|
||||
"T=%d B=%d L=%d R=%d px",
|
||||
self.surface.padding.top,
|
||||
self.surface.padding.bottom,
|
||||
self.surface.padding.left,
|
||||
self.surface.padding.right,
|
||||
self.surface.size.padding.top,
|
||||
self.surface.size.padding.bottom,
|
||||
self.surface.size.padding.left,
|
||||
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.igTableSetColumnIndex(0);
|
||||
@ -795,8 +803,8 @@ fn renderSizeWindow(self: *Inspector) void {
|
||||
_ = cimgui.c.igTableSetColumnIndex(1);
|
||||
cimgui.c.igText(
|
||||
"(%dpx, %dpx)",
|
||||
@as(i64, @intFromFloat(adjusted.x)),
|
||||
@as(i64, @intFromFloat(adjusted.y)),
|
||||
@as(i64, @intFromFloat(coord.x)),
|
||||
@as(i64, @intFromFloat(coord.y)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ pub const Thread = @import("renderer/Thread.zig");
|
||||
pub const State = @import("renderer/State.zig");
|
||||
pub const CursorStyle = cursor.Style;
|
||||
pub const Message = message.Message;
|
||||
pub const Size = size.Size;
|
||||
pub const Coordinate = size.Coordinate;
|
||||
pub const CellSize = size.CellSize;
|
||||
pub const ScreenSize = size.ScreenSize;
|
||||
pub const GridSize = size.GridSize;
|
||||
|
@ -70,12 +70,8 @@ surface_mailbox: apprt.surface.Mailbox,
|
||||
/// Current font metrics defining our grid.
|
||||
grid_metrics: font.face.Metrics,
|
||||
|
||||
/// Current screen size dimensions for this grid. This is set on the first
|
||||
/// resize event, and is not immediately available.
|
||||
screen_size: ?renderer.ScreenSize,
|
||||
|
||||
/// Explicit padding.
|
||||
padding: renderer.Options.Padding,
|
||||
/// The size of everything.
|
||||
size: renderer.Size,
|
||||
|
||||
/// True if the window is focused
|
||||
focused: bool,
|
||||
@ -626,13 +622,12 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
};
|
||||
errdefer if (display_link) |v| v.release();
|
||||
|
||||
return Metal{
|
||||
var result: Metal = .{
|
||||
.alloc = alloc,
|
||||
.config = options.config,
|
||||
.surface_mailbox = options.surface_mailbox,
|
||||
.grid_metrics = font_critical.metrics,
|
||||
.screen_size = null,
|
||||
.padding = options.padding,
|
||||
.size = options.size,
|
||||
.focused = true,
|
||||
.foreground_color = options.config.foreground,
|
||||
.background_color = options.config.background,
|
||||
@ -668,6 +663,12 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
.custom_shader_state = custom_shader_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 {
|
||||
@ -796,19 +797,6 @@ pub fn hasVsync(self: *const Metal) bool {
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
// the setScreenSize automatically.
|
||||
if (self.screen_size) |size| {
|
||||
self.setScreenSize(size, self.padding.explicit) catch |err| {
|
||||
self.setScreenSize(self.size) catch |err| {
|
||||
// The setFontGrid function can't fail but resizing our cell
|
||||
// buffer definitely can fail. If it does, our renderer is probably
|
||||
// screwed but let's just log it and continue until we can figure
|
||||
// out a better way to handle this.
|
||||
log.err("error resizing cells buffer err={}", .{err});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the frame data.
|
||||
@ -898,9 +884,6 @@ pub fn updateFrame(
|
||||
) !void {
|
||||
_ = 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.
|
||||
const Critical = struct {
|
||||
bg: terminal.color.RGB,
|
||||
@ -1116,9 +1099,6 @@ pub fn updateFrame(
|
||||
pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
_ = 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
|
||||
// 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
|
||||
@ -1972,38 +1952,19 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
||||
/// Resize the screen.
|
||||
pub fn setScreenSize(
|
||||
self: *Metal,
|
||||
dim: renderer.ScreenSize,
|
||||
pad: renderer.Padding,
|
||||
size: renderer.Size,
|
||||
) !void {
|
||||
// Store our sizes
|
||||
self.screen_size = dim;
|
||||
self.padding.explicit = pad;
|
||||
|
||||
// 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);
|
||||
self.size = size;
|
||||
const grid_size = size.grid();
|
||||
const terminal_size = size.terminal();
|
||||
|
||||
// Blank space around the grid.
|
||||
const blank: renderer.Padding = dim.blankPadding(padding, grid_size, .{
|
||||
.width = self.grid_metrics.cell_width,
|
||||
.height = self.grid_metrics.cell_height,
|
||||
}).add(padding);
|
||||
const blank: renderer.Padding = size.screen.blankPadding(
|
||||
size.padding,
|
||||
grid_size,
|
||||
size.cell,
|
||||
).add(size.padding);
|
||||
|
||||
var padding_extend = self.uniforms.padding_extend;
|
||||
switch (self.config.padding_color) {
|
||||
@ -2030,18 +1991,18 @@ pub fn setScreenSize(
|
||||
|
||||
// Set the size of the drawable surface to the bounds
|
||||
self.layer.setProperty("drawableSize", macos.graphics.Size{
|
||||
.width = @floatFromInt(dim.width),
|
||||
.height = @floatFromInt(dim.height),
|
||||
.width = @floatFromInt(size.screen.width),
|
||||
.height = @floatFromInt(size.screen.height),
|
||||
});
|
||||
|
||||
// Setup our uniforms
|
||||
const old = self.uniforms;
|
||||
self.uniforms = .{
|
||||
.projection_matrix = math.ortho2d(
|
||||
-1 * @as(f32, @floatFromInt(padding.left)),
|
||||
@floatFromInt(padded_dim.width + padding.right),
|
||||
@floatFromInt(padded_dim.height + padding.bottom),
|
||||
-1 * @as(f32, @floatFromInt(padding.top)),
|
||||
-1 * @as(f32, @floatFromInt(size.padding.left)),
|
||||
@floatFromInt(terminal_size.width + size.padding.right),
|
||||
@floatFromInt(terminal_size.height + size.padding.bottom),
|
||||
-1 * @as(f32, @floatFromInt(size.padding.top)),
|
||||
),
|
||||
.cell_size = .{
|
||||
@floatFromInt(self.grid_metrics.cell_width),
|
||||
@ -2082,8 +2043,8 @@ pub fn setScreenSize(
|
||||
}
|
||||
|
||||
state.uniforms.resolution = .{
|
||||
@floatFromInt(dim.width),
|
||||
@floatFromInt(dim.height),
|
||||
@floatFromInt(size.screen.width),
|
||||
@floatFromInt(size.screen.height),
|
||||
1,
|
||||
};
|
||||
|
||||
@ -2097,8 +2058,8 @@ pub fn setScreenSize(
|
||||
break :init id_init;
|
||||
};
|
||||
desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm));
|
||||
desc.setProperty("width", @as(c_ulong, @intCast(dim.width)));
|
||||
desc.setProperty("height", @as(c_ulong, @intCast(dim.height)));
|
||||
desc.setProperty("width", @as(c_ulong, @intCast(size.screen.width)));
|
||||
desc.setProperty("height", @as(c_ulong, @intCast(size.screen.height)));
|
||||
desc.setProperty(
|
||||
"usage",
|
||||
@intFromEnum(mtl.MTLTextureUsage.render_target) |
|
||||
@ -2127,8 +2088,8 @@ pub fn setScreenSize(
|
||||
break :init id_init;
|
||||
};
|
||||
desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm));
|
||||
desc.setProperty("width", @as(c_ulong, @intCast(dim.width)));
|
||||
desc.setProperty("height", @as(c_ulong, @intCast(dim.height)));
|
||||
desc.setProperty("width", @as(c_ulong, @intCast(size.screen.width)));
|
||||
desc.setProperty("height", @as(c_ulong, @intCast(size.screen.height)));
|
||||
desc.setProperty(
|
||||
"usage",
|
||||
@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
|
||||
|
@ -51,10 +51,8 @@ config: DerivedConfig,
|
||||
/// Current font metrics defining our grid.
|
||||
grid_metrics: font.face.Metrics,
|
||||
|
||||
/// Current screen size dimensions for this grid. This is set on the first
|
||||
/// resize event, and is not immediately available.
|
||||
screen_size: ?renderer.ScreenSize,
|
||||
grid_size: renderer.GridSize,
|
||||
/// The size of everything.
|
||||
size: renderer.Size,
|
||||
|
||||
/// The current set of cells to render. Each set of cells goes into
|
||||
/// a separate shader call.
|
||||
@ -108,9 +106,6 @@ cursor_color: ?terminal.color.RGB,
|
||||
/// foreground color as the cursor color.
|
||||
cursor_invert: bool,
|
||||
|
||||
/// Padding options
|
||||
padding: renderer.Options.Padding,
|
||||
|
||||
/// The mailbox for communicating with the window.
|
||||
surface_mailbox: apprt.surface.Mailbox,
|
||||
|
||||
@ -141,7 +136,7 @@ image_virtual: bool = false,
|
||||
|
||||
/// Defererred OpenGL operation to update the screen size.
|
||||
const SetScreenSize = struct {
|
||||
size: renderer.ScreenSize,
|
||||
size: renderer.Size,
|
||||
|
||||
fn apply(self: SetScreenSize, r: *OpenGL) !void {
|
||||
const gl_state: *GLState = if (r.gl_state) |*v|
|
||||
@ -150,19 +145,8 @@ const SetScreenSize = struct {
|
||||
return error.OpenGLUninitialized;
|
||||
|
||||
// Apply our padding
|
||||
const grid_size = r.gridSize(self.size);
|
||||
const padding = if (r.padding.balance)
|
||||
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);
|
||||
const grid_size = self.size.grid();
|
||||
const terminal_size = self.size.terminal();
|
||||
|
||||
// Blank space around the grid.
|
||||
const blank: renderer.Padding = switch (r.config.padding_color) {
|
||||
@ -170,30 +154,20 @@ const SetScreenSize = struct {
|
||||
// clear color.
|
||||
.background => .{},
|
||||
|
||||
.extend, .@"extend-always" => self.size.blankPadding(padding, grid_size, .{
|
||||
.width = r.grid_metrics.cell_width,
|
||||
.height = r.grid_metrics.cell_height,
|
||||
}).add(padding),
|
||||
.extend, .@"extend-always" => self.size.screen.blankPadding(
|
||||
self.size.padding,
|
||||
grid_size,
|
||||
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.
|
||||
// OpenGL works in pixels, so we have to use the pixel size.
|
||||
try gl.viewport(
|
||||
0,
|
||||
0,
|
||||
@intCast(self.size.width),
|
||||
@intCast(self.size.height),
|
||||
@intCast(self.size.screen.width),
|
||||
@intCast(self.size.screen.height),
|
||||
);
|
||||
|
||||
// Update the projection uniform within our shader
|
||||
@ -206,10 +180,10 @@ const SetScreenSize = struct {
|
||||
|
||||
// 2D orthographic projection with the full w/h
|
||||
math.ortho2d(
|
||||
-1 * @as(f32, @floatFromInt(padding.left)),
|
||||
@floatFromInt(padded_size.width + padding.right),
|
||||
@floatFromInt(padded_size.height + padding.bottom),
|
||||
-1 * @as(f32, @floatFromInt(padding.top)),
|
||||
-1 * @as(f32, @floatFromInt(self.size.padding.left)),
|
||||
@floatFromInt(terminal_size.width + self.size.padding.right),
|
||||
@floatFromInt(terminal_size.height + self.size.padding.bottom),
|
||||
-1 * @as(f32, @floatFromInt(self.size.padding.top)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -405,8 +379,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||
.cells_bg = .{},
|
||||
.cells = .{},
|
||||
.grid_metrics = grid.metrics,
|
||||
.screen_size = null,
|
||||
.grid_size = .{},
|
||||
.size = options.size,
|
||||
.gl_state = gl_state,
|
||||
.font_grid = grid,
|
||||
.font_shaper = shaper,
|
||||
@ -417,7 +390,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||
.background_color = options.config.background,
|
||||
.cursor_color = options.config.cursor_color,
|
||||
.cursor_invert = options.config.cursor_invert,
|
||||
.padding = options.padding,
|
||||
.surface_mailbox = options.surface_mailbox,
|
||||
.deferred_font_size = .{ .metrics = grid.metrics },
|
||||
.deferred_config = .{},
|
||||
@ -558,9 +530,7 @@ pub fn displayRealize(self: *OpenGL) !void {
|
||||
self.texture_color_resized = 0;
|
||||
|
||||
// We need to reset our uniforms
|
||||
if (self.screen_size) |size| {
|
||||
self.deferred_screen_size = .{ .size = size };
|
||||
}
|
||||
self.deferred_screen_size = .{ .size = self.size };
|
||||
self.deferred_font_size = .{ .metrics = self.grid_metrics };
|
||||
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 = font_shaper_cache;
|
||||
|
||||
if (self.screen_size) |size| {
|
||||
// Update our grid size if we have a screen size. If we don't, its okay
|
||||
// because this will get set when we get the screen size set.
|
||||
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 };
|
||||
}
|
||||
self.deferred_screen_size = .{ .size = self.size };
|
||||
|
||||
// Defer our GPU updates
|
||||
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.
|
||||
var critical: Critical = critical: {
|
||||
const grid_size = self.size.grid();
|
||||
|
||||
state.mutex.lock();
|
||||
defer state.mutex.unlock();
|
||||
|
||||
@ -753,8 +719,8 @@ pub fn updateFrame(
|
||||
//
|
||||
// For some reason this doesn't seem to cause any significant issues
|
||||
// with flickering while resizing. '\_('-')_/'
|
||||
if (self.grid_size.rows != state.terminal.rows or
|
||||
self.grid_size.columns != state.terminal.cols)
|
||||
if (grid_size.rows != state.terminal.rows or
|
||||
grid_size.columns != state.terminal.cols)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -1314,7 +1280,7 @@ pub fn rebuildCells(
|
||||
color_palette,
|
||||
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(
|
||||
color_palette,
|
||||
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.
|
||||
pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
|
||||
// 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.
|
||||
pub fn setScreenSize(
|
||||
self: *OpenGL,
|
||||
dim: renderer.ScreenSize,
|
||||
pad: renderer.Padding,
|
||||
size: renderer.Size,
|
||||
) !void {
|
||||
if (single_threaded_draw) self.draw_mutex.lock();
|
||||
defer if (single_threaded_draw) self.draw_mutex.unlock();
|
||||
@ -2177,22 +2130,12 @@ pub fn setScreenSize(
|
||||
self.cells_bg.clearAndFree(self.alloc);
|
||||
|
||||
// Store our screen size
|
||||
self.screen_size = dim;
|
||||
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,
|
||||
});
|
||||
self.size = size;
|
||||
|
||||
// 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.
|
||||
|
@ -11,8 +11,8 @@ config: renderer.Renderer.DerivedConfig,
|
||||
/// The font grid that should be used along with the key for deref-ing.
|
||||
font_grid: *font.SharedGrid,
|
||||
|
||||
/// Padding options for the viewport.
|
||||
padding: Padding,
|
||||
/// The size of everything.
|
||||
size: renderer.Size,
|
||||
|
||||
/// 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.
|
||||
@ -20,12 +20,3 @@ surface_mailbox: apprt.surface.Mailbox,
|
||||
|
||||
/// The 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,
|
||||
};
|
||||
|
@ -371,9 +371,7 @@ fn drainMailbox(self: *Thread) !void {
|
||||
self.renderer.markDirty();
|
||||
},
|
||||
|
||||
.resize => |v| {
|
||||
try self.renderer.setScreenSize(v.screen_size, v.padding);
|
||||
},
|
||||
.resize => |v| try self.renderer.setScreenSize(v),
|
||||
|
||||
.change_config => |config| {
|
||||
defer config.alloc.destroy(config.thread);
|
||||
|
@ -54,14 +54,8 @@ pub const Message = union(enum) {
|
||||
/// config file in response to an OSC 12 command.
|
||||
cursor_color: ?terminal.color.RGB,
|
||||
|
||||
/// Changes the screen size.
|
||||
resize: struct {
|
||||
/// The full screen (drawable) size. This does NOT include padding.
|
||||
screen_size: renderer.ScreenSize,
|
||||
|
||||
/// The explicit padding values.
|
||||
padding: renderer.Padding,
|
||||
},
|
||||
/// Changes the size. The screen size might change, padding, grid, etc.
|
||||
resize: renderer.Size,
|
||||
|
||||
/// The derived configuration to update the renderer with.
|
||||
change_config: struct {
|
||||
|
@ -1,7 +1,7 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const gl = @import("opengl");
|
||||
const ScreenSize = @import("../size.zig").ScreenSize;
|
||||
const Size = @import("../size.zig").Size;
|
||||
|
||||
const log = std.log.scoped(.opengl_custom);
|
||||
|
||||
@ -154,11 +154,11 @@ pub const State = struct {
|
||||
self.fbo.destroy();
|
||||
}
|
||||
|
||||
pub fn setScreenSize(self: *State, size: ScreenSize) !void {
|
||||
pub fn setScreenSize(self: *State, size: Size) !void {
|
||||
// Update our uniforms
|
||||
self.uniforms.resolution = .{
|
||||
@floatFromInt(size.width),
|
||||
@floatFromInt(size.height),
|
||||
@floatFromInt(size.screen.width),
|
||||
@floatFromInt(size.screen.height),
|
||||
1,
|
||||
};
|
||||
try self.syncUniforms();
|
||||
@ -168,8 +168,8 @@ pub const State = struct {
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.rgb,
|
||||
@intCast(size.width),
|
||||
@intCast(size.height),
|
||||
@intCast(size.screen.width),
|
||||
@intCast(size.screen.height),
|
||||
0,
|
||||
.rgb,
|
||||
.UnsignedByte,
|
||||
|
@ -5,6 +5,139 @@ const terminal = @import("../terminal/main.zig");
|
||||
|
||||
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 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.
|
||||
pub const GridSize = struct {
|
||||
const Unit = terminal.size.CellCountInt;
|
||||
pub const Unit = terminal.size.CellCountInt;
|
||||
|
||||
columns: 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, 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);
|
||||
}
|
||||
}
|
||||
|
@ -8,17 +8,8 @@ const Command = @import("../Command.zig");
|
||||
const Config = @import("../config.zig").Config;
|
||||
const termio = @import("../termio.zig");
|
||||
|
||||
/// The size of the terminal grid.
|
||||
grid_size: renderer.GridSize,
|
||||
|
||||
/// 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,
|
||||
/// All size metrics for the terminal.
|
||||
size: renderer.Size,
|
||||
|
||||
/// 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
|
||||
|
@ -56,11 +56,8 @@ renderer_mailbox: *renderer.Thread.Mailbox,
|
||||
/// The mailbox for communicating with the surface.
|
||||
surface_mailbox: apprt.surface.Mailbox,
|
||||
|
||||
/// The cached grid size whenever a resize is called.
|
||||
grid_size: renderer.GridSize,
|
||||
|
||||
/// The size of a single cell. Used for size reports.
|
||||
cell_size: renderer.CellSize,
|
||||
/// The cached size info
|
||||
size: renderer.Size,
|
||||
|
||||
/// The mailbox implementation to use.
|
||||
mailbox: termio.Mailbox,
|
||||
@ -131,10 +128,13 @@ pub const DerivedConfig = struct {
|
||||
/// to run a child process.
|
||||
pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
|
||||
// Create our terminal
|
||||
var term = try terminal.Terminal.init(alloc, .{
|
||||
.cols = opts.grid_size.columns,
|
||||
.rows = opts.grid_size.rows,
|
||||
var term = try terminal.Terminal.init(alloc, opts: {
|
||||
const grid_size = opts.size.grid();
|
||||
break :opts .{
|
||||
.cols = grid_size.columns,
|
||||
.rows = grid_size.rows,
|
||||
.max_scrollback = opts.full_config.@"scrollback-limit",
|
||||
};
|
||||
});
|
||||
errdefer term.deinit(alloc);
|
||||
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
|
||||
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.
|
||||
var backend = opts.backend;
|
||||
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
|
||||
// isn't safe to use until self.* is set.
|
||||
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_wakeup = opts.renderer_wakeup,
|
||||
.renderer_mailbox = opts.renderer_mailbox,
|
||||
.grid_size = &self.grid_size,
|
||||
.size = &self.size,
|
||||
.terminal = &self.terminal,
|
||||
.osc_color_report_format = opts.config.osc_color_report_format,
|
||||
.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_mailbox = opts.renderer_mailbox,
|
||||
.surface_mailbox = opts.surface_mailbox,
|
||||
.grid_size = opts.grid_size,
|
||||
.cell_size = opts.cell_size,
|
||||
.size = opts.size,
|
||||
.backend = opts.backend,
|
||||
.mailbox = opts.mailbox,
|
||||
.terminal_stream = .{
|
||||
@ -349,18 +348,12 @@ pub fn changeConfig(self: *Termio, td: *ThreadData, config: *DerivedConfig) !voi
|
||||
pub fn resize(
|
||||
self: *Termio,
|
||||
td: *ThreadData,
|
||||
grid_size: renderer.GridSize,
|
||||
cell_size: renderer.CellSize,
|
||||
screen_size: renderer.ScreenSize,
|
||||
padding: renderer.Padding,
|
||||
size: renderer.Size,
|
||||
) !void {
|
||||
// Update the size of our pty.
|
||||
const padded_size = screen_size.subPadding(padding);
|
||||
try self.backend.resize(grid_size, padded_size);
|
||||
const grid_size = size.grid();
|
||||
|
||||
// Update our cached grid size
|
||||
self.grid_size = grid_size;
|
||||
self.cell_size = cell_size;
|
||||
// Update the size of our pty.
|
||||
try self.backend.resize(grid_size, size.terminal());
|
||||
|
||||
// Enter the critical area that we want to keep small
|
||||
{
|
||||
@ -375,8 +368,8 @@ pub fn resize(
|
||||
);
|
||||
|
||||
// Update our pixel sizes
|
||||
self.terminal.width_px = self.grid_size.columns * self.cell_size.width;
|
||||
self.terminal.height_px = self.grid_size.rows * self.cell_size.height;
|
||||
self.terminal.width_px = grid_size.columns * self.size.cell.width;
|
||||
self.terminal.height_px = grid_size.rows * self.size.cell.height;
|
||||
|
||||
// Disable synchronized output mode so that we show changes
|
||||
// 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
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.resize = .{
|
||||
.screen_size = screen_size,
|
||||
.padding = padding,
|
||||
},
|
||||
}, .{ .forever = {} });
|
||||
_ = self.renderer_mailbox.push(.{ .resize = size }, .{ .forever = {} });
|
||||
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 {
|
||||
const grid_size = self.size.grid();
|
||||
|
||||
// 1024 bytes should be enough for size report since report
|
||||
// in columns and pixels.
|
||||
var buf: [1024]u8 = undefined;
|
||||
@ -414,34 +404,34 @@ fn sizeReportLocked(self: *Termio, td: *ThreadData, style: termio.Message.SizeRe
|
||||
&buf,
|
||||
"\x1B[48;{};{};{};{}t",
|
||||
.{
|
||||
self.grid_size.rows,
|
||||
self.grid_size.columns,
|
||||
self.grid_size.rows * self.cell_size.height,
|
||||
self.grid_size.columns * self.cell_size.width,
|
||||
grid_size.rows,
|
||||
grid_size.columns,
|
||||
grid_size.rows * self.size.cell.height,
|
||||
grid_size.columns * self.size.cell.width,
|
||||
},
|
||||
),
|
||||
.csi_14_t => try std.fmt.bufPrint(
|
||||
&buf,
|
||||
"\x1b[4;{};{}t",
|
||||
.{
|
||||
self.grid_size.rows * self.cell_size.height,
|
||||
self.grid_size.columns * self.cell_size.width,
|
||||
grid_size.rows * self.size.cell.height,
|
||||
grid_size.columns * self.size.cell.width,
|
||||
},
|
||||
),
|
||||
.csi_16_t => try std.fmt.bufPrint(
|
||||
&buf,
|
||||
"\x1b[6;{};{}t",
|
||||
.{
|
||||
self.cell_size.height,
|
||||
self.cell_size.width,
|
||||
self.size.cell.height,
|
||||
self.size.cell.width,
|
||||
},
|
||||
),
|
||||
.csi_18_t => try std.fmt.bufPrint(
|
||||
&buf,
|
||||
"\x1b[8;{};{}t",
|
||||
.{
|
||||
self.grid_size.rows,
|
||||
self.grid_size.columns,
|
||||
grid_size.rows,
|
||||
grid_size.columns,
|
||||
},
|
||||
),
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ const builtin = @import("builtin");
|
||||
const xev = @import("xev");
|
||||
const crash = @import("../crash/main.zig");
|
||||
const termio = @import("../termio.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
@ -28,7 +29,7 @@ const Coalesce = struct {
|
||||
/// Not all message types are coalesced.
|
||||
const min_ms = 25;
|
||||
|
||||
resize: ?termio.Message.Resize = null,
|
||||
resize: ?renderer.Size = null,
|
||||
};
|
||||
|
||||
/// 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;
|
||||
|
||||
// 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| {
|
||||
cb.self.coalesce_data.resize = null;
|
||||
cb.io.resize(
|
||||
&cb.data,
|
||||
v.grid_size,
|
||||
v.cell_size,
|
||||
v.screen_size,
|
||||
v.padding,
|
||||
) catch |err| {
|
||||
cb.io.resize(&cb.data, v) catch |err| {
|
||||
log.warn("error during resize err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
@ -16,22 +16,6 @@ pub const Message = union(enum) {
|
||||
/// in the future.
|
||||
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.
|
||||
/// See the "crash" binding action.
|
||||
crash: void,
|
||||
@ -47,7 +31,7 @@ pub const Message = union(enum) {
|
||||
inspector: bool,
|
||||
|
||||
/// Resize the window.
|
||||
resize: Resize,
|
||||
resize: renderer.Size,
|
||||
|
||||
/// Request a size report is sent to the pty ([in-band
|
||||
/// size report, mode 2048](https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83) and
|
||||
|
@ -26,7 +26,7 @@ const disable_kitty_keyboard_protocol = apprt.runtime == apprt.glfw;
|
||||
/// unless all of the member fields are copied.
|
||||
pub const StreamHandler = struct {
|
||||
alloc: Allocator,
|
||||
grid_size: *renderer.GridSize,
|
||||
size: *renderer.Size,
|
||||
terminal: *terminal.Terminal,
|
||||
|
||||
/// Mailbox for data to the termio thread.
|
||||
@ -611,12 +611,15 @@ pub const StreamHandler = struct {
|
||||
},
|
||||
|
||||
// Force resize back to the window size
|
||||
.enable_mode_3 => self.terminal.resize(
|
||||
.enable_mode_3 => {
|
||||
const grid_size = self.size.grid();
|
||||
self.terminal.resize(
|
||||
self.alloc,
|
||||
self.grid_size.columns,
|
||||
self.grid_size.rows,
|
||||
grid_size.columns,
|
||||
grid_size.rows,
|
||||
) catch |err| {
|
||||
log.err("error updating terminal size: {}", .{err});
|
||||
};
|
||||
},
|
||||
|
||||
.@"132_column" => try self.terminal.deccolm(
|
||||
|
Reference in New Issue
Block a user