nightshade-editor 0.14.2

Interactive map editor for the Nightshade game engine
use crate::HDR_BYTES;
use crate::ecs::EditorWorld;
use crate::systems::retained_ui;
use crate::systems::shell::EditorShellContext;
use crate::systems::{
    atmosphere, camera, 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, pan_orbit_update_transform};
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());

        capture_procedural_atmosphere_ibl(world, Atmosphere::Sky, 0.0);

        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);
    }

    fn run_systems(&mut self, world: &mut World) {
        let grab_active = grab::is_active(&self.editor_world);
        if !shell::is_capturing_input(&self.shell) && !input::ui_capturing(world) && !grab_active {
            escape_key_exit_system(world);
            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);
        skeleton_debug::update(&mut self.editor_world, world);
        sync_snap_to_engine(&self.editor_world, world);
        model::rotate(&mut self.editor_world, world);

        let shell_capturing = shell::is_capturing_input(&self.shell);
        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 !grab::is_active(&self.editor_world) {
            picking::update(&mut self.editor_world, world);
        } else {
            crate::systems::selection::sync_to_engine(&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);

        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 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;
    }
}