From bae3eda75c3a1b835d205439070961feeb76cf90 Mon Sep 17 00:00:00 2001 From: Cameron Mcloughlin Date: Wed, 4 Feb 2026 23:00:56 +0000 Subject: [PATCH] git: Shader for checkerboard pattern for side-by-side diff (#48417) Adds `checkerboard` to `Background`, and use it for the side-by-side diff. Note that, since the blockmap can contain multiple `Spacer`s without any gaps in between, we ensure that the checkerboard pattern always shows an even integer number of squares per line, so there are no obvious discontinuities in the pattern when this happens. image Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Cole Miller --- crates/editor/src/element.rs | 61 ++++++++++++++++--- crates/gpui/src/color.rs | 41 ++++++++----- crates/gpui/src/platform/blade/shaders.wgsl | 16 ++++- crates/gpui/src/platform/mac/shaders.metal | 15 ++++- crates/gpui/src/platform/windows/shaders.hlsl | 15 ++++- crates/gpui/src/style.rs | 6 +- 6 files changed, 128 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 12a7708903b16f82991b7c4e39244f8343ea849f..d516de75d98be94b000a673548e546e5e2adac92 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -47,8 +47,8 @@ use gpui::{ MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, PressureStage, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, StyledText, TextAlign, TextRun, - TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill, linear_color_stop, - linear_gradient, outline, pattern_slash, point, px, quad, relative, size, solid_background, + TextStyleRefinement, WeakEntity, Window, anchored, checkerboard, deferred, div, fill, + linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background, transparent_black, }; use itertools::Itertools; @@ -60,6 +60,7 @@ use multi_buffer::{ }; use edit_prediction_types::EditPredictionGranularity; + use project::{ DisableAiSettings, Entry, ProjectPath, debugger::breakpoint_store::{Breakpoint, BreakpointSessionState}, @@ -4007,11 +4008,11 @@ impl EditorElement { .id(block_id) .w_full() .h((*height as f32) * line_height) - .bg(pattern_slash( - cx.theme().colors().panel_background, - 8.0, - 8.0, - )) + .bg(checkerboard(cx.theme().colors().panel_background, { + let target_size = 16.0; + let scale = window.scale_factor(); + Self::checkerboard_size(f32::from(line_height) * scale, target_size * scale) + })) .into_any(), }; @@ -4073,6 +4074,24 @@ impl EditorElement { Some((element, final_size, row, x_offset)) } + /// The checkerboard pattern height must be an even factor of the line + /// height, so that two consecutive spacer blocks can render contiguously + /// without an obvious break in the pattern. + fn checkerboard_size(line_height: f32, target_height: f32) -> f32 { + let k_approx = line_height / (2.0 * target_height); + let k_floor = (k_approx.floor() as u32).max(1); + let k_ceil = (k_approx.ceil() as u32).max(1); + + let size_floor = line_height / (2 * k_floor) as f32; + let size_ceil = line_height / (2 * k_ceil) as f32; + + if (size_floor - target_height).abs() <= (size_ceil - target_height).abs() { + size_floor + } else { + size_ceil + } + } + fn render_buffer_header( &self, for_excerpt: &ExcerptInfo, @@ -12123,6 +12142,7 @@ mod tests { use gpui::{TestAppContext, VisualTestContext}; use language::{Buffer, language_settings, tree_sitter_python}; use log::info; + use rand::{RngCore, rngs::StdRng}; use std::num::NonZeroU32; use util::test::sample_text; @@ -13224,4 +13244,31 @@ mod tests { assert_eq!(out[3].color, adjusted_bg1); } } + + #[test] + fn test_checkerboard_size() { + // line height is smaller than target height, so we just return half the line height + assert_eq!(EditorElement::checkerboard_size(10.0, 20.0), 5.0); + + // line height is exactly half the target height, perfect match + assert_eq!(EditorElement::checkerboard_size(20.0, 10.0), 10.0); + + // line height is close to half the target height + assert_eq!(EditorElement::checkerboard_size(20.0, 9.0), 10.0); + + // line height is close to 1/4 the target height + assert_eq!(EditorElement::checkerboard_size(20.0, 4.8), 5.0); + } + + #[gpui::test(iterations = 100)] + fn test_random_checkerboard_size(mut rng: StdRng) { + let line_height = rng.next_u32() as f32; + let target_height = rng.next_u32() as f32; + + let result = EditorElement::checkerboard_size(line_height, target_height); + + let k = line_height / result; + assert!(k - k.round() < 0.0000001); // approximately integer + assert!((k.round() as u32).is_multiple_of(2)); + } } diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 83f8d6c2bd4fbabec1d71bfc18342dbf1ca63b64..86af97c8279406f2f1830a02462c00d53050802c 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -658,6 +658,7 @@ pub(crate) enum BackgroundTag { Solid = 0, LinearGradient = 1, PatternSlash = 2, + Checkerboard = 3, } /// A color space for color interpolation. @@ -701,20 +702,21 @@ impl std::fmt::Debug for Background { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self.tag { BackgroundTag::Solid => write!(f, "Solid({:?})", self.solid), - BackgroundTag::LinearGradient => { - write!( - f, - "LinearGradient({}, {:?}, {:?})", - self.gradient_angle_or_pattern_height, self.colors[0], self.colors[1] - ) - } - BackgroundTag::PatternSlash => { - write!( - f, - "PatternSlash({:?}, {})", - self.solid, self.gradient_angle_or_pattern_height - ) - } + BackgroundTag::LinearGradient => write!( + f, + "LinearGradient({}, {:?}, {:?})", + self.gradient_angle_or_pattern_height, self.colors[0], self.colors[1] + ), + BackgroundTag::PatternSlash => write!( + f, + "PatternSlash({:?}, {})", + self.solid, self.gradient_angle_or_pattern_height + ), + BackgroundTag::Checkerboard => write!( + f, + "Checkerboard({:?}, {})", + self.solid, self.gradient_angle_or_pattern_height + ), } } } @@ -747,6 +749,16 @@ pub fn pattern_slash(color: impl Into, width: f32, interval: f32) -> Backg } } +/// Creates a checkerboard pattern background +pub fn checkerboard(color: impl Into, size: f32) -> Background { + Background { + tag: BackgroundTag::Checkerboard, + solid: color.into(), + gradient_angle_or_pattern_height: size, + ..Default::default() + } +} + /// Creates a solid background color. pub fn solid_background(color: impl Into) -> Background { Background { @@ -833,6 +845,7 @@ impl Background { BackgroundTag::Solid => self.solid.is_transparent(), BackgroundTag::LinearGradient => self.colors.iter().all(|c| c.color.is_transparent()), BackgroundTag::PatternSlash => self.solid.is_transparent(), + BackgroundTag::Checkerboard => self.solid.is_transparent(), } } } diff --git a/crates/gpui/src/platform/blade/shaders.wgsl b/crates/gpui/src/platform/blade/shaders.wgsl index 8e9921f5d758550856b7e7c2b0ea6ee3c5baa011..95d6ac76b436953fe709579c047d6d2543048f60 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/blade/shaders.wgsl @@ -129,6 +129,7 @@ struct Background { // 0u is Solid // 1u is LinearGradient // 2u is PatternSlash + // 3u is Checkerboard tag: u32, // 0u is sRGB linear color // 1u is Oklab color @@ -399,7 +400,7 @@ fn prepare_gradient_color(tag: u32, color_space: u32, solid: Hsla, colors: array) -> GradientColor { var result = GradientColor(); - if (tag == 0u || tag == 2u) { + if (tag == 0u || tag == 2u || tag == 3u) { result.solid = hsla_to_rgba(solid); } else if (tag == 1u) { // The hsla_to_rgba is returns a linear sRGB color @@ -473,6 +474,7 @@ fn gradient_color(background: Background, position: vec2, bounds: Bounds, } } case 2u: { + // pattern slash let gradient_angle_or_pattern_height = background.gradient_angle_or_pattern_height; let pattern_width = (gradient_angle_or_pattern_height / 65535.0f) / 255.0f; let pattern_interval = (gradient_angle_or_pattern_height % 65535.0f) / 255.0f; @@ -490,6 +492,18 @@ fn gradient_color(background: Background, position: vec2, bounds: Bounds, background_color = solid_color; background_color.a *= saturate(0.5 - distance); } + case 3u: { + // checkerboard + let size = background.gradient_angle_or_pattern_height; + let relative_position = position - bounds.origin; + + let x_index = floor(relative_position.x / size); + let y_index = floor(relative_position.y / size); + let should_be_colored = (x_index + y_index) % 2.0; + + background_color = solid_color; + background_color.a *= saturate(should_be_colored); + } } return background_color; diff --git a/crates/gpui/src/platform/mac/shaders.metal b/crates/gpui/src/platform/mac/shaders.metal index 7c3886031ab915e674469a6d85ef368c35b2b759..3c6adac3359ac41ee0cc265480dae6e63a2c2136 100644 --- a/crates/gpui/src/platform/mac/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders.metal @@ -1140,7 +1140,7 @@ float4 over(float4 below, float4 above) { GradientColor prepare_fill_color(uint tag, uint color_space, Hsla solid, Hsla color0, Hsla color1) { GradientColor out; - if (tag == 0 || tag == 2) { + if (tag == 0 || tag == 2 || tag == 3) { out.solid = hsla_to_rgba(solid); } else if (tag == 1) { out.color0 = hsla_to_rgba(color0); @@ -1233,6 +1233,19 @@ float4 fill_color(Background background, color.a *= saturate(0.5 - distance); break; } + case 3: { + // checkerboard + float size = background.gradient_angle_or_pattern_height; + float2 relative_position = position - float2(bounds.origin.x, bounds.origin.y); + + float x_index = floor(relative_position.x / size); + float y_index = floor(relative_position.y / size); + float should_be_colored = fmod(x_index + y_index, 2.0); + + color = solid_color; + color.a *= saturate(should_be_colored); + break; + } } return color; diff --git a/crates/gpui/src/platform/windows/shaders.hlsl b/crates/gpui/src/platform/windows/shaders.hlsl index 0b1543a286c957f47a92417a00ad5fb957df25ae..f508387daf9c98ffcce521209d2c981cf04db983 100644 --- a/crates/gpui/src/platform/windows/shaders.hlsl +++ b/crates/gpui/src/platform/windows/shaders.hlsl @@ -309,7 +309,7 @@ float quad_sdf(float2 pt, Bounds bounds, Corners corner_radii) { GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, LinearColorStop colors[2]) { GradientColor output; - if (tag == 0 || tag == 2) { + if (tag == 0 || tag == 2 || tag == 3) { output.solid = hsla_to_rgba(solid); } else if (tag == 1) { output.color0 = hsla_to_rgba(colors[0].color); @@ -402,6 +402,19 @@ float4 gradient_color(Background background, color.a *= saturate(0.5 - distance); break; } + case 3: { + // checkerboard + float size = background.gradient_angle_or_pattern_height; + float2 relative_position = position - bounds.origin; + + float x_index = floor(relative_position.x / size); + float y_index = floor(relative_position.y / size); + float should_be_colored = (x_index + y_index) % 2.0; + + color = solid_color; + color.a *= saturate(should_be_colored); + break; + } } return color; diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index d1ab41ff30a7b9da3fb9748919b9f2afe87f8cf2..dda49d990ac525f8b9f14b8a61a9c55c43e58e3b 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -639,13 +639,15 @@ impl Style { if background_color.is_some_and(|color| !color.is_transparent()) { let mut border_color = match background_color { Some(color) => match color.tag { - BackgroundTag::Solid => color.solid, + BackgroundTag::Solid + | BackgroundTag::PatternSlash + | BackgroundTag::Checkerboard => color.solid, + BackgroundTag::LinearGradient => color .colors .first() .map(|stop| stop.color) .unwrap_or_default(), - BackgroundTag::PatternSlash => color.solid, }, None => Hsla::default(), };