mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #34 from mitchellh/font-resize
Font Resize at Runtime
This commit is contained in:
122
src/Window.zig
122
src/Window.zig
@ -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);
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -275,6 +275,10 @@ fn drainMailbox(self: *Thread) !void {
|
||||
_ = try self.cursor_h.again();
|
||||
}
|
||||
},
|
||||
|
||||
.font_size => |size| {
|
||||
try self.renderer.setFontSize(size);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user