This commit is contained in:
Talin 2025-07-08 20:51:15 +02:00 committed by GitHub
commit 4901ec5346
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 750 additions and 3 deletions

View File

@ -21,11 +21,13 @@ bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
"bevy_ui_picking_backend",
] }
bevy_ui_render = { path = "../bevy_ui_render", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_winit = { path = "../bevy_winit", version = "0.17.0-dev" }

View File

@ -0,0 +1,59 @@
use bevy_app::Plugin;
use bevy_asset::{Asset, Assets, Handle};
use bevy_ecs::{
component::Component,
lifecycle::Add,
observer::On,
resource::Resource,
system::{Query, Res},
world::FromWorld,
};
use bevy_reflect::TypePath;
use bevy_render::render_resource::{AsBindGroup, ShaderRef};
use bevy_ui_render::ui_material::{MaterialNode, UiMaterial};
#[derive(AsBindGroup, Asset, TypePath, Default, Debug, Clone)]
pub(crate) struct AlphaPatternMaterial {}
impl UiMaterial for AlphaPatternMaterial {
fn fragment_shader() -> ShaderRef {
"embedded://bevy_feathers/assets/shaders/alpha_pattern.wgsl".into()
}
}
#[derive(Resource)]
pub(crate) struct AlphaPatternResource(pub(crate) Handle<AlphaPatternMaterial>);
impl FromWorld for AlphaPatternResource {
fn from_world(world: &mut bevy_ecs::world::World) -> Self {
let mut ui_materials = world
.get_resource_mut::<Assets<AlphaPatternMaterial>>()
.unwrap();
Self(ui_materials.add(AlphaPatternMaterial::default()))
}
}
/// Marker that tells us we want to fill in the [`MaterialNode`] with the alpha material.
#[derive(Component, Default, Clone)]
pub(crate) struct AlphaPattern;
/// Observer to fill in the material handle (since we don't have access to the materials asset
/// in the template)
fn on_add_color_swatch(
ev: On<Add, AlphaPattern>,
mut q_swatch: Query<&mut MaterialNode<AlphaPatternMaterial>>,
r_material: Res<AlphaPatternResource>,
) {
if let Ok(mut material) = q_swatch.get_mut(ev.target()) {
material.0 = r_material.0.clone();
}
}
/// Plugin which registers the systems for updating the button styles.
pub struct AlphaPatternPlugin;
impl Plugin for AlphaPatternPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_observer(on_add_color_swatch);
}
}

View File

@ -0,0 +1,39 @@
// This shader draws a checkerboard pattern
#import bevy_ui::ui_vertex_output::UiVertexOutput
@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
let uv = (in.uv - vec2<f32>(0.5, 0.5)) * in.size / 16.;
let check = select(0.0, 1.0, (fract(uv.x) < 0.5) != (fract(uv.y) < 0.5));
let bg = mix(vec3<f32>(0.2, 0.2, 0.2), vec3<f32>(0.6, 0.6, 0.6), check);
let size = vec2<f32>(in.size.x, in.size.y);
let external_distance = sd_rounded_box((in.uv - 0.5) * size, size, in.border_radius);
let alpha = smoothstep(0.5, -0.5, external_distance);
return vec4<f32>(bg, alpha);
}
// From: https://github.com/bevyengine/bevy/pull/8973
// The returned value is the shortest distance from the given point to the boundary of the rounded box.
// Negative values indicate that the point is inside the rounded box, positive values that the point is outside, and zero is exactly on the boundary.
// arguments
// point -> The function will return the distance from this point to the closest point on the boundary.
// size -> The maximum width and height of the box.
// corner_radii -> The radius of each rounded corner. Ordered counter clockwise starting top left:
// x = top left, y = top right, z = bottom right, w = bottom left.
fn sd_rounded_box(point: vec2<f32>, size: vec2<f32>, corner_radii: vec4<f32>) -> f32 {
// if 0.0 < y then select bottom left (w) and bottom right corner radius (z)
// else select top left (x) and top right corner radius (y)
let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y);
// w and z are swapped so that both pairs are in left to right order, otherwise this second select statement would return the incorrect value for the bottom pair.
let radius = select(rs.x, rs.y, 0.0 < point.x);
// Vector from the corner closest to the point, to the point
let corner_to_point = abs(point) - 0.5 * size;
// Vector from the center of the radius circle to the point
let q = corner_to_point + radius;
// length from center of the radius circle to the point, 0s a component if the point is not within the quadrant of the radius circle that is part of the curved corner.
let l = length(max(q, vec2(0.0)));
let m = min(max(q.x, q.y), 0.0);
return l + m - radius;
}

View File

@ -0,0 +1,378 @@
use core::f32::consts::PI;
use bevy_app::{Plugin, PreUpdate};
use bevy_asset::Handle;
use bevy_color::{Alpha, Color, Hsla};
use bevy_core_widgets::{
Callback, CoreSlider, CoreSliderThumb, SliderRange, SliderValue, TrackClick,
};
use bevy_ecs::{
bundle::Bundle,
children,
component::Component,
entity::Entity,
hierarchy::Children,
query::{Changed, Or, With},
schedule::IntoScheduleConfigs,
spawn::SpawnRelated,
system::{In, Query},
};
use bevy_input_focus::tab_navigation::TabIndex;
use bevy_log::warn_once;
use bevy_picking::PickingSystems;
use bevy_ui::{
AlignItems, BackgroundColor, BackgroundGradient, BorderColor, BorderRadius, ColorStop, Display,
FlexDirection, Gradient, InterpolationColorSpace, LinearGradient, Node, Outline, PositionType,
UiRect, UiTransform, Val, Val2, ZIndex,
};
use bevy_ui_render::ui_material::MaterialNode;
use bevy_winit::cursor::CursorIcon;
use crate::{
alpha_pattern::{AlphaPattern, AlphaPatternMaterial},
palette,
rounded_corners::RoundedCorners,
};
const SLIDER_HEIGHT: f32 = 16.0;
const TRACK_PADDING: f32 = 3.0;
const TRACK_RADIUS: f32 = SLIDER_HEIGHT * 0.5 - TRACK_PADDING;
const THUMB_SIZE: f32 = SLIDER_HEIGHT - 2.0;
/// Indicates which color channel we want to edit.
#[derive(Component, Default, Clone)]
pub enum ColorChannel {
/// Editing the RGB red channel (0..=1)
#[default]
Red,
/// Editing the RGB green channel (0..=1)
Green,
/// Editing the RGB blue channel (0..=1)
Blue,
/// Editing the hue channel (0..=360)
HslHue,
/// Editing the chroma / saturation channel (0..=1)
HslSaturation,
/// Editing the luminance channel (0..=1)
HslLightness,
/// Editing the alpha channel (0..=1)
Alpha,
}
impl ColorChannel {
/// Return the range of this color channel.
pub fn range(&self) -> SliderRange {
match self {
ColorChannel::Red
| ColorChannel::Green
| ColorChannel::Blue
| ColorChannel::Alpha
| ColorChannel::HslSaturation
| ColorChannel::HslLightness => SliderRange::new(0., 1.),
ColorChannel::HslHue => SliderRange::new(0., 360.),
}
}
/// Return the color endpoints and midpoint of the gradient. This is determined by both the
/// channel being edited and the base color.
pub fn gradient_ends(&self, base_color: Color) -> (Color, Color, Color) {
match self {
ColorChannel::Red => {
let base_rgb = base_color.to_srgba();
(
Color::srgb(0.0, base_rgb.green, base_rgb.blue),
Color::srgb(0.5, base_rgb.green, base_rgb.blue),
Color::srgb(1.0, base_rgb.green, base_rgb.blue),
)
}
ColorChannel::Green => {
let base_rgb = base_color.to_srgba();
(
Color::srgb(base_rgb.red, 0.0, base_rgb.blue),
Color::srgb(base_rgb.red, 0.5, base_rgb.blue),
Color::srgb(base_rgb.red, 1.0, base_rgb.blue),
)
}
ColorChannel::Blue => {
let base_rgb = base_color.to_srgba();
(
Color::srgb(base_rgb.red, base_rgb.green, 0.0),
Color::srgb(base_rgb.red, base_rgb.green, 0.5),
Color::srgb(base_rgb.red, base_rgb.green, 1.0),
)
}
ColorChannel::HslHue => (
Color::hsl(0.0 + 0.0001, 1.0, 0.5),
Color::hsl(180.0, 1.0, 0.5),
Color::hsl(360.0 - 0.0001, 1.0, 0.5),
),
ColorChannel::HslSaturation => {
let base_hsla: Hsla = base_color.into();
(
Color::hsl(base_hsla.hue, 0.0, base_hsla.lightness),
Color::hsl(base_hsla.hue, 0.5, base_hsla.lightness),
Color::hsl(base_hsla.hue, 1.0, base_hsla.lightness),
)
}
ColorChannel::HslLightness => {
let base_hsla: Hsla = base_color.into();
(
Color::hsl(base_hsla.hue, base_hsla.saturation, 0.0),
Color::hsl(base_hsla.hue, base_hsla.saturation, 0.5),
Color::hsl(base_hsla.hue, base_hsla.saturation, 1.0),
)
}
ColorChannel::Alpha => (
base_color.with_alpha(0.),
base_color.with_alpha(0.5),
base_color.with_alpha(1.),
),
}
}
}
/// Used to store the color channels that we are not editing: the components of the color
/// that are constant for this slider.
#[derive(Component, Default, Clone)]
pub struct SliderBaseColor(pub Color);
/// Slider template properties, passed to [`color_slider`] function.
pub struct ColorSliderProps {
/// Slider current value
pub value: f32,
/// On-change handler
pub on_change: Callback<In<f32>>,
/// Which color component we're editing
pub channel: ColorChannel,
}
impl Default for ColorSliderProps {
fn default() -> Self {
Self {
value: 0.0,
on_change: Callback::Ignore,
channel: ColorChannel::Alpha,
}
}
}
/// A color slider widget.
#[derive(Component, Default, Clone)]
#[require(CoreSlider, SliderBaseColor(Color::WHITE))]
pub struct ColorSlider {
/// Which channel is being edited by this slider.
pub channel: ColorChannel,
}
/// Marker for the track
#[derive(Component, Default, Clone)]
struct ColorSliderTrack;
/// Marker for the thumb
#[derive(Component, Default, Clone)]
struct ColorSliderThumb;
/// Spawn a new slider widget.
///
/// # Arguments
///
/// * `props` - construction properties for the slider.
/// * `overrides` - a bundle of components that are merged in with the normal slider components.
pub fn color_slider<B: Bundle>(props: ColorSliderProps, overrides: B) -> impl Bundle {
(
Node {
display: Display::Flex,
flex_direction: FlexDirection::Row,
height: Val::Px(SLIDER_HEIGHT),
align_items: AlignItems::Stretch,
flex_grow: 1.0,
..Default::default()
},
CoreSlider {
on_change: props.on_change,
track_click: TrackClick::Snap,
},
ColorSlider {
channel: props.channel.clone(),
},
SliderValue(props.value),
props.channel.range(),
CursorIcon::System(bevy_window::SystemCursorIcon::Pointer),
TabIndex(0),
overrides,
children![
// track
(
Node {
position_type: PositionType::Absolute,
left: Val::Px(0.),
right: Val::Px(0.),
top: Val::Px(TRACK_PADDING),
bottom: Val::Px(TRACK_PADDING),
..Default::default()
},
RoundedCorners::All.to_border_radius(TRACK_RADIUS),
ColorSliderTrack,
AlphaPattern,
MaterialNode::<AlphaPatternMaterial>(Handle::default()),
children![
// Left endcap
(
Node {
width: Val::Px(THUMB_SIZE * 0.5),
..Default::default()
},
RoundedCorners::Left.to_border_radius(TRACK_RADIUS),
BackgroundColor(palette::X_AXIS),
),
// Track with gradient
(
Node {
flex_grow: 1.0,
..Default::default()
},
BackgroundGradient(vec![Gradient::Linear(LinearGradient {
angle: PI * 0.5,
stops: vec![
ColorStop::new(Color::NONE, Val::Percent(0.)),
ColorStop::new(Color::NONE, Val::Percent(50.)),
ColorStop::new(Color::NONE, Val::Percent(100.)),
],
color_space: InterpolationColorSpace::Srgb,
})]),
ZIndex(1),
children![(
Node {
position_type: PositionType::Absolute,
left: Val::Percent(0.),
top: Val::Percent(50.),
width: Val::Px(THUMB_SIZE),
height: Val::Px(THUMB_SIZE),
border: UiRect::all(Val::Px(2.0)),
..Default::default()
},
CoreSliderThumb,
ColorSliderThumb,
BorderRadius::MAX,
BorderColor::all(palette::WHITE),
Outline {
width: Val::Px(1.),
offset: Val::Px(0.),
color: palette::BLACK
},
UiTransform::from_translation(Val2::new(
Val::Percent(-50.0),
Val::Percent(-50.0),
))
)]
),
// Right endcap
(
Node {
width: Val::Px(THUMB_SIZE * 0.5),
..Default::default()
},
RoundedCorners::Right.to_border_radius(TRACK_RADIUS),
BackgroundColor(palette::Z_AXIS),
),
]
),
],
)
}
fn update_slider_pos(
mut q_sliders: Query<
(Entity, &SliderValue, &SliderRange),
(
With<ColorSlider>,
Or<(Changed<SliderValue>, Changed<SliderRange>)>,
),
>,
q_children: Query<&Children>,
mut q_slider_thumb: Query<&mut Node, With<ColorSliderThumb>>,
) {
for (slider_ent, value, range) in q_sliders.iter_mut() {
for child in q_children.iter_descendants(slider_ent) {
if let Ok(mut thumb_node) = q_slider_thumb.get_mut(child) {
thumb_node.left = Val::Percent(range.thumb_position(value.0) * 100.0);
}
}
}
}
fn update_track_color(
mut q_sliders: Query<(Entity, &ColorSlider, &SliderBaseColor), Changed<SliderBaseColor>>,
q_children: Query<&Children>,
q_track: Query<(), With<ColorSliderTrack>>,
mut q_background: Query<&mut BackgroundColor>,
mut q_gradient: Query<&mut BackgroundGradient>,
) {
for (slider_ent, slider, SliderBaseColor(base_color)) in q_sliders.iter_mut() {
let (start, middle, end) = slider.channel.gradient_ends(*base_color);
if let Some(track_ent) = q_children
.iter_descendants(slider_ent)
.find(|ent| q_track.contains(*ent))
{
let Ok(track_children) = q_children.get(track_ent) else {
continue;
};
if let Ok(mut cap_bg) = q_background.get_mut(track_children[0]) {
cap_bg.0 = start;
}
if let Ok(mut gradient) = q_gradient.get_mut(track_children[1]) {
if let [Gradient::Linear(linear_gradient)] = &mut gradient.0[..] {
linear_gradient.stops[0].color = start;
linear_gradient.stops[1].color = middle;
linear_gradient.stops[2].color = end;
linear_gradient.color_space = match slider.channel {
ColorChannel::Red | ColorChannel::Green | ColorChannel::Blue => {
InterpolationColorSpace::Srgb
}
ColorChannel::HslHue
| ColorChannel::HslLightness
| ColorChannel::HslSaturation => InterpolationColorSpace::Hsl,
ColorChannel::Alpha => match base_color {
Color::Srgba(_) => InterpolationColorSpace::Srgb,
Color::LinearRgba(_) => InterpolationColorSpace::LinearRgb,
Color::Oklaba(_) => InterpolationColorSpace::OkLab,
Color::Oklcha(_) => InterpolationColorSpace::OkLchLong,
Color::Hsla(_) | Color::Hsva(_) => InterpolationColorSpace::Hsl,
_ => {
warn_once!(
"Unsupported color space for ColorSlider: {:?}",
base_color
);
InterpolationColorSpace::Srgb
}
},
};
}
}
if let Ok(mut cap_bg) = q_background.get_mut(track_children[2]) {
cap_bg.0 = end;
}
}
}
}
/// Plugin which registers the systems for updating the slider styles.
pub struct ColorSliderPlugin;
impl Plugin for ColorSliderPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_systems(
PreUpdate,
(update_slider_pos, update_track_color).in_set(PickingSystems::Last),
);
}
}

View File

@ -0,0 +1,53 @@
use bevy_asset::Handle;
use bevy_color::Alpha;
use bevy_ecs::{bundle::Bundle, children, component::Component, spawn::SpawnRelated};
use bevy_ui::{BackgroundColor, BorderRadius, Node, PositionType, Val};
use bevy_ui_render::ui_material::MaterialNode;
use crate::{
alpha_pattern::{AlphaPattern, AlphaPatternMaterial},
constants::size,
palette,
};
/// Marker identifying a color swatch.
#[derive(Component, Default, Clone)]
pub struct ColorSwatch;
/// Marker identifying the color swatch foreground, the piece that actually displays the color
/// in front of the alpha pattern. This exists so that users can reach in and change the color
/// dynamically.
#[derive(Component, Default, Clone)]
pub struct ColorSwatchFg;
/// Template function to spawn a color swatch.
///
/// # Arguments
/// * `overrides` - a bundle of components that are merged in with the normal swatch components.
pub fn color_swatch<B: Bundle>(overrides: B) -> impl Bundle {
(
Node {
height: size::ROW_HEIGHT,
min_width: size::ROW_HEIGHT,
..Default::default()
},
ColorSwatch,
AlphaPattern,
MaterialNode::<AlphaPatternMaterial>(Handle::default()),
BorderRadius::all(Val::Px(5.0)),
overrides,
children![(
Node {
position_type: PositionType::Absolute,
left: Val::Px(0.),
top: Val::Px(0.),
bottom: Val::Px(0.),
right: Val::Px(0.),
..Default::default()
},
ColorSwatchFg,
BackgroundColor(palette::ACCENT.with_alpha(0.5)),
BorderRadius::all(Val::Px(5.0))
),],
)
}

View File

@ -3,24 +3,34 @@ use bevy_app::Plugin;
mod button;
mod checkbox;
mod color_slider;
mod color_swatch;
mod radio;
mod slider;
mod toggle_switch;
pub use button::{button, ButtonPlugin, ButtonProps, ButtonVariant};
pub use checkbox::{checkbox, CheckboxPlugin, CheckboxProps};
pub use color_slider::{
color_slider, ColorChannel, ColorSlider, ColorSliderPlugin, ColorSliderProps, SliderBaseColor,
};
pub use color_swatch::{color_swatch, ColorSwatch, ColorSwatchFg};
pub use radio::{radio, RadioPlugin};
pub use slider::{slider, SliderPlugin, SliderProps};
pub use toggle_switch::{toggle_switch, ToggleSwitchPlugin, ToggleSwitchProps};
use crate::alpha_pattern::AlphaPatternPlugin;
/// Plugin which registers all `bevy_feathers` controls.
pub struct ControlsPlugin;
impl Plugin for ControlsPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_plugins((
AlphaPatternPlugin,
ButtonPlugin,
CheckboxPlugin,
ColorSliderPlugin,
RadioPlugin,
SliderPlugin,
ToggleSwitchPlugin,

View File

@ -22,14 +22,17 @@ use bevy_app::{HierarchyPropagatePlugin, Plugin, PostUpdate};
use bevy_asset::embedded_asset;
use bevy_ecs::query::With;
use bevy_text::{TextColor, TextFont};
use bevy_ui_render::UiMaterialPlugin;
use bevy_winit::cursor::CursorIcon;
use crate::{
alpha_pattern::{AlphaPatternMaterial, AlphaPatternResource},
controls::ControlsPlugin,
cursor::{CursorIconPlugin, DefaultCursorIcon},
theme::{ThemedText, UiTheme},
};
mod alpha_pattern;
pub mod constants;
pub mod controls;
pub mod cursor;
@ -48,17 +51,22 @@ impl Plugin for FeathersPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<UiTheme>();
// Embedded font
embedded_asset!(app, "assets/fonts/FiraSans-Bold.ttf");
embedded_asset!(app, "assets/fonts/FiraSans-BoldItalic.ttf");
embedded_asset!(app, "assets/fonts/FiraSans-Regular.ttf");
embedded_asset!(app, "assets/fonts/FiraSans-Italic.ttf");
embedded_asset!(app, "assets/fonts/FiraMono-Medium.ttf");
// Embedded shader
embedded_asset!(app, "assets/shaders/alpha_pattern.wgsl");
app.add_plugins((
ControlsPlugin,
CursorIconPlugin,
HierarchyPropagatePlugin::<TextColor, With<ThemedText>>::default(),
HierarchyPropagatePlugin::<TextFont, With<ThemedText>>::default(),
UiMaterialPlugin::<AlphaPatternMaterial>::default(),
));
app.insert_resource(DefaultCursorIcon(CursorIcon::System(
@ -70,5 +78,7 @@ impl Plugin for FeathersPlugin {
.add_observer(theme::on_changed_border)
.add_observer(theme::on_changed_font_color)
.add_observer(font_styles::on_changed_font);
app.init_resource::<AlphaPatternResource>();
}
}

View File

@ -1,11 +1,15 @@
//! This example shows off the various Bevy Feathers widgets.
use bevy::{
core_widgets::{Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugin, SliderStep},
color::palettes,
core_widgets::{
Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugin, SliderStep, SliderValue,
},
feathers::{
controls::{
button, checkbox, radio, slider, toggle_switch, ButtonProps, ButtonVariant,
CheckboxProps, SliderProps, ToggleSwitchProps,
button, checkbox, color_slider, color_swatch, radio, slider, toggle_switch,
ButtonProps, ButtonVariant, CheckboxProps, ColorChannel, ColorSlider, ColorSliderProps,
ColorSwatch, SliderBaseColor, SliderProps, ToggleSwitchProps,
},
dark_theme::create_dark_theme,
rounded_corners::RoundedCorners,
@ -21,6 +25,19 @@ use bevy::{
winit::WinitSettings,
};
/// A struct to hold the state of various widgets shown in the demo.
#[derive(Resource)]
struct DemoWidgetStates {
rgb_color: Srgba,
hsl_color: Hsla,
}
#[derive(Component, Clone, Copy, PartialEq)]
enum SwatchType {
Rgb,
Hsl,
}
fn main() {
App::new()
.add_plugins((
@ -31,9 +48,14 @@ fn main() {
FeathersPlugin,
))
.insert_resource(UiTheme(create_dark_theme()))
.insert_resource(DemoWidgetStates {
rgb_color: palettes::tailwind::EMERALD_800.with_alpha(0.7),
hsl_color: palettes::tailwind::AMBER_800.into(),
})
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
.insert_resource(WinitSettings::desktop_app())
.add_systems(Startup, setup)
.add_systems(Update, update_colors)
.run();
}
@ -58,6 +80,41 @@ fn demo_root(commands: &mut Commands) -> impl Bundle {
},
);
let change_red =
commands.register_system(|value: In<f32>, mut color: ResMut<DemoWidgetStates>| {
color.rgb_color.red = *value;
});
let change_green =
commands.register_system(|value: In<f32>, mut color: ResMut<DemoWidgetStates>| {
color.rgb_color.green = *value;
});
let change_blue =
commands.register_system(|value: In<f32>, mut color: ResMut<DemoWidgetStates>| {
color.rgb_color.blue = *value;
});
let change_alpha =
commands.register_system(|value: In<f32>, mut color: ResMut<DemoWidgetStates>| {
color.rgb_color.alpha = *value;
});
let change_hue =
commands.register_system(|value: In<f32>, mut color: ResMut<DemoWidgetStates>| {
color.hsl_color.hue = *value;
});
let change_saturation =
commands.register_system(|value: In<f32>, mut color: ResMut<DemoWidgetStates>| {
color.hsl_color.saturation = *value;
});
let change_lightness =
commands.register_system(|value: In<f32>, mut color: ResMut<DemoWidgetStates>| {
color.hsl_color.lightness = *value;
});
(
Node {
width: Val::Percent(100.0),
@ -261,7 +318,146 @@ fn demo_root(commands: &mut Commands) -> impl Bundle {
},
SliderStep(10.)
),
(
Node {
display: Display::Flex,
flex_direction: FlexDirection::Row,
justify_content: JustifyContent::SpaceBetween,
..default()
},
children![Text("Srgba".to_owned()), color_swatch(SwatchType::Rgb),]
),
color_slider(
ColorSliderProps {
value: 0.5,
on_change: Callback::System(change_red),
channel: ColorChannel::Red
},
()
),
color_slider(
ColorSliderProps {
value: 0.5,
on_change: Callback::System(change_green),
channel: ColorChannel::Green
},
()
),
color_slider(
ColorSliderProps {
value: 0.5,
on_change: Callback::System(change_blue),
channel: ColorChannel::Blue
},
()
),
color_slider(
ColorSliderProps {
value: 0.5,
on_change: Callback::System(change_alpha),
channel: ColorChannel::Alpha
},
()
),
(
Node {
display: Display::Flex,
flex_direction: FlexDirection::Row,
justify_content: JustifyContent::SpaceBetween,
..default()
},
children![Text("Hsl".to_owned()), color_swatch(SwatchType::Hsl),]
),
color_slider(
ColorSliderProps {
value: 0.5,
on_change: Callback::System(change_hue),
channel: ColorChannel::HslHue
},
()
),
color_slider(
ColorSliderProps {
value: 0.5,
on_change: Callback::System(change_saturation),
channel: ColorChannel::HslSaturation
},
()
),
color_slider(
ColorSliderProps {
value: 0.5,
on_change: Callback::System(change_lightness),
channel: ColorChannel::HslLightness
},
()
)
]
),],
)
}
fn update_colors(
colors: Res<DemoWidgetStates>,
mut sliders: Query<(Entity, &ColorSlider, &mut SliderBaseColor)>,
swatches: Query<(&SwatchType, &Children), With<ColorSwatch>>,
mut commands: Commands,
) {
if colors.is_changed() {
for (slider_ent, slider, mut base) in sliders.iter_mut() {
match slider.channel {
ColorChannel::Red => {
base.0 = colors.rgb_color.into();
commands
.entity(slider_ent)
.insert(SliderValue(colors.rgb_color.red));
}
ColorChannel::Green => {
base.0 = colors.rgb_color.into();
commands
.entity(slider_ent)
.insert(SliderValue(colors.rgb_color.green));
}
ColorChannel::Blue => {
base.0 = colors.rgb_color.into();
commands
.entity(slider_ent)
.insert(SliderValue(colors.rgb_color.blue));
}
ColorChannel::HslHue => {
base.0 = colors.hsl_color.into();
commands
.entity(slider_ent)
.insert(SliderValue(colors.hsl_color.hue));
}
ColorChannel::HslSaturation => {
base.0 = colors.hsl_color.into();
commands
.entity(slider_ent)
.insert(SliderValue(colors.hsl_color.saturation));
}
ColorChannel::HslLightness => {
base.0 = colors.hsl_color.into();
commands
.entity(slider_ent)
.insert(SliderValue(colors.hsl_color.lightness));
}
ColorChannel::Alpha => {
base.0 = colors.rgb_color.into();
commands
.entity(slider_ent)
.insert(SliderValue(colors.rgb_color.alpha));
}
}
}
for (swatch_type, children) in swatches.iter() {
commands
.entity(children[0])
.insert(BackgroundColor(match swatch_type {
SwatchType::Rgb => colors.rgb_color.into(),
SwatchType::Hsl => colors.hsl_color.into(),
}));
}
}
}