From c42de9955cb5522708cd2a18df1cbcad73b2b130 Mon Sep 17 00:00:00 2001 From: Sebastian Kootz Date: Thu, 11 Jun 2026 19:27:25 +0200 Subject: [PATCH] gpui: Add `alpha` method to `Rgba` (#52125) ## Context GPUI API change only: - added `.alpha(a: f32)` method to `RGBA` - added `.opacity(factor: f32)` method to `RGBA` - added documentation for both new methods - added test coverage for both methods These implementations already exist for the `HSLA` struct and were missing for `RGBA`. > [!NOTE] > In my opinion, a trait implementation could be beneficial here. Let me know what you think about that idea before I end up making unwanted changes. ## How to Review Just added the new methods, including documentation and test coverage. ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Added `gpui::Rgba::alpha` and `gpui::Rgba::opacity` methods for setting and scaling a color's alpha channel, matching the existing `gpui::Hsla` API --------- Co-authored-by: dino --- crates/gpui/examples/painting.rs | 18 +++---- crates/gpui/src/color.rs | 89 +++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs index 11c3b33371..41cbcb2802 100644 --- a/crates/gpui/examples/painting.rs +++ b/crates/gpui/examples/painting.rs @@ -67,8 +67,7 @@ impl PaintingViewer { builder.line_to(point(px(50.), px(130.))); builder.close(); let path = builder.build().unwrap(); - let mut red = rgb(0xFF0000); - red.a = 0.5; + let red = rgb(0xFF0000).alpha(0.5); lines.push((path, red.into())); // 50% opaque blue path that extends across black quad. @@ -79,8 +78,7 @@ impl PaintingViewer { builder.line_to(point(px(150.), px(130.))); builder.close(); let path = builder.build().unwrap(); - let mut blue = rgb(0x0000FF); - blue.a = 0.5; + let blue = rgb(0x0000FF).alpha(0.5); lines.push((path, blue.into())); // 50% opaque green path that extends across black quad. @@ -91,8 +89,7 @@ impl PaintingViewer { builder.line_to(point(px(250.), px(130.))); builder.close(); let path = builder.build().unwrap(); - let mut green = rgb(0x00FF00); - green.a = 0.5; + let green = rgb(0x00FF00).alpha(0.5); lines.push((path, green.into())); // 50% opaque black path that extends across black quad. @@ -103,8 +100,7 @@ impl PaintingViewer { builder.line_to(point(px(350.), px(130.))); builder.close(); let path = builder.build().unwrap(); - let mut black = rgb(0x000000); - black.a = 0.5; + let black = rgb(0x000000).alpha(0.5); lines.push((path, black.into())); // Two 50% opaque red circles overlapping - center should be darker red @@ -128,8 +124,7 @@ impl PaintingViewer { ); builder.close(); let path = builder.build().unwrap(); - let mut red1 = rgb(0xFF0000); - red1.a = 0.5; + let red1 = rgb(0xFF0000).alpha(0.5); lines.push((path, red1.into())); let mut builder = PathBuilder::fill(); @@ -152,8 +147,7 @@ impl PaintingViewer { ); builder.close(); let path = builder.build().unwrap(); - let mut red2 = rgb(0xFF0000); - red2.a = 0.5; + let red2 = rgb(0xFF0000).alpha(0.5); lines.push((path, red2.into())); // draw a Rust logo diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index be8169e81d..8ec816f49d 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -69,6 +69,68 @@ impl Rgba { } } } + + /// Returns a new RGBA color with the same red, green and blue channels, but + /// with a new alpha value. + /// + /// Example: + /// ``` + /// use gpui::rgba; + /// let color = rgba(0xFF0000FF); + /// let faded = color.alpha(0.25); + /// assert_eq!(faded.a, 0.25); + /// ``` + /// + /// This will return a red color with 25% opacity. + /// + /// Example: + /// ``` + /// use gpui::rgba; + /// let color = rgba(0x3399FFCC); + /// let transparent = color.alpha(0.0); + /// assert_eq!(transparent.a, 0.0); + /// ``` + /// + /// This will return the same blue color, fully transparent. + pub fn alpha(&self, a: f32) -> Self { + Rgba { + r: self.r, + g: self.g, + b: self.b, + a: a.clamp(0., 1.), + } + } + + /// Returns a new RGBA color with the same red, green, and blue channels, + /// but with the alpha channel multiplied by the given factor. + /// + /// Example: + /// ``` + /// use gpui::rgba; + /// let color = rgba(0xFF0000FF); // Fully opaque red + /// let faded = color.opacity(0.5); + /// assert_eq!(faded.a, 0.5); + /// ``` + /// + /// This will return a red color with 50% opacity. + /// + /// Example: + /// ``` + /// use gpui::rgba; + /// let color = rgba(0x3399FFCC); // A light blue with 80% opacity + /// let faded = color.opacity(0.5); + /// assert!((faded.a - 0.4).abs() < 1e-6); + /// ``` + /// + /// This will return the same blue color scaled down to 40% opacity. + pub fn opacity(&self, factor: f32) -> Self { + Rgba { + r: self.r, + g: self.g, + b: self.b, + a: self.a * factor.clamp(0., 1.), + } + } } impl From for u32 { @@ -591,7 +653,7 @@ impl Hsla { /// assert_eq!(red_color.a, 0.25); /// ``` /// - /// This will return a red color with half the opacity. + /// This will return a red color with 25% opacity. /// /// Example: /// ``` @@ -979,4 +1041,29 @@ mod tests { assert!(!background.is_transparent()); assert!(background.opacity(0.0).is_transparent()); } + + #[test] + fn test_rgba_alpha() { + let color = Rgba { + r: 0.2, + g: 0.6, + b: 1.0, + a: 0.8, + }; + + assert_eq!(color.alpha(0.25).a, 0.25); + assert_eq!(color.alpha(1.5).a, 1.0); + } + + #[test] + fn test_rgba_opacity() { + let color = Rgba { + r: 0.2, + g: 0.6, + b: 1.0, + a: 0.8, + }; + assert!((color.opacity(0.5).a - 0.4).abs() < 1e-6); + assert_eq!(color.opacity(2.0).a, 0.8); + } }