Skip to content

Commit 9a5d907

Browse files
authored
Color swatch widget. (#20237)
A simple color swatch with an alpha pattern; this has been split out from the other color widgets. The main reason for wanting this in is to set up the infrastructure for custom shaders in feathers. Part of #16900 Note this is pre-BSN, and will need to be revised when merging with the BSN stuff. I deliberately left some stuff out to make merging easier. <img width="376" height="66" alt="color-swatch" src="https://github.com/user-attachments/assets/2f41aeda-b780-467d-a060-a242b510bca4" />
1 parent ce57258 commit 9a5d907

File tree

8 files changed

+172
-3
lines changed

8 files changed

+172
-3
lines changed

crates/bevy_feathers/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
2121
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
2222
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
2323
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
24+
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
2425
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
2526
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
2627
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
2728
"bevy_ui_picking_backend",
2829
] }
30+
bevy_ui_render = { path = "../bevy_ui_render", version = "0.17.0-dev" }
2931
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
3032
bevy_winit = { path = "../bevy_winit", version = "0.17.0-dev" }
3133

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use bevy_app::Plugin;
2+
use bevy_asset::{Asset, Assets, Handle};
3+
use bevy_ecs::{
4+
component::Component,
5+
lifecycle::Add,
6+
observer::On,
7+
resource::Resource,
8+
system::{Query, Res},
9+
world::FromWorld,
10+
};
11+
use bevy_reflect::TypePath;
12+
use bevy_render::render_resource::{AsBindGroup, ShaderRef};
13+
use bevy_ui_render::ui_material::{MaterialNode, UiMaterial};
14+
15+
#[derive(AsBindGroup, Asset, TypePath, Default, Debug, Clone)]
16+
pub(crate) struct AlphaPatternMaterial {}
17+
18+
impl UiMaterial for AlphaPatternMaterial {
19+
fn fragment_shader() -> ShaderRef {
20+
"embedded://bevy_feathers/assets/shaders/alpha_pattern.wgsl".into()
21+
}
22+
}
23+
24+
#[derive(Resource)]
25+
pub(crate) struct AlphaPatternResource(pub(crate) Handle<AlphaPatternMaterial>);
26+
27+
impl FromWorld for AlphaPatternResource {
28+
fn from_world(world: &mut bevy_ecs::world::World) -> Self {
29+
let mut ui_materials = world
30+
.get_resource_mut::<Assets<AlphaPatternMaterial>>()
31+
.unwrap();
32+
Self(ui_materials.add(AlphaPatternMaterial::default()))
33+
}
34+
}
35+
36+
/// Marker that tells us we want to fill in the [`MaterialNode`] with the alpha material.
37+
#[derive(Component, Default, Clone)]
38+
pub(crate) struct AlphaPattern;
39+
40+
/// Observer to fill in the material handle (since we don't have access to the materials asset
41+
/// in the template)
42+
fn on_add_alpha_pattern(
43+
ev: On<Add, AlphaPattern>,
44+
mut q_material_node: Query<&mut MaterialNode<AlphaPatternMaterial>>,
45+
r_material: Res<AlphaPatternResource>,
46+
) {
47+
if let Ok(mut material) = q_material_node.get_mut(ev.target()) {
48+
material.0 = r_material.0.clone();
49+
}
50+
}
51+
52+
/// Plugin which registers the systems for updating the button styles.
53+
pub struct AlphaPatternPlugin;
54+
55+
impl Plugin for AlphaPatternPlugin {
56+
fn build(&self, app: &mut bevy_app::App) {
57+
app.add_observer(on_add_alpha_pattern);
58+
}
59+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// This shader draws a checkerboard pattern
2+
#import bevy_ui::ui_vertex_output::UiVertexOutput
3+
4+
@fragment
5+
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
6+
let uv = (in.uv - vec2<f32>(0.5, 0.5)) * in.size / 16.;
7+
let check = select(0.0, 1.0, (fract(uv.x) < 0.5) != (fract(uv.y) < 0.5));
8+
let bg = mix(vec3<f32>(0.2, 0.2, 0.2), vec3<f32>(0.6, 0.6, 0.6), check);
9+
10+
let size = vec2<f32>(in.size.x, in.size.y);
11+
let external_distance = sd_rounded_box((in.uv - 0.5) * size, size, in.border_radius);
12+
let alpha = smoothstep(0.5, -0.5, external_distance);
13+
14+
return vec4<f32>(bg, alpha);
15+
}
16+
17+
// From: https://github.com/bevyengine/bevy/pull/8973
18+
// The returned value is the shortest distance from the given point to the boundary of the rounded box.
19+
// 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.
20+
// arguments
21+
// point -> The function will return the distance from this point to the closest point on the boundary.
22+
// size -> The maximum width and height of the box.
23+
// corner_radii -> The radius of each rounded corner. Ordered counter clockwise starting top left:
24+
// x = top left, y = top right, z = bottom right, w = bottom left.
25+
fn sd_rounded_box(point: vec2<f32>, size: vec2<f32>, corner_radii: vec4<f32>) -> f32 {
26+
// if 0.0 < y then select bottom left (w) and bottom right corner radius (z)
27+
// else select top left (x) and top right corner radius (y)
28+
let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y);
29+
// 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.
30+
let radius = select(rs.x, rs.y, 0.0 < point.x);
31+
// Vector from the corner closest to the point, to the point
32+
let corner_to_point = abs(point) - 0.5 * size;
33+
// Vector from the center of the radius circle to the point
34+
let q = corner_to_point + radius;
35+
// 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.
36+
let l = length(max(q, vec2(0.0)));
37+
let m = min(max(q.x, q.y), 0.0);
38+
return l + m - radius;
39+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use bevy_asset::Handle;
2+
use bevy_color::Alpha;
3+
use bevy_ecs::{bundle::Bundle, children, component::Component, spawn::SpawnRelated};
4+
use bevy_ui::{BackgroundColor, BorderRadius, Node, PositionType, Val};
5+
use bevy_ui_render::ui_material::MaterialNode;
6+
7+
use crate::{
8+
alpha_pattern::{AlphaPattern, AlphaPatternMaterial},
9+
constants::size,
10+
palette,
11+
};
12+
13+
/// Marker identifying a color swatch.
14+
#[derive(Component, Default, Clone)]
15+
pub struct ColorSwatch;
16+
17+
/// Marker identifying the color swatch foreground, the piece that actually displays the color
18+
/// in front of the alpha pattern. This exists so that users can reach in and change the color
19+
/// dynamically.
20+
#[derive(Component, Default, Clone)]
21+
pub struct ColorSwatchFg;
22+
23+
/// Template function to spawn a color swatch.
24+
///
25+
/// # Arguments
26+
/// * `overrides` - a bundle of components that are merged in with the normal swatch components.
27+
pub fn color_swatch<B: Bundle>(overrides: B) -> impl Bundle {
28+
(
29+
Node {
30+
height: size::ROW_HEIGHT,
31+
min_width: size::ROW_HEIGHT,
32+
..Default::default()
33+
},
34+
ColorSwatch,
35+
AlphaPattern,
36+
MaterialNode::<AlphaPatternMaterial>(Handle::default()),
37+
BorderRadius::all(Val::Px(5.0)),
38+
overrides,
39+
children![(
40+
Node {
41+
position_type: PositionType::Absolute,
42+
left: Val::Px(0.),
43+
top: Val::Px(0.),
44+
bottom: Val::Px(0.),
45+
right: Val::Px(0.),
46+
..Default::default()
47+
},
48+
ColorSwatchFg,
49+
BackgroundColor(palette::ACCENT.with_alpha(0.5)),
50+
BorderRadius::all(Val::Px(5.0))
51+
),],
52+
)
53+
}

crates/bevy_feathers/src/controls/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ use bevy_app::Plugin;
33

44
mod button;
55
mod checkbox;
6+
mod color_swatch;
67
mod radio;
78
mod slider;
89
mod toggle_switch;
910

1011
pub use button::{button, ButtonPlugin, ButtonProps, ButtonVariant};
1112
pub use checkbox::{checkbox, CheckboxPlugin, CheckboxProps};
13+
pub use color_swatch::{color_swatch, ColorSwatch, ColorSwatchFg};
1214
pub use radio::{radio, RadioPlugin};
1315
pub use slider::{slider, SliderPlugin, SliderProps};
1416
pub use toggle_switch::{toggle_switch, ToggleSwitchPlugin, ToggleSwitchProps};
1517

18+
use crate::alpha_pattern::AlphaPatternPlugin;
19+
1620
/// Plugin which registers all `bevy_feathers` controls.
1721
pub struct ControlsPlugin;
1822

1923
impl Plugin for ControlsPlugin {
2024
fn build(&self, app: &mut bevy_app::App) {
2125
app.add_plugins((
26+
AlphaPatternPlugin,
2227
ButtonPlugin,
2328
CheckboxPlugin,
2429
RadioPlugin,

crates/bevy_feathers/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ use bevy_app::{HierarchyPropagatePlugin, Plugin, PostUpdate};
2222
use bevy_asset::embedded_asset;
2323
use bevy_ecs::query::With;
2424
use bevy_text::{TextColor, TextFont};
25+
use bevy_ui_render::UiMaterialPlugin;
2526
use bevy_winit::cursor::CursorIcon;
2627

2728
use crate::{
29+
alpha_pattern::{AlphaPatternMaterial, AlphaPatternResource},
2830
controls::ControlsPlugin,
2931
cursor::{CursorIconPlugin, DefaultCursorIcon},
3032
theme::{ThemedText, UiTheme},
3133
};
3234

35+
mod alpha_pattern;
3336
pub mod constants;
3437
pub mod controls;
3538
pub mod cursor;
@@ -48,17 +51,22 @@ impl Plugin for FeathersPlugin {
4851
fn build(&self, app: &mut bevy_app::App) {
4952
app.init_resource::<UiTheme>();
5053

54+
// Embedded font
5155
embedded_asset!(app, "assets/fonts/FiraSans-Bold.ttf");
5256
embedded_asset!(app, "assets/fonts/FiraSans-BoldItalic.ttf");
5357
embedded_asset!(app, "assets/fonts/FiraSans-Regular.ttf");
5458
embedded_asset!(app, "assets/fonts/FiraSans-Italic.ttf");
5559
embedded_asset!(app, "assets/fonts/FiraMono-Medium.ttf");
5660

61+
// Embedded shader
62+
embedded_asset!(app, "assets/shaders/alpha_pattern.wgsl");
63+
5764
app.add_plugins((
5865
ControlsPlugin,
5966
CursorIconPlugin,
6067
HierarchyPropagatePlugin::<TextColor, With<ThemedText>>::default(),
6168
HierarchyPropagatePlugin::<TextFont, With<ThemedText>>::default(),
69+
UiMaterialPlugin::<AlphaPatternMaterial>::default(),
6270
));
6371

6472
app.insert_resource(DefaultCursorIcon(CursorIcon::System(
@@ -70,5 +78,7 @@ impl Plugin for FeathersPlugin {
7078
.add_observer(theme::on_changed_border)
7179
.add_observer(theme::on_changed_font_color)
7280
.add_observer(font_styles::on_changed_font);
81+
82+
app.init_resource::<AlphaPatternResource>();
7383
}
7484
}

examples/ui/feathers.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use bevy::{
77
},
88
feathers::{
99
controls::{
10-
button, checkbox, radio, slider, toggle_switch, ButtonProps, ButtonVariant,
11-
CheckboxProps, SliderProps, ToggleSwitchProps,
10+
button, checkbox, color_swatch, radio, slider, toggle_switch, ButtonProps,
11+
ButtonVariant, CheckboxProps, SliderProps, ToggleSwitchProps,
1212
},
1313
dark_theme::create_dark_theme,
1414
rounded_corners::RoundedCorners,
@@ -276,6 +276,7 @@ fn demo_root(commands: &mut Commands) -> impl Bundle {
276276
},
277277
(SliderStep(10.), SliderPrecision(2)),
278278
),
279+
color_swatch(()),
279280
]
280281
),],
281282
)

release-content/release-notes/feathers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Bevy Feathers
33
authors: ["@viridia", "@Atlas16A"]
4-
pull_requests: [19730, 19900, 19928]
4+
pull_requests: [19730, 19900, 19928, 20237]
55
---
66

77
To make it easier for Bevy engine developers and third-party tool creators to make comfortable, visually cohesive tooling,

0 commit comments

Comments
 (0)