use super::{
open_commit::{
DesignFileLoad, LoadedNativeOutcome, ReplaceGuard, SelfContainedLoad, commit_loaded_native,
open_converted_design, open_design_file, open_native_self_contained, open_plain_asc,
},
open_picker::{PickedNative, PickedPair, pick_design_then, read_picked_path_then},
};
use crate::{
EditorModel, MainWindow,
bridge::render_thread::RenderContext,
gui::{
editor::state::{EditorState, PendingUnsavedAction},
show_toast,
solid_preview::preview_state::SolidPreviewState,
},
};
use indicatrix_cut_core::{FingerprintCheck, TierOverlay, load_paired};
use slint::ComponentHandle;
use std::{
cell::RefCell,
path::PathBuf,
rc::Rc,
sync::{Arc, Mutex},
};
pub(in crate::gui::editor) fn setup_open_native_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
) {
setup_mismatch_dialog_callbacks(ui, state, render_ctx, preview_state, solid_last_solved);
let state_open = Rc::clone(state);
let render_ctx_open = Arc::clone(render_ctx);
let preview_state_open = Arc::clone(preview_state);
let solid_last_solved_open = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_open_native(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
crate::gui::editor::stall_guard::stall_guard("open_native", || {
if state_open.borrow().is_dirty() {
state_open.borrow_mut().pending_unsaved_action =
Some(PendingUnsavedAction::OpenNative);
ui.global::<EditorModel>().set_unsaved_dialog_message(
"Opening a file will discard the current design's unsaved changes.".into(),
);
ui.global::<EditorModel>().set_unsaved_dialog_open(true);
return;
}
do_open_native(
&ui,
&state_open,
&render_ctx_open,
&preview_state_open,
&solid_last_solved_open,
);
});
});
let state_recent = Rc::clone(state);
let render_ctx_recent = Arc::clone(render_ctx);
let preview_state_recent = Arc::clone(preview_state);
let solid_last_solved_recent = Arc::clone(solid_last_solved);
let ui_weak_recent = ui.as_weak();
ui.on_open_recent_native_file(move |path| {
let Some(ui) = ui_weak_recent.upgrade() else {
return;
};
crate::gui::editor::stall_guard::stall_guard("open_recent_native_file", || {
open_recent_native_path(
&ui,
&state_recent,
&render_ctx_recent,
&preview_state_recent,
&solid_last_solved_recent,
PathBuf::from(path.as_str()),
);
});
});
}
pub(in crate::gui::editor) fn open_recent_native_path(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
native_path: PathBuf,
) {
if state.borrow().is_dirty() {
show_toast(
ui,
"Save or discard the current design's unsaved changes before opening a recent file.",
"error",
);
return;
}
let guard = ReplaceGuard::capture(&state.borrow(), false);
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
read_picked_path_then(ui, native_path, move |ui, picked| {
dispatch_picked(
ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
guard,
picked,
);
});
}
fn dispatch_picked(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
guard: ReplaceGuard,
picked: Option<PickedNative>,
) {
match picked {
Some(_) if !guard.allows_replace(ui, state) => {}
Some(PickedNative::Pair(picked)) => {
open_native_pair(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
*picked,
guard,
);
}
Some(PickedNative::Design {
native_path,
loaded,
}) => open_design_file(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
DesignFileLoad {
native_path,
loaded: *loaded,
},
),
Some(PickedNative::AscOnly { asc_path, asc_text }) => open_plain_asc(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
&asc_path,
&asc_text,
),
Some(PickedNative::SelfContained {
native_path,
loaded,
asc_filename,
}) => open_native_self_contained(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
SelfContainedLoad {
native_path: &native_path,
loaded: *loaded,
asc_filename: &asc_filename,
},
),
Some(PickedNative::Converted(converted)) => open_converted_design(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
converted,
),
None => {}
}
}
struct PendingMismatch {
native_path_display: String,
asc_filename: String,
asc_text: String,
native_text: String,
guard: ReplaceGuard,
}
thread_local! {
static PENDING_MISMATCH: RefCell<Option<PendingMismatch>> = const { RefCell::new(None) };
}
pub(in crate::gui::editor) fn do_open_native(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
) {
let guard = ReplaceGuard::capture(&state.borrow(), true);
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
pick_design_then(ui, move |ui, picked| {
dispatch_picked(
ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
guard,
picked,
);
});
}
fn open_native_pair(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
picked: PickedPair,
guard: ReplaceGuard,
) {
let PickedPair {
native_path,
parsed_native,
asc_text,
native_text,
} = picked;
match load_paired(&asc_text, &native_text, false) {
Ok(loaded) => {
if matches!(loaded.tier_overlay, TierOverlay::SkippedFingerprintMismatch) {
let can_apply = parsed_native.tiers.len() == loaded.design.tiers.len();
PENDING_MISMATCH.with(|cell| {
*cell.borrow_mut() = Some(PendingMismatch {
native_path_display: native_path.display().to_string(),
asc_filename: parsed_native.asc_filename,
asc_text,
native_text,
guard,
});
});
ui.global::<EditorModel>()
.set_mismatch_dialog_can_apply(can_apply);
ui.global::<EditorModel>().set_mismatch_dialog_open(true);
return;
}
let is_mismatch = !matches!(loaded.fingerprint, FingerprintCheck::Match);
commit_loaded_native(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
LoadedNativeOutcome {
loaded,
native_path_display: native_path.display().to_string(),
asc_filename: parsed_native.asc_filename,
asc_text,
is_mismatch,
},
);
}
Err(e) => show_toast(ui, &format!("Cannot open: {e}"), "error"),
}
}
fn setup_mismatch_dialog_callbacks(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &crate::gui::editor::view::SolidLastSolved,
) {
let state_apply = Rc::clone(state);
let render_ctx_apply = Arc::clone(render_ctx);
let preview_state_apply = Arc::clone(preview_state);
let solid_last_solved_apply = Arc::clone(solid_last_solved);
let ui_weak_apply = ui.as_weak();
ui.global::<EditorModel>()
.on_mismatch_dialog_apply_anyway(move || {
let Some(ui) = ui_weak_apply.upgrade() else {
return;
};
ui.global::<EditorModel>().set_mismatch_dialog_open(false);
let Some(pending) = PENDING_MISMATCH.with(RefCell::take) else {
return;
};
if !pending.guard.allows_replace(&ui, &state_apply) {
return;
}
match load_paired(&pending.asc_text, &pending.native_text, true) {
Ok(loaded) => commit_loaded_native(
&ui,
&state_apply,
&render_ctx_apply,
&preview_state_apply,
&solid_last_solved_apply,
LoadedNativeOutcome {
loaded,
native_path_display: pending.native_path_display,
asc_filename: pending.asc_filename,
asc_text: pending.asc_text,
is_mismatch: true,
},
),
Err(e) => show_toast(&ui, &format!("Cannot open: {e}"), "error"),
}
});
let state_asc_only = Rc::clone(state);
let render_ctx_asc_only = Arc::clone(render_ctx);
let preview_state_asc_only = Arc::clone(preview_state);
let solid_last_solved_asc_only = Arc::clone(solid_last_solved);
let ui_weak_asc_only = ui.as_weak();
ui.global::<EditorModel>()
.on_mismatch_dialog_asc_only(move || {
let Some(ui) = ui_weak_asc_only.upgrade() else {
return;
};
ui.global::<EditorModel>().set_mismatch_dialog_open(false);
let Some(pending) = PENDING_MISMATCH.with(RefCell::take) else {
return;
};
if !pending.guard.allows_replace(&ui, &state_asc_only) {
return;
}
match load_paired(&pending.asc_text, &pending.native_text, false) {
Ok(loaded) => commit_loaded_native(
&ui,
&state_asc_only,
&render_ctx_asc_only,
&preview_state_asc_only,
&solid_last_solved_asc_only,
LoadedNativeOutcome {
loaded,
native_path_display: pending.native_path_display,
asc_filename: pending.asc_filename,
asc_text: pending.asc_text,
is_mismatch: true,
},
),
Err(e) => show_toast(&ui, &format!("Cannot open: {e}"), "error"),
}
});
ui.global::<EditorModel>()
.on_mismatch_dialog_cancel(move || {
PENDING_MISMATCH.with(|cell| *cell.borrow_mut() = None);
});
}