Merge pull request #34 from mitchellh/font-resize

Font Resize at Runtime
This commit is contained in:
Mitchell Hashimoto
2022-11-15 20:39:38 -08:00
committed by GitHub
14 changed files with 502 additions and 39 deletions

View File

@ -49,6 +49,7 @@ app: *App,
/// The font structures
font_lib: font.Library,
font_group: *font.GroupCache,
font_size: font.face.DesiredSize,
/// The glfw window handle.
window: glfw.Window,
@ -79,8 +80,10 @@ io: termio.Impl,
io_thread: termio.Thread,
io_thr: std.Thread,
/// The dimensions of the grid in rows and columns.
/// 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,
@ -262,6 +265,9 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
});
errdefer font_group.deinit(alloc);
// Pre-calculate our initial cell size ourselves.
const cell_size = try renderer.CellSize.init(alloc, font_group);
// 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;
@ -279,6 +285,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
.explicit = padding,
.balance = config.@"window-padding-balance",
},
.window_mailbox = .{ .window = self, .app = app.mailbox },
});
errdefer renderer_impl.deinit();
renderer_impl.background = .{
@ -298,13 +305,16 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
.width = window_size.width,
.height = window_size.height,
};
const grid_size = renderer_impl.gridSize(screen_size);
const grid_size = renderer.GridSize.init(
screen_size.subPadding(padding),
cell_size,
);
// Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app
// but is otherwise somewhat arbitrary.
try window.setSizeLimits(.{
.width = @floatToInt(u32, renderer_impl.cell_size.width * 10),
.height = @floatToInt(u32, renderer_impl.cell_size.height * 4),
.width = @floatToInt(u32, cell_size.width * 10),
.height = @floatToInt(u32, cell_size.height * 4),
}, .{ .width = null, .height = null });
// Create the cursor
@ -352,6 +362,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
.app = app,
.font_lib = font_lib,
.font_group = font_group,
.font_size = font_size,
.window = window,
.cursor = cursor,
.renderer = renderer_impl,
@ -371,7 +382,9 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
.io = io,
.io_thread = io_thread,
.io_thr = undefined,
.screen_size = screen_size,
.grid_size = grid_size,
.cell_size = cell_size,
.padding = padding,
.config = config,
@ -516,9 +529,48 @@ pub fn handleMessage(self: *Window, msg: Message) !void {
log.debug("changing title \"{s}\"", .{slice});
try self.window.setTitle(slice.ptr);
},
.cell_size => |size| try self.setCellSize(size),
}
}
/// Change the cell size for the terminal grid. This can happen as
/// a result of changing the font size at runtime.
fn setCellSize(self: *Window, 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,
);
// Notify the terminal
_ = self.io_thread.mailbox.push(.{
.resize = .{
.grid_size = self.grid_size,
.screen_size = self.screen_size,
.padding = self.padding,
},
}, .{ .forever = {} });
self.io_thread.wakeup.send() catch {};
}
/// Change the font size.
fn setFontSize(self: *Window, size: font.face.DesiredSize) void {
// Update our font size so future changes work
self.font_size = size;
// Notify our render thread of the font size. This triggers everything else.
_ = self.renderer_thread.mailbox.push(.{
.font_size = size,
}, .{ .forever = {} });
// Schedule render which also drains our mailbox
self.queueRender() catch unreachable;
}
/// This queues a render operation with the renderer thread. The render
/// isn't guaranteed to happen immediately but it will happen as soon as
/// practical.
@ -569,18 +621,22 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
};
};
const screen_size: renderer.ScreenSize = .{
.width = px_size.width,
.height = px_size.height,
};
const win = window.getUserPointer(Window) orelse return;
// TODO: if our screen size didn't change, then we should avoid the
// overhead of inter-thread communication
// Save our screen size
win.screen_size = .{
.width = px_size.width,
.height = px_size.height,
};
// Recalculate our grid size
win.grid_size = win.renderer.gridSize(screen_size);
win.grid_size = renderer.GridSize.init(
win.screen_size.subPadding(win.padding),
win.cell_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?", .{});
@ -594,7 +650,7 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
_ = win.io_thread.mailbox.push(.{
.resize = .{
.grid_size = win.grid_size,
.screen_size = screen_size,
.screen_size = win.screen_size,
.padding = win.padding,
},
}, .{ .forever = {} });
@ -720,6 +776,16 @@ fn keyCallback(
.x => .x,
.y => .y,
.z => .z,
.zero => .zero,
.one => .one,
.two => .three,
.three => .four,
.four => .four,
.five => .five,
.six => .six,
.seven => .seven,
.eight => .eight,
.nine => .nine,
.up => .up,
.down => .down,
.right => .right,
@ -742,6 +808,8 @@ fn keyCallback(
.F11 => .f11,
.F12 => .f12,
.grave_accent => .grave_accent,
.minus => .minus,
.equal => .equal,
else => .invalid,
},
};
@ -818,6 +886,30 @@ fn keyCallback(
}
},
.increase_font_size => |delta| {
log.debug("increase font size={}", .{delta});
var size = win.font_size;
size.points +|= delta;
win.setFontSize(size);
},
.decrease_font_size => |delta| {
log.debug("decrease font size={}", .{delta});
var size = win.font_size;
size.points = @max(1, size.points -| delta);
win.setFontSize(size);
},
.reset_font_size => {
log.debug("reset font size", .{});
var size = win.font_size;
size.points = win.config.@"font-size";
win.setFontSize(size);
},
.toggle_dev_mode => if (DevMode.enabled) {
DevMode.instance.visible = !DevMode.instance.visible;
win.queueRender() catch unreachable;
@ -1386,10 +1478,10 @@ fn cursorPosCallback(
//
// the boundary point at which we consider selection or non-selection
const cell_xboundary = win.renderer.cell_size.width * 0.6;
const cell_xboundary = win.cell_size.width * 0.6;
// first xpos of the clicked cell
const cell_xstart = @intToFloat(f32, win.mouse.left_click_point.x) * win.renderer.cell_size.width;
const cell_xstart = @intToFloat(f32, win.mouse.left_click_point.x) * win.cell_size.width;
const cell_start_xpos = win.mouse.left_click_xpos - cell_xstart;
// If this is the same cell, then we only start the selection if weve
@ -1464,7 +1556,7 @@ fn posToViewport(self: Window, xpos: f64, ypos: f64) terminal.point.Viewport {
return .{
.x = if (xpos < 0) 0 else x: {
// Our cell is the mouse divided by cell width
const cell_width = @floatCast(f64, self.renderer.cell_size.width);
const cell_width = @floatCast(f64, self.cell_size.width);
const x = @floatToInt(usize, xpos / cell_width);
// Can be off the screen if the user drags it out, so max
@ -1473,7 +1565,7 @@ fn posToViewport(self: Window, xpos: f64, ypos: f64) terminal.point.Viewport {
},
.y = if (ypos < 0) 0 else y: {
const cell_height = @floatCast(f64, self.renderer.cell_size.height);
const cell_height = @floatCast(f64, self.cell_size.height);
const y = @floatToInt(usize, ypos / cell_height);
break :y @min(y, self.io.terminal.rows - 1);
},

View File

@ -176,6 +176,23 @@ pub const Config = struct {
try result.keybind.set.put(alloc, .{ .key = .f11 }, .{ .csi = "23~" });
try result.keybind.set.put(alloc, .{ .key = .f12 }, .{ .csi = "24~" });
// Fonts
try result.keybind.set.put(
alloc,
.{ .key = .equal, .mods = .{ .super = true } },
.{ .increase_font_size = 1 },
);
try result.keybind.set.put(
alloc,
.{ .key = .minus, .mods = .{ .super = true } },
.{ .decrease_font_size = 1 },
);
try result.keybind.set.put(
alloc,
.{ .key = .zero, .mods = .{ .super = true } },
.{ .reset_font_size = {} },
);
// Dev Mode
try result.keybind.set.put(
alloc,

View File

@ -74,6 +74,25 @@ pub fn addFace(self: *Group, alloc: Allocator, style: Style, face: DeferredFace)
try self.faces.getPtr(style).append(alloc, face);
}
/// Resize the fonts to the desired size.
pub fn setSize(self: *Group, size: font.face.DesiredSize) !void {
// Note: there are some issues here with partial failure. We don't
// currently handle it in any meaningful way if one face can resize
// but another can't.
// Resize all our faces that are loaded
var it = self.faces.iterator();
while (it.next()) |entry| {
for (entry.value.items) |*deferred| {
if (!deferred.loaded()) continue;
try deferred.face.?.setSize(size);
}
}
// Set our size for future loads
self.size = size;
}
/// This represents a specific font in the group.
pub const FontIndex = packed struct {
/// The number of bits we use for the index.
@ -226,7 +245,57 @@ test {
}
}
test {
test "resize" {
const testing = std.testing;
const alloc = testing.allocator;
const testFont = @import("test.zig").fontRegular;
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
defer atlas_greyscale.deinit(alloc);
var lib = try Library.init();
defer lib.deinit();
var group = try init(alloc, lib, .{ .points = 12 });
defer group.deinit(alloc);
try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })));
// Load a letter
{
const idx = group.indexForCodepoint('A', .regular, null).?;
const face = try group.faceFromIndex(idx);
const glyph_index = face.glyphIndex('A').?;
const glyph = try group.renderGlyph(
alloc,
&atlas_greyscale,
idx,
glyph_index,
null,
);
try testing.expectEqual(@as(u32, 11), glyph.height);
}
// Resize
try group.setSize(.{ .points = 24 });
{
const idx = group.indexForCodepoint('A', .regular, null).?;
const face = try group.faceFromIndex(idx);
const glyph_index = face.glyphIndex('A').?;
const glyph = try group.renderGlyph(
alloc,
&atlas_greyscale,
idx,
glyph_index,
null,
);
try testing.expectEqual(@as(u32, 21), glyph.height);
}
}
test "discover monospace with fontconfig and freetype" {
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
const testing = std.testing;

View File

@ -6,6 +6,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Atlas = @import("../Atlas.zig");
const font = @import("main.zig");
const Face = @import("main.zig").Face;
const DeferredFace = @import("main.zig").DeferredFace;
const Library = @import("main.zig").Library;
@ -83,6 +84,18 @@ pub fn reset(self: *GroupCache) void {
self.glyphs.clearRetainingCapacity();
}
/// Resize the fonts in the group. This will clear the cache.
pub fn setSize(self: *GroupCache, size: font.face.DesiredSize) !void {
try self.group.setSize(size);
// Reset our internal state
self.reset();
// Clear our atlases
self.atlas_greyscale.clear();
self.atlas_color.clear();
}
/// Get the font index for a given codepoint. This is cached.
pub fn indexForCodepoint(
self: *GroupCache,
@ -216,3 +229,61 @@ test {
}
}
}
test "resize" {
const testing = std.testing;
const alloc = testing.allocator;
const testFont = @import("test.zig").fontRegular;
// const testEmoji = @import("test.zig").fontEmoji;
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
defer atlas_greyscale.deinit(alloc);
var lib = try Library.init();
defer lib.deinit();
var cache = try init(alloc, try Group.init(
alloc,
lib,
.{ .points = 12 },
));
defer cache.deinit(alloc);
// Setup group
try cache.group.addFace(
alloc,
.regular,
DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })),
);
// Load a letter
{
const idx = (try cache.indexForCodepoint(alloc, 'A', .regular, null)).?;
const face = try cache.group.faceFromIndex(idx);
const glyph_index = face.glyphIndex('A').?;
const glyph = try cache.renderGlyph(
alloc,
idx,
glyph_index,
null,
);
try testing.expectEqual(@as(u32, 11), glyph.height);
}
// Resize
try cache.setSize(.{ .points = 24 });
{
const idx = (try cache.indexForCodepoint(alloc, 'A', .regular, null)).?;
const face = try cache.group.faceFromIndex(idx);
const glyph_index = face.glyphIndex('A').?;
const glyph = try cache.renderGlyph(
alloc,
idx,
glyph_index,
null,
);
try testing.expectEqual(@as(u32, 21), glyph.height);
}
}

View File

@ -65,6 +65,15 @@ pub const Face = struct {
self.* = undefined;
}
/// Resize the font in-place. If this succeeds, the caller is responsible
/// for clearing any glyph caches, font atlas data, etc.
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
// We just create a copy and replace ourself
const face = try initFontCopy(self.font, size);
self.deinit();
self.* = face;
}
/// Returns the glyph index for the given Unicode code point. If this
/// face doesn't support this glyph, null is returned.
pub fn glyphIndex(self: Face, cp: u32) ?u32 {

View File

@ -71,6 +71,13 @@ pub const Face = struct {
self.* = undefined;
}
/// Resize the font in-place. If this succeeds, the caller is responsible
/// for clearing any glyph caches, font atlas data, etc.
pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
try setSize_(self.face, size);
self.metrics = calcMetrics(self.face);
}
fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void {
// If we have fixed sizes, we just have to try to pick the one closest
// to what the user requested. Otherwise, we can choose an arbitrary
@ -456,7 +463,6 @@ test {
var ft_font = try Face.init(lib, testFont, .{ .points = 12 });
defer ft_font.deinit();
log.warn("FT={}", .{ft_font.metrics});
try testing.expectEqual(Presentation.text, ft_font.presentation);
@ -465,6 +471,16 @@ test {
while (i < 127) : (i += 1) {
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex(i).?, null);
}
// Test resizing
{
const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null);
try testing.expectEqual(@as(u32, 11), g1.height);
try ft_font.setSize(.{ .points = 24 });
const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null);
try testing.expectEqual(@as(u32, 21), g2.height);
}
}
test "color emoji" {
@ -491,6 +507,42 @@ test "color emoji" {
}
}
test "metrics" {
const testFont = @import("../test.zig").fontRegular;
const alloc = testing.allocator;
var lib = try Library.init();
defer lib.deinit();
var atlas = try Atlas.init(alloc, 512, .greyscale);
defer atlas.deinit(alloc);
var ft_font = try Face.init(lib, testFont, .{ .points = 12 });
defer ft_font.deinit();
try testing.expectEqual(font.face.Metrics{
.cell_width = 8,
.cell_height = 1.8e1,
.cell_baseline = 4,
.underline_position = 18,
.underline_thickness = 1,
.strikethrough_position = 10,
.strikethrough_thickness = 1,
}, ft_font.metrics);
// Resize should change metrics
try ft_font.setSize(.{ .points = 24 });
try testing.expectEqual(font.face.Metrics{
.cell_width = 16,
.cell_height = 35,
.cell_baseline = 7,
.underline_position = 36,
.underline_thickness = 2,
.strikethrough_position = 20,
.strikethrough_thickness = 2,
}, ft_font.metrics);
}
test "mono to rgba" {
const alloc = testing.allocator;
const testFont = @import("../test.zig").fontEmoji;

View File

@ -131,6 +131,13 @@ pub const Action = union(enum) {
copy_to_clipboard: void,
paste_from_clipboard: void,
/// Increase/decrease the font size by a certain amount
increase_font_size: u16,
decrease_font_size: u16,
/// Reset the font size to the original configured size
reset_font_size: void,
/// Dev mode
toggle_dev_mode: void,

View File

@ -49,8 +49,22 @@ pub const Key = enum {
y,
z,
// numbers
zero,
one,
two,
three,
four,
five,
six,
seven,
eight,
nine,
// other
grave_accent, // `
minus,
equal,
// control
up,

View File

@ -16,6 +16,7 @@ const terminal = @import("../terminal/main.zig");
const renderer = @import("../renderer.zig");
const math = @import("../math.zig");
const DevMode = @import("../DevMode.zig");
const Window = @import("../Window.zig");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Terminal = terminal.Terminal;
@ -30,6 +31,9 @@ const log = std.log.scoped(.metal);
/// Allocator that can be used
alloc: std.mem.Allocator,
/// The mailbox for communicating with the window.
window_mailbox: Window.Mailbox,
/// Current cell dimensions for this grid.
cell_size: renderer.CellSize,
@ -208,6 +212,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
return Metal{
.alloc = alloc,
.window_mailbox = options.window_mailbox,
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
.padding = options.padding,
.background = .{ .r = 0, .g = 0, .b = 0 },
@ -308,7 +313,7 @@ pub fn threadExit(self: *const Metal) void {
/// 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 {
fn gridSize(self: *Metal, screen_size: renderer.ScreenSize) renderer.GridSize {
return renderer.GridSize.init(
screen_size.subPadding(self.padding.explicit),
self.cell_size,
@ -316,15 +321,57 @@ pub fn gridSize(self: *Metal, screen_size: renderer.ScreenSize) renderer.GridSiz
}
/// Callback when the focus changes for the terminal this is rendering.
///
/// Must be called on the render thread.
pub fn setFocus(self: *Metal, focus: bool) !void {
self.focused = focus;
}
/// Called to toggle the blink state of the cursor
///
/// Must be called on the render thread.
pub fn blinkCursor(self: *Metal, reset: bool) void {
self.cursor_visible = reset or !self.cursor_visible;
}
/// Set the new font size.
///
/// Must be called on the render thread.
pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
log.info("set font size={}", .{size});
// Set our new size, this will also reset our font atlas.
try self.font_group.setSize(size);
// Recalculate our metrics
const metrics = metrics: {
const index = (try self.font_group.indexForCodepoint(self.alloc, 'M', .regular, .text)).?;
const face = try self.font_group.group.faceFromIndex(index);
break :metrics face.metrics;
};
const new_cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height };
// Update our uniforms
self.uniforms = .{
.projection_matrix = self.uniforms.projection_matrix,
.cell_size = .{ new_cell_size.width, new_cell_size.height },
.underline_position = metrics.underline_position,
.underline_thickness = metrics.underline_thickness,
.strikethrough_position = metrics.strikethrough_position,
.strikethrough_thickness = metrics.strikethrough_thickness,
};
// Recalculate our cell size. If it is the same as before, then we do
// nothing since the grid size couldn't have possibly changed.
if (std.meta.eql(self.cell_size, new_cell_size)) return;
self.cell_size = new_cell_size;
// Notify the window that the cell size changed.
_ = self.window_mailbox.push(.{
.cell_size = new_cell_size,
}, .{ .forever = {} });
}
/// The primary render callback that is completely thread-safe.
pub fn render(
self: *Metal,

View File

@ -18,6 +18,7 @@ const trace = @import("tracy").trace;
const math = @import("../math.zig");
const lru = @import("../lru.zig");
const DevMode = @import("../DevMode.zig");
const Window = @import("../Window.zig");
const log = std.log.scoped(.grid);
@ -80,6 +81,9 @@ focused: bool,
/// Padding options
padding: renderer.Options.Padding,
/// The mailbox for communicating with the window.
window_mailbox: Window.Mailbox,
/// 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.
@ -156,22 +160,15 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
var shaper = try font.Shaper.init(shape_buf);
errdefer shaper.deinit();
// Get our cell metrics based on a regular font ascii 'M'. Why 'M'?
// 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 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});
// Create our shader
const program = try gl.Program.createVF(
@embedFile("shaders/cell.v.glsl"),
@embedFile("shaders/cell.f.glsl"),
);
// Setup our font metrics uniform
const metrics = try resetFontMetrics(alloc, program, options.font_group);
// Set our cell dimensions
const pbind = try program.use();
defer pbind.unbind();
@ -300,6 +297,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
.foreground = .{ .r = 255, .g = 255, .b = 255 },
.focused = true,
.padding = options.padding,
.window_mailbox = options.window_mailbox,
};
}
@ -314,19 +312,27 @@ pub fn deinit(self: *OpenGL) void {
self.vao.destroy();
self.program.destroy();
{
self.resetCellsLRU();
self.cells_lru.deinit(self.alloc);
self.cells.deinit(self.alloc);
self.* = undefined;
}
fn resetCellsLRU(self: *OpenGL) void {
// Preserve the old capacity so that we have space in our LRU
const cap = self.cells_lru.capacity;
// Our LRU values are array lists so we need to deallocate those first
var it = self.cells_lru.queue.first;
while (it) |node| {
it = node.next;
node.data.value.deinit(self.alloc);
}
self.cells_lru.deinit(self.alloc);
}
self.cells.deinit(self.alloc);
self.* = undefined;
// Initialize our new LRU
self.cells_lru = CellsLRU.init(cap);
}
/// Returns the hints that we want for this
@ -448,15 +454,75 @@ pub fn threadExit(self: *const OpenGL) void {
}
/// Callback when the focus changes for the terminal this is rendering.
///
/// Must be called on the render thread.
pub fn setFocus(self: *OpenGL, focus: bool) !void {
self.focused = focus;
}
/// Called to toggle the blink state of the cursor
///
/// Must be called on the render thread.
pub fn blinkCursor(self: *OpenGL, reset: bool) void {
self.cursor_visible = reset or !self.cursor_visible;
}
/// Set the new font size.
///
/// Must be called on the render thread.
pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void {
log.info("set font size={}", .{size});
// Set our new size, this will also reset our font atlas.
try self.font_group.setSize(size);
// Invalidate our cell cache.
self.resetCellsLRU();
// Reset our GPU uniforms
const metrics = try resetFontMetrics(self.alloc, self.program, self.font_group);
// Recalculate our cell size. If it is the same as before, then we do
// nothing since the grid size couldn't have possibly changed.
const new_cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height };
if (std.meta.eql(self.cell_size, new_cell_size)) return;
self.cell_size = new_cell_size;
// Notify the window that the cell size changed.
_ = self.window_mailbox.push(.{
.cell_size = new_cell_size,
}, .{ .forever = {} });
}
/// Reload the font metrics, recalculate cell size, and send that all
/// down to the GPU.
fn resetFontMetrics(
alloc: Allocator,
program: gl.Program,
font_group: *font.GroupCache,
) !font.face.Metrics {
// Get our cell metrics based on a regular font ascii 'M'. Why 'M'?
// 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);
break :metrics face.metrics;
};
log.debug("cell dimensions={}", .{metrics});
// Set our uniforms that rely on metrics
const pbind = try program.use();
defer pbind.unbind();
try program.setUniform("cell_size", @Vector(2, f32){ metrics.cell_width, metrics.cell_height });
try program.setUniform("underline_position", metrics.underline_position);
try program.setUniform("underline_thickness", metrics.underline_thickness);
try program.setUniform("strikethrough_position", metrics.strikethrough_position);
try program.setUniform("strikethrough_thickness", metrics.strikethrough_thickness);
return metrics;
}
/// The primary render callback that is completely thread-safe.
pub fn render(
self: *OpenGL,
@ -946,7 +1012,7 @@ pub fn updateCell(
/// 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 {
fn gridSize(self: *OpenGL, screen_size: renderer.ScreenSize) renderer.GridSize {
return renderer.GridSize.init(
screen_size.subPadding(self.padding.explicit),
self.cell_size,

View File

@ -2,6 +2,7 @@
const font = @import("../font/main.zig");
const renderer = @import("../renderer.zig");
const Window = @import("../Window.zig");
/// The font group that should be used.
font_group: *font.GroupCache,
@ -9,6 +10,10 @@ font_group: *font.GroupCache,
/// Padding options for the viewport.
padding: Padding,
/// The mailbox for sending the window messages. This is only valid
/// once the thread has started and should not be used outside of the thread.
window_mailbox: Window.Mailbox,
pub const Padding = struct {
// Explicit padding options, in pixels. The windowing thread is
// expected to convert points to pixels for a given DPI.

View File

@ -275,6 +275,10 @@ fn drainMailbox(self: *Thread) !void {
_ = try self.cursor_h.again();
}
},
.font_size => |size| {
try self.renderer.setFontSize(size);
},
}
}
}

View File

@ -1,6 +1,7 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const font = @import("../font/main.zig");
/// The messages that can be sent to a renderer thread.
pub const Message = union(enum) {
@ -12,4 +13,9 @@ pub const Message = union(enum) {
/// Reset the cursor blink by immediately showing the cursor then
/// restarting the timer.
reset_cursor_blink: void,
/// Change the font size. This should recalculate the grid size and
/// send a grid size change message back to the window thread if
/// the size changes.
font_size: font.face.DesiredSize,
};

View File

@ -1,5 +1,6 @@
const App = @import("../App.zig");
const Window = @import("../Window.zig");
const renderer = @import("../renderer.zig");
/// The message types that can be sent to a single window.
pub const Message = union(enum) {
@ -8,6 +9,9 @@ pub const Message = union(enum) {
/// the termio message so that we can more efficiently send strings
/// of any length
set_title: [256]u8,
/// Change the cell size.
cell_size: renderer.CellSize,
};
/// A window mailbox.