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