gpui: Add linear gradient support to fill background (#20812)
Release Notes: - gpui: Add linear gradient support to fill background Run example: ``` cargo run -p gpui --example gradient cargo run -p gpui --example gradient --features macos-blade ``` ## Demo In GPUI (sRGB): <img width="761" alt="image" src="https://github.com/user-attachments/assets/568c02e8-3065-43c2-b5c2-5618d553dd6e"> In GPUI (Oklab): <img width="761" alt="image" src="https://github.com/user-attachments/assets/b008b0de-2705-4f99-831d-998ce48eed42"> In CSS (sRGB): https://codepen.io/huacnlee/pen/rNXgxBY <img width="505" alt="image" src="https://github.com/user-attachments/assets/239f4b65-24b3-4797-9491-a13eea420158"> In CSS (Oklab): https://codepen.io/huacnlee/pen/wBwBKOp <img width="658" alt="image" src="https://github.com/user-attachments/assets/56fdd55f-d219-45de-922f-7227f535b210"> --- Currently only support 2 color stops with linear-gradient. I think this is we first introduce the gradient feature in GPUI, and the linear-gradient is most popular for use. So we can just add this first and then to add more other supports.
This commit is contained in:
parent
c594ccb0af
commit
de89f8cf83
9 changed files with 902 additions and 73 deletions
|
@ -548,6 +548,164 @@ impl<'de> Deserialize<'de> for Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) enum BackgroundTag {
|
||||
Solid = 0,
|
||||
LinearGradient = 1,
|
||||
}
|
||||
|
||||
/// A color space for color interpolation.
|
||||
///
|
||||
/// References:
|
||||
/// - https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method
|
||||
/// - https://www.w3.org/TR/css-color-4/#typedef-color-space
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[repr(C)]
|
||||
pub enum ColorSpace {
|
||||
#[default]
|
||||
/// The sRGB color space.
|
||||
Srgb = 0,
|
||||
/// The Oklab color space.
|
||||
Oklab = 1,
|
||||
}
|
||||
|
||||
impl Display for ColorSpace {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ColorSpace::Srgb => write!(f, "sRGB"),
|
||||
ColorSpace::Oklab => write!(f, "Oklab"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A background color, which can be either a solid color or a linear gradient.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Background {
|
||||
pub(crate) tag: BackgroundTag,
|
||||
pub(crate) color_space: ColorSpace,
|
||||
pub(crate) solid: Hsla,
|
||||
pub(crate) angle: f32,
|
||||
pub(crate) colors: [LinearColorStop; 2],
|
||||
/// Padding for alignment for repr(C) layout.
|
||||
pad: u32,
|
||||
}
|
||||
|
||||
impl Eq for Background {}
|
||||
impl Default for Background {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tag: BackgroundTag::Solid,
|
||||
solid: Hsla::default(),
|
||||
color_space: ColorSpace::default(),
|
||||
angle: 0.0,
|
||||
colors: [LinearColorStop::default(), LinearColorStop::default()],
|
||||
pad: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a LinearGradient background color.
|
||||
///
|
||||
/// The gradient line's angle of direction. A value of `0.` is equivalent to to top; increasing values rotate clockwise from there.
|
||||
///
|
||||
/// The `angle` is in degrees value in the range 0.0 to 360.0.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient
|
||||
pub fn linear_gradient(
|
||||
angle: f32,
|
||||
from: impl Into<LinearColorStop>,
|
||||
to: impl Into<LinearColorStop>,
|
||||
) -> Background {
|
||||
Background {
|
||||
tag: BackgroundTag::LinearGradient,
|
||||
angle,
|
||||
colors: [from.into(), to.into()],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// A color stop in a linear gradient.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient#linear-color-stop
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct LinearColorStop {
|
||||
/// The color of the color stop.
|
||||
pub color: Hsla,
|
||||
/// The percentage of the gradient, in the range 0.0 to 1.0.
|
||||
pub percentage: f32,
|
||||
}
|
||||
|
||||
/// Creates a new linear color stop.
|
||||
///
|
||||
/// The percentage of the gradient, in the range 0.0 to 1.0.
|
||||
pub fn linear_color_stop(color: impl Into<Hsla>, percentage: f32) -> LinearColorStop {
|
||||
LinearColorStop {
|
||||
color: color.into(),
|
||||
percentage,
|
||||
}
|
||||
}
|
||||
|
||||
impl LinearColorStop {
|
||||
/// Returns a new color stop with the same color, but with a modified alpha value.
|
||||
pub fn opacity(&self, factor: f32) -> Self {
|
||||
Self {
|
||||
percentage: self.percentage,
|
||||
color: self.color.opacity(factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Background {
|
||||
/// Use specified color space for color interpolation.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method
|
||||
pub fn color_space(mut self, color_space: ColorSpace) -> Self {
|
||||
self.color_space = color_space;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a new background color with the same hue, saturation, and lightness, but with a modified alpha value.
|
||||
pub fn opacity(&self, factor: f32) -> Self {
|
||||
let mut background = *self;
|
||||
background.solid = background.solid.opacity(factor);
|
||||
background.colors = [
|
||||
self.colors[0].opacity(factor),
|
||||
self.colors[1].opacity(factor),
|
||||
];
|
||||
background
|
||||
}
|
||||
|
||||
/// Returns whether the background color is transparent.
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
match self.tag {
|
||||
BackgroundTag::Solid => self.solid.is_transparent(),
|
||||
BackgroundTag::LinearGradient => self.colors.iter().all(|c| c.color.is_transparent()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for Background {
|
||||
fn from(value: Hsla) -> Self {
|
||||
Background {
|
||||
tag: BackgroundTag::Solid,
|
||||
solid: value,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Rgba> for Background {
|
||||
fn from(value: Rgba) -> Self {
|
||||
Background {
|
||||
tag: BackgroundTag::Solid,
|
||||
solid: Hsla::from(value),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
@ -595,4 +753,32 @@ mod tests {
|
|||
|
||||
assert_eq!(actual, rgba(0xdeadbeef))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_background_solid() {
|
||||
let color = Hsla::from(rgba(0xff0099ff));
|
||||
let mut background = Background::from(color);
|
||||
assert_eq!(background.tag, BackgroundTag::Solid);
|
||||
assert_eq!(background.solid, color);
|
||||
|
||||
assert_eq!(background.opacity(0.5).solid, color.opacity(0.5));
|
||||
assert_eq!(background.is_transparent(), false);
|
||||
background.solid = hsla(0.0, 0.0, 0.0, 0.0);
|
||||
assert_eq!(background.is_transparent(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_background_linear_gradient() {
|
||||
let from = linear_color_stop(rgba(0xff0099ff), 0.0);
|
||||
let to = linear_color_stop(rgba(0x00ff99ff), 1.0);
|
||||
let background = linear_gradient(90.0, from, to);
|
||||
assert_eq!(background.tag, BackgroundTag::LinearGradient);
|
||||
assert_eq!(background.colors[0], from);
|
||||
assert_eq!(background.colors[1], to);
|
||||
|
||||
assert_eq!(background.opacity(0.5).colors[0], from.opacity(0.5));
|
||||
assert_eq!(background.opacity(0.5).colors[1], to.opacity(0.5));
|
||||
assert_eq!(background.is_transparent(), false);
|
||||
assert_eq!(background.opacity(0.0).is_transparent(), true);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue