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(), };