mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
font: cursor size remains original cell height if height adjusted
Fixes #1067
This commit is contained in:
@ -147,6 +147,13 @@ const c = @cImport({
|
|||||||
/// position is clamped to the height of a cell. If you set the underline
|
/// position is clamped to the height of a cell. If you set the underline
|
||||||
/// position so high that it extends beyond the bottom of the cell size,
|
/// position so high that it extends beyond the bottom of the cell size,
|
||||||
/// it will be clamped to the bottom of the cell.
|
/// it will be clamped to the bottom of the cell.
|
||||||
|
///
|
||||||
|
/// "adjust-cell-height" has some additional behaviors to describe:
|
||||||
|
/// - The font will be centered vertically in the cell.
|
||||||
|
/// - The cursor will remain the same size as the font.
|
||||||
|
/// - Powerline glyphs will be adjusted along with the cell height so
|
||||||
|
/// that things like status lines continue to look aligned.
|
||||||
|
///
|
||||||
@"adjust-cell-width": ?MetricModifier = null,
|
@"adjust-cell-width": ?MetricModifier = null,
|
||||||
@"adjust-cell-height": ?MetricModifier = null,
|
@"adjust-cell-height": ?MetricModifier = null,
|
||||||
@"adjust-font-baseline": ?MetricModifier = null,
|
@"adjust-font-baseline": ?MetricModifier = null,
|
||||||
|
@ -21,6 +21,11 @@ underline_thickness: u32,
|
|||||||
strikethrough_position: u32,
|
strikethrough_position: u32,
|
||||||
strikethrough_thickness: u32,
|
strikethrough_thickness: u32,
|
||||||
|
|
||||||
|
/// Original cell width and height. These are used to render the cursor
|
||||||
|
/// in the original cell size after modification.
|
||||||
|
original_cell_width: ?u32 = null,
|
||||||
|
original_cell_height: ?u32 = null,
|
||||||
|
|
||||||
/// Apply a set of modifiers.
|
/// Apply a set of modifiers.
|
||||||
pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
||||||
var it = mods.iterator();
|
var it = mods.iterator();
|
||||||
@ -31,8 +36,18 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
|||||||
inline .cell_width,
|
inline .cell_width,
|
||||||
.cell_height,
|
.cell_height,
|
||||||
=> |tag| {
|
=> |tag| {
|
||||||
|
// Compute the new value. If it is the same avoid the work.
|
||||||
const original = @field(self, @tagName(tag));
|
const original = @field(self, @tagName(tag));
|
||||||
const new = @max(entry.value_ptr.apply(original), 1);
|
const new = @max(entry.value_ptr.apply(original), 1);
|
||||||
|
if (new == original) continue;
|
||||||
|
|
||||||
|
// Preserve the original cell width and height if not set.
|
||||||
|
if (self.original_cell_width == null) {
|
||||||
|
self.original_cell_width = self.cell_width;
|
||||||
|
self.original_cell_height = self.cell_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new value
|
||||||
@field(self, @tagName(tag)) = new;
|
@field(self, @tagName(tag)) = new;
|
||||||
|
|
||||||
// For cell height, we have to also modify some positions
|
// For cell height, we have to also modify some positions
|
||||||
@ -42,10 +57,17 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
|||||||
if (comptime tag == .cell_height) {
|
if (comptime tag == .cell_height) {
|
||||||
// We split the difference in half because we want to
|
// We split the difference in half because we want to
|
||||||
// center the baseline in the cell.
|
// center the baseline in the cell.
|
||||||
const diff = (new - original) / 2;
|
if (new > original) {
|
||||||
self.cell_baseline += diff;
|
const diff = (new - original) / 2;
|
||||||
self.underline_position += diff;
|
self.cell_baseline +|= diff;
|
||||||
self.strikethrough_position += diff;
|
self.underline_position +|= diff;
|
||||||
|
self.strikethrough_position +|= diff;
|
||||||
|
} else {
|
||||||
|
const diff = (original - new) / 2;
|
||||||
|
self.cell_baseline -|= diff;
|
||||||
|
self.underline_position -|= diff;
|
||||||
|
self.strikethrough_position -|= diff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -124,18 +146,18 @@ pub const Modifier = union(enum) {
|
|||||||
pub const Key = key: {
|
pub const Key = key: {
|
||||||
const field_infos = std.meta.fields(Metrics);
|
const field_infos = std.meta.fields(Metrics);
|
||||||
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
|
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
|
||||||
|
var count: usize = 0;
|
||||||
for (field_infos, 0..) |field, i| {
|
for (field_infos, 0..) |field, i| {
|
||||||
enumFields[i] = .{
|
if (field.type != u32) continue;
|
||||||
.name = field.name,
|
enumFields[i] = .{ .name = field.name, .value = i };
|
||||||
.value = i,
|
count += 1;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var decls = [_]std.builtin.Type.Declaration{};
|
var decls = [_]std.builtin.Type.Declaration{};
|
||||||
break :key @Type(.{
|
break :key @Type(.{
|
||||||
.Enum = .{
|
.Enum = .{
|
||||||
.tag_type = std.math.IntFittingRange(0, field_infos.len - 1),
|
.tag_type = std.math.IntFittingRange(0, count - 1),
|
||||||
.fields = &enumFields,
|
.fields = enumFields[0..count],
|
||||||
.decls = &decls,
|
.decls = &decls,
|
||||||
.is_exhaustive = true,
|
.is_exhaustive = true,
|
||||||
},
|
},
|
||||||
@ -170,6 +192,48 @@ test "Metrics: apply modifiers" {
|
|||||||
try testing.expectEqual(@as(u32, 120), m.cell_width);
|
try testing.expectEqual(@as(u32, 120), m.cell_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Metrics: adjust cell height smaller" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var set: ModifierSet = .{};
|
||||||
|
defer set.deinit(alloc);
|
||||||
|
try set.put(alloc, .cell_height, .{ .percent = 0.5 });
|
||||||
|
|
||||||
|
var m: Metrics = init();
|
||||||
|
m.cell_baseline = 50;
|
||||||
|
m.underline_position = 55;
|
||||||
|
m.strikethrough_position = 30;
|
||||||
|
m.cell_height = 100;
|
||||||
|
m.apply(set);
|
||||||
|
try testing.expectEqual(@as(u32, 50), m.cell_height);
|
||||||
|
try testing.expectEqual(@as(u32, 25), m.cell_baseline);
|
||||||
|
try testing.expectEqual(@as(u32, 30), m.underline_position);
|
||||||
|
try testing.expectEqual(@as(u32, 5), m.strikethrough_position);
|
||||||
|
try testing.expectEqual(@as(u32, 100), m.original_cell_height.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Metrics: adjust cell height larger" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var set: ModifierSet = .{};
|
||||||
|
defer set.deinit(alloc);
|
||||||
|
try set.put(alloc, .cell_height, .{ .percent = 2 });
|
||||||
|
|
||||||
|
var m: Metrics = init();
|
||||||
|
m.cell_baseline = 50;
|
||||||
|
m.underline_position = 55;
|
||||||
|
m.strikethrough_position = 30;
|
||||||
|
m.cell_height = 100;
|
||||||
|
m.apply(set);
|
||||||
|
try testing.expectEqual(@as(u32, 200), m.cell_height);
|
||||||
|
try testing.expectEqual(@as(u32, 100), m.cell_baseline);
|
||||||
|
try testing.expectEqual(@as(u32, 105), m.underline_position);
|
||||||
|
try testing.expectEqual(@as(u32, 80), m.strikethrough_position);
|
||||||
|
try testing.expectEqual(@as(u32, 100), m.original_cell_height.?);
|
||||||
|
}
|
||||||
|
|
||||||
test "Modifier: parse absolute" {
|
test "Modifier: parse absolute" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
@ -68,13 +68,31 @@ pub fn renderGlyph(
|
|||||||
// Safe to ".?" because of the above assertion.
|
// Safe to ".?" because of the above assertion.
|
||||||
return switch (Kind.init(cp).?) {
|
return switch (Kind.init(cp).?) {
|
||||||
.box => box: {
|
.box => box: {
|
||||||
|
// For box fonts, we want to adjust the height of the box
|
||||||
|
// based on the original cell height, not the adjusted height.
|
||||||
|
// This is because adjusted cell height doesn't change font size.
|
||||||
|
const height, const offset = metrics: {
|
||||||
|
const metrics = opts.grid_metrics orelse break :metrics .{ self.height, 0 };
|
||||||
|
const height = metrics.original_cell_height orelse break :metrics .{ self.height, 0 };
|
||||||
|
|
||||||
|
// If our height shrunk, then we use the original adjusted
|
||||||
|
// height because we don't want to overflow the cell.
|
||||||
|
if (height >= self.height) break :metrics .{ self.height, 0 };
|
||||||
|
|
||||||
|
// The offset is divided by two because it is vertically
|
||||||
|
// centered.
|
||||||
|
break :metrics .{ height, (self.height - height) / 2 };
|
||||||
|
};
|
||||||
|
|
||||||
const f: Box = .{
|
const f: Box = .{
|
||||||
.width = width,
|
.width = width,
|
||||||
.height = self.height,
|
.height = height,
|
||||||
.thickness = self.thickness,
|
.thickness = self.thickness,
|
||||||
};
|
};
|
||||||
|
|
||||||
break :box try f.renderGlyph(alloc, atlas, cp);
|
var g = try f.renderGlyph(alloc, atlas, cp);
|
||||||
|
g.offset_y += @intCast(offset);
|
||||||
|
break :box g;
|
||||||
},
|
},
|
||||||
|
|
||||||
.underline => try underline.renderGlyph(
|
.underline => try underline.renderGlyph(
|
||||||
|
Reference in New Issue
Block a user