From 5c71f55363ca75390073f17262b6f2623bee033e Mon Sep 17 00:00:00 2001 From: "Elijah C. Voigt" Date: Thu, 27 Jun 2024 21:45:23 -0700 Subject: [PATCH] Skeleton of an editor. Just a window with solid toggling. --- rust-toolchain.toml | 12 ++-- src/conditions.rs | 5 ++ src/editor.rs | 163 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 20 ++++-- src/menu.rs | 22 +++--- src/prelude.rs | 8 +++ src/ui.rs | 6 ++ src/window.rs | 41 +++++++++++ 8 files changed, 255 insertions(+), 22 deletions(-) create mode 100644 src/conditions.rs create mode 100644 src/editor.rs create mode 100644 src/window.rs diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e591d56..57704d5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -6,13 +6,13 @@ components = [ "rls", "rustc-dev", "rustfmt", - "wasm-bindgen-cli", - "wasm-server-runner", + # "wasm-bindgen-cli", + # "wasm-server-runner", ] targets = [ - "aarch64-apple-darwin", - "wasm32-unknown-unknown", - "x86_64-apple-darwin", + # "aarch64-apple-darwin", + # "wasm32-unknown-unknown", + # "x86_64-apple-darwin", + # "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu", ] \ No newline at end of file diff --git a/src/conditions.rs b/src/conditions.rs new file mode 100644 index 0000000..0dc608a --- /dev/null +++ b/src/conditions.rs @@ -0,0 +1,5 @@ +use bevy::prelude::*; + +pub(crate) fn any_component_added(q: Query>) -> bool { + !q.is_empty() +} diff --git a/src/editor.rs b/src/editor.rs new file mode 100644 index 0000000..d08a2ab --- /dev/null +++ b/src/editor.rs @@ -0,0 +1,163 @@ +use crate::prelude::*; + +pub(crate) struct EditorPlugin; + +impl Plugin for EditorPlugin { + fn build(&self, app: &mut App) { + app.init_state::() + .add_systems(Startup, init_editor) + .add_systems( + Update, + toggle_editor_state.run_if(input_just_pressed(KeyCode::F3)), + ) + .add_systems( + OnTransition { + from: EditorState::Open, + to: EditorState::Closed, + }, + toggle_editor_window, + ) + .add_systems( + OnTransition { + from: EditorState::Closed, + to: EditorState::Open, + }, + toggle_editor_window, + ) + .add_systems( + Update, + handle_window_close.run_if(on_event::()), + ); + } +} + +/// Tracking the open/closed state of the editor +#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default, Component)] +enum EditorState { + /// The editor is closed => the editor window is disabled + #[default] + Closed, + /// The editor is open => the editor window is visible + Open, +} + +impl std::ops::Not for &EditorState { + type Output = EditorState; + + fn not(self) -> Self::Output { + match self { + EditorState::Open => EditorState::Closed, + EditorState::Closed => EditorState::Open, + } + } +} + +impl std::convert::From<&EditorState> for bool { + fn from(value: &EditorState) -> bool { + match value { + EditorState::Open => true, + EditorState::Closed => false, + } + } +} + +/// Tag component for editor entities +#[derive(Component)] +struct EditorTag; + +/// At startup initialize some editor components +fn init_editor(mut commands: Commands) { + // Editor window + let editor_window = commands + .spawn(( + EditorTag, + Window { + visible: false, + title: "Editor".into(), + ..default() + }, + )) + .id(); + + // Editor camera + let editor_camera = commands + .spawn(( + EditorTag, + Camera3dBundle { + camera: Camera { + target: RenderTarget::Window(WindowRef::Entity(editor_window)), + ..default() + }, + ..default() + }, + )) + .id(); + + // Editor UI elements + commands + .spawn(( + EditorTag, + TargetCamera(editor_camera), + NodeBundle { ..default() }, + )) + .with_children(|parent| { + parent.spawn(( + EditorTag, + TextBundle::from_section( + "Welcome to the editor", + TextStyle { + color: Color::WHITE, + ..default() + }, + ), + )); + }); +} + +fn toggle_editor_state( + curr_state: Res>, + mut next_state: ResMut>, + keys: Res>, + mut catch: Local, +) { + if keys.pressed(KeyCode::F3) && !*catch { + *catch = true; + match curr_state.get() { + EditorState::Open => next_state.set(EditorState::Closed), + EditorState::Closed => next_state.set(EditorState::Open), + } + } else { + *catch = false; + } +} + +fn toggle_editor_window( + state: Res>, + mut windows: Query<&mut Window, With>, + mut cameras: Query<&mut Camera, With>, +) { + let curr = state.get().into(); + cameras.iter_mut().for_each(|mut camera| { + camera.is_active = curr; + }); + windows.iter_mut().for_each(|mut window| { + window.visible = curr; + }) +} + +/// Handles window close requests which are only weird because of the editor +/// When the editor window closes, just make it invisible and change the editor state to "Closed". +fn handle_window_close( + mut events: EventReader, + editor_windows: Query>, + mut editor_state: ResMut>, +) { + events + .read() + // Filter events down to jus those affecting the editor window + .filter_map(|WindowCloseRequested { window }| editor_windows.get(*window).ok()) + // For each related close event, send out a "Close the editor" state transition + .for_each(|_| { + editor_state.set(EditorState::Closed); + }); +} diff --git a/src/main.rs b/src/main.rs index 2c9bc1e..9a17ddd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,26 @@ +/// ECS scheduling `run_if` conditions +pub(crate) mod conditions; +/// Editor: debugging and run-time modifications to the game +pub(crate) mod editor; +/// Menu: Main and otherwise +pub(crate) mod menu; /// Helper module containing common imports across the project pub(crate) mod prelude; - -/// Menu plugin, state, systems -pub(crate) mod menu; +/// Window handling +pub(crate) mod window; use crate::prelude::*; fn main() { App::new() - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + // handled in crate::window::handle_window_close and crate::editor::handle_window_close + close_when_requested: false, + exit_condition: bevy::window::ExitCondition::OnPrimaryClosed, + ..default() + })) .add_plugins(menu::MenuPlugin) + .add_plugins(editor::EditorPlugin) + .add_plugins(window::WindowPlugin) .run(); } diff --git a/src/menu.rs b/src/menu.rs index 4aed6f2..2a3ede8 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -9,23 +9,21 @@ impl Plugin for MenuPlugin { } fn init_menu(mut commands: Commands) { - commands.spawn(Camera3dBundle { ..default() }); + commands.spawn(Camera3dBundle { ..default() }); commands .spawn(NodeBundle { - style: Style { - max_width: Val::Percent(60.0), - max_height: Val::Percent(100.0), - ..default() - }, - ..default() - }) + style: Style { + max_width: Val::Percent(60.0), + max_height: Val::Percent(100.0), + ..default() + }, + ..default() + }) .with_children(|parent| { parent.spawn(TextBundle { - style: Style { - ..default() - }, + style: Style { ..default() }, text: Text { - justify: JustifyText::Center, + justify: JustifyText::Center, sections: vec![ TextSection::new( "ACTS", diff --git a/src/prelude.rs b/src/prelude.rs index f5a3adf..ebb594d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1 +1,9 @@ +pub(crate) use bevy::app::AppExit; +pub(crate) use bevy::input::common_conditions::input_just_pressed; pub(crate) use bevy::prelude::*; +pub(crate) use bevy::render::camera::RenderTarget; +pub(crate) use bevy::window::PrimaryWindow; +pub(crate) use bevy::window::WindowCloseRequested; +pub(crate) use bevy::window::WindowRef; + +pub(crate) use crate::conditions::*; diff --git a/src/ui.rs b/src/ui.rs index 2f25855..a6b82b9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -7,3 +7,9 @@ impl Plugin for UiPlugin { app.add_systems(Startup, init_ui); } } + +fn init_ui( + mut commands: Commands, +) { + todo!() +} \ No newline at end of file diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..0953feb --- /dev/null +++ b/src/window.rs @@ -0,0 +1,41 @@ +use crate::prelude::*; + +pub(crate) struct WindowPlugin; + +impl Plugin for WindowPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + setup_primary.run_if(any_component_added::), + ) + .add_systems( + Update, + handle_window_close.run_if(on_event::()), + ); + } +} + +fn setup_primary(mut events: Query<&mut Window, Added>) { + events.iter_mut().for_each(|mut window| { + window.title = "Acts of Gods".into(); + }); +} + +/// Handles window close requests which are only weird because of the editor +/// When the primary window closes shut down the game. +fn handle_window_close( + mut events: EventReader, + primary: Query>, + mut exit: EventWriter, +) { + events + .read() + // FilterMap this entity to the primary window + // Meaning if we get Some(entity) the event was for the primary + // If we get None it was for a different window so we skip the for_each + .filter_map(|WindowCloseRequested { window }| primary.get(*window).ok()) + // If this was the primary window, send an AppExit + .for_each(|_| { + exit.send(AppExit); + }); +}