Merge pull request #32 from mitchellh/auto-pad

Balanced Viewport Margins/Padding
This commit is contained in:
Mitchell Hashimoto
2022-11-14 15:59:43 -08:00
committed by GitHub
3 changed files with 117 additions and 45 deletions

View File

@ -390,14 +390,13 @@ pub fn render(
defer objc_autoreleasePoolPop(pool); defer objc_autoreleasePoolPop(pool);
// If we're resizing, then we have to update a bunch of things... // If we're resizing, then we have to update a bunch of things...
if (critical.screen_size) |screen_size| { if (critical.screen_size) |_| {
// Update our grid size // Note: we ignore the screen size value because our view should be
try self.setScreenSize(screen_size); // automatically updated by being in the window.
const bounds = self.swapchain.getProperty(macos.graphics.Rect, "bounds");
// Scale the bounds based on the layer content scale so that we // Scale the bounds based on the layer content scale so that we
// properly handle Retina. // properly handle Retina.
const bounds = self.swapchain.getProperty(macos.graphics.Rect, "bounds");
const scaled: macos.graphics.Size = scaled: { const scaled: macos.graphics.Size = scaled: {
const scaleFactor = self.swapchain.getProperty(macos.graphics.c.CGFloat, "contentsScale"); const scaleFactor = self.swapchain.getProperty(macos.graphics.c.CGFloat, "contentsScale");
break :scaled .{ break :scaled .{
@ -406,25 +405,8 @@ pub fn render(
}; };
}; };
// Set the size of the drawable surface to the scaled bounds // Handle our new size
self.swapchain.setProperty("drawableSize", scaled); try self.setScreenSize(scaled);
//log.warn("bounds={} screen={} scaled={}", .{ bounds, screen_size, scaled });
// Setup our uniforms
const old = self.uniforms;
self.uniforms = .{
.projection_matrix = math.ortho2d(
0,
@floatCast(f32, scaled.width),
@floatCast(f32, scaled.height),
0,
),
.cell_size = .{ self.cell_size.width, self.cell_size.height },
.underline_position = old.underline_position,
.underline_thickness = old.underline_thickness,
.strikethrough_position = old.strikethrough_position,
.strikethrough_thickness = old.strikethrough_thickness,
};
} }
// Build our GPU cells // Build our GPU cells
@ -572,10 +554,22 @@ pub fn render(
} }
/// Resize the screen. /// Resize the screen.
fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void { fn setScreenSize(self: *Metal, bounds: macos.graphics.Size) !void {
// Easier to work with our own types
const dim: renderer.ScreenSize = .{
.width = @floatToInt(u32, bounds.width),
.height = @floatToInt(u32, bounds.height),
};
// Recalculate the rows/columns. // Recalculate the rows/columns.
const grid_size = renderer.GridSize.init(dim, self.cell_size); const grid_size = renderer.GridSize.init(dim, self.cell_size);
// 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 padded_dim = dim.subPadding(padding);
// Update our shaper // Update our shaper
// TODO: don't reallocate if it is close enough (but bigger) // TODO: don't reallocate if it is close enough (but bigger)
var shape_buf = try self.alloc.alloc(font.Shaper.Cell, grid_size.columns * 2); var shape_buf = try self.alloc.alloc(font.Shaper.Cell, grid_size.columns * 2);
@ -583,6 +577,25 @@ fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void {
self.alloc.free(self.font_shaper.cell_buf); self.alloc.free(self.font_shaper.cell_buf);
self.font_shaper.cell_buf = shape_buf; self.font_shaper.cell_buf = shape_buf;
// Set the size of the drawable surface to the bounds
self.swapchain.setProperty("drawableSize", bounds);
// Setup our uniforms
const old = self.uniforms;
self.uniforms = .{
.projection_matrix = math.ortho2d(
-1 * padding.left,
@intToFloat(f32, padded_dim.width),
@intToFloat(f32, padded_dim.height),
-1 * padding.top,
),
.cell_size = .{ self.cell_size.width, self.cell_size.height },
.underline_position = old.underline_position,
.underline_thickness = old.underline_thickness,
.strikethrough_position = old.strikethrough_position,
.strikethrough_thickness = old.strikethrough_thickness,
};
log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size }); log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size });
} }

View File

@ -547,10 +547,6 @@ pub fn render(
if (critical.screen_size) |size| { if (critical.screen_size) |size| {
// Update our grid size // Update our grid size
try self.setScreenSize(size); try self.setScreenSize(size);
// 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(i32, size.width), @intCast(i32, size.height));
} }
// Build our GPU cells // Build our GPU cells
@ -950,24 +946,17 @@ pub fn updateCell(
/// Set the screen size for rendering. This will update the projection /// Set the screen size for rendering. This will update the projection
/// used for the shader so that the scaling of the grid is correct. /// used for the shader so that the scaling of the grid is correct.
fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void { fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void {
// Update the projection uniform within our shader
const bind = try self.program.use();
defer bind.unbind();
try self.program.setUniform(
"projection",
// 2D orthographic projection with the full w/h
math.ortho2d(
0,
@intToFloat(f32, dim.width),
@intToFloat(f32, dim.height),
0,
),
);
// Recalculate the rows/columns. // Recalculate the rows/columns.
const grid_size = renderer.GridSize.init(dim, self.cell_size); const grid_size = renderer.GridSize.init(dim, self.cell_size);
// 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 padded_dim = dim.subPadding(padding);
log.debug("screen size padded={} screen={} grid={} cell={}", .{ padded_dim, dim, grid_size, self.cell_size });
// Update our LRU. We arbitrarily support a certain number of pages here. // 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 // We also always support a minimum number of caching in case a user
// is resizing tiny then growing again we can save some of the renders. // is resizing tiny then growing again we can save some of the renders.
@ -983,7 +972,31 @@ fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void {
self.alloc.free(self.font_shaper.cell_buf); self.alloc.free(self.font_shaper.cell_buf);
self.font_shaper.cell_buf = shape_buf; self.font_shaper.cell_buf = shape_buf;
log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size }); // 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(i32, dim.width),
@intCast(i32, dim.height),
);
// Update the projection uniform within our shader
{
const bind = try self.program.use();
defer bind.unbind();
try self.program.setUniform(
"projection",
// 2D orthographic projection with the full w/h
math.ortho2d(
-1 * padding.left,
@intToFloat(f32, padded_dim.width),
@intToFloat(f32, padded_dim.height),
-1 * padding.top,
),
);
}
} }
/// Updates the font texture atlas if it is dirty. /// Updates the font texture atlas if it is dirty.

View File

@ -44,6 +44,14 @@ pub const CellSize = struct {
pub const ScreenSize = struct { pub const ScreenSize = struct {
width: u32, width: u32,
height: u32, height: u32,
/// 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),
};
}
}; };
/// The dimensions of the grid itself, in rows/columns units. /// The dimensions of the grid itself, in rows/columns units.
@ -68,6 +76,44 @@ pub const GridSize = struct {
} }
}; };
/// The padding to add to a screen.
pub const Padding = struct {
top: f32,
bottom: f32,
right: f32,
left: f32,
/// Returns padding that balances the whitespace around the screen
/// for the given grid and cell sizes.
pub fn balanced(screen: ScreenSize, grid: GridSize, cell: CellSize) Padding {
// The size of our full grid
const grid_width = @intToFloat(f32, grid.columns) * cell.width;
const grid_height = @intToFloat(f32, grid.rows) * cell.height;
// The empty space to the right of a line and bottom of the last row
const space_right = @intToFloat(f32, screen.width) - grid_width;
const space_bot = @intToFloat(f32, screen.height) - grid_height;
// The left/right padding is just an equal split.
const padding_right = @floor(space_right / 2);
const padding_left = padding_right;
// The top/bottom padding is interesting. Subjectively, lots of padding
// at the top looks bad. So instead of always being equal (like left/right),
// we force the top padding to be at most equal to the left, and the bottom
// padding is the difference thereafter.
const padding_top = @min(padding_left, @floor(space_bot / 2));
const padding_bot = space_bot - padding_top;
return .{
.top = padding_top,
.bottom = padding_bot,
.right = padding_right,
.left = padding_left,
};
}
};
test "GridSize update exact" { test "GridSize update exact" {
const testing = std.testing; const testing = std.testing;