Merge pull request #33 from mitchellh/padding-cfg

Explicit Padding Configuration
This commit is contained in:
Mitchell Hashimoto
2022-11-14 17:44:14 -08:00
committed by GitHub
11 changed files with 178 additions and 42 deletions

View File

@@ -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 {};

View File

@@ -138,6 +138,12 @@ fn parseIntoField(
0,
),
u32 => try std.fmt.parseInt(
u32,
value orelse return error.ValueRequired,
0,
),
else => unreachable,
};

View File

@@ -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 = .{},

View File

@@ -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");

View File

@@ -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 },

View File

@@ -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
View 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,
};

View File

@@ -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" {

View File

@@ -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

View File

@@ -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| {

View File

@@ -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.