ui_material/
ui_material.rs

1//! Demonstrates the use of [`UiMaterials`](UiMaterial) and how to change material values
2
3use bevy::{
4    color::palettes::css::DARK_BLUE, prelude::*, reflect::TypePath, render::render_resource::*,
5};
6
7/// This example uses a shader source file from the assets subdirectory
8const SHADER_ASSET_PATH: &str = "shaders/custom_ui_material.wgsl";
9
10fn main() {
11    App::new()
12        .add_plugins(DefaultPlugins)
13        .add_plugins(UiMaterialPlugin::<CustomUiMaterial>::default())
14        .add_systems(Startup, setup)
15        .add_systems(Update, animate)
16        .run();
17}
18
19fn setup(
20    mut commands: Commands,
21    mut ui_materials: ResMut<Assets<CustomUiMaterial>>,
22    asset_server: Res<AssetServer>,
23) {
24    // Camera so we can see UI
25    commands.spawn(Camera2d);
26
27    commands
28        .spawn(Node {
29            width: Val::Percent(100.0),
30            height: Val::Percent(100.0),
31            align_items: AlignItems::Center,
32            justify_content: JustifyContent::Center,
33            ..default()
34        })
35        .with_children(|parent| {
36            let banner_scale_factor = 0.5;
37            parent.spawn((
38                Node {
39                    position_type: PositionType::Absolute,
40                    width: Val::Px(905.0 * banner_scale_factor),
41                    height: Val::Px(363.0 * banner_scale_factor),
42                    border: UiRect::all(Val::Px(20.)),
43                    ..default()
44                },
45                MaterialNode(ui_materials.add(CustomUiMaterial {
46                    color: LinearRgba::WHITE.to_f32_array().into(),
47                    slider: Vec4::splat(0.5),
48                    color_texture: asset_server.load("branding/banner.png"),
49                    border_color: LinearRgba::WHITE.to_f32_array().into(),
50                })),
51                BorderRadius::all(Val::Px(20.)),
52                // UI material nodes can have outlines and shadows like any other UI node
53                Outline {
54                    width: Val::Px(2.),
55                    offset: Val::Px(100.),
56                    color: DARK_BLUE.into(),
57                },
58            ));
59        });
60}
61
62#[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
63struct CustomUiMaterial {
64    /// Color multiplied with the image
65    #[uniform(0)]
66    color: Vec4,
67    /// Represents how much of the image is visible
68    /// Goes from 0 to 1
69    /// A `Vec4` is used here because Bevy with webgl2 requires that uniforms are 16-byte aligned but only the first component is read.
70    #[uniform(1)]
71    slider: Vec4,
72    /// Image used to represent the slider
73    #[texture(2)]
74    #[sampler(3)]
75    color_texture: Handle<Image>,
76    /// Color of the image's border
77    #[uniform(4)]
78    border_color: Vec4,
79}
80
81impl UiMaterial for CustomUiMaterial {
82    fn fragment_shader() -> ShaderRef {
83        SHADER_ASSET_PATH.into()
84    }
85}
86
87// Fills the slider slowly over 2 seconds and resets it
88// Also updates the color of the image to a rainbow color
89fn animate(
90    mut materials: ResMut<Assets<CustomUiMaterial>>,
91    q: Query<&MaterialNode<CustomUiMaterial>>,
92    time: Res<Time>,
93) {
94    let duration = 2.0;
95    for handle in &q {
96        if let Some(material) = materials.get_mut(handle) {
97            // rainbow color effect
98            let new_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 1., 0.5);
99            let border_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 0.75, 0.75);
100            material.color = new_color.to_linear().to_vec4();
101            material.slider.x =
102                ((time.elapsed_secs() % (duration * 2.0)) - duration).abs() / duration;
103            material.border_color = border_color.to_linear().to_vec4();
104        }
105    }
106}