use crate::Editor;
#[cfg(not(target_arch = "wasm32"))]
use crate::mosaic::ToastKind;
#[cfg(not(target_arch = "wasm32"))]
use crate::mosaic::WindowLayout;
use crate::project_io::{EditorProjectFile, project_data};
#[cfg(not(target_arch = "wasm32"))]
use crate::project_io::{new_editor_project, project_data_mut};
use nightshade::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
fn resolve_project_relative_paths(
project: &mut EditorProjectFile,
project_dir: Option<&std::path::Path>,
) {
let Some(base_dir) = project_dir else {
return;
};
for map in project_data_mut(project).scenes.values_mut() {
if let Some(nightshade::ecs::scene::SceneHdrSkybox::Reference { path }) =
&mut map.hdr_skybox
{
let path_buf = std::path::Path::new(&*path);
if path_buf.is_relative() {
let absolute = base_dir.join(path_buf);
if let Some(absolute_str) = absolute.to_str() {
*path = absolute_str.to_string();
}
}
}
}
}
impl Editor {
#[cfg(not(target_arch = "wasm32"))]
pub fn load_project(&mut self, world: &mut World) {
let filters = [
nightshade::filesystem::FileFilter {
name: "JSON Project".to_string(),
extensions: vec!["project.json".to_string()],
},
nightshade::filesystem::FileFilter {
name: "Binary Project".to_string(),
extensions: vec!["project.bin".to_string()],
},
];
if let Some(path) = nightshade::filesystem::pick_file(&filters) {
self.load_project_from_path(world, &path);
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_project_from_path(&mut self, world: &mut World, path: &std::path::Path) {
match EditorProjectFile::load_from_path(path) {
Ok(mut project) => {
let project_dir = path.parent();
resolve_project_relative_paths(&mut project, project_dir);
super::clear_scene(&mut self.context.editor, world);
world.resources.physics.paused = true;
if let Some(active_map) = project_data(&project)
.and_then(|d| d.active_scene())
.cloned()
{
super::update_hdr_skybox_from_scene(
&mut self.context.project_edit,
&active_map,
);
if !self.spawn_scene_with_feedback(world, &active_map) {
return;
}
} else {
self.context.project_edit.hdr_skybox_path = None;
}
if project.windows.is_empty() {
let default_tree = crate::project_io::create_default_tile_tree();
project
.windows
.push(WindowLayout::new(default_tree, "Default"));
}
let active_index = project_data(&project)
.map(|d| d.active_layout_index)
.unwrap_or(0);
self.mosaic
.load_layouts(project.windows.clone(), active_index);
self.context.project_edit.current_name =
Some(project.name.clone()).filter(|n| !n.is_empty());
self.context.editor.selection.clear();
self.context.project_edit.editing_name = false;
self.context.project_edit.name_edit_buffer.clear();
self.project = Some(project);
self.scene_browser_dirty = true;
self.set_active_project_path(path);
self.toasts.push(
ToastKind::Success,
format!("Loaded from {}", path.display()),
3.0,
);
}
Err(error) => {
self.toasts
.push(ToastKind::Error, format!("Failed to load: {}", error), 3.0);
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_scene_from_path(&mut self, world: &mut World, path: &std::path::Path) {
match nightshade::ecs::scene::load_scene(path) {
Ok(mut scene) => {
scene.rebuild_uuid_index();
super::clear_scene(&mut self.context.editor, world);
super::update_hdr_skybox_from_scene(&mut self.context.project_edit, &scene);
world.resources.physics.paused = true;
let scene_name = if scene.header.name.is_empty() {
path.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "Untitled".to_string())
} else {
scene.header.name.clone()
};
if !self.spawn_scene_with_feedback(world, &scene) {
return;
}
if self.project.is_none() {
let mut new_project = new_editor_project(Some(scene_name.clone()));
let default_tree = crate::project_io::create_default_tile_tree();
new_project
.windows
.push(WindowLayout::new(default_tree.clone(), "Default"));
self.mosaic.set_tree(default_tree);
self.project = Some(new_project);
self.project_state.is_open = true;
}
if let Some(ref mut project) = self.project {
let data = project_data_mut(project);
data.add_scene(scene_name.clone(), scene);
data.set_active_scene(&scene_name);
}
self.project_state.mark_modified();
self.scene_browser_dirty = true;
self.context.editor.selection.clear();
self.toasts.push(
ToastKind::Success,
format!("Loaded scene from {}", path.display()),
3.0,
);
}
Err(error) => {
self.toasts.push(
ToastKind::Error,
format!("Failed to load scene: {}", error),
3.0,
);
}
}
}
pub fn load_project_from_bytes(&mut self, world: &mut World, bytes: &[u8]) {
let parse_result: Result<EditorProjectFile, crate::mosaic::MosaicError> =
EditorProjectFile::from_bytes(bytes);
match parse_result {
Ok(project) => {
super::clear_scene(&mut self.context.editor, world);
if let Some(active_map) = project_data(&project)
.and_then(|d| d.active_scene())
.cloned()
&& !self.spawn_scene_with_feedback(world, &active_map)
{
return;
}
let active_index = project_data(&project)
.map(|d| d.active_layout_index)
.unwrap_or(0);
self.mosaic
.load_layouts(project.windows.clone(), active_index);
self.context.project_edit.current_path = None;
self.context.project_edit.current_name =
Some(project.name.clone()).filter(|n| !n.is_empty());
self.context.editor.selection.clear();
self.project = Some(project);
self.scene_browser_dirty = true;
self.project_state.is_open = true;
self.project_state.clear_modified();
}
Err(error) => {
tracing::error!("Failed to parse project: {}", error);
}
}
}
pub fn process_pending_project_load(&mut self, world: &mut World) {
if let Some(ref pending) = self.context.pending_file_load
&& let Some(loaded_file) = pending.take()
{
self.context.pending_file_load = None;
self.load_project_from_bytes(world, &loaded_file.bytes);
}
}
#[cfg(target_arch = "wasm32")]
pub fn load_project(&mut self, _world: &mut World) {
let filters = [nightshade::filesystem::FileFilter {
name: "JSON Project".to_string(),
extensions: vec!["project.json".to_string()],
}];
self.context.pending_file_load = Some(nightshade::filesystem::request_file_load(&filters));
}
}