use std::{marker::PhantomData, sync::Mutex};
use bevy_app::{Plugin, Update};
use bevy_asset::Asset;
use bevy_core::TypeRegistrationPlugin;
use bevy_ecs::{prelude::*, query::QueryFilter, schedule::BoxedCondition};
use bevy_egui::{EguiContext, EguiPlugin};
use bevy_reflect::Reflect;
use bevy_window::PrimaryWindow;
use pretty_type_name::pretty_type_name;
use crate::{bevy_inspector, DefaultInspectorConfigPlugin};
const DEFAULT_SIZE: (f32, f32) = (320., 160.);
#[derive(Default)]
pub struct WorldInspectorPlugin {
condition: Mutex<Option<BoxedCondition>>,
}
impl WorldInspectorPlugin {
pub fn new() -> Self {
Self::default()
}
pub fn run_if<M>(mut self, condition: impl Condition<M>) -> Self {
let condition_system = IntoSystem::into_system(condition);
self.condition = Mutex::new(Some(Box::new(condition_system) as BoxedCondition));
self
}
}
impl Plugin for WorldInspectorPlugin {
fn build(&self, app: &mut bevy_app::App) {
check_default_plugins(app, "WorldInspectorPlugin");
if !app.is_plugin_added::<DefaultInspectorConfigPlugin>() {
app.add_plugins(DefaultInspectorConfigPlugin);
}
if !app.is_plugin_added::<EguiPlugin>() {
app.add_plugins(EguiPlugin);
}
let condition = self.condition.lock().unwrap().take();
let mut system = world_inspector_ui.into_configs();
if let Some(condition) = condition {
system.run_if_dyn(condition);
}
app.add_systems(Update, system);
}
}
fn world_inspector_ui(world: &mut World) {
let egui_context = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world);
let Ok(egui_context) = egui_context else {
return;
};
let mut egui_context = egui_context.clone();
egui::Window::new("World Inspector")
.default_size(DEFAULT_SIZE)
.show(egui_context.get_mut(), |ui| {
egui::ScrollArea::both().show(ui, |ui| {
bevy_inspector::ui_for_world(world, ui);
ui.allocate_space(ui.available_size());
});
});
}
pub struct ResourceInspectorPlugin<T> {
condition: Mutex<Option<BoxedCondition>>,
marker: PhantomData<fn() -> T>,
}
impl<T> Default for ResourceInspectorPlugin<T> {
fn default() -> Self {
Self {
marker: PhantomData,
condition: Mutex::new(None),
}
}
}
impl<T> ResourceInspectorPlugin<T> {
pub fn new() -> Self {
Self::default()
}
pub fn run_if<M>(mut self, condition: impl Condition<M>) -> Self {
let condition_system = IntoSystem::into_system(condition);
self.condition = Mutex::new(Some(Box::new(condition_system) as BoxedCondition));
self
}
}
impl<T: Resource + Reflect> Plugin for ResourceInspectorPlugin<T> {
fn build(&self, app: &mut bevy_app::App) {
check_default_plugins(app, "ResourceInspectorPlugin");
if !app.is_plugin_added::<DefaultInspectorConfigPlugin>() {
app.add_plugins(DefaultInspectorConfigPlugin);
}
if !app.is_plugin_added::<EguiPlugin>() {
app.add_plugins(EguiPlugin);
}
let condition = self.condition.lock().unwrap().take();
let mut system = inspector_ui::<T>.into_configs();
if let Some(condition) = condition {
system.run_if_dyn(condition);
}
app.add_systems(Update, system);
}
}
fn inspector_ui<T: Resource + Reflect>(world: &mut World) {
let egui_context = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world);
let Ok(egui_context) = egui_context else {
return;
};
let mut egui_context = egui_context.clone();
egui::Window::new(pretty_type_name::<T>())
.default_size((0., 0.))
.show(egui_context.get_mut(), |ui| {
egui::ScrollArea::both().show(ui, |ui| {
bevy_inspector::ui_for_resource::<T>(world, ui);
ui.allocate_space(ui.available_size());
});
});
}
pub struct StateInspectorPlugin<T> {
condition: Mutex<Option<BoxedCondition>>,
marker: PhantomData<fn() -> T>,
}
impl<T> Default for StateInspectorPlugin<T> {
fn default() -> Self {
StateInspectorPlugin {
condition: Mutex::new(None),
marker: PhantomData,
}
}
}
impl<T> StateInspectorPlugin<T> {
pub fn new() -> Self {
Self::default()
}
pub fn run_if<M>(mut self, condition: impl Condition<M>) -> Self {
let condition_system = IntoSystem::into_system(condition);
self.condition = Mutex::new(Some(Box::new(condition_system) as BoxedCondition));
self
}
}
impl<T: States + Reflect> Plugin for StateInspectorPlugin<T> {
fn build(&self, app: &mut bevy_app::App) {
check_default_plugins(app, "StateInspectorPlugin");
if !app.is_plugin_added::<DefaultInspectorConfigPlugin>() {
app.add_plugins(DefaultInspectorConfigPlugin);
}
if !app.is_plugin_added::<EguiPlugin>() {
app.add_plugins(EguiPlugin);
}
let condition = self.condition.lock().unwrap().take();
let mut system = state_ui::<T>.into_configs();
if let Some(condition) = condition {
system.run_if_dyn(condition);
}
app.add_systems(Update, system);
}
}
fn state_ui<T: States + Reflect>(world: &mut World) {
let egui_context = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world);
let Ok(egui_context) = egui_context else {
return;
};
let mut egui_context = egui_context.clone();
egui::Window::new(std::any::type_name::<T>())
.resizable(false)
.title_bar(false)
.show(egui_context.get_mut(), |ui| {
egui::ScrollArea::both().show(ui, |ui| {
ui.heading(pretty_type_name::<T>());
bevy_inspector::ui_for_state::<T>(world, ui);
});
});
}
pub struct AssetInspectorPlugin<A> {
condition: Mutex<Option<BoxedCondition>>,
marker: PhantomData<fn() -> A>,
}
impl<A> Default for AssetInspectorPlugin<A> {
fn default() -> Self {
Self {
condition: Mutex::new(None),
marker: PhantomData,
}
}
}
impl<A> AssetInspectorPlugin<A> {
pub fn new() -> Self {
Self::default()
}
pub fn run_if<M>(mut self, condition: impl Condition<M>) -> Self {
let condition_system = IntoSystem::into_system(condition);
self.condition = Mutex::new(Some(Box::new(condition_system) as BoxedCondition));
self
}
}
impl<A: Asset + Reflect> Plugin for AssetInspectorPlugin<A> {
fn build(&self, app: &mut bevy_app::App) {
check_default_plugins(app, "AssetInspectorPlugin");
if !app.is_plugin_added::<DefaultInspectorConfigPlugin>() {
app.add_plugins(DefaultInspectorConfigPlugin);
}
if !app.is_plugin_added::<EguiPlugin>() {
app.add_plugins(EguiPlugin);
}
let condition = self.condition.lock().unwrap().take();
let mut system = asset_inspector_ui::<A>.into_configs();
if let Some(condition) = condition {
system.run_if_dyn(condition);
}
app.add_systems(Update, system);
}
}
fn asset_inspector_ui<A: Asset + Reflect>(world: &mut World) {
let egui_context = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world);
let Ok(egui_context) = egui_context else {
return;
};
let mut egui_context = egui_context.clone();
egui::Window::new(pretty_type_name::<A>())
.default_size(DEFAULT_SIZE)
.show(egui_context.get_mut(), |ui| {
egui::ScrollArea::both().show(ui, |ui| {
bevy_inspector::ui_for_assets::<A>(world, ui);
ui.allocate_space(ui.available_size());
});
});
}
pub struct FilterQueryInspectorPlugin<F> {
condition: Mutex<Option<BoxedCondition>>,
marker: PhantomData<fn() -> F>,
}
impl<F> Default for FilterQueryInspectorPlugin<F> {
fn default() -> Self {
Self {
condition: Mutex::new(None),
marker: PhantomData,
}
}
}
impl<A> FilterQueryInspectorPlugin<A> {
pub fn new() -> Self {
Self::default()
}
pub fn run_if<M>(mut self, condition: impl Condition<M>) -> Self {
let condition_system = IntoSystem::into_system(condition);
self.condition = Mutex::new(Some(Box::new(condition_system) as BoxedCondition));
self
}
}
impl<F: 'static> Plugin for FilterQueryInspectorPlugin<F>
where
F: QueryFilter,
{
fn build(&self, app: &mut bevy_app::App) {
check_default_plugins(app, "FilterQueryInspectorPlugin");
if !app.is_plugin_added::<DefaultInspectorConfigPlugin>() {
app.add_plugins(DefaultInspectorConfigPlugin);
}
if !app.is_plugin_added::<EguiPlugin>() {
app.add_plugins(EguiPlugin);
}
let condition: Option<Box<dyn ReadOnlySystem<In = (), Out = bool>>> =
self.condition.lock().unwrap().take();
let mut system = entity_query_ui::<F>.into_configs();
if let Some(condition) = condition {
system.run_if_dyn(condition);
}
app.add_systems(Update, system);
}
}
fn entity_query_ui<F: QueryFilter>(world: &mut World) {
let egui_context = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world);
let Ok(egui_context) = egui_context else {
return;
};
let mut egui_context = egui_context.clone();
egui::Window::new(pretty_type_name::<F>())
.default_size(DEFAULT_SIZE)
.show(egui_context.get_mut(), |ui| {
egui::ScrollArea::both().show(ui, |ui| {
bevy_inspector::ui_for_world_entities_filtered::<F>(world, ui, false);
ui.allocate_space(ui.available_size());
});
});
}
fn check_default_plugins(app: &bevy_app::App, name: &str) {
if !app.is_plugin_added::<TypeRegistrationPlugin>() {
panic!(
r#"`{name}` should be added after the default plugins:
.add_plugins(DefaultPlugins)
.add_plugins({name}::default())
"#,
name = name,
);
}
}