diff --git a/crates/bevy_ui_render/src/gradient.wgsl b/crates/bevy_ui_render/src/gradient.wgsl index 7ee4ce886272a..54bc35eb146b8 100644 --- a/crates/bevy_ui_render/src/gradient.wgsl +++ b/crates/bevy_ui_render/src/gradient.wgsl @@ -154,29 +154,28 @@ fn mix_linear_rgba_in_oklaba_space(a: vec4, b: vec4, t: f32) -> vec4) -> vec4 { - let maxc = max(max(c.r, c.g), c.b); - let minc = min(min(c.r, c.g), c.b); - let delta = maxc - minc; - let l = (maxc + minc) * 0.5; - var h: f32 = 0.0; - var s: f32 = 0.0; - if delta != 0.0 { - s = delta / (1.0 - abs(2.0 * l - 1.0)); - if maxc == c.r { - h = ((c.g - c.b) / delta) % 6.0; - } else if maxc == c.g { - h = ((c.b - c.r) / delta) + 2.0; + let max = max(max(c.r, c.g), c.b); + let min = min(min(c.r, c.g), c.b); + let l = (max + min) * 0.5; + if max == min { + return vec4(0., 0., l, c.a); + } else { + let delta = max - min; + let s = delta / (1. - abs(2. * l - 1.)); + var h = 0.; + if max == c.r { + h = ((c.g - c.b) / delta) % 6.; + } else if max == c.g { + h = ((c.b - c.r) / delta) + 2.; } else { - h = ((c.r - c.g) / delta) + 4.0; - } - h = h / 6.0; - if h < 0.0 { - h = h + 1.0; + h = ((c.r - c.g) / delta) + 4.; } + h = h / 6.; + return vec4(h, s, l, c.a); } - return vec4(h, s, l, c.a); } + fn hsla_to_linear_rgba(hsl: vec4) -> vec4 { let h = hsl.x; let s = hsl.y; @@ -259,7 +258,7 @@ fn linear_rgba_to_oklcha(c: vec4) -> vec4 { let o = linear_rgba_to_oklaba(c); let chroma = sqrt(o.y * o.y + o.z * o.z); let hue = atan2(o.z, o.y); - return vec4(o.x, chroma, select(hue + TAU, hue, hue < 0.0), o.a); + return vec4(o.x, chroma, rem_euclid(hue, TAU), o.a); } fn oklcha_to_linear_rgba(c: vec4) -> vec4 { @@ -279,22 +278,26 @@ fn lerp_hue(a: f32, b: f32, t: f32) -> f32 { fn lerp_hue_long(a: f32, b: f32, t: f32) -> f32 { let diff = rem_euclid(b - a + PI, TAU) - PI; - return rem_euclid(a + select(diff - TAU, diff + TAU, 0. < diff) * t, TAU); + return rem_euclid(a + (diff + select(TAU, -TAU, 0. < diff)) * t, TAU); } fn mix_oklcha(a: vec4, b: vec4, t: f32) -> vec4 { + let ah = select(a.z, b.z, a.y == 0.); + let bh = select(b.z, a.z, b.y == 0.); return vec4( mix(a.xy, b.xy, t), - lerp_hue(a.z, b.z, t), + lerp_hue(ah, bh, t), mix(a.a, b.a, t) ); } fn mix_oklcha_long(a: vec4, b: vec4, t: f32) -> vec4 { + let ah = select(a.z, b.z, a.y == 0.); + let bh = select(b.z, a.z, b.y == 0.); return vec4( mix(a.xy, b.xy, t), - lerp_hue_long(a.z, b.z, t), - mix(a.a, b.a, t) + lerp_hue_long(ah, bh, t), + mix(a.w, b.w, t) ); } diff --git a/examples/testbed/ui.rs b/examples/testbed/ui.rs index a2f6d6a14a901..4bbc8e5d770c0 100644 --- a/examples/testbed/ui.rs +++ b/examples/testbed/ui.rs @@ -20,6 +20,7 @@ fn main() { .add_systems(OnEnter(Scene::Overflow), overflow::setup) .add_systems(OnEnter(Scene::Slice), slice::setup) .add_systems(OnEnter(Scene::LayoutRounding), layout_rounding::setup) + .add_systems(OnEnter(Scene::LinearGradient), linear_gradient::setup) .add_systems(OnEnter(Scene::RadialGradient), radial_gradient::setup) .add_systems(Update, switch_scene); @@ -42,6 +43,7 @@ enum Scene { Overflow, Slice, LayoutRounding, + LinearGradient, RadialGradient, } @@ -56,7 +58,8 @@ impl Next for Scene { Scene::TextWrap => Scene::Overflow, Scene::Overflow => Scene::Slice, Scene::Slice => Scene::LayoutRounding, - Scene::LayoutRounding => Scene::RadialGradient, + Scene::LayoutRounding => Scene::LinearGradient, + Scene::LinearGradient => Scene::RadialGradient, Scene::RadialGradient => Scene::Image, } } @@ -551,6 +554,90 @@ mod layout_rounding { } } +mod linear_gradient { + use bevy::color::palettes::css::RED; + use bevy::color::palettes::css::YELLOW; + use bevy::color::Color; + use bevy::ecs::prelude::*; + use bevy::render::camera::Camera2d; + use bevy::state::state_scoped::DespawnOnExitState; + use bevy::ui::AlignItems; + use bevy::ui::BackgroundGradient; + use bevy::ui::ColorStop; + use bevy::ui::InterpolationColorSpace; + use bevy::ui::JustifyContent; + use bevy::ui::LinearGradient; + use bevy::ui::Node; + use bevy::ui::PositionType; + use bevy::ui::Val; + use bevy::utils::default; + + pub fn setup(mut commands: Commands) { + commands.spawn((Camera2d, DespawnOnExitState(super::Scene::LinearGradient))); + commands + .spawn(( + Node { + flex_direction: bevy::ui::FlexDirection::Column, + width: Val::Percent(100.), + height: Val::Percent(100.), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + row_gap: Val::Px(5.), + ..default() + }, + DespawnOnExitState(super::Scene::LinearGradient), + )) + .with_children(|commands| { + for stops in [ + vec![ColorStop::auto(RED), ColorStop::auto(YELLOW)], + vec![ + ColorStop::auto(Color::BLACK), + ColorStop::auto(RED), + ColorStop::auto(Color::WHITE), + ], + ] { + for color_space in [ + InterpolationColorSpace::LinearRgb, + InterpolationColorSpace::Srgb, + InterpolationColorSpace::OkLab, + InterpolationColorSpace::OkLch, + InterpolationColorSpace::OkLchLong, + InterpolationColorSpace::Hsl, + InterpolationColorSpace::HslLong, + InterpolationColorSpace::Hsv, + InterpolationColorSpace::HsvLong, + ] { + commands.spawn(( + Node { + justify_content: JustifyContent::SpaceEvenly, + ..Default::default() + }, + children![( + Node { + height: Val::Px(30.), + width: Val::Px(300.), + ..Default::default() + }, + BackgroundGradient::from(LinearGradient { + color_space, + angle: LinearGradient::TO_RIGHT, + stops: stops.clone(), + }), + children![ + Node { + position_type: PositionType::Absolute, + ..default() + }, + bevy::ui::widget::Text(format!("{color_space:?}")), + ] + )], + )); + } + } + }); + } +} + mod radial_gradient { use bevy::color::palettes::css::RED; use bevy::color::palettes::tailwind::GRAY_700;