use crate::Editor;
#[cfg(not(target_arch = "wasm32"))]
use crate::mosaic::ToastKind;
#[cfg(not(target_arch = "wasm32"))]
use crate::project_io::project_data_mut;
use nightshade::prelude::*;
impl Editor {
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn set_active_project_path(&mut self, path: &std::path::Path) {
self.context.project_edit.current_path = Some(path.to_path_buf());
self.project_state.is_open = true;
self.project_state.clear_modified();
self.settings
.data
.recent_projects
.add(std::path::PathBuf::from(path.display().to_string()));
if let Err(error) = self.settings.save() {
tracing::error!("Failed to save settings: {error}");
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_project(&mut self, world: &World) {
if let Some(path) = &self.context.project_edit.current_path.clone() {
self.update_project_from_current_state(world);
if let Some(ref project) = self.project {
match project.save_to_path(path) {
Ok(()) => {
let path_str = path.display().to_string();
self.project_state.clear_modified();
self.toasts
.push(ToastKind::Success, format!("Saved to {}", path_str), 3.0);
}
Err(error) => {
self.toasts.push(
ToastKind::Error,
format!("Failed to save: {}", error),
3.0,
);
}
}
}
} else {
self.save_project_as(world);
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_project_as(&mut self, world: &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::save_file_dialog(&filters, Some("project.json"))
{
self.ensure_project_exists(world);
self.update_project_from_current_state(world);
if let Some(ref mut project) = self.project {
project.name = self
.context
.project_edit
.current_name
.clone()
.unwrap_or_default();
match project.save_to_path(&path) {
Ok(()) => {
self.set_active_project_path(&path);
self.toasts.push(
ToastKind::Success,
format!("Saved to {}", path.display()),
3.0,
);
}
Err(error) => {
self.toasts.push(
ToastKind::Error,
format!("Failed to save: {}", error),
3.0,
);
}
}
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn collect_all_and_save(&mut self, world: &World) {
let Some(save_dir) = nightshade::filesystem::pick_folder() else {
return;
};
self.ensure_project_exists(world);
self.update_project_from_current_state(world);
let Some(ref mut project) = self.project else {
return;
};
let assets_dir = save_dir.join("assets");
if let Err(error) = std::fs::create_dir_all(&assets_dir) {
self.toasts.push(
ToastKind::Error,
format!("Failed to create assets directory: {}", error),
3.0,
);
return;
}
let (copied_count, copy_errors) =
collect_scene_assets(project_data_mut(project), &assets_dir);
for error in ©_errors {
self.toasts.push(ToastKind::Warning, error.clone(), 3.0);
}
let project_name = if project.name.is_empty() {
"project".to_string()
} else {
project.name.clone()
};
let project_file = save_dir.join(format!("{}.project.json", project_name));
project.name = self
.context
.project_edit
.current_name
.clone()
.unwrap_or_default();
match project.save_to_path(&project_file) {
Ok(()) => {
self.set_active_project_path(&project_file);
self.toasts.push(
ToastKind::Success,
format!(
"Saved to {} with {} assets collected",
project_file.display(),
copied_count
),
3.0,
);
}
Err(error) => {
self.toasts
.push(ToastKind::Error, format!("Failed to save: {}", error), 3.0);
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn export_scene_to_path(&mut self, world: &World, path: &std::path::Path) {
let mut scene = nightshade::ecs::scene::world_to_scene(world, "Exported Scene");
match nightshade::ecs::scene::save_scene(&mut scene, path) {
Ok(()) => {
self.toasts.push(
ToastKind::Success,
format!("Exported scene to {}", path.display()),
3.0,
);
}
Err(error) => {
self.toasts.push(
ToastKind::Error,
format!("Failed to export scene: {}", error),
3.0,
);
}
}
}
#[cfg(target_arch = "wasm32")]
pub fn save_project(&mut self, world: &World) {
self.ensure_project_exists(world);
self.update_project_from_current_state(world);
let Some(project) = &self.project else {
return;
};
let bytes = match project.to_json_bytes() {
Ok(bytes) => bytes,
Err(error) => {
tracing::error!("Failed to serialize project: {}", error);
return;
}
};
let filters = [nightshade::filesystem::FileFilter {
name: "JSON Project".to_string(),
extensions: vec!["project.json".to_string()],
}];
if let Err(error) = nightshade::filesystem::save_file("project.json", &bytes, &filters) {
tracing::error!("Failed to save project: {}", error);
return;
}
self.project_state.clear_modified();
}
}
#[cfg(not(target_arch = "wasm32"))]
fn collect_scene_assets(
data: &mut super::types::EditorProjectData,
assets_dir: &std::path::Path,
) -> (usize, Vec<String>) {
let mut copied_files: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
let mut copy_errors: Vec<String> = Vec::new();
for map in data.scenes.values_mut() {
if let Some(nightshade::ecs::scene::SceneHdrSkybox::Reference { path }) =
&mut map.hdr_skybox
{
let source_path = std::path::Path::new(&*path);
if source_path.exists() {
let file_name = source_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("skybox.hdr");
if let Some(new_relative_path) = copied_files.get(&*path) {
*path = new_relative_path.clone();
} else {
let dest_path = assets_dir.join(file_name);
let relative_path = format!("assets/{}", file_name);
if !dest_path.exists()
&& let Err(error) = std::fs::copy(source_path, &dest_path)
{
copy_errors.push(format!("Failed to copy HDR '{}': {}", path, error));
}
if dest_path.exists() {
copied_files.insert(path.clone(), relative_path.clone());
*path = relative_path;
}
}
} else {
copy_errors.push(format!("HDR skybox file not found: {}", path));
}
}
}
(copied_files.len(), copy_errors)
}