Skeleton of an editor. Just a window with solid toggling.
parent
c31801e876
commit
5c71f55363
@ -0,0 +1,5 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub(crate) fn any_component_added<T: Component>(q: Query<Entity, Added<T>>) -> bool {
|
||||
!q.is_empty()
|
||||
}
|
||||
@ -0,0 +1,163 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub(crate) struct EditorPlugin;
|
||||
|
||||
impl Plugin for EditorPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_state::<EditorState>()
|
||||
.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::<WindowCloseRequested>()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<State<EditorState>>,
|
||||
mut next_state: ResMut<NextState<EditorState>>,
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut catch: Local<bool>,
|
||||
) {
|
||||
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<State<EditorState>>,
|
||||
mut windows: Query<&mut Window, With<EditorTag>>,
|
||||
mut cameras: Query<&mut Camera, With<EditorTag>>,
|
||||
) {
|
||||
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<WindowCloseRequested>,
|
||||
editor_windows: Query<Entity, With<EditorTag>>,
|
||||
mut editor_state: ResMut<NextState<EditorState>>,
|
||||
) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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::<PrimaryWindow>),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
handle_window_close.run_if(on_event::<WindowCloseRequested>()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_primary(mut events: Query<&mut Window, Added<PrimaryWindow>>) {
|
||||
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<WindowCloseRequested>,
|
||||
primary: Query<Entity, With<PrimaryWindow>>,
|
||||
mut exit: EventWriter<AppExit>,
|
||||
) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue