fix(terminal): eliminate seams between Powerline separators (#336)

* fix(terminal): eliminate seams between Powerline separators

* fix(terminal): skip the separator cover quad when the glyph is dim

The cover quad and the anti-aliased path overlap on the closing edge's
device pixel. With an opaque foreground that is a no-op, but a DIM cell
carries fg.a = 0.66, so the two compositing passes push that one column
to 1 - 0.34^2 = 0.884 alpha and tint the neighboring cell's background.
Emit the quad only for opaque separators.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ARNO
2026-08-04 20:14:38 +08:00
committed by GitHub
co-authored by Claude Opus 5 l0ng-ai
parent a1d89a3752
commit 47aa6532f5
+160 -1
View File
@@ -659,6 +659,21 @@ impl PowerlineShape {
_ => return None,
})
}
fn closing_edge_x(self, bounds: Bounds<Pixels>) -> Pixels {
// Keep this match exhaustive so adding a shape cannot silently assign
// its solid closing edge to the wrong side of the cell.
match self {
Self::TriangleRight
| Self::HalfCircleRight
| Self::SlantLowerLeft
| Self::SlantUpperLeft => bounds.left(),
Self::TriangleLeft
| Self::HalfCircleLeft
| Self::SlantLowerRight
| Self::SlantUpperRight => bounds.right(),
}
}
}
fn powerline_path(bounds: Bounds<Pixels>, shape: PowerlineShape) -> gpui::Path<Pixels> {
@@ -694,6 +709,40 @@ fn powerline_path(bounds: Bounds<Pixels>, shape: PowerlineShape) -> gpui::Path<P
}
}
fn powerline_solid_edge(
bounds: Bounds<Pixels>,
shape: PowerlineShape,
scale_factor: f32,
fg_alpha: f32,
) -> Option<Bounds<Pixels>> {
// A filled path anti-aliases a closing edge that falls between device
// pixels. Cover exactly that partially occupied pixel with opaque
// foreground color; an already aligned edge needs no extra primitive.
//
// Only opaque separators qualify. A translucent one (DIM) would composite
// the cover quad and the path on top of each other, pushing that single
// column past the glyph's own alpha and tinting the neighboring cell.
if !fg_alpha.is_finite() || fg_alpha < 1. {
return None;
}
let scale_factor = if scale_factor.is_finite() && scale_factor > 0. {
scale_factor
} else {
1.
};
let physical_edge = shape.closing_edge_x(bounds).as_f32() * scale_factor;
let physical_left = physical_edge.floor();
let physical_right = physical_edge.ceil();
if physical_left == physical_right {
return None;
}
Some(Bounds::from_corners(
point(px(physical_left / scale_factor), bounds.top()),
point(px(physical_right / scale_factor), bounds.bottom()),
))
}
fn native_cell_residue(style: &GlyphStyle) -> Option<char> {
// Special underlines are painted directly from the cell buffer, so a
// shaped blank is only needed for decorations GPUI owns.
@@ -764,8 +813,14 @@ fn paint_glyphs(
size(geom.cell_width, geom.line_height),
);
let native = if let Some(shape) = PowerlineShape::of(cell.c) {
let fg = GlyphStyle::of(cell).fg;
if let Some(edge) =
powerline_solid_edge(cell_bounds, shape, window.scale_factor(), fg.a)
{
window.paint_quad(fill(edge, fg));
}
let path = powerline_path(cell_bounds, shape);
window.paint_path(path, GlyphStyle::of(cell).fg);
window.paint_path(path, fg);
true
} else if let Some(ink) =
super::boxdraw::glyph(cell.c, cell_bounds, window.scale_factor())
@@ -1952,6 +2007,110 @@ mod tests {
}
}
#[test]
fn powerline_solid_edge_covers_only_the_fractional_device_pixel() {
let bounds = Bounds::new(point(px(10.2), px(20.)), size(px(9.2), px(21.)));
let scale = 1.25;
for shape in [
PowerlineShape::TriangleRight,
PowerlineShape::HalfCircleRight,
PowerlineShape::SlantLowerLeft,
PowerlineShape::SlantUpperLeft,
] {
assert_eq!(
powerline_solid_edge(bounds, shape, scale, 1.),
Some(Bounds::from_corners(
point(px(12. / scale), bounds.top()),
point(px(13. / scale), bounds.bottom()),
)),
"{shape:?} must cover only the device pixel containing its left edge"
);
}
for shape in [
PowerlineShape::TriangleLeft,
PowerlineShape::HalfCircleLeft,
PowerlineShape::SlantLowerRight,
PowerlineShape::SlantUpperRight,
] {
assert_eq!(
powerline_solid_edge(bounds, shape, scale, 1.),
Some(Bounds::from_corners(
point(px(24. / scale), bounds.top()),
point(px(25. / scale), bounds.bottom()),
)),
"{shape:?} must cover only the device pixel containing its right edge"
);
}
}
#[test]
fn powerline_solid_edge_skips_device_aligned_edges() {
let bounds = Bounds::new(point(px(10.), px(20.)), size(px(9.), px(21.)));
for shape in [
PowerlineShape::TriangleRight,
PowerlineShape::TriangleLeft,
PowerlineShape::HalfCircleRight,
PowerlineShape::HalfCircleLeft,
PowerlineShape::SlantLowerLeft,
PowerlineShape::SlantLowerRight,
PowerlineShape::SlantUpperLeft,
PowerlineShape::SlantUpperRight,
] {
assert_eq!(
powerline_solid_edge(bounds, shape, 2., 1.),
None,
"{shape:?}"
);
}
}
#[test]
fn powerline_solid_edge_sanitizes_invalid_scale() {
let bounds = Bounds::new(point(px(10.25), px(20.)), size(px(9.), px(21.)));
let expected = Some(Bounds::from_corners(
point(px(10.), bounds.top()),
point(px(11.), bounds.bottom()),
));
for scale in [0., -1., f32::NAN] {
assert_eq!(
powerline_solid_edge(bounds, PowerlineShape::TriangleRight, scale, 1.),
expected
);
}
}
#[test]
fn powerline_solid_edge_skips_translucent_separators() {
let bounds = Bounds::new(point(px(10.2), px(20.)), size(px(9.2), px(21.)));
for shape in [
PowerlineShape::TriangleRight,
PowerlineShape::TriangleLeft,
PowerlineShape::HalfCircleRight,
PowerlineShape::HalfCircleLeft,
PowerlineShape::SlantLowerLeft,
PowerlineShape::SlantLowerRight,
PowerlineShape::SlantUpperLeft,
PowerlineShape::SlantUpperRight,
] {
assert!(
powerline_solid_edge(bounds, shape, 1.25, 1.).is_some(),
"{shape:?} still needs the cover quad when opaque"
);
for alpha in [DIM_OPACITY, 0., 0.99, f32::NAN] {
assert_eq!(
powerline_solid_edge(bounds, shape, 1.25, alpha),
None,
"{shape:?} must not stack a cover quad under a translucent path"
);
}
}
}
#[test]
fn seg_clip_width_frees_solo_symbols_but_pins_batched_runs() {
let cell = px(10.);