#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![deny(missing_docs)]
#[allow(unused_imports)]
use bevy_app::prelude::*;
use bevy_asset::{prelude::*, Asset};
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_picking_core::{focus::PickingInteraction, PickSet, PickingPluginsSettings};
#[cfg(feature = "selection")]
use bevy_picking_selection::PickSelection;
pub mod prelude {
pub use crate::{
DefaultHighlightingPlugin, GlobalHighlight, Highlight, HighlightKind, HighlightPlugin,
HighlightPluginSettings, PickHighlight,
};
}
#[derive(Resource, Debug, Reflect)]
#[reflect(Resource, Default)]
pub struct HighlightPluginSettings {
pub is_enabled: bool,
}
impl HighlightPluginSettings {
pub fn should_run(settings: Res<Self>, main_settings: Res<PickingPluginsSettings>) -> bool {
settings.is_enabled && main_settings.is_enabled
}
}
impl Default for HighlightPluginSettings {
fn default() -> Self {
Self { is_enabled: true }
}
}
#[derive(Component, Clone, Debug, Default, Reflect)]
#[reflect(Component, Default)]
pub struct PickHighlight;
pub struct DefaultHighlightingPlugin;
impl Plugin for DefaultHighlightingPlugin {
#[allow(unused_variables)]
fn build(&self, app: &mut App) {
app.init_resource::<HighlightPluginSettings>()
.register_type::<PickHighlight>()
.register_type::<HighlightPluginSettings>();
#[cfg(feature = "pbr")]
app.add_plugins(HighlightPlugin::<bevy_pbr::StandardMaterial> {
highlighting_default: |mut assets| GlobalHighlight {
hovered: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.35)),
pressed: assets.add(bevy_render::color::Color::rgb(0.35, 0.75, 0.35)),
#[cfg(feature = "selection")]
selected: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.75)),
},
});
#[cfg(feature = "sprite")]
app.add_plugins(HighlightPlugin::<bevy_sprite::ColorMaterial> {
highlighting_default: |mut assets| GlobalHighlight {
hovered: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.35)),
pressed: assets.add(bevy_render::color::Color::rgb(0.35, 0.75, 0.35)),
#[cfg(feature = "selection")]
selected: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.75)),
},
});
}
}
pub struct HighlightPlugin<T: 'static + Asset + Sync + Send> {
pub highlighting_default: fn(ResMut<Assets<T>>) -> GlobalHighlight<T>,
}
impl<T> Plugin for HighlightPlugin<T>
where
T: 'static + Asset + Sync + Send,
{
fn build(&self, app: &mut App) {
let highlighting_default = self.highlighting_default;
app.add_systems(
Startup,
move |mut commands: Commands, assets: ResMut<Assets<T>>| {
commands.insert_resource(highlighting_default(assets));
},
)
.add_systems(
PreUpdate,
(
get_initial_highlight_asset::<T>,
Highlight::<T>::update_dynamic,
update_highlight_assets::<T>,
#[cfg(feature = "selection")]
update_selection::<T>,
)
.chain()
.in_set(PickSet::Last)
.distributive_run_if(HighlightPluginSettings::should_run),
)
.register_type::<InitialHighlight<T>>()
.register_type::<GlobalHighlight<T>>()
.register_type::<Highlight<T>>();
}
}
#[derive(Component, Clone, Debug, Reflect)]
pub struct InitialHighlight<T: Asset> {
pub initial: Handle<T>,
}
#[derive(Resource, Reflect)]
pub struct GlobalHighlight<T: Asset> {
pub hovered: Handle<T>,
pub pressed: Handle<T>,
#[cfg(feature = "selection")]
pub selected: Handle<T>,
}
impl<T: Asset> GlobalHighlight<T> {
pub fn hovered(&self, h_override: &Option<&Highlight<T>>) -> Handle<T> {
h_override
.and_then(|h| h.hovered.as_ref())
.and_then(|h| h.get_handle())
.unwrap_or_else(|| self.hovered.clone())
}
pub fn pressed(&self, h_override: &Option<&Highlight<T>>) -> Handle<T> {
h_override
.and_then(|h| h.pressed.as_ref())
.and_then(|h| h.get_handle())
.unwrap_or_else(|| self.pressed.clone())
}
#[cfg(feature = "selection")]
pub fn selected(&self, h_override: &Option<&Highlight<T>>) -> Handle<T> {
h_override
.and_then(|h| h.selected.as_ref())
.and_then(|h| h.get_handle())
.unwrap_or_else(|| self.selected.clone())
}
}
#[derive(Clone)]
pub enum HighlightKind<T: Asset> {
Fixed(Handle<T>),
Dynamic {
function: fn(initial: &T) -> T,
cache: Option<Handle<T>>,
},
}
impl<T: Asset> HighlightKind<T> {
pub fn get_handle(&self) -> Option<Handle<T>> {
match self {
HighlightKind::Fixed(handle) => Some(handle.to_owned()),
HighlightKind::Dynamic { cache, .. } => cache.to_owned(),
}
}
pub fn get_dynamic(&mut self) -> Option<(&fn(initial: &T) -> T, &mut Option<Handle<T>>)> {
match self {
Self::Dynamic { function, cache } => Some((function, cache)),
_ => None,
}
}
pub const fn new_dynamic(function: fn(initial: &T) -> T) -> Self {
Self::Dynamic {
function,
cache: None,
}
}
}
impl<T: Asset> std::fmt::Debug for HighlightKind<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fixed(arg0) => f.debug_tuple("Fixed").field(arg0).finish(),
Self::Dynamic { cache, .. } => f.debug_struct("Dynamic").field("cache", cache).finish(),
}
}
}
#[derive(Component, Clone, Debug, Default, Reflect)]
#[reflect(from_reflect = false)]
pub struct Highlight<T: Asset> {
#[reflect(ignore)]
pub hovered: Option<HighlightKind<T>>,
#[reflect(ignore)]
pub pressed: Option<HighlightKind<T>>,
#[reflect(ignore)]
#[cfg(feature = "selection")]
pub selected: Option<HighlightKind<T>>,
}
impl<T: Asset> Highlight<T> {
fn update_dynamic(
mut asset_server: ResMut<Assets<T>>,
mut entities: Query<
(&mut Highlight<T>, &InitialHighlight<T>),
Changed<InitialHighlight<T>>,
>,
) {
for (mut highlight_override, highlight_initial) in entities.iter_mut() {
let Highlight {
hovered,
pressed,
#[cfg(feature = "selection")]
selected,
} = highlight_override.as_mut();
let mut h = hovered.as_mut().and_then(|h| h.get_dynamic());
let mut p = pressed.as_mut().and_then(|h| h.get_dynamic());
let iter = h.iter_mut().chain(p.iter_mut());
#[cfg(feature = "selection")]
let mut s = selected.as_mut().and_then(|h| h.get_dynamic());
#[cfg(feature = "selection")]
let iter = iter.chain(s.iter_mut());
for (function, cache) in iter {
if let Some(asset) = asset_server.get(&highlight_initial.initial).map(function) {
**cache = Some(asset_server.add(asset));
}
}
}
}
}
pub fn get_initial_highlight_asset<T: Asset>(
mut commands: Commands,
entity_asset_query: Query<(Entity, &Handle<T>), Added<PickHighlight>>,
mut highlighting_query: Query<Option<&mut InitialHighlight<T>>>,
) {
for (entity, material) in entity_asset_query.iter() {
match highlighting_query.get_mut(entity) {
Ok(Some(mut highlighting)) => material.clone_into(&mut highlighting.initial),
_ => {
commands.entity(entity).try_insert(InitialHighlight {
initial: material.to_owned(),
});
}
}
}
}
pub fn update_highlight_assets<T: Asset>(
global_defaults: Res<GlobalHighlight<T>>,
mut interaction_query: Query<
(
&mut Handle<T>,
&PickingInteraction,
&InitialHighlight<T>,
Option<&Highlight<T>>,
),
Changed<PickingInteraction>,
>,
) {
for (mut asset, interaction, init_highlight, h_override) in &mut interaction_query {
*asset = match interaction {
PickingInteraction::Pressed => global_defaults.pressed(&h_override),
PickingInteraction::Hovered => global_defaults.hovered(&h_override),
PickingInteraction::None => init_highlight.initial.to_owned(),
}
}
}
#[cfg(feature = "selection")]
pub fn update_selection<T: Asset>(
global_defaults: Res<GlobalHighlight<T>>,
mut interaction_query: Query<
(
&mut Handle<T>,
&PickingInteraction,
&PickSelection,
&InitialHighlight<T>,
Option<&Highlight<T>>,
),
Or<(Changed<PickSelection>, Changed<PickingInteraction>)>,
>,
) {
for (mut asset, interaction, selection, init_highlight, h_override) in &mut interaction_query {
if let PickingInteraction::None = interaction {
*asset = if selection.is_selected {
global_defaults.selected(&h_override)
} else {
init_highlight.initial.to_owned()
}
}
}
}