foxtrot 0.1.0

A 3D reference project and tech demo for the Bevy Engine.
//! Toggles for the different debug UIs that our plugins provide.

use super::input::{ForceFreeCursor, ToggleDebugUi};
use crate::RenderLayer;
use crate::{
    PostPhysicsAppSystems, gameplay::crosshair::cursor::IsCursorForceUnlocked, theme::widget,
};
use avian3d::prelude::*;
use bevy::render::view::RenderLayers;
use bevy::ui::Val::*;
use bevy::{
    dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin},
    prelude::*,
};
use bevy_enhanced_input::prelude::*;
use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
use bevy_landmass::debug::{EnableLandmassDebug, Landmass3dDebugPlugin, LandmassGizmoConfigGroup};
#[cfg(feature = "hot_patch")]
use bevy_simple_subsecond_system::hot;

pub(super) fn plugin(app: &mut App) {
    app.init_resource::<DebugState>();
    app.init_resource::<InspectorActive>();
    app.add_plugins(FpsOverlayPlugin {
        config: FpsOverlayConfig {
            enabled: false,
            ..default()
        },
    });
    app.add_plugins((
        EguiPlugin {
            enable_multipass_for_primary_context: true,
        },
        WorldInspectorPlugin::new().run_if(is_inspector_active),
    ));

    app.add_plugins((
        PhysicsDebugPlugin::default(),
        PhysicsDiagnosticsPlugin,
        PhysicsDiagnosticsUiPlugin,
        Landmass3dDebugPlugin {
            draw_on_start: false,
            ..default()
        },
    ));
    app.insert_resource(PhysicsDiagnosticsUiSettings {
        enabled: false,
        ..default()
    });
    app.insert_gizmo_config(
        PhysicsGizmos::default(),
        GizmoConfig {
            enabled: false,
            render_layers: RenderLayers::from(RenderLayer::GIZMO3),
            ..default()
        },
    );
    app.insert_gizmo_config(
        LightGizmoConfigGroup {
            draw_all: true,
            ..default()
        },
        GizmoConfig {
            enabled: false,
            render_layers: RenderLayers::from(RenderLayer::GIZMO3),
            ..default()
        },
    );
    app.insert_gizmo_config(
        LandmassGizmoConfigGroup,
        GizmoConfig {
            enabled: true,
            depth_bias: -0.03,
            render_layers: RenderLayers::from(RenderLayer::GIZMO3),
            ..default()
        },
    );
    app.add_observer(advance_debug_state);
    app.add_observer(toggle_egui_inspector);
    app.add_systems(Startup, setup_debug_ui_text);
    app.add_systems(
        Update,
        update_debug_ui_text.run_if(resource_exists_and_changed::<DebugState>),
    );
    app.add_systems(
        Update,
        (
            toggle_fps_overlay.run_if(toggled_state(DebugState::None)),
            toggle_debug_ui.run_if(toggled_state(DebugState::Ui)),
            toggle_lighting_debug_ui.run_if(toggled_state(DebugState::Lighting)),
            toggle_physics_debug_ui.run_if(toggled_state(DebugState::Physics)),
            toggle_landmass_debug_ui.run_if(toggled_state(DebugState::Landmass)),
        )
            .chain()
            .in_set(PostPhysicsAppSystems::ChangeUi),
    );
}

#[cfg_attr(feature = "hot_patch", hot)]
fn setup_debug_ui_text(mut commands: Commands) {
    commands.spawn((
        Name::new("Debug UI"),
        Node {
            position_type: PositionType::Absolute,
            width: Percent(100.0),
            height: Percent(100.0),
            justify_content: JustifyContent::End,
            ..default()
        },
        Pickable::IGNORE,
        children![(widget::label("Debug UI"), DebugUiText)],
    ));
}

#[derive(Component)]
struct DebugUiText;

#[cfg_attr(feature = "hot_patch", hot)]
fn advance_debug_state(
    _trigger: Trigger<Started<ToggleDebugUi>>,
    mut debug_state: ResMut<DebugState>,
) {
    *debug_state = debug_state.next();
}

#[cfg_attr(feature = "hot_patch", hot)]
fn update_debug_ui_text(
    debug_state: Res<DebugState>,
    mut text: Single<&mut Text, With<DebugUiText>>,
) {
    text.0 = match *debug_state {
        DebugState::None => "",
        DebugState::Ui => "Ui",
        DebugState::Lighting => "Lighting",
        DebugState::Physics => "Physics",
        DebugState::Landmass => "Landmass",
    }
    .to_string();
}

#[cfg_attr(feature = "hot_patch", hot)]
fn toggle_debug_ui(mut options: ResMut<UiDebugOptions>) {
    options.toggle();
}

#[cfg_attr(feature = "hot_patch", hot)]
fn toggle_physics_debug_ui(
    mut config_store: ResMut<GizmoConfigStore>,
    mut physics_diagnostics: ResMut<PhysicsDiagnosticsUiSettings>,
) {
    let config = config_store.config_mut::<PhysicsGizmos>().0;
    config.enabled = !config.enabled;
    physics_diagnostics.enabled = !physics_diagnostics.enabled;
}

#[cfg_attr(feature = "hot_patch", hot)]
fn toggle_lighting_debug_ui(mut config_store: ResMut<GizmoConfigStore>) {
    let config = config_store.config_mut::<LightGizmoConfigGroup>().0;
    config.enabled = !config.enabled;
}

#[cfg_attr(feature = "hot_patch", hot)]
fn toggle_landmass_debug_ui(mut debug: ResMut<EnableLandmassDebug>) {
    **debug = !**debug;
}

#[cfg_attr(feature = "hot_patch", hot)]
fn toggle_fps_overlay(mut config: ResMut<FpsOverlayConfig>) {
    config.enabled = !config.enabled;
}

#[cfg_attr(feature = "hot_patch", hot)]
fn toggle_egui_inspector(
    _trigger: Trigger<Started<ForceFreeCursor>>,
    mut is_cursor_forced_free: ResMut<IsCursorForceUnlocked>,
    mut inspector_active: ResMut<InspectorActive>,
) {
    is_cursor_forced_free.0 = !is_cursor_forced_free.0;
    inspector_active.0 = !inspector_active.0;
}

#[derive(Resource, Debug, Default, Eq, PartialEq)]
struct InspectorActive(bool);

fn is_inspector_active(inspector_active: Res<InspectorActive>) -> bool {
    inspector_active.0
}

#[derive(Resource, Debug, Default, Eq, PartialEq)]
enum DebugState {
    #[default]
    None,
    Ui,
    Lighting,
    Physics,
    Landmass,
}

impl DebugState {
    fn next(&self) -> Self {
        match self {
            Self::None => Self::Ui,
            Self::Ui => Self::Lighting,
            Self::Lighting => Self::Physics,
            Self::Physics => Self::Landmass,
            Self::Landmass => Self::None,
        }
    }
}

fn toggled_state(state: DebugState) -> impl Condition<()> {
    IntoSystem::into_system(move |current_state: Res<DebugState>| {
        let was_just_changed = current_state.is_changed() && !current_state.is_added();
        let entered_state = *current_state == state;
        let exited_state = *current_state == state.next();
        was_just_changed && (entered_state || exited_state)
    })
}