pub(crate) mod assets;
mod load;
mod save;
pub(crate) mod startup;
mod types;
pub use types::{EditorProjectFile, project_data};
#[derive(Default)]
pub struct ProjectState {
pub is_open: bool,
pub is_modified: bool,
}
impl ProjectState {
pub fn mark_modified(&mut self) {
self.is_modified = true;
}
pub fn clear_modified(&mut self) {
self.is_modified = false;
}
}
pub(crate) use types::{new_editor_project, project_data_mut};
use crate::Editor;
use crate::app_context::ProjectEditState;
use crate::engine_editor::EditorContext;
#[cfg(not(target_arch = "wasm32"))]
use crate::mosaic::ToastKind;
use crate::mosaic::WindowLayout;
use crate::widgets::{CameraWidget, EditorWidget};
use nightshade::prelude::*;
type Pane = crate::mosaic::Pane<EditorWidget>;
fn world_to_scene(world: &World) -> nightshade::ecs::scene::Scene {
nightshade::ecs::scene::world_to_scene(world, "Untitled")
}
pub fn create_default_tile_tree() -> egui_tiles::Tree<Pane> {
let mut tiles = egui_tiles::Tiles::default();
let viewport = tiles.insert_pane(crate::mosaic::Pane::new(EditorWidget::Camera(
CameraWidget { camera_index: 0 },
)));
egui_tiles::Tree::new("tile_tree", viewport, tiles)
}
pub fn clear_scene(context: &mut EditorContext, world: &mut World) {
use nightshade::ecs::gizmos;
if let Some(old_gizmo) = context.gizmo_interaction.active.take() {
gizmos::destroy_gizmo(world, &old_gizmo);
}
let camera_entity = world.resources.active_camera;
let all_entities: Vec<Entity> = world.core.get_all_entities();
for entity in all_entities {
if Some(entity) == camera_entity {
continue;
}
despawn_recursive_immediate(world, entity);
}
context.selection.clear();
context.undo_history.clear();
context.gizmo_interaction.hover_axis = None;
context.gizmo_interaction.drag_mode = nightshade::ecs::gizmos::GizmoDragMode::None;
world.resources.graphics.atmosphere = Atmosphere::Sky;
spawn_sun(world);
}
fn update_hdr_skybox_from_scene(
project_edit: &mut ProjectEditState,
scene: &nightshade::ecs::scene::Scene,
) {
project_edit.hdr_skybox_path = None;
if let Some(nightshade::ecs::scene::SceneHdrSkybox::Reference { path }) = &scene.hdr_skybox {
project_edit.hdr_skybox_path = Some(std::path::PathBuf::from(path));
}
}
impl Editor {
pub fn ensure_project_exists(&mut self, world: &World) {
if self.project.is_none() {
let mut new_project =
new_editor_project(self.context.project_edit.current_name.clone());
project_data_mut(&mut new_project)
.add_scene("Untitled".to_string(), world_to_scene(world));
if let Some(tree) = self.mosaic.current_tree().cloned() {
new_project.windows.push(WindowLayout::new(tree, "Default"));
}
self.project = Some(new_project);
self.scene_browser_dirty = true;
}
}
pub fn new_project(&mut self, world: &mut World) {
clear_scene(&mut self.context.editor, world);
self.context.project_edit.current_path = None;
self.context.project_edit.current_name = None;
self.context.editor.selection.clear();
self.context.project_edit.hdr_skybox_path = None;
self.context.project_edit.editing_name = false;
self.context.project_edit.name_edit_buffer.clear();
let default_tree = create_default_tile_tree();
let layouts = vec![WindowLayout::new(default_tree, "Default")];
self.mosaic.load_layouts(layouts.clone(), 0);
self.scene_browser_dirty = true;
#[cfg(not(target_arch = "wasm32"))]
{
let mut project = new_editor_project(None);
project_data_mut(&mut project).add_scene("Untitled".to_string(), world_to_scene(world));
project.windows = layouts;
self.project = Some(project);
self.project_state = ProjectState::default();
self.toasts
.push(ToastKind::Success, "Created new project", 3.0);
}
}
pub fn update_project_from_current_state(&mut self, world: &World) {
let scene = world_to_scene(world);
let (layouts, active_index) = self.mosaic.save_layouts();
if let Some(ref mut project) = self.project {
let data = project_data_mut(project);
if let Some(active_scene_name) = &data.active_scene_name.clone() {
data.scenes.insert(active_scene_name.clone(), scene);
}
data.active_layout_index = active_index;
project.windows = layouts;
}
}
fn spawn_scene_with_feedback(
&mut self,
world: &mut World,
scene: &nightshade::ecs::scene::Scene,
) -> bool {
match nightshade::ecs::scene::spawn_scene(world, scene, None) {
Ok(result) => {
for warning in result.warnings {
tracing::warn!("{}", warning);
#[cfg(not(target_arch = "wasm32"))]
self.toasts.push(ToastKind::Warning, warning, 3.0);
}
crate::engine_editor::recreate_gizmo_for_mode(&mut self.context.editor, world);
true
}
Err(error) => {
tracing::error!("Failed to spawn scene: {}", error);
#[cfg(not(target_arch = "wasm32"))]
self.toasts
.push(ToastKind::Error, format!("Failed to spawn: {}", error), 3.0);
false
}
}
}
pub fn switch_to_map(&mut self, world: &mut World, map_name: &str) {
self.update_project_from_current_state(world);
let map_to_spawn = if let Some(ref mut project) = self.project {
let data = project_data_mut(project);
if data.set_active_scene(map_name) {
data.active_scene().cloned()
} else {
None
}
} else {
None
};
self.scene_browser_dirty = true;
if let Some(map) = map_to_spawn {
update_hdr_skybox_from_scene(&mut self.context.project_edit, &map);
clear_scene(&mut self.context.editor, world);
if self.spawn_scene_with_feedback(world, &map) {
#[cfg(not(target_arch = "wasm32"))]
self.toasts.push(
ToastKind::Success,
format!("Switched to map: {}", map_name),
3.0,
);
}
}
}
pub fn create_new_map(&mut self, world: &mut World, map_name: String) {
self.update_project_from_current_state(world);
clear_scene(&mut self.context.editor, world);
world.resources.graphics.atmosphere = Atmosphere::Sky;
spawn_sun(world);
let map = world_to_scene(world);
if let Some(ref mut project) = self.project {
let data = project_data_mut(project);
data.add_scene(map_name.clone(), map);
data.set_active_scene(&map_name);
}
self.project_state.mark_modified();
self.scene_browser_dirty = true;
#[cfg(not(target_arch = "wasm32"))]
self.toasts.push(
ToastKind::Success,
format!("Created map: {}", map_name),
3.0,
);
}
pub fn delete_current_map(&mut self, world: &mut World) {
let can_delete = self
.project
.as_ref()
.map(|p| project_data(p).map(|d| d.scenes.len() > 1).unwrap_or(false))
.unwrap_or(false);
if !can_delete {
#[cfg(not(target_arch = "wasm32"))]
self.toasts
.push(ToastKind::Warning, "Cannot delete the last map", 3.0);
#[cfg(target_arch = "wasm32")]
tracing::warn!("Cannot delete the last map");
return;
}
let (deleted_name, map_to_spawn) = if let Some(ref mut project) = self.project {
let data = project_data_mut(project);
if let Some(map_name) = data.active_scene_name.clone() {
data.remove_scene(&map_name);
(Some(map_name), data.active_scene().cloned())
} else {
(None, None)
}
} else {
(None, None)
};
if let Some(map_name) = deleted_name {
self.project_state.mark_modified();
self.scene_browser_dirty = true;
clear_scene(&mut self.context.editor, world);
if let Some(map) = map_to_spawn {
update_hdr_skybox_from_scene(&mut self.context.project_edit, &map);
self.spawn_scene_with_feedback(world, &map);
} else {
self.context.project_edit.hdr_skybox_path = None;
}
#[cfg(not(target_arch = "wasm32"))]
self.toasts.push(
ToastKind::Success,
format!("Deleted map: {}", map_name),
3.0,
);
#[cfg(target_arch = "wasm32")]
let _ = map_name;
}
}
}