use std::{marker::PhantomData, sync::Mutex};
use bevy_app::{Plugin, Update};
use bevy_asset::Asset;
use bevy_ecs::{
component::Tick, prelude::*, query::ReadOnlyWorldQuery, schedule::BoxedCondition,
system::ReadOnlySystem, world::unsafe_world_cell::UnsafeWorldCell,
};
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) {
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 = system.run_if(BoxedConditionHelper(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::vertical().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) {
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 = system.run_if(BoxedConditionHelper(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::vertical().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) {
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 = system.run_if(BoxedConditionHelper(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::vertical().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) {
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 = system.run_if(BoxedConditionHelper(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::vertical().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: ReadOnlyWorldQuery,
{
fn build(&self, app: &mut bevy_app::App) {
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 = system.run_if(BoxedConditionHelper(condition));
}
app.add_systems(Update, system);
}
}
fn entity_query_ui<F: ReadOnlyWorldQuery>(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::vertical().show(ui, |ui| {
bevy_inspector::ui_for_world_entities_filtered::<F>(world, ui, false);
ui.allocate_space(ui.available_size());
});
});
}
struct BoxedConditionHelper(BoxedCondition);
unsafe impl ReadOnlySystem for BoxedConditionHelper {}
impl System for BoxedConditionHelper {
type In = ();
type Out = bool;
fn name(&self) -> std::borrow::Cow<'static, str> {
self.0.name()
}
fn type_id(&self) -> std::any::TypeId {
self.0.type_id()
}
fn component_access(&self) -> &bevy_ecs::query::Access<bevy_ecs::component::ComponentId> {
self.0.component_access()
}
fn archetype_component_access(
&self,
) -> &bevy_ecs::query::Access<bevy_ecs::archetype::ArchetypeComponentId> {
self.0.archetype_component_access()
}
fn is_send(&self) -> bool {
self.0.is_send()
}
fn is_exclusive(&self) -> bool {
self.0.is_exclusive()
}
unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
unsafe { self.0.run_unsafe(input, world) }
}
fn apply_deferred(&mut self, world: &mut World) {
self.0.apply_deferred(world)
}
fn initialize(&mut self, _world: &mut World) {
self.0.initialize(_world)
}
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
self.0.update_archetype_component_access(world)
}
fn check_change_tick(&mut self, change_tick: Tick) {
self.0.check_change_tick(change_tick)
}
fn get_last_run(&self) -> Tick {
self.0.get_last_run()
}
fn set_last_run(&mut self, last_run: Tick) {
self.0.set_last_run(last_run)
}
}