use crate::HDR_BYTES;
use crate::ecs::EditorWorld;
use crate::systems::retained_ui;
use crate::systems::shell::EditorShellContext;
use crate::systems::{
atmosphere, camera, camera_gizmos, cutscene_edit, grab, graphics, input, light_gizmos, loading,
mode, model, perf_test, picking, project_io, random, render, shell, skeleton_debug, sun,
};
use nightshade::ecs::camera::systems::{
camera_controllers_system, fly_camera_system, pan_orbit_update_transform,
};
use nightshade::ecs::input::resources::MouseState;
use nightshade::prelude::*;
use nightshade::render::wgpu::rendergraph::RenderGraph;
use nightshade::run::RenderResources;
use nightshade::shell::ShellState;
pub struct Editor {
pub editor_world: EditorWorld,
pub shell: ShellState<EditorShellContext>,
}
impl Default for Editor {
fn default() -> Self {
Self {
editor_world: EditorWorld::default(),
shell: shell::new_shell(),
}
}
}
impl State for Editor {
fn configure_render_graph(
&mut self,
graph: &mut RenderGraph<World>,
device: &wgpu::Device,
_: wgpu::TextureFormat,
resources: RenderResources,
) {
render::configure_graph(graph, device, resources);
}
fn initialize(&mut self, world: &mut World) {
world.resources.window.title = "Nightshade Editor".to_string();
graphics::configure_defaults(world);
load_hdr_skybox(world, HDR_BYTES.to_vec());
sun::spawn_with_shadows(&mut self.editor_world, world);
camera::spawn_default(&mut self.editor_world, world);
project_io::new_project(&mut self.editor_world, world);
const GLTF_DATA: &[u8] = include_bytes!("../assets/gltf/DamagedHelmet.glb");
loading::load_gltf_bytes(&mut self.editor_world, "DamagedHelmet.glb", GLTF_DATA);
loading::preload_browsers(&mut self.editor_world);
retained_ui::init(&mut self.editor_world, world);
retained_ui::build::load_palette_textures(&mut self.editor_world, world);
}
fn run_systems(&mut self, world: &mut World) {
let shell_capturing = shell::is_capturing_input(&self.shell);
if !shell_capturing {
crate::systems::play::poll_hotkey(&mut self.editor_world, world);
}
if self.editor_world.resources.play.active {
crate::systems::play::tick(&mut self.editor_world, world, shell_capturing);
shell::run(&mut self.shell, world);
let events = std::mem::take(&mut world.resources.input.events);
for event in events {
if let AppEvent::Keyboard { key, state } = event {
shell::handle_key(&mut self.shell, world, key, state);
}
}
return;
}
camera::handle_fly_toggle(&mut self.editor_world, world);
let grab_active = grab::is_active(&self.editor_world);
let marquee_intent = marquee_drag_intent(world);
if self.editor_world.resources.camera.fly_mode {
if !shell_capturing {
set_cursor_locked(world, true);
set_cursor_visible(world, false);
fly_camera_system(world);
} else {
set_cursor_locked(world, false);
set_cursor_visible(world, true);
}
} else if !shell_capturing && !input::ui_capturing(world) && !grab_active {
if marquee_intent
|| placement_draw_active(&self.editor_world)
|| crate::systems::push_pull::is_dragging(&self.editor_world)
{
pan_orbit_update_transform(world);
} else {
camera_controllers_system(world);
}
} else {
pan_orbit_update_transform(world);
}
camera::handle_reset_input(&mut self.editor_world, world);
atmosphere::switch_system(&mut self.editor_world, world);
let mut shortcut_actions: Vec<crate::systems::retained_ui::Action> = Vec::new();
if !grab_active {
input::poll_shortcuts(
&mut self.editor_world.resources.shortcuts,
world,
&mut shortcut_actions,
);
if !shortcut_actions.is_empty() {
self.editor_world
.resources
.ui_interaction
.actions
.extend(shortcut_actions);
}
}
random::poll(&mut self.editor_world);
perf_test::poll(&mut self.editor_world, world);
loading::poll_pending_imports(&mut self.editor_world, world);
loading::poll_browsers(&mut self.editor_world, world);
loading::poll_thumbnails(&mut self.editor_world, world);
loading::poll_fit_frames(&mut self.editor_world, world);
loading::update_top_progress_bar(&self.editor_world, world);
project_io::poll_pending_loads(&mut self.editor_world, world);
project_io::poll_thumbnail_isolation(&mut self.editor_world, world);
let force_resync = std::mem::take(
&mut self
.editor_world
.resources
.writeback_state
.needs_full_resync,
);
crate::scene_writeback::reconcile(
&mut self.editor_world.resources.project,
&mut self.editor_world.resources.editor_scene,
&mut self.editor_world.resources.writeback_state,
world,
force_resync,
);
atmosphere::tick_day_night(&mut self.editor_world, world);
sun::update(&mut self.editor_world, world);
light_gizmos::update(&mut self.editor_world, world);
camera_gizmos::update(&mut self.editor_world, world);
skeleton_debug::update(&mut self.editor_world, world);
sync_snap_to_engine(&self.editor_world, world);
model::rotate(&mut self.editor_world, world);
mode::tick_mode(&mut self.editor_world, world, shell_capturing);
retained_ui::update(&mut self.editor_world, world);
grab::tick(&mut self.editor_world, world);
if !greybox_tab_active(&self.editor_world, world) {
if self.editor_world.resources.placement.active {
crate::systems::retained_ui::create_shape::stop(&mut self.editor_world, world);
}
self.editor_world.resources.push_pull.active = false;
}
if grab::is_active(&self.editor_world) {
crate::systems::selection::sync_to_engine(&self.editor_world, world);
} else if self.editor_world.resources.placement.active {
crate::systems::retained_ui::create_shape::tick(&mut self.editor_world, world);
} else if self.editor_world.resources.push_pull.active {
crate::systems::push_pull::tick(&mut self.editor_world, world);
} else {
picking::update(&mut self.editor_world, world);
}
crate::systems::retained_ui::build::update_grid(&mut self.editor_world, world);
let undo_count_before = self.editor_world.resources.undo.undo_len();
crate::undo::poll_gizmo_drag(
&mut self.editor_world.resources.gizmo_drag,
&mut self.editor_world.resources.undo,
&self.editor_world.resources.editor_scene,
world,
);
if self.editor_world.resources.undo.undo_len() > undo_count_before {
self.editor_world.resources.ui_handles.inspector.dirty = true;
}
shell::run(&mut self.shell, world);
cutscene_edit::apply_commands(&mut self.editor_world, world, &mut self.shell.context);
cutscene_edit::sync(&mut self.editor_world, world);
let events = std::mem::take(&mut world.resources.input.events);
let mut dropped_files: Vec<nightshade::ecs::input::resources::DroppedFile> = Vec::new();
for event in events {
match event {
AppEvent::Keyboard { key, state } => {
shell::handle_key(&mut self.shell, world, key, state);
}
AppEvent::FileDropped(file) => {
dropped_files.push(file);
}
#[cfg(not(target_arch = "wasm32"))]
AppEvent::FileDroppedPath(path) => {
loading::handle_dropped_path(&mut self.editor_world, world, &path);
}
_ => {}
}
}
if !dropped_files.is_empty() {
loading::handle_dropped_files(&mut self.editor_world, world, &dropped_files);
}
}
}
fn greybox_tab_active(editor_world: &EditorWorld, world: &World) -> bool {
ui_tab_bar_selected(world, editor_world.resources.ui_handles.tree.tab_bar) == Some(1)
}
fn placement_draw_active(editor_world: &EditorWorld) -> bool {
matches!(
editor_world.resources.placement.phase,
crate::ecs::PlacementPhase::Footprint { .. } | crate::ecs::PlacementPhase::Height { .. }
)
}
fn marquee_drag_intent(world: &World) -> bool {
let keyboard = &world.resources.input.keyboard;
let shift =
keyboard.is_key_pressed(KeyCode::ShiftLeft) || keyboard.is_key_pressed(KeyCode::ShiftRight);
if !shift {
return false;
}
let left_held = nightshade::ecs::input::access::mouse_for_active(world)
.state
.contains(MouseState::LEFT_CLICKED);
left_held && crate::systems::input::mouse_in_active_viewport(world)
}
fn sync_snap_to_engine(editor_world: &EditorWorld, world: &mut World) {
let snap = &editor_world.resources.snap;
let gizmos = &mut world.resources.user_interface.gizmos;
if snap.enabled {
gizmos.translation_snap = Some(snap.translation_step);
gizmos.rotation_snap_radians = Some(snap.rotation_step_degrees.to_radians());
gizmos.scale_snap = Some(snap.scale_step);
} else {
gizmos.translation_snap = None;
gizmos.rotation_snap_radians = None;
gizmos.scale_snap = None;
}
}