use crate::{
EditorModel, MainWindow, RetargetModel, RetargetRowItem, ViewportModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
compare::snapshot_summary,
native_io,
state::{EditorState, design_label_text},
view,
},
show_toast,
tutorial_events::raise,
},
};
use indicatrix::{geometry::meet_solver::SolvedTier, optics::LightingPreset};
use indicatrix_cut_core::{Design, TierDelta, diff_tiers};
use indicatrix_editor::{
guide::solving_events::{COMPARE_OPENED, SNAPSHOT_TAKEN},
snapshot::DesignSnapshot,
};
use slint::{Color, ComponentHandle, ModelRc, VecModel};
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, Mutex, PoisonError},
};
thread_local! {
static DESIGN_SNAPSHOT: RefCell<Option<DesignSnapshot>> = const { RefCell::new(None) };
}
#[must_use]
pub(in crate::gui::editor) fn snapshot_for_compare() -> Option<(Design, String)> {
DESIGN_SNAPSHOT.with(|cell| {
cell.borrow()
.as_ref()
.map(|snapshot| (snapshot.design.clone(), snapshot.label.clone()))
})
}
pub(in crate::gui::editor) fn hold_reference_snapshot(
ui: &MainWindow,
design: Design,
solved: Vec<SolvedTier>,
label: &str,
) {
DESIGN_SNAPSHOT.with(|cell| {
*cell.borrow_mut() = Some(DesignSnapshot {
design,
solved: Some(solved),
label: label.to_string(),
from_retarget_apply: false,
});
});
ui.global::<EditorModel>().set_has_snapshot(true);
}
#[must_use]
pub(in crate::gui::editor) fn original_snapshot_label(target: &str) -> String {
format!("Before retarget to {target}")
}
#[must_use]
pub(in crate::gui::editor) fn original_for_compare() -> Option<(Design, String)> {
DESIGN_SNAPSHOT.with(|cell| {
cell.borrow()
.as_ref()
.filter(|snapshot| snapshot.from_retarget_apply)
.map(|snapshot| (snapshot.design.clone(), snapshot.label.clone()))
})
}
pub(in crate::gui::editor) fn hold_original_before_retarget(
ui: &MainWindow,
design: Design,
solved: Option<Vec<SolvedTier>>,
label: &str,
) {
DESIGN_SNAPSHOT.with(|cell| {
*cell.borrow_mut() = Some(DesignSnapshot {
design,
solved,
label: label.to_string(),
from_retarget_apply: true,
});
});
ui.global::<EditorModel>().set_has_snapshot(true);
}
pub(super) use indicatrix_editor::snapshot::diff_row_view;
#[must_use]
pub(super) fn diff_rows_from_deltas(deltas: &[TierDelta]) -> Vec<RetargetRowItem> {
deltas
.iter()
.map(diff_row_view)
.map(|v| RetargetRowItem {
tier_index: i32::try_from(v.tier_index).unwrap_or(i32::MAX),
block: "".into(),
name: v.name.into(),
old_angle: v.old_angle.into(),
new_angle: v.new_angle.into(),
margin: v.mast_delta.into(),
risk_label: v.status_label.into(),
risk_color: Color::from_rgb_u8(v.status_rgb.0, v.status_rgb.1, v.status_rgb.2),
})
.collect()
}
fn store_design_snapshot(
ui: &MainWindow,
design: &Design,
solved: Option<Vec<SolvedTier>>,
label: &str,
) {
DESIGN_SNAPSHOT.with(|cell| {
*cell.borrow_mut() = Some(DesignSnapshot {
design: design.clone(),
solved,
label: label.to_string(),
from_retarget_apply: false,
});
});
ui.global::<EditorModel>().set_has_snapshot(true);
show_toast(ui, &format!("Snapshot taken: \"{label}\"."), "success");
raise(ui, SNAPSHOT_TAKEN);
}
fn show_compare_to_snapshot(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
snapshot: &DesignSnapshot,
design: &Design,
current_label: &str,
current_solved: Option<&[SolvedTier]>,
) {
let deltas = diff_tiers(
&snapshot.design.tiers,
snapshot.solved.as_deref(),
&design.tiers,
current_solved,
);
let rows = diff_rows_from_deltas(&deltas);
let custom = render_ctx
.lock()
.unwrap_or_else(PoisonError::into_inner)
.custom_materials
.as_ref()
.clone();
let lighting =
LightingPreset::from_index(ui.global::<ViewportModel>().get_selected_lighting_index());
let optical = snapshot_summary(
&snapshot.design,
snapshot.solved.as_deref(),
design,
current_solved,
&custom,
lighting,
);
ui.global::<RetargetModel>()
.set_compare_rows(ModelRc::new(VecModel::from(rows)));
ui.global::<RetargetModel>().set_compare_label(
format!(
"\"{}\" vs. current (\"{current_label}\")\n{optical}",
snapshot.label
)
.into(),
);
ui.global::<RetargetModel>().set_compare_open(true);
raise(ui, COMPARE_OPENED);
}
pub(in crate::gui::editor) fn setup_snapshot_callbacks(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
solid_last_solved: &view::SolidLastSolved,
) {
{
let state = Rc::clone(state);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_snapshot_design(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let (design, label, cached) = {
let st = state.borrow();
let cached = solid_last_solved
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
.filter(|(_, s)| s.len() == st.design.tiers.len())
.map(|(_, s)| s);
(
Arc::new(st.design.clone()),
design_label_text(st.asc_filename.as_deref()),
cached,
)
};
if let Some(solved) = cached {
store_design_snapshot(&ui, &design, Some(solved), &label);
return;
}
native_io::resolve_solved_then(&ui, design, move |ui, design, solved| {
store_design_snapshot(ui, &design, solved.ok(), &label);
});
});
}
{
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_compare_to_snapshot(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let snapshot = DESIGN_SNAPSHOT.with(|cell| cell.borrow().clone());
let Some(snapshot) = snapshot else {
show_toast(
&ui,
"No snapshot taken yet -- use Snapshot Design first.",
"error",
);
return;
};
let (design, current_label, cached) = {
let st = state.borrow();
let cached = solid_last_solved
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
.filter(|(_, s)| s.len() == st.design.tiers.len())
.map(|(_, s)| s);
(
Arc::new(st.design.clone()),
design_label_text(st.asc_filename.as_deref()),
cached,
)
};
if let Some(current_solved) = &cached {
show_compare_to_snapshot(
&ui,
&render_ctx,
&snapshot,
&design,
¤t_label,
Some(current_solved),
);
return;
}
let render_ctx = Arc::clone(&render_ctx);
native_io::resolve_solved_then(&ui, design, move |ui, design, solved| {
show_compare_to_snapshot(
ui,
&render_ctx,
&snapshot,
&design,
¤t_label,
solved.ok().as_deref(),
);
});
});
}
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>().on_compare_close(move || {
if let Some(ui) = ui_weak.upgrade() {
ui.global::<RetargetModel>().set_compare_open(false);
}
});
}