├── .gitattributes ├── assets ├── bevy.png ├── heart.png ├── ferris.png ├── rings1.glb └── rings2.glb ├── derive ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── .gitignore ├── Cargo.toml ├── LICENSE ├── LICENSE-MIT ├── src ├── sprite.rs ├── pbr.rs ├── ui.rs └── lib.rs ├── tests └── derives.rs ├── README.md ├── examples └── ui.rs └── LICENSE-APACHE /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /assets/bevy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlu8/bevy_mod_opacity/HEAD/assets/bevy.png -------------------------------------------------------------------------------- /assets/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlu8/bevy_mod_opacity/HEAD/assets/heart.png -------------------------------------------------------------------------------- /derive/README.md: -------------------------------------------------------------------------------- 1 | # bevy_mod_opacity_derive 2 | 3 | Derive macros for `bevy_mod_opacity`. 4 | -------------------------------------------------------------------------------- /assets/ferris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlu8/bevy_mod_opacity/HEAD/assets/ferris.png -------------------------------------------------------------------------------- /assets/rings1.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlu8/bevy_mod_opacity/HEAD/assets/rings1.glb -------------------------------------------------------------------------------- /assets/rings2.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlu8/bevy_mod_opacity/HEAD/assets/rings2.glb -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_mod_opacity_derive" 3 | version = "0.3.0" 4 | edition = "2021" 5 | 6 | authors = ["Mincong Lu "] 7 | license = "MIT OR Apache-2.0" 8 | 9 | readme = "README.md" 10 | repository = "https://github.com/mintlu8/bevy_mod_opacity" 11 | description = """ 12 | Hierarchical opacity for bevy. 13 | """ 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | proc-macro-error = "1.0.4" 20 | proc-macro2 = "1.0.86" 21 | quote = "1.0.37" 22 | syn = "2.0.79" 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | 17 | # Added by cargo 18 | 19 | /target 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["derive"] 3 | 4 | [package] 5 | name = "bevy_mod_opacity" 6 | version = "0.4.0" 7 | edition = "2021" 8 | 9 | authors = ["Mincong Lu "] 10 | license = "MIT OR Apache-2.0" 11 | 12 | readme = "README.md" 13 | repository = "https://github.com/mintlu8/bevy_mod_opacity" 14 | description = """ 15 | Hierarchical opacity for bevy. 16 | """ 17 | keywords = ["bevy", "opacity", "fading"] 18 | 19 | [features] 20 | default = ["2d", "3d", "ui", "derive"] 21 | 2d = ["bevy/bevy_sprite", "bevy/bevy_sprite_render", "bevy/bevy_text"] 22 | 3d = ["bevy/bevy_pbr"] 23 | ui = ["bevy/bevy_ui", "bevy/bevy_text"] 24 | derive = ["bevy_mod_opacity_derive"] 25 | serde = ["dep:serde"] 26 | reflect = [] 27 | 28 | [dependencies] 29 | bevy = { version = "0.17.0", default-features = false, features = ["bevy_render", "bevy_asset"]} 30 | bevy_mod_opacity_derive = { version = "0.3", optional = true, path = "./derive" } 31 | serde = { version = "1", default-features = false, optional = true } 32 | 33 | 34 | [dev-dependencies] 35 | bevy = "0.17.0" 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 mintlu8 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mincong Lu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/sprite.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | app::App, 3 | asset::Assets, 4 | color::Alpha, 5 | ecs::system::{ResMut, SystemParam}, 6 | sprite::Sprite, 7 | sprite_render::{ColorMaterial, Material2d, MeshMaterial2d, Wireframe2dMaterial}, 8 | }; 9 | 10 | use crate::{OpacityAsset, OpacityExtension, OpacityQuery}; 11 | 12 | impl OpacityQuery for &mut Sprite { 13 | type Cx = (); 14 | 15 | fn apply_opacity(this: &mut Self::Item<'_, '_>, _: &mut (), opacity: f32) { 16 | this.color.set_alpha(opacity); 17 | } 18 | } 19 | 20 | impl OpacityAsset for ColorMaterial { 21 | fn apply_opacity(&mut self, opacity: f32) { 22 | self.color.set_alpha(opacity) 23 | } 24 | } 25 | 26 | impl OpacityAsset for Wireframe2dMaterial { 27 | fn apply_opacity(&mut self, opacity: f32) { 28 | self.color.set_alpha(opacity) 29 | } 30 | } 31 | 32 | impl OpacityQuery for &MeshMaterial2d 33 | where 34 | T: OpacityAsset + Material2d, 35 | { 36 | type Cx = ResMut<'static, Assets>; 37 | 38 | fn apply_opacity( 39 | this: &mut Self::Item<'_, '_>, 40 | cx: &mut ::Item<'_, '_>, 41 | opacity: f32, 42 | ) { 43 | if let Some(mat) = cx.get_mut(this.id()) { 44 | mat.apply_opacity(opacity); 45 | } 46 | } 47 | } 48 | 49 | pub fn opacity_plugin_2d(app: &mut App) { 50 | app.register_opacity_component::(); 51 | app.register_opacity_material2d::(); 52 | } 53 | -------------------------------------------------------------------------------- /tests/derives.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | app::App, 3 | asset::Asset, 4 | color::Srgba, 5 | pbr::{ExtendedMaterial, Material, MaterialExtension, StandardMaterial}, 6 | prelude::Component, 7 | reflect::TypePath, 8 | render::render_resource::AsBindGroup, 9 | }; 10 | use bevy_mod_opacity::{Opacity, OpacityExtension, OpacityPlugin}; 11 | 12 | #[derive(Debug, Component, Opacity)] 13 | pub struct MyColor { 14 | pub r: f32, 15 | pub g: f32, 16 | pub b: f32, 17 | #[opacity] 18 | pub a: f32, 19 | } 20 | 21 | #[derive(Debug, Clone, TypePath, Asset, Opacity, AsBindGroup)] 22 | #[opacity(asset)] 23 | pub struct MyColorMaterial { 24 | #[opacity] 25 | pub color: Srgba, 26 | } 27 | 28 | impl Material for MyColorMaterial {} 29 | 30 | #[derive(Debug, Clone, AsBindGroup, TypePath, Asset, Opacity)] 31 | #[opacity(extends = StandardMaterial)] 32 | pub struct MyColorMaterialExt { 33 | #[opacity] 34 | pub color: Srgba, 35 | } 36 | 37 | impl MaterialExtension for MyColorMaterialExt {} 38 | 39 | #[derive(Debug, Clone, AsBindGroup, TypePath, Asset, Opacity)] 40 | #[opacity(masks = StandardMaterial)] 41 | pub struct MyColorMaterialExtMask { 42 | #[opacity] 43 | pub color: Srgba, 44 | } 45 | 46 | impl MaterialExtension for MyColorMaterialExtMask {} 47 | 48 | #[test] 49 | fn test() { 50 | let _app = App::new() 51 | .add_plugins(OpacityPlugin) 52 | .register_opacity_component::() 53 | .register_opacity_material3d::() 54 | .register_opacity_material3d::>() 55 | .register_opacity_material3d::>( 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/pbr.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | ecs::system::SystemParam, 3 | pbr::{ 4 | decal::ForwardDecalMaterialExt, wireframe::WireframeMaterial, ExtendedMaterial, Material, 5 | MaterialExtension, MeshMaterial3d, StandardMaterial, 6 | }, 7 | prelude::*, 8 | }; 9 | 10 | use crate::{OpacityAsset, OpacityExtension, OpacityQuery}; 11 | 12 | /// A [`MaterialExtension`] with an opacity value. 13 | pub trait OpacityMaterialExtension { 14 | fn apply_opacity(a: &mut A, b: &mut Self, opacity: f32); 15 | } 16 | 17 | impl OpacityAsset for ExtendedMaterial 18 | where 19 | T: OpacityMaterialExtension, 20 | { 21 | fn apply_opacity(&mut self, opacity: f32) { 22 | OpacityMaterialExtension::apply_opacity(&mut self.base, &mut self.extension, opacity); 23 | } 24 | } 25 | 26 | impl OpacityMaterialExtension for ForwardDecalMaterialExt { 27 | fn apply_opacity(a: &mut T, _: &mut Self, opacity: f32) { 28 | a.apply_opacity(opacity); 29 | } 30 | } 31 | 32 | impl OpacityAsset for StandardMaterial { 33 | fn apply_opacity(&mut self, opacity: f32) { 34 | self.base_color.set_alpha(opacity) 35 | } 36 | } 37 | 38 | impl OpacityAsset for WireframeMaterial { 39 | fn apply_opacity(&mut self, opacity: f32) { 40 | self.color.set_alpha(opacity) 41 | } 42 | } 43 | 44 | impl OpacityQuery for &MeshMaterial3d 45 | where 46 | T: OpacityAsset + Material, 47 | { 48 | type Cx = ResMut<'static, Assets>; 49 | 50 | fn apply_opacity( 51 | this: &mut Self::Item<'_, '_>, 52 | cx: &mut ::Item<'_, '_>, 53 | opacity: f32, 54 | ) { 55 | if let Some(mat) = cx.get_mut(this.id()) { 56 | mat.apply_opacity(opacity); 57 | } 58 | } 59 | } 60 | 61 | pub fn opacity_plugin_3d(app: &mut App) { 62 | app.register_opacity_material3d::(); 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bevy_mod_opacity 2 | 3 | Hierarchical opacity for bevy. 4 | 5 | ## The `Opacity` component 6 | 7 | When `Opacity` is inserted to an entity, the entity and all its descendants 8 | will be affected by the opacity value. Unlike bevy components like `Visibility` 9 | `Opacity` does not need to be put on every entity in the tree. 10 | Entities with no `Opacity` ancestor will not be affected by this crate. 11 | 12 | ## Support for native types 13 | 14 | We innately support `2d`, `3d` and `ui`, this includes `Sprite`, `TextColor`, `StandardMaterial`, 15 | `ColorMaterial`, `Image`, `BackgroundColor` and `ForegroundColor`. 16 | 17 | Additionally you can implement `OpacityQuery` or derive `Opacity` to make your own types 18 | and materials work with this crate. Combining `OpacityQuery` with custom `QueryData` can 19 | add support for third party types. 20 | 21 | ## Fade in and fade out 22 | 23 | Call functions like `Opacity::fade_in` and `Opacity::fade_out` to fade items in and out via tweening. 24 | `fade_out` can also be responsible for deleting the entity. 25 | 26 | ## FAQ 27 | 28 | * My 3d scene is not fading correctly 29 | 30 | Ensure materials are duplicated and unique, since we write to the underlying material directly. 31 | Also make sure `AlphaMode` is set to `Blend` if applicable. 32 | 33 | ## Versions 34 | 35 | | bevy | bevy_mod_opacity | 36 | |------|--------------------| 37 | | 0.14 | 0.1 | 38 | | 0.15 | 0.2 | 39 | | 0.16 | 0.3 | 40 | 41 | ## License 42 | 43 | License under either of 44 | 45 | Apache License, Version 2.0 (LICENSE-APACHE or ) 46 | MIT license (LICENSE-MIT or ) 47 | at your option. 48 | 49 | ## Contribution 50 | 51 | Contributions are welcome! 52 | 53 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 54 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | use crate::{OpacityExtension, OpacityQuery}; 2 | #[cfg(feature = "reflect")] 3 | use bevy::prelude::{ReflectComponent, ReflectDefault}; 4 | use bevy::ui::{BackgroundColor, BorderColor}; 5 | use bevy::{ 6 | app::App, 7 | color::Alpha, 8 | ecs::query::QueryData, 9 | prelude::{Component, ImageNode}, 10 | }; 11 | 12 | impl OpacityQuery for &mut ImageNode { 13 | type Cx = (); 14 | 15 | fn apply_opacity(this: &mut Self::Item<'_, '_>, _: &mut (), opacity: f32) { 16 | this.color.set_alpha(opacity); 17 | } 18 | } 19 | 20 | /// Determine whether [`BorderColor`] and [`BackgroundColor`] are controlled by 21 | /// opacity or should stay transparent. 22 | /// 23 | /// Items without this component are ignored. 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Component)] 25 | #[cfg_attr(feature = "reflect", derive(bevy::prelude::Reflect))] 26 | #[cfg_attr(feature = "reflect", reflect(Component, Default))] 27 | pub enum UiOpacity { 28 | /// Both should stay transparent 29 | #[default] 30 | None, 31 | /// Opacity controls border color. 32 | Border, 33 | /// Opacity controls background color. 34 | Background, 35 | /// Opacity controls border and background color. 36 | Both, 37 | } 38 | 39 | #[derive(Debug, QueryData)] 40 | #[query_data(mutable)] 41 | pub struct UiColorQuery { 42 | pub ui_color: &'static UiOpacity, 43 | pub background: &'static mut BackgroundColor, 44 | pub border: &'static mut BorderColor, 45 | } 46 | 47 | impl OpacityQuery for UiColorQuery { 48 | type Cx = (); 49 | 50 | fn apply_opacity(this: &mut Self::Item<'_, '_>, _: &mut (), opacity: f32) { 51 | match this.ui_color { 52 | UiOpacity::None => (), 53 | UiOpacity::Border => { 54 | this.border.left.set_alpha(opacity); 55 | this.border.right.set_alpha(opacity); 56 | this.border.top.set_alpha(opacity); 57 | this.border.bottom.set_alpha(opacity); 58 | } 59 | UiOpacity::Background => { 60 | this.background.0.set_alpha(opacity); 61 | } 62 | UiOpacity::Both => { 63 | this.border.left.set_alpha(opacity); 64 | this.border.right.set_alpha(opacity); 65 | this.border.top.set_alpha(opacity); 66 | this.border.bottom.set_alpha(opacity); 67 | this.background.0.set_alpha(opacity); 68 | } 69 | } 70 | } 71 | } 72 | 73 | pub fn opacity_plugin_ui(app: &mut App) { 74 | app.register_opacity_component::(); 75 | app.register_opacity::(); 76 | } 77 | -------------------------------------------------------------------------------- /examples/ui.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | app::{App, Startup}, 3 | asset::AssetServer, 4 | color::Color, 5 | light::AmbientLight, 6 | math::Vec3, 7 | prelude::{Camera, Camera2d, Camera3d, Commands, Component, ImageNode, Res, Text, Transform}, 8 | scene::SceneRoot, 9 | text::TextFont, 10 | ui::{ 11 | AlignItems, BorderColor, BorderRadius, Display, FlexDirection, JustifyContent, Node, 12 | UiRect, Val, 13 | }, 14 | DefaultPlugins, 15 | }; 16 | use bevy_mod_opacity::{Opacity, OpacityPlugin, UiOpacity}; 17 | 18 | #[derive(Debug, Component)] 19 | pub struct OnDelete; 20 | 21 | impl Drop for OnDelete { 22 | fn drop(&mut self) { 23 | println!("An entity has been deleted!") 24 | } 25 | } 26 | 27 | pub fn main() { 28 | App::new() 29 | .add_plugins(DefaultPlugins) 30 | .add_plugins(OpacityPlugin) 31 | .insert_resource(AmbientLight { 32 | color: Color::WHITE, 33 | brightness: 1000., 34 | ..Default::default() 35 | }) 36 | .add_systems(Startup, init) 37 | .run(); 38 | } 39 | 40 | pub fn init(mut commands: Commands, assets: Res) { 41 | let style = TextFont { 42 | font_size: 64.0, 43 | ..Default::default() 44 | }; 45 | commands.spawn(( 46 | Camera2d, 47 | Camera { 48 | order: 1, 49 | ..Default::default() 50 | }, 51 | )); 52 | 53 | commands.spawn(( 54 | Camera3d::default(), 55 | Transform::from_translation(Vec3::new(4., 4., 4.)).looking_at(Vec3::ZERO, Vec3::Y), 56 | )); 57 | 58 | commands 59 | .spawn(Node { 60 | width: Val::Percent(100.0), 61 | height: Val::Auto, 62 | display: Display::Flex, 63 | flex_direction: FlexDirection::Row, 64 | justify_content: JustifyContent::Center, 65 | align_items: AlignItems::Center, 66 | column_gap: Val::Px(5.0), 67 | border: UiRect::all(Val::Px(2.)), 68 | ..Default::default() 69 | }) 70 | .insert(Opacity::new_fade_in(10.)) 71 | .with_children(|build| { 72 | build 73 | .spawn(( 74 | Node { 75 | width: Val::Auto, 76 | height: Val::Auto, 77 | display: Display::Flex, 78 | flex_direction: FlexDirection::Row, 79 | justify_content: JustifyContent::Center, 80 | align_items: AlignItems::Center, 81 | column_gap: Val::Px(5.0), 82 | border: UiRect::all(Val::Px(5.)), 83 | padding: UiRect::all(Val::Px(10.)), 84 | ..Default::default() 85 | }, 86 | BorderColor::all(Color::WHITE), 87 | BorderRadius::all(Val::Px(20.)), 88 | UiOpacity::Border, 89 | )) 90 | .with_children(|build| { 91 | build.spawn((Text::new("Made with"), style.clone())); 92 | build.spawn(( 93 | ImageNode::new(assets.load("heart.png")), 94 | Node { 95 | width: Val::Auto, 96 | padding: UiRect::horizontal(Val::Px(5.0)), 97 | height: Val::Px(64.0), 98 | ..Default::default() 99 | }, 100 | )); 101 | build.spawn((Text::new("using"), style.clone())); 102 | build.spawn(( 103 | ImageNode::new(assets.load("ferris.png")), 104 | Node { 105 | width: Val::Auto, 106 | padding: UiRect::horizontal(Val::Px(5.0)), 107 | height: Val::Px(64.0), 108 | ..Default::default() 109 | }, 110 | )); 111 | build.spawn((Text::new("and"), style.clone())); 112 | build.spawn(( 113 | ImageNode::new(assets.load("bevy.png")), 114 | Node { 115 | width: Val::Auto, 116 | padding: UiRect::horizontal(Val::Px(5.0)), 117 | height: Val::Px(64.0), 118 | ..Default::default() 119 | }, 120 | )); 121 | build.spawn((Text::new("!"), style.clone())); 122 | }); 123 | }); 124 | commands.spawn(( 125 | SceneRoot(assets.load("rings1.glb#Scene0")), 126 | Transform::from_translation(Vec3::new(1., 0., -1.)), 127 | { 128 | let mut op = Opacity::OPAQUE; 129 | op.fade_out(10.); 130 | op 131 | }, 132 | OnDelete, 133 | )); 134 | commands.spawn(( 135 | SceneRoot(assets.load("rings2.glb#Scene0")), 136 | Transform::from_translation(Vec3::new(-1., 0., 1.)), 137 | Opacity::new_fade_in(4.), 138 | OnDelete, 139 | )); 140 | } 141 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Literal, TokenTree}; 3 | use proc_macro_error::{abort, proc_macro_error}; 4 | use quote::quote; 5 | use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Type}; 6 | 7 | /// Declare a `Component` or `Asset` as affected by opacity. 8 | /// 9 | /// For more complicated behaviors, implement `OpacityQuery` manually. 10 | /// 11 | /// # Field Attributes 12 | /// 13 | /// * `#[opacity]` 14 | /// 15 | /// Makes `bevy_mod_opacity` set its value as alpha, 16 | /// valid on `f32` or bevy's color types. 17 | /// 18 | /// # Type Attributes 19 | /// 20 | /// * `#[opacity(asset)]` 21 | /// 22 | /// Register as an asset. 23 | /// 24 | /// * `#[opacity(extends = StandardMaterial)]` 25 | /// 26 | /// Registers `ExtendedMaterial` where `Base` is also `OpacityAsset`. 27 | /// 28 | /// * `#[opacity(masks = StandardMaterial)]` 29 | /// 30 | /// Registers `ExtendedMaterial` where `Base` is not affected by opacity. 31 | #[proc_macro_error] 32 | #[proc_macro_derive(Opacity, attributes(opacity))] 33 | pub fn opacity(tokens: TokenStream) -> TokenStream { 34 | let input = parse_macro_input!(tokens as DeriveInput); 35 | 36 | let mut asset = false; 37 | let mut extends = Vec::new(); 38 | let mut masks = Vec::new(); 39 | let mut fields = Vec::new(); 40 | let name = input.ident; 41 | 42 | let Data::Struct(s) = input.data else { 43 | abort!(name.span(), "Only supports struct.") 44 | }; 45 | match s.fields { 46 | syn::Fields::Named(fields_named) => { 47 | for field in fields_named.named { 48 | for attribute in field.attrs { 49 | if attribute.path().is_ident("opacity") { 50 | fields.push(TokenTree::Ident(field.ident.clone().unwrap())); 51 | } 52 | } 53 | } 54 | } 55 | syn::Fields::Unnamed(fields_unnamed) => { 56 | for (index, field) in fields_unnamed.unnamed.into_iter().enumerate() { 57 | for attribute in field.attrs { 58 | if attribute.path().is_ident("opacity") { 59 | fields.push(TokenTree::Literal(Literal::usize_unsuffixed(index))); 60 | } 61 | } 62 | } 63 | } 64 | syn::Fields::Unit => (), 65 | } 66 | 67 | for attribute in &input.attrs { 68 | if !attribute.path().is_ident("opacity") { 69 | continue; 70 | } 71 | #[allow(clippy::blocks_in_conditions)] 72 | if attribute 73 | .parse_nested_meta(|meta| { 74 | if meta.path.is_ident("asset") { 75 | asset = true; 76 | } else if meta.path.is_ident("extends") { 77 | extends.push(meta.value()?.parse::()?); 78 | } else if meta.path.is_ident("masks") { 79 | masks.push(meta.value()?.parse::()?); 80 | } else { 81 | abort!(meta.path.span(), "Expected 'asset', 'extends' or 'masks'."); 82 | } 83 | Ok(()) 84 | }) 85 | .is_err() 86 | { 87 | abort!(attribute.meta.span(), "Expected a type.") 88 | } 89 | } 90 | let crate0 = quote! {::bevy_mod_opacity}; 91 | if asset || !extends.is_empty() || !masks.is_empty() { 92 | let mut result = quote! {}; 93 | 94 | if asset { 95 | result.extend(quote! { 96 | const _: () = { 97 | impl #crate0::OpacityAsset for #name { 98 | fn apply_opacity( 99 | &mut self, 100 | opacity: f32, 101 | ) { 102 | #(#crate0::Alpha::set_alpha(&mut self.#fields, opacity);)* 103 | } 104 | } 105 | }; 106 | }); 107 | } 108 | 109 | for ty in extends { 110 | result.extend(quote! { 111 | const _: () = { 112 | impl #crate0::OpacityMaterialExtension<#ty> for #name { 113 | fn apply_opacity(a: &mut #ty, b: &mut Self, opacity: f32) { 114 | #crate0::OpacityAsset::apply_opacity(a, opacity); 115 | #(#crate0::Alpha::set_alpha(&mut b.#fields, opacity);)* 116 | } 117 | } 118 | }; 119 | }); 120 | } 121 | for ty in masks { 122 | result.extend(quote! { 123 | const _: () = { 124 | impl #crate0::OpacityMaterialExtension<#ty> for #name { 125 | fn apply_opacity(a: &mut #ty, b: &mut Self, opacity: f32) { 126 | #(#crate0::Alpha::set_alpha(&mut b.#fields, opacity);)* 127 | } 128 | } 129 | }; 130 | }); 131 | } 132 | result.into() 133 | } else { 134 | quote! { 135 | const _: () = { 136 | impl #crate0::OpacityQuery for &mut #name { 137 | type Cx = (); 138 | 139 | fn apply_opacity( 140 | this: &mut ::Item<'_, '_>, 141 | _: &mut (), 142 | opacity: f32, 143 | ) { 144 | #(#crate0::Alpha::set_alpha(&mut this.#fields, opacity);)* 145 | } 146 | } 147 | }; 148 | } 149 | .into() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2024 Mincong Lu 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #[doc(hidden)] 3 | pub use bevy::asset::{Assets, Handle}; 4 | use bevy::camera::visibility::VisibilitySystems; 5 | #[doc(hidden)] 6 | pub use bevy::color::Alpha; 7 | #[doc(hidden)] 8 | pub use bevy::ecs::query::QueryData; 9 | 10 | use bevy::ecs::schedule::{ApplyDeferred, IntoScheduleConfigs}; 11 | use bevy::ecs::system::Commands; 12 | use bevy::time::{Time, Virtual}; 13 | use bevy::{ 14 | app::{App, Plugin, PostUpdate}, 15 | asset::Asset, 16 | ecs::{ 17 | entity::EntityHashMap, 18 | system::{StaticSystemParam, SystemParam}, 19 | }, 20 | prelude::{Children, Component, Entity, Query, Res, ResMut, Resource, SystemSet}, 21 | transform::systems::{propagate_parent_transforms, sync_simple_transforms}, 22 | }; 23 | use std::marker::PhantomData; 24 | 25 | #[cfg(feature = "derive")] 26 | pub use bevy_mod_opacity_derive::Opacity; 27 | 28 | #[cfg(feature = "reflect")] 29 | use bevy::prelude::{ReflectComponent, ReflectDefault}; 30 | 31 | #[cfg(feature = "3d")] 32 | mod pbr; 33 | #[cfg(feature = "2d")] 34 | mod sprite; 35 | #[cfg(feature = "ui")] 36 | mod ui; 37 | #[cfg(feature = "3d")] 38 | pub use pbr::OpacityMaterialExtension; 39 | #[cfg(feature = "ui")] 40 | pub use ui::UiOpacity; 41 | 42 | /// [`Component`] of opacity of this entity and its children. 43 | #[derive(Debug, Clone, Copy, Component, PartialEq, PartialOrd)] 44 | #[cfg_attr(feature = "reflect", derive(bevy::prelude::Reflect))] 45 | #[cfg_attr(feature = "reflect", reflect(Component, Default))] 46 | pub struct Opacity { 47 | current: f32, 48 | target: f32, 49 | speed: f32, 50 | despawns: bool, 51 | } 52 | 53 | impl Opacity { 54 | /// Opacity `0.0`. 55 | pub const INVISIBLE: Opacity = Opacity::new(0.); 56 | /// Opacity `1.0`. 57 | pub const OPAQUE: Opacity = Opacity::new(1.); 58 | 59 | /// Creates a new opacity value. 60 | pub const fn new(opacity: f32) -> Opacity { 61 | Opacity { 62 | current: opacity, 63 | target: opacity, 64 | speed: 0.0, 65 | despawns: false, 66 | } 67 | } 68 | 69 | /// Returns the current opacity value. 70 | pub const fn get(&self) -> f32 { 71 | self.current 72 | } 73 | 74 | /// Returns the target opacity value. 75 | pub const fn get_target(&self) -> f32 { 76 | self.target 77 | } 78 | 79 | /// Set the opacity value and cancels interpolation or fade out. 80 | pub fn set(&mut self, opacity: f32) { 81 | *self = Self::new(opacity) 82 | } 83 | 84 | /// Returns true if opacity is greater than or equal to `1.0`. 85 | pub const fn is_opaque(&self) -> bool { 86 | self.current >= 1.0 87 | } 88 | 89 | /// Returns true if opacity is greater than to `0.0`. 90 | pub const fn is_visible(&self) -> bool { 91 | self.current > 0.0 92 | } 93 | 94 | /// Returns true if opacity is less than or equal to `0.0`. 95 | pub const fn is_invisible(&self) -> bool { 96 | self.current <= 0.0 97 | } 98 | 99 | /// Returns true if is despawning, only when `fade_out` was called but not completed. 100 | pub const fn is_despawning(&self) -> bool { 101 | self.despawns 102 | } 103 | 104 | /// Set opacity to `0.0` and interpolate to `1.0`. 105 | pub const fn new_fade_in(seconds: f32) -> Opacity { 106 | Opacity { 107 | current: 0.0, 108 | target: 1.0, 109 | speed: 1.0 / seconds, 110 | despawns: false, 111 | } 112 | } 113 | 114 | /// Interpolate to `1.0`. 115 | pub const fn and_fade_in(mut self, seconds: f32) -> Self { 116 | self.target = 1.0; 117 | self.speed = 1.0 / seconds; 118 | self.despawns = false; 119 | self 120 | } 121 | 122 | /// Interpolate opacity to `1.0`. 123 | pub fn fade_in(&mut self, seconds: f32) { 124 | self.target = 1.0; 125 | self.despawns = false; 126 | self.speed = 1.0 / seconds; 127 | } 128 | 129 | /// Interpolate opacity to `0.0` and despawns the entity when that happens. 130 | /// 131 | /// Deletion can be stopped by calling `set`, `fade_in` or `interpolate_to` before fade out completed. 132 | /// If deletion is not desired, call `interpolate_to` with opacity `0.0` instead. 133 | pub fn fade_out(&mut self, seconds: f32) { 134 | self.target = 0.0; 135 | self.despawns = true; 136 | self.speed = -1.0 / seconds; 137 | } 138 | 139 | /// Interpolate opacity to a specific value. 140 | pub fn interpolate_to(&mut self, opacity: f32, seconds: f32) { 141 | self.target = opacity; 142 | self.despawns = false; 143 | self.speed = (opacity - self.current) / seconds; 144 | } 145 | 146 | /// Interpolate opacity to a specific value. 147 | pub fn interpolate_by_speed(&mut self, opacity: f32, seconds_zero_to_one: f32) { 148 | self.target = opacity; 149 | self.despawns = false; 150 | self.speed = (opacity - self.current).signum() / seconds_zero_to_one; 151 | } 152 | } 153 | 154 | /// # Why default `1.0` 155 | /// 156 | /// It's better to show something by default than hide it implicitly. 157 | impl Default for Opacity { 158 | fn default() -> Self { 159 | Self::OPAQUE 160 | } 161 | } 162 | 163 | #[cfg(feature = "serde")] 164 | const _: () = { 165 | use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; 166 | 167 | impl Serialize for Opacity { 168 | fn serialize(&self, serializer: S) -> Result { 169 | self.target.serialize(serializer) 170 | } 171 | } 172 | 173 | impl<'de> Deserialize<'de> for Opacity { 174 | fn deserialize>(deserializer: D) -> Result { 175 | Ok(Opacity::new(f32::deserialize(deserializer)?)) 176 | } 177 | } 178 | }; 179 | 180 | /// A map of entity to opacity, if not present, the entity does not have an opacity root node. 181 | /// This means the entity is out of the scope of this crate and should not be handled. 182 | #[derive(Debug, Resource, Default)] 183 | pub struct OpacityMap(EntityHashMap); 184 | 185 | /// [`SystemSet`] of opacity, 186 | /// runs in [`PostUpdate`] between transform propagation and visibility calculation. 187 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SystemSet)] 188 | pub enum OpacitySet { 189 | Fading, 190 | PostFade, 191 | Calculate, 192 | Apply, 193 | } 194 | 195 | /// A [`QueryData`] with an opacity value. 196 | pub trait OpacityQuery: QueryData + Send + Sync { 197 | type Cx: SystemParam; 198 | 199 | fn apply_opacity( 200 | this: &mut Self::Item<'_, '_>, 201 | cx: &mut ::Item<'_, '_>, 202 | opacity: f32, 203 | ); 204 | } 205 | 206 | /// An [`Asset`] with an opacity value. 207 | pub trait OpacityAsset: Asset { 208 | fn apply_opacity(&mut self, opacity: f32); 209 | } 210 | 211 | fn interpolate( 212 | mut commands: Commands, 213 | time: Res>, 214 | mut query: Query<(Entity, &mut Opacity)>, 215 | ) { 216 | let dt = time.delta_secs(); 217 | for (entity, mut opacity) in &mut query { 218 | match opacity.speed { 219 | 0.0 => continue, 220 | s if s > 0.0 => { 221 | opacity.current += opacity.speed * dt; 222 | if opacity.current > opacity.target { 223 | opacity.current = opacity.target; 224 | opacity.speed = 0.0; 225 | } 226 | } 227 | _ => { 228 | opacity.current += opacity.speed * dt; 229 | if opacity.current < opacity.target { 230 | opacity.current = opacity.target; 231 | opacity.speed = 0.0; 232 | } 233 | } 234 | } 235 | if opacity.despawns && opacity.current <= 0.0 { 236 | commands.entity(entity).try_despawn(); 237 | } 238 | } 239 | } 240 | 241 | fn calculate_opacity( 242 | mut map: ResMut, 243 | query: Query<(Entity, &Opacity)>, 244 | children: Query<&Children>, 245 | ) { 246 | map.0.clear(); 247 | let mut stack = Vec::new(); 248 | for (entity, opacity) in &query { 249 | if map.0.contains_key(&entity) { 250 | continue; 251 | } 252 | stack.push((entity, opacity.get())); 253 | while let Some((entity, opacity)) = stack.pop() { 254 | map.0.insert(entity, opacity); 255 | if let Ok(children) = children.get(entity) { 256 | for entity in children.iter().copied() { 257 | let op = query.get(entity).map(|(_, x)| x.get()).unwrap_or(1.); 258 | stack.push((entity, opacity * op)); 259 | } 260 | } 261 | } 262 | } 263 | } 264 | 265 | /// Add support for writing opacity to a [`QueryData`]. 266 | #[derive(Debug)] 267 | pub(crate) struct OpacityQueryPlugin(PhantomData); 268 | 269 | impl Plugin for OpacityQueryPlugin { 270 | fn build(&self, app: &mut App) { 271 | app.add_systems( 272 | PostUpdate, 273 | apply_opacity_query::.in_set(OpacitySet::Apply), 274 | ); 275 | } 276 | } 277 | 278 | fn apply_opacity_query( 279 | map: Res, 280 | cx: StaticSystemParam, 281 | mut query: Query<(Entity, Q)>, 282 | ) { 283 | let mut cx = cx.into_inner(); 284 | for (entity, mut component) in &mut query { 285 | if let Some(opacity) = map.0.get(&entity) { 286 | Q::apply_opacity(&mut component, &mut cx, *opacity); 287 | } 288 | } 289 | } 290 | 291 | /// Plugin for [`bevy_mod_opacity`](crate) that adds support for basic bevy types. 292 | pub struct OpacityPlugin; 293 | 294 | /// Extensions for [`App`]. 295 | pub trait OpacityExtension { 296 | fn register_opacity(&mut self) -> &mut Self; 297 | fn register_opacity_component(&mut self) -> &mut Self 298 | where 299 | &'static mut C: OpacityQuery; 300 | #[cfg(feature = "2d")] 301 | fn register_opacity_material2d( 302 | &mut self, 303 | ) -> &mut Self; 304 | #[cfg(feature = "3d")] 305 | fn register_opacity_material3d(&mut self) -> &mut Self; 306 | } 307 | 308 | impl OpacityExtension for App { 309 | fn register_opacity(&mut self) -> &mut Self { 310 | self.add_plugins(OpacityQueryPlugin::(PhantomData)); 311 | self 312 | } 313 | 314 | fn register_opacity_component(&mut self) -> &mut Self 315 | where 316 | &'static mut C: OpacityQuery, 317 | { 318 | self.add_plugins(OpacityQueryPlugin::<&mut C>(PhantomData)); 319 | self 320 | } 321 | 322 | #[cfg(feature = "2d")] 323 | fn register_opacity_material2d( 324 | &mut self, 325 | ) -> &mut Self { 326 | self.add_plugins( 327 | OpacityQueryPlugin::<&bevy::sprite_render::MeshMaterial2d>(PhantomData), 328 | ); 329 | self 330 | } 331 | 332 | #[cfg(feature = "3d")] 333 | fn register_opacity_material3d(&mut self) -> &mut Self { 334 | self.add_plugins(OpacityQueryPlugin::<&bevy::pbr::MeshMaterial3d>( 335 | PhantomData, 336 | )); 337 | self 338 | } 339 | } 340 | 341 | #[cfg(any(feature = "2d", feature = "ui"))] 342 | impl OpacityQuery for &mut bevy::text::TextColor { 343 | type Cx = (); 344 | 345 | fn apply_opacity(this: &mut Self::Item<'_, '_>, _: &mut (), opacity: f32) { 346 | this.set_alpha(opacity); 347 | } 348 | } 349 | 350 | impl Plugin for OpacityPlugin { 351 | fn build(&self, app: &mut App) { 352 | use OpacitySet::*; 353 | app.init_resource::(); 354 | app.configure_sets( 355 | PostUpdate, 356 | (Fading, PostFade, Calculate, Apply) 357 | .chain() 358 | .after(propagate_parent_transforms) 359 | .after(sync_simple_transforms) 360 | .before(VisibilitySystems::CheckVisibility) 361 | .before(VisibilitySystems::UpdateFrusta), 362 | ); 363 | app.add_systems(PostUpdate, interpolate.in_set(Fading)); 364 | app.add_systems(PostUpdate, ApplyDeferred.in_set(PostFade)); 365 | app.add_systems(PostUpdate, calculate_opacity.in_set(Calculate)); 366 | #[cfg(any(feature = "2d", feature = "ui"))] 367 | app.register_opacity_component::(); 368 | #[cfg(feature = "2d")] 369 | sprite::opacity_plugin_2d(app); 370 | #[cfg(feature = "3d")] 371 | pbr::opacity_plugin_3d(app); 372 | #[cfg(feature = "ui")] 373 | ui::opacity_plugin_ui(app); 374 | } 375 | } 376 | --------------------------------------------------------------------------------