diff --git a/studio-frontend/src/app/help.element.ts b/studio-frontend/src/app/help.element.ts
index d7c207b..4cbef5c 100644
--- a/studio-frontend/src/app/help.element.ts
+++ b/studio-frontend/src/app/help.element.ts
@@ -1,7 +1,8 @@
-import { LitElement, html, unsafeCSS } from "lit";
+import { LitElement, html, unsafeCSS, nothing, HTMLTemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import styles from "./help.element.scss?inline";
+import { isDevEnvironment } from "./util";
@customElement("sts-help")
export class HelpElement extends LitElement {
@@ -9,68 +10,62 @@ export class HelpElement extends LitElement {
protected override render = () => html`
-
-
-
-
-
- | Action |
- Controls |
- Alternate |
-
-
-
-
- | Pan view |
- MMB |
-
-
- | Orbit around point |
- Alt + LMB |
-
-
- | Zoom |
- Alt + RMB |
- Mouse Wheel |
-
-
- | Free fly |
- W, A, S, D, E, Q |
-
-
- | Free look |
- RMB |
- Shift |
-
-
-
-
-
-
-
- | Action |
- Controls |
- Alternate |
-
-
-
-
- | Select |
- LMB |
-
-
- | Clear Selection |
- Esc |
-
-
- | Focus Selection |
- F |
-
-
+ ${section("Navigation", [
+ { name: "Pan View", controls: "MMB" },
+ { name: "Orbit around point", controls: "Alt + LMB" },
+ { name: "Zoom", controls: ["Alt + RMB", "Mouse Wheel"]},
+ { name: "Free fly", controls: "W, A, S, D, E, Q" },
+ { name: "Free look", controls: ["RMB", "Shift"] },
+ ])}
+ ${section("Interaction", [
+ { name: "Select", controls: "LMB" },
+ { name: "Clear selection", controls: "Esc" },
+ { name: "Focus selection", controls: "F" },
+ ])}
+ ${isDevEnvironment()
+ ? section("Developer", [
+ { name: "Toggle debug info", controls: html`Ctrl + `` }
+ ])
+ : nothing}
`;
}
+
+interface Action {
+ name: string | HTMLTemplateResult;
+ controls:
+ | string
+ | HTMLTemplateResult
+ | [
+ string | HTMLTemplateResult,
+ string | HTMLTemplateResult
+ ];
+}
+
+function section(title: string, actions: Action[]): HTMLTemplateResult {
+ return html`
+
+
+
+
+
+ | Action |
+ Controls |
+ Alternate |
+
+
+
+ ${actions.map(({ name, controls }) => html`
+
+ | ${name} |
+ ${Array.isArray(controls) ? html`
+ ${controls[0]} |
+ ${controls[1]} |
+ ` : html`
+ ${controls} |
+ `}
+
+ `)}
+
+ `
+}
diff --git a/studio-frontend/src/app/util.ts b/studio-frontend/src/app/util.ts
new file mode 100644
index 0000000..302c206
--- /dev/null
+++ b/studio-frontend/src/app/util.ts
@@ -0,0 +1,5 @@
+export function isDevEnvironment(): boolean {
+ return window.location.hostname === "localhost"
+ || window.location.hostname === "127.0.0.1"
+ || window.location.hostname.includes("deploy-preview");
+}
diff --git a/studio/src/debug.rs b/studio/src/debug.rs
new file mode 100644
index 0000000..567e201
--- /dev/null
+++ b/studio/src/debug.rs
@@ -0,0 +1,83 @@
+use bevy::{prelude::*, window::PrimaryWindow};
+use bevy_inspector_egui::quick::WorldInspectorPlugin;
+use bevy_mod_picking::debug::DebugPickingMode;
+
+use crate::{
+ camera_controller::{CameraController, CameraMotionSystem},
+ input::{Action, InputAction, Lifecycle},
+};
+
+#[derive(Resource, Deref, DerefMut)]
+pub struct DebugMode(bool);
+
+pub struct DebugPlugin;
+impl Plugin for DebugPlugin {
+ fn build(&self, app: &mut App) {
+ app.insert_resource(DebugMode(false))
+ .add_plugins(WorldInspectorPlugin::new().run_if(is_debug_mode))
+ .insert_resource(State::new(DebugPickingMode::Disabled))
+ .insert_resource(GizmoConfig {
+ enabled: true,
+ depth_bias: -0.1,
+ line_perspective: false,
+ line_width: 2.,
+ ..default()
+ })
+ .add_systems(
+ Update,
+ (
+ toggle_debug_mode,
+ update_debug_picking_mode.run_if(resource_changed::()),
+ update_gizmo_line_thickness,
+ draw_world_axes
+ .after(CameraMotionSystem)
+ .run_if(is_debug_mode),
+ ),
+ );
+ }
+}
+
+fn toggle_debug_mode(mut input: EventReader, mut r_debug: ResMut) {
+ if input
+ .iter()
+ .any(|action| matches!(action, InputAction(Lifecycle::Stop, Action::ToggleDebug)))
+ {
+ **r_debug = !**r_debug;
+ }
+}
+
+fn update_debug_picking_mode(
+ r_debug: Res,
+ mut s_debug_picking: ResMut>,
+) {
+ s_debug_picking.set(match **r_debug {
+ true => DebugPickingMode::Normal,
+ false => DebugPickingMode::Disabled,
+ });
+}
+
+pub fn is_debug_mode(r_debug: Res) -> bool {
+ **r_debug
+}
+
+fn update_gizmo_line_thickness(
+ mut config: ResMut,
+ q_window: Query<&Window, (Changed, With)>,
+) {
+ if let Ok(scale_factor) = q_window.get_single().map(|win| win.scale_factor() as f32) {
+ config.line_width = 2. * scale_factor;
+ }
+}
+
+fn draw_world_axes(mut gizmos: Gizmos, q: Query<&CameraController>) {
+ let &CameraController { focus, radius, .. } = q.single();
+
+ let mut draw_axis = |direction: Vec3, color: Color| {
+ let end = focus + direction * radius / 15.;
+ gizmos.line(focus, end, color);
+ };
+
+ draw_axis(Vec3::X, Color::RED);
+ draw_axis(Vec3::Y, Color::GREEN);
+ draw_axis(Vec3::Z, Color::BLUE);
+}
diff --git a/studio/src/input.rs b/studio/src/input.rs
index bd404af..00f31b8 100644
--- a/studio/src/input.rs
+++ b/studio/src/input.rs
@@ -15,6 +15,7 @@ impl Plugin for StudioInputPlugin {
PreUpdate,
(
translate_selection_events,
+ translate_debug_toggle,
translate_dcc_camera_events,
translate_fps_camera_events,
)
@@ -38,6 +39,7 @@ pub enum Lifecycle {
pub enum Action {
Camera(CameraMotion),
Selection(Selection),
+ ToggleDebug,
}
#[derive(Clone, Copy)]
@@ -83,6 +85,16 @@ fn translate_selection_events(keyboard: Res>, mut input: EventWri
}
}
+fn translate_debug_toggle(keyboard: Res>, mut input: EventWriter) {
+ if keyboard.pressed(KeyCode::ControlLeft) || keyboard.pressed(KeyCode::ControlRight) {
+ if keyboard.just_pressed(KeyCode::Grave) {
+ input.send(InputAction(Lifecycle::Start, Action::ToggleDebug));
+ } else if keyboard.just_released(KeyCode::Grave) {
+ input.send(InputAction(Lifecycle::Stop, Action::ToggleDebug));
+ }
+ }
+}
+
fn translate_dcc_camera_events(
mouse_motion: EventReader,
mouse_buttons: Res>,
diff --git a/studio/src/interaction.rs b/studio/src/interaction.rs
index 73ad115..1b08a45 100644
--- a/studio/src/interaction.rs
+++ b/studio/src/interaction.rs
@@ -6,6 +6,7 @@ use bevy_mod_picking::{prelude::Pointer, selection::Select};
use crate::{
camera_controller::CameraMotionSystem,
+ debug,
input::{self, Action, InputAction, Lifecycle},
};
@@ -29,6 +30,7 @@ impl Plugin for InteractionPlugin {
draw_selection_outline.run_if(resource_changed_or_removed::()),
draw_selection_axes
.run_if(resource_exists::())
+ .run_if(debug::is_debug_mode)
.after(CameraMotionSystem),
),
);
diff --git a/studio/src/lib.rs b/studio/src/lib.rs
index 9d8f3cd..24d92b9 100644
--- a/studio/src/lib.rs
+++ b/studio/src/lib.rs
@@ -1,9 +1,9 @@
#![allow(clippy::type_complexity, clippy::too_many_arguments)]
-use bevy::{prelude::*, window::PrimaryWindow};
-use bevy_inspector_egui::quick::WorldInspectorPlugin;
+use bevy::prelude::*;
use bevy_mod_picking::DefaultPickingPlugins;
-use camera_controller::{CameraController, CameraControllerPlugin, CameraMotionSystem};
+use camera_controller::CameraControllerPlugin;
+use debug::DebugPlugin;
use input::StudioInputPlugin;
use interaction::InteractionPlugin;
use math::InterpolationPlugin;
@@ -14,6 +14,7 @@ use viewport::ViewportSize;
use wasm_bindgen::prelude::*;
mod camera_controller;
+mod debug;
mod input;
mod interaction;
mod math;
@@ -42,7 +43,7 @@ pub fn init(viewport_size: ViewportSize) {
..default()
}),
DefaultPickingPlugins,
- WorldInspectorPlugin::new(),
+ DebugPlugin,
InteractionPlugin,
StudioInputPlugin,
InterpolationPlugin,
@@ -51,41 +52,5 @@ pub fn init(viewport_size: ViewportSize) {
SkyboxPlugin,
UiLayerPlugin,
))
- .insert_resource(GizmoConfig {
- enabled: true,
- depth_bias: -0.1,
- line_perspective: false,
- line_width: 2.,
- ..default()
- })
- .add_systems(
- Update,
- (
- update_gizmo_line_thickness,
- draw_world_axes.after(CameraMotionSystem),
- ),
- )
.run();
}
-
-fn update_gizmo_line_thickness(
- mut config: ResMut,
- q_window: Query<&Window, (Changed, With)>,
-) {
- if let Ok(scale_factor) = q_window.get_single().map(|win| win.scale_factor() as f32) {
- config.line_width = 2. * scale_factor;
- }
-}
-
-fn draw_world_axes(mut gizmos: Gizmos, q: Query<&CameraController>) {
- let &CameraController { focus, radius, .. } = q.single();
-
- let mut draw_axis = |direction: Vec3, color: Color| {
- let end = focus + direction * radius / 15.;
- gizmos.line(focus, end, color);
- };
-
- draw_axis(Vec3::X, Color::RED);
- draw_axis(Vec3::Y, Color::GREEN);
- draw_axis(Vec3::Z, Color::BLUE);
-}
diff --git a/studio/src/ui/fps.rs b/studio/src/ui/fps.rs
index 101566d..4c6799d 100644
--- a/studio/src/ui/fps.rs
+++ b/studio/src/ui/fps.rs
@@ -3,13 +3,19 @@ use bevy::{
prelude::*,
};
+use crate::debug::DebugMode;
+
pub(super) struct FpsCounterPlugin;
impl Plugin for FpsCounterPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(FrameTimeDiagnosticsPlugin)
.add_systems(Startup, layout)
- .add_systems(Update, update);
+ .add_systems(Update, update)
+ .add_systems(
+ Update,
+ update_visibility.run_if(resource_exists_and_changed::()),
+ );
}
}
@@ -17,24 +23,28 @@ impl Plugin for FpsCounterPlugin {
struct FramerateCounter;
fn layout(mut cmd: Commands) {
+ let mut text_bundle = TextBundle::from_section(
+ "0 fps",
+ TextStyle {
+ color: Color::GREEN,
+ font_size: 14.,
+ ..default()
+ },
+ )
+ .with_text_alignment(TextAlignment::Right)
+ .with_style(Style {
+ position_type: PositionType::Absolute,
+ right: Val::Px(16.),
+ bottom: Val::Px(16.),
+ ..default()
+ });
+
+ text_bundle.visibility = Visibility::Hidden;
+
cmd.spawn((
FramerateCounter,
Name::new("Framerate Counter"),
- TextBundle::from_section(
- "0 fps",
- TextStyle {
- color: Color::GREEN,
- font_size: 14.,
- ..default()
- },
- )
- .with_text_alignment(TextAlignment::Right)
- .with_style(Style {
- position_type: PositionType::Absolute,
- right: Val::Px(16.),
- bottom: Val::Px(16.),
- ..default()
- }),
+ text_bundle,
));
}
@@ -53,3 +63,15 @@ fn update(
counter.single_mut().sections[0].value = format!("{fps:.2} fps");
}
}
+
+fn update_visibility(
+ r_debug: Res,
+ mut q_visibility: Query<&mut Visibility, With>,
+) {
+ for mut visibility in q_visibility.iter_mut() {
+ *visibility = match **r_debug {
+ true => Visibility::Inherited,
+ false => Visibility::Hidden,
+ }
+ }
+}