use super::{CURRENT_NATIVE_PATH, autosave::record_recent_native_file};
use crate::{
EditorModel, MainWindow,
bridge::render_thread::RenderContext,
gui::{
editor::{
callbacks::clear_analysis_results,
state::{EditorState, MaterialComboCache, PushedScratch},
view::{push_has_design, refresh_all},
},
show_toast,
solid_preview::preview_state::SolidPreviewState,
},
};
use indicatrix_cut_core::{
FingerprintCheck, History, LoadPairedResult, TierOverlay,
native::{LoadNativeOnlyResult, gem_material_from_custom_snapshot},
};
use slint::ComponentHandle;
use std::{
cell::RefCell,
path::{Path, PathBuf},
rc::Rc,
sync::{Arc, Mutex, atomic::AtomicU64},
};
pub(super) struct SelfContainedLoad<'a> {
pub(super) native_path: &'a Path,
pub(super) loaded: LoadNativeOnlyResult,
pub(super) asc_filename: &'a str,
}
pub(super) fn open_native_self_contained(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
load: SelfContainedLoad<'_>,
) {
let SelfContainedLoad {
native_path,
loaded,
asc_filename,
} = load;
let native_path_display = native_path.display().to_string();
record_recent_native_file(ui, &native_path_display);
CURRENT_NATIVE_PATH.with(|cell| *cell.borrow_mut() = Some(native_path.to_path_buf()));
let (material_note, material_still_unresolved) = if let (Some(snapshot), Some(name)) = (
loaded.restorable_custom_material.as_ref(),
loaded.design.material.name.as_deref(),
) {
let gem = gem_material_from_custom_snapshot(name, snapshot);
let mut ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let materials = Arc::make_mut(&mut ctx.custom_materials);
if let Some(pos) = materials
.iter()
.position(|m| m.name.eq_ignore_ascii_case(name))
{
materials[pos] = gem;
} else {
materials.push(gem);
}
drop(ctx);
(
Some(format!(
" '{name}' was restored from this file's own saved material data."
)),
false,
)
} else {
let unresolved = matches!(
loaded.material_resolution,
indicatrix_cut_core::native::MaterialResolution::Unresolved
);
(
unresolved.then(|| format!(" {}", loaded.material_resolution)),
unresolved,
)
};
let newer_version_note = loaded.written_by_newer_version.then(|| {
" This file was written by a newer version of Indicatrix Cut; some settings \
may not have been understood and could be lost on your next save."
.to_string()
});
let is_mismatch = material_still_unresolved || loaded.written_by_newer_version;
let printed_proportions = loaded.printed_proportions;
state.borrow_mut().replace_wholesale(EditorState {
deep_solve_result_generation: None,
design: loaded.design,
history: History::new(),
printed_proportions,
generation: Arc::new(AtomicU64::new(0)),
design_epoch: Arc::new(AtomicU64::new(0)),
saved_generation: 0,
pending_unsaved_action: None,
deep_solve: None,
optimize: None,
pending_optimize: Arc::new(Mutex::new(None)),
asc_filename: Some(asc_filename.to_string()),
original_asc_text: None,
pending_gear_remap: None,
pending_retarget: None,
multi_selected: std::collections::BTreeSet::new(),
last_pushed_scratch: RefCell::new(PushedScratch::default()),
material_combo_cache: RefCell::new(MaterialComboCache::default()),
source_entry_id: None,
used_placeholder: false,
has_design: true,
});
finish_state_replace(ui, render_ctx, preview_state, solid_last_solved, state);
show_toast(
ui,
&format!(
"Recovered '{native_path_display}' -- no paired .asc file was found, so this design \
was rebuilt directly from the native file's own saved data.{}{}",
material_note.unwrap_or_default(),
newer_version_note.unwrap_or_default()
),
if is_mismatch { "warning" } else { "success" },
);
}
pub(super) fn open_plain_asc(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
asc_path: &Path,
asc_text: &str,
) {
let file_name = asc_path.file_name().map_or_else(
|| "design.asc".to_string(),
|n| n.to_string_lossy().into_owned(),
);
match crate::gui::editor::loading::design_from_asc_text(&file_name, asc_text, None) {
Ok(loaded) => {
CURRENT_NATIVE_PATH.with(|cell| *cell.borrow_mut() = None);
state.borrow_mut().replace_wholesale(EditorState {
design: loaded.design,
history: History::new(),
printed_proportions: None,
generation: Arc::new(AtomicU64::new(0)),
design_epoch: Arc::new(AtomicU64::new(0)),
saved_generation: 0,
pending_unsaved_action: None,
deep_solve: None,
optimize: None,
pending_optimize: Arc::new(Mutex::new(None)),
deep_solve_result_generation: None,
asc_filename: loaded.asc_filename,
original_asc_text: loaded.original_asc_text,
pending_gear_remap: None,
pending_retarget: None,
multi_selected: std::collections::BTreeSet::new(),
last_pushed_scratch: RefCell::new(PushedScratch::default()),
material_combo_cache: RefCell::new(MaterialComboCache::default()),
source_entry_id: None,
used_placeholder: false,
has_design: true,
});
finish_state_replace(ui, render_ctx, preview_state, solid_last_solved, state);
show_toast(
ui,
&format!(
"Loaded '{}' (plain .asc, no native sidecar found -- authored meet \
constraints and detached facets are not available).",
asc_path.display()
),
"success",
);
}
Err(e) => show_toast(ui, &format!("Cannot open: {e}"), "error"),
}
}
fn finish_state_replace(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
state: &Rc<RefCell<EditorState>>,
) {
clear_analysis_results(ui);
let st = state.borrow();
refresh_all(ui, render_ctx, preview_state, solid_last_solved, &st, true);
ui.set_loaded_design_name(st.asc_filename.clone().unwrap_or_default().into());
push_has_design(ui, &st);
drop(st);
ui.global::<EditorModel>().set_selected_tier_index(-1);
let pulse = ui.global::<EditorModel>().get_form_reset_pulse();
ui.global::<EditorModel>()
.set_form_reset_pulse(pulse.wrapping_add(1));
ui.global::<EditorModel>().set_is_dirty(false);
}
pub(super) struct LoadedNativeOutcome {
pub(super) loaded: LoadPairedResult,
pub(super) native_path_display: String,
pub(super) asc_filename: String,
pub(super) asc_text: String,
pub(super) is_mismatch: bool,
}
pub(super) fn commit_loaded_native(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
outcome: LoadedNativeOutcome,
) {
let LoadedNativeOutcome {
loaded,
native_path_display,
asc_filename,
asc_text,
is_mismatch,
} = outcome;
record_recent_native_file(ui, &native_path_display);
CURRENT_NATIVE_PATH.with(|cell| *cell.borrow_mut() = Some(PathBuf::from(&native_path_display)));
let outcome_note = plain_load_outcome_text(&loaded.fingerprint, &loaded.tier_overlay);
let (material_note, material_still_unresolved) = if let (Some(snapshot), Some(name)) = (
loaded.restorable_custom_material.as_ref(),
loaded.design.material.name.as_deref(),
) {
let gem = gem_material_from_custom_snapshot(name, snapshot);
let mut ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let materials = Arc::make_mut(&mut ctx.custom_materials);
if let Some(pos) = materials
.iter()
.position(|m| m.name.eq_ignore_ascii_case(name))
{
materials[pos] = gem;
} else {
materials.push(gem);
}
drop(ctx);
(
Some(format!(
" '{name}' was restored from this file's own saved material data."
)),
false,
)
} else {
let unresolved = matches!(
loaded.material_resolution,
indicatrix_cut_core::native::MaterialResolution::Unresolved
);
(
unresolved.then(|| format!(" {}", loaded.material_resolution)),
unresolved,
)
};
let newer_version_note = loaded.written_by_newer_version.then(|| {
" This file was written by a newer version of Indicatrix Cut; some settings \
may not have been understood and could be lost on your next save."
.to_string()
});
let is_mismatch = is_mismatch || material_still_unresolved || loaded.written_by_newer_version;
let printed_proportions = loaded.printed_proportions;
state.borrow_mut().replace_wholesale(EditorState {
deep_solve_result_generation: None,
design: loaded.design,
history: History::new(),
printed_proportions,
generation: Arc::new(AtomicU64::new(0)),
design_epoch: Arc::new(AtomicU64::new(0)),
saved_generation: 0,
pending_unsaved_action: None,
deep_solve: None,
optimize: None,
pending_optimize: Arc::new(Mutex::new(None)),
asc_filename: Some(asc_filename),
original_asc_text: Some(asc_text),
pending_gear_remap: None,
pending_retarget: None,
multi_selected: std::collections::BTreeSet::new(),
last_pushed_scratch: RefCell::new(PushedScratch::default()),
material_combo_cache: RefCell::new(MaterialComboCache::default()),
source_entry_id: None,
used_placeholder: false,
has_design: true,
});
finish_state_replace(ui, render_ctx, preview_state, solid_last_solved, state);
show_toast(
ui,
&format!(
"Loaded '{native_path_display}'. {outcome_note}{}{}",
material_note.unwrap_or_default(),
newer_version_note.unwrap_or_default()
),
if is_mismatch { "warning" } else { "success" },
);
}
pub(super) fn plain_load_outcome_text(
fingerprint: &FingerprintCheck,
tier_overlay: &TierOverlay,
) -> String {
if matches!(fingerprint, FingerprintCheck::Match) {
return match tier_overlay {
TierOverlay::Applied | TierOverlay::AppliedDespiteMismatch => {
"Your saved meet constraints were restored.".to_string()
}
TierOverlay::SkippedTierCountMismatch {
native_tiers,
asc_tiers,
} => format!(
"This file's saved tier count ({native_tiers}) does not match the .asc file's \
({asc_tiers}), so your saved meet constraints could not be restored."
),
TierOverlay::SkippedFingerprintMismatch => {
"Your saved meet constraints were restored.".to_string()
}
TierOverlay::AppliedFromDraft => "Loaded from an unsolved draft: the masts shown are \
placeholders, not a real solve."
.to_string(),
};
}
match tier_overlay {
TierOverlay::AppliedDespiteMismatch => "The .asc file changed since this sidecar was \
saved, so its geometry was used as-is; your saved meet constraints were re-applied \
anyway, at your request, and may no longer line up with the changed tiers."
.to_string(),
_ => "The .asc file changed since this sidecar was saved, so its geometry was used as-is \
and your saved meet constraints were not restored."
.to_string(),
}
}