mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-05 01:16:33 +00:00
Merge pull request #33 from mitchellh/padding-cfg
Explicit Padding Configuration
This commit is contained in:
@@ -82,6 +82,9 @@ io_thr: std.Thread,
|
||||
/// The dimensions of the grid in rows and columns.
|
||||
grid_size: renderer.GridSize,
|
||||
|
||||
/// Explicit padding due to configuration
|
||||
padding: renderer.Padding,
|
||||
|
||||
/// The app configuration
|
||||
config: *const Config,
|
||||
|
||||
@@ -259,8 +262,24 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
||||
});
|
||||
errdefer font_group.deinit(alloc);
|
||||
|
||||
// Convert our padding from points to pixels
|
||||
const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72;
|
||||
const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72;
|
||||
const padding: renderer.Padding = .{
|
||||
.top = padding_y,
|
||||
.bottom = padding_y,
|
||||
.right = padding_x,
|
||||
.left = padding_x,
|
||||
};
|
||||
|
||||
// Create our terminal grid with the initial window size
|
||||
var renderer_impl = try Renderer.init(alloc, font_group);
|
||||
var renderer_impl = try Renderer.init(alloc, .{
|
||||
.font_group = font_group,
|
||||
.padding = .{
|
||||
.explicit = padding,
|
||||
.balance = config.@"window-padding-balance",
|
||||
},
|
||||
});
|
||||
errdefer renderer_impl.deinit();
|
||||
renderer_impl.background = .{
|
||||
.r = config.background.r,
|
||||
@@ -279,7 +298,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
||||
.width = window_size.width,
|
||||
.height = window_size.height,
|
||||
};
|
||||
const grid_size = renderer.GridSize.init(screen_size, renderer_impl.cell_size);
|
||||
const grid_size = renderer_impl.gridSize(screen_size);
|
||||
|
||||
// Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app
|
||||
// but is otherwise somewhat arbitrary.
|
||||
@@ -353,6 +372,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
||||
.io_thread = io_thread,
|
||||
.io_thr = undefined,
|
||||
.grid_size = grid_size,
|
||||
.padding = padding,
|
||||
.config = config,
|
||||
|
||||
.imgui_ctx = if (!DevMode.enabled) {} else try imgui.Context.create(),
|
||||
@@ -560,13 +580,22 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
||||
// overhead of inter-thread communication
|
||||
|
||||
// Recalculate our grid size
|
||||
win.grid_size.update(screen_size, win.renderer.cell_size);
|
||||
win.grid_size = win.renderer.gridSize(screen_size);
|
||||
if (win.grid_size.columns < 5 and (win.padding.left > 0 or win.padding.right > 0)) {
|
||||
log.warn("WARNING: very small terminal grid detected with padding " ++
|
||||
"set. Is your padding reasonable?", .{});
|
||||
}
|
||||
if (win.grid_size.rows < 2 and (win.padding.top > 0 or win.padding.bottom > 0)) {
|
||||
log.warn("WARNING: very small terminal grid detected with padding " ++
|
||||
"set. Is your padding reasonable?", .{});
|
||||
}
|
||||
|
||||
// Mail the IO thread
|
||||
_ = win.io_thread.mailbox.push(.{
|
||||
.resize = .{
|
||||
.grid_size = win.grid_size,
|
||||
.screen_size = screen_size,
|
||||
.padding = win.padding,
|
||||
},
|
||||
}, .{ .forever = {} });
|
||||
win.io_thread.wakeup.send() catch {};
|
||||
|
@@ -138,6 +138,12 @@ fn parseIntoField(
|
||||
0,
|
||||
),
|
||||
|
||||
u32 => try std.fmt.parseInt(
|
||||
u32,
|
||||
value orelse return error.ValueRequired,
|
||||
0,
|
||||
),
|
||||
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
|
@@ -89,6 +89,32 @@ pub const Config = struct {
|
||||
///
|
||||
keybind: Keybinds = .{},
|
||||
|
||||
/// Window padding. This applies padding between the terminal cells and
|
||||
/// the window border. The "x" option applies to the left and right
|
||||
/// padding and the "y" option is top and bottom. The value is in points,
|
||||
/// meaning that it will be scaled appropriately for screen DPI.
|
||||
///
|
||||
/// If this value is set too large, the screen will render nothing, because
|
||||
/// the grid will be completely squished by the padding. It is up to you
|
||||
/// as the user to pick a reasonable value. If you pick an unreasonable
|
||||
/// value, a warning will appear in the logs.
|
||||
@"window-padding-x": u32 = 0,
|
||||
@"window-padding-y": u32 = 0,
|
||||
|
||||
/// The viewport dimensions are usually not perfectly divisible by
|
||||
/// the cell size. In this case, some extra padding on the end of a
|
||||
/// column and the bottom of the final row may exist. If this is true,
|
||||
/// then this extra padding is automatically balanced between all four
|
||||
/// edges to minimize imbalance on one side. If this is false, the top
|
||||
/// left grid cell will always hug the edge with zero padding other than
|
||||
/// what may be specified with the other "window-padding" options.
|
||||
///
|
||||
/// If other "window-padding" fields are set and this is true, this will
|
||||
/// still apply. The other padding is applied first and may affect how
|
||||
/// many grid cells actually exist, and this is applied last in order
|
||||
/// to balance the padding given a certain viewport size and grid cell size.
|
||||
@"window-padding-balance": bool = true,
|
||||
|
||||
/// Additional configuration files to read.
|
||||
@"config-file": RepeatableString = .{},
|
||||
|
||||
|
@@ -14,6 +14,7 @@ pub usingnamespace @import("renderer/message.zig");
|
||||
pub usingnamespace @import("renderer/size.zig");
|
||||
pub const Metal = @import("renderer/Metal.zig");
|
||||
pub const OpenGL = @import("renderer/OpenGL.zig");
|
||||
pub const Options = @import("renderer/Options.zig");
|
||||
pub const Thread = @import("renderer/Thread.zig");
|
||||
pub const State = @import("renderer/State.zig");
|
||||
|
||||
|
@@ -33,6 +33,9 @@ alloc: std.mem.Allocator,
|
||||
/// Current cell dimensions for this grid.
|
||||
cell_size: renderer.CellSize,
|
||||
|
||||
/// Explicit padding.
|
||||
padding: renderer.Options.Padding,
|
||||
|
||||
/// True if the window is focused
|
||||
focused: bool,
|
||||
|
||||
@@ -130,7 +133,7 @@ pub fn windowInit(window: glfw.Window) !void {
|
||||
// else up during actual initialization.
|
||||
}
|
||||
|
||||
pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
|
||||
pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
// Initialize our metal stuff
|
||||
const device = objc.Object.fromId(MTLCreateSystemDefaultDevice());
|
||||
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
|
||||
@@ -150,8 +153,8 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
|
||||
// Doesn't matter, any normal ASCII will do we're just trying to make
|
||||
// sure we use the regular font.
|
||||
const metrics = metrics: {
|
||||
const index = (try font_group.indexForCodepoint(alloc, 'M', .regular, .text)).?;
|
||||
const face = try font_group.group.faceFromIndex(index);
|
||||
const index = (try options.font_group.indexForCodepoint(alloc, 'M', .regular, .text)).?;
|
||||
const face = try options.font_group.group.faceFromIndex(index);
|
||||
break :metrics face.metrics;
|
||||
};
|
||||
log.debug("cell dimensions={}", .{metrics});
|
||||
@@ -200,12 +203,13 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
|
||||
// Initialize our shader (MTLLibrary)
|
||||
const library = try initLibrary(device, @embedFile("../shaders/cell.metal"));
|
||||
const pipeline_state = try initPipelineState(device, library);
|
||||
const texture_greyscale = try initAtlasTexture(device, &font_group.atlas_greyscale);
|
||||
const texture_color = try initAtlasTexture(device, &font_group.atlas_color);
|
||||
const texture_greyscale = try initAtlasTexture(device, &options.font_group.atlas_greyscale);
|
||||
const texture_color = try initAtlasTexture(device, &options.font_group.atlas_color);
|
||||
|
||||
return Metal{
|
||||
.alloc = alloc,
|
||||
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
|
||||
.padding = options.padding,
|
||||
.background = .{ .r = 0, .g = 0, .b = 0 },
|
||||
.foreground = .{ .r = 255, .g = 255, .b = 255 },
|
||||
.focused = true,
|
||||
@@ -224,7 +228,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
|
||||
},
|
||||
|
||||
// Fonts
|
||||
.font_group = font_group,
|
||||
.font_group = options.font_group,
|
||||
.font_shaper = font_shaper,
|
||||
|
||||
// Metal stuff
|
||||
@@ -302,6 +306,15 @@ pub fn threadExit(self: *const Metal) void {
|
||||
// Metal requires no per-thread state.
|
||||
}
|
||||
|
||||
/// Returns the grid size for a given screen size. This is safe to call
|
||||
/// on any thread.
|
||||
pub fn gridSize(self: *Metal, screen_size: renderer.ScreenSize) renderer.GridSize {
|
||||
return renderer.GridSize.init(
|
||||
screen_size.subPadding(self.padding.explicit),
|
||||
self.cell_size,
|
||||
);
|
||||
}
|
||||
|
||||
/// Callback when the focus changes for the terminal this is rendering.
|
||||
pub fn setFocus(self: *Metal, focus: bool) !void {
|
||||
self.focused = focus;
|
||||
@@ -562,12 +575,14 @@ fn setScreenSize(self: *Metal, bounds: macos.graphics.Size) !void {
|
||||
};
|
||||
|
||||
// Recalculate the rows/columns.
|
||||
const grid_size = renderer.GridSize.init(dim, self.cell_size);
|
||||
const grid_size = self.gridSize(dim);
|
||||
|
||||
// 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 = renderer.Padding.balanced(dim, grid_size, self.cell_size);
|
||||
const padding = self.padding.explicit.add(if (self.padding.balance)
|
||||
renderer.Padding.balanced(dim, grid_size, self.cell_size)
|
||||
else .{});
|
||||
const padded_dim = dim.subPadding(padding);
|
||||
|
||||
// Update our shaper
|
||||
@@ -585,8 +600,8 @@ fn setScreenSize(self: *Metal, bounds: macos.graphics.Size) !void {
|
||||
self.uniforms = .{
|
||||
.projection_matrix = math.ortho2d(
|
||||
-1 * padding.left,
|
||||
@intToFloat(f32, padded_dim.width),
|
||||
@intToFloat(f32, padded_dim.height),
|
||||
@intToFloat(f32, padded_dim.width) + padding.right,
|
||||
@intToFloat(f32, padded_dim.height) + padding.bottom,
|
||||
-1 * padding.top,
|
||||
),
|
||||
.cell_size = .{ self.cell_size.width, self.cell_size.height },
|
||||
|
@@ -77,6 +77,9 @@ background: terminal.color.RGB,
|
||||
/// True if the window is focused
|
||||
focused: bool,
|
||||
|
||||
/// Padding options
|
||||
padding: renderer.Options.Padding,
|
||||
|
||||
/// The raw structure that maps directly to the buffer sent to the vertex shader.
|
||||
/// This must be "extern" so that the field order is not reordered by the
|
||||
/// Zig compiler.
|
||||
@@ -146,7 +149,7 @@ const GPUCellMode = enum(u8) {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL {
|
||||
pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||
// Create the initial font shaper
|
||||
var shape_buf = try alloc.alloc(font.Shaper.Cell, 1);
|
||||
errdefer alloc.free(shape_buf);
|
||||
@@ -157,8 +160,8 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL {
|
||||
// Doesn't matter, any normal ASCII will do we're just trying to make
|
||||
// sure we use the regular font.
|
||||
const metrics = metrics: {
|
||||
const index = (try font_group.indexForCodepoint(alloc, 'M', .regular, .text)).?;
|
||||
const face = try font_group.group.faceFromIndex(index);
|
||||
const index = (try options.font_group.indexForCodepoint(alloc, 'M', .regular, .text)).?;
|
||||
const face = try options.font_group.group.faceFromIndex(index);
|
||||
break :metrics face.metrics;
|
||||
};
|
||||
log.debug("cell dimensions={}", .{metrics});
|
||||
@@ -248,12 +251,12 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL {
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.Red,
|
||||
@intCast(c_int, font_group.atlas_greyscale.size),
|
||||
@intCast(c_int, font_group.atlas_greyscale.size),
|
||||
@intCast(c_int, options.font_group.atlas_greyscale.size),
|
||||
@intCast(c_int, options.font_group.atlas_greyscale.size),
|
||||
0,
|
||||
.Red,
|
||||
.UnsignedByte,
|
||||
font_group.atlas_greyscale.data.ptr,
|
||||
options.font_group.atlas_greyscale.data.ptr,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -269,12 +272,12 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL {
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.RGBA,
|
||||
@intCast(c_int, font_group.atlas_color.size),
|
||||
@intCast(c_int, font_group.atlas_color.size),
|
||||
@intCast(c_int, options.font_group.atlas_color.size),
|
||||
@intCast(c_int, options.font_group.atlas_color.size),
|
||||
0,
|
||||
.BGRA,
|
||||
.UnsignedByte,
|
||||
font_group.atlas_color.data.ptr,
|
||||
options.font_group.atlas_color.data.ptr,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -289,13 +292,14 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL {
|
||||
.vbo = vbo,
|
||||
.texture = tex,
|
||||
.texture_color = tex_color,
|
||||
.font_group = font_group,
|
||||
.font_group = options.font_group,
|
||||
.font_shaper = shaper,
|
||||
.cursor_visible = true,
|
||||
.cursor_style = .box,
|
||||
.background = .{ .r = 0, .g = 0, .b = 0 },
|
||||
.foreground = .{ .r = 255, .g = 255, .b = 255 },
|
||||
.focused = true,
|
||||
.padding = options.padding,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -943,19 +947,34 @@ pub fn updateCell(
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns the grid size for a given screen size. This is safe to call
|
||||
/// on any thread.
|
||||
pub fn gridSize(self: *OpenGL, screen_size: renderer.ScreenSize) renderer.GridSize {
|
||||
return renderer.GridSize.init(
|
||||
screen_size.subPadding(self.padding.explicit),
|
||||
self.cell_size,
|
||||
);
|
||||
}
|
||||
|
||||
/// Set the screen size for rendering. This will update the projection
|
||||
/// used for the shader so that the scaling of the grid is correct.
|
||||
fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void {
|
||||
// Recalculate the rows/columns.
|
||||
const grid_size = renderer.GridSize.init(dim, self.cell_size);
|
||||
const grid_size = self.gridSize(dim);
|
||||
|
||||
// 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 = renderer.Padding.balanced(dim, grid_size, self.cell_size);
|
||||
// Apply our padding
|
||||
const padding = self.padding.explicit.add(if (self.padding.balance)
|
||||
renderer.Padding.balanced(dim, grid_size, self.cell_size)
|
||||
else .{});
|
||||
const padded_dim = dim.subPadding(padding);
|
||||
|
||||
log.debug("screen size padded={} screen={} grid={} cell={}", .{ padded_dim, dim, grid_size, self.cell_size });
|
||||
log.debug("screen size padded={} screen={} grid={} cell={} padding={}", .{
|
||||
padded_dim,
|
||||
dim,
|
||||
grid_size,
|
||||
self.cell_size,
|
||||
self.padding.explicit,
|
||||
});
|
||||
|
||||
// Update our LRU. We arbitrarily support a certain number of pages here.
|
||||
// We also always support a minimum number of caching in case a user
|
||||
@@ -991,8 +1010,8 @@ fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void {
|
||||
// 2D orthographic projection with the full w/h
|
||||
math.ortho2d(
|
||||
-1 * padding.left,
|
||||
@intToFloat(f32, padded_dim.width),
|
||||
@intToFloat(f32, padded_dim.height),
|
||||
@intToFloat(f32, padded_dim.width) + padding.right,
|
||||
@intToFloat(f32, padded_dim.height) + padding.bottom,
|
||||
-1 * padding.top,
|
||||
),
|
||||
);
|
||||
|
19
src/renderer/Options.zig
Normal file
19
src/renderer/Options.zig
Normal file
@@ -0,0 +1,19 @@
|
||||
//! The options that are used to configure a renderer.
|
||||
|
||||
const font = @import("../font/main.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
|
||||
/// The font group that should be used.
|
||||
font_group: *font.GroupCache,
|
||||
|
||||
/// Padding options for the viewport.
|
||||
padding: Padding,
|
||||
|
||||
pub const Padding = struct {
|
||||
// Explicit padding options, in pixels. The windowing thread is
|
||||
// expected to convert points to pixels for a given DPI.
|
||||
explicit: renderer.Padding,
|
||||
|
||||
// Balance options
|
||||
balance: bool = false,
|
||||
};
|
@@ -48,8 +48,8 @@ pub const ScreenSize = struct {
|
||||
/// Subtract padding from the screen size.
|
||||
pub fn subPadding(self: ScreenSize, padding: Padding) ScreenSize {
|
||||
return .{
|
||||
.width = self.width - @floatToInt(u32, padding.left + padding.right),
|
||||
.height = self.height - @floatToInt(u32, padding.top + padding.bottom),
|
||||
.width = self.width -| @floatToInt(u32, padding.left + padding.right),
|
||||
.height = self.height -| @floatToInt(u32, padding.top + padding.bottom),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -71,17 +71,17 @@ pub const GridSize = struct {
|
||||
/// Update the columns/rows for the grid based on the given screen and
|
||||
/// cell size.
|
||||
pub fn update(self: *GridSize, screen: ScreenSize, cell: CellSize) void {
|
||||
self.columns = @floatToInt(Unit, @intToFloat(f32, screen.width) / cell.width);
|
||||
self.rows = @floatToInt(Unit, @intToFloat(f32, screen.height) / cell.height);
|
||||
self.columns = @max(1, @floatToInt(Unit, @intToFloat(f32, screen.width) / cell.width));
|
||||
self.rows = @max(1, @floatToInt(Unit, @intToFloat(f32, screen.height) / cell.height));
|
||||
}
|
||||
};
|
||||
|
||||
/// The padding to add to a screen.
|
||||
pub const Padding = struct {
|
||||
top: f32,
|
||||
bottom: f32,
|
||||
right: f32,
|
||||
left: f32,
|
||||
top: f32 = 0,
|
||||
bottom: f32 = 0,
|
||||
right: f32 = 0,
|
||||
left: f32 = 0,
|
||||
|
||||
/// Returns padding that balances the whitespace around the screen
|
||||
/// for the given grid and cell sizes.
|
||||
@@ -112,6 +112,16 @@ pub const Padding = struct {
|
||||
.left = padding_left,
|
||||
};
|
||||
}
|
||||
|
||||
/// Add another padding to ths one
|
||||
pub fn add(self: Padding, other: Padding) Padding {
|
||||
return .{
|
||||
.top = self.top + other.top,
|
||||
.bottom = self.bottom + other.bottom,
|
||||
.right = self.right + other.right,
|
||||
.left = self.left + other.left,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "GridSize update exact" {
|
||||
|
@@ -231,13 +231,16 @@ pub fn resize(
|
||||
self: *Exec,
|
||||
grid_size: renderer.GridSize,
|
||||
screen_size: renderer.ScreenSize,
|
||||
padding: renderer.Padding,
|
||||
) !void {
|
||||
const padded_size = screen_size.subPadding(padding);
|
||||
|
||||
// Update the size of our pty
|
||||
try self.pty.setSize(.{
|
||||
.ws_row = @intCast(u16, grid_size.rows),
|
||||
.ws_col = @intCast(u16, grid_size.columns),
|
||||
.ws_xpixel = @intCast(u16, screen_size.width),
|
||||
.ws_ypixel = @intCast(u16, screen_size.height),
|
||||
.ws_xpixel = @intCast(u16, padded_size.width),
|
||||
.ws_ypixel = @intCast(u16, padded_size.height),
|
||||
});
|
||||
|
||||
// Update our cached grid size
|
||||
|
@@ -172,7 +172,7 @@ fn drainMailbox(self: *Thread) !void {
|
||||
|
||||
log.debug("mailbox message={}", .{message});
|
||||
switch (message) {
|
||||
.resize => |v| try self.impl.resize(v.grid_size, v.screen_size),
|
||||
.resize => |v| try self.impl.resize(v.grid_size, v.screen_size, v.padding),
|
||||
.write_small => |v| try self.impl.queueWrite(v.data[0..v.len]),
|
||||
.write_stable => |v| try self.impl.queueWrite(v),
|
||||
.write_alloc => |v| {
|
||||
|
@@ -12,8 +12,16 @@ const terminal = @import("../terminal/main.zig");
|
||||
pub const Message = union(enum) {
|
||||
/// Resize the window.
|
||||
resize: struct {
|
||||
/// The grid size for the given screen size with padding applied.
|
||||
grid_size: renderer.GridSize,
|
||||
|
||||
/// 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,
|
||||
},
|
||||
|
||||
/// Write where the data fits in the union.
|
||||
|
Reference in New Issue
Block a user