use super::{
RETARGET_ASYNC,
check_run::{ApplyGate, apply_gate, cached_original_solved, reset_check},
material::{target_display_name, target_material_selection},
optimize_run::forget_results,
};
use crate::{
CompareModel, MainWindow, RetargetModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
relation_ui::edit_error_text,
retarget::{self, AnchorChange, RetargetProposal},
stale::{self, ResultKind},
stall_guard::stall_guard,
state::EditorState,
view,
},
render::render_visibility::recompute_tab_visible,
show_toast,
solid_preview::preview_state::SolidPreviewState,
},
};
use indicatrix::geometry::meet_solver::SolvedTier;
use indicatrix_cut_core::{Edit, EditError, MaterialSelection};
use slint::ComponentHandle;
use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex, atomic::Ordering as AtomicOrdering},
};
pub(super) enum RetargetApplyError {
Stale,
Edit(EditError),
}
pub(super) fn apply_pending_retarget(
state: &mut EditorState,
pending: (RetargetProposal, u64),
material_change: Option<MaterialSelection>,
anchors: &[AnchorChange],
) -> Result<usize, RetargetApplyError> {
let (proposal, started_generation) = pending;
if state.generation.load(AtomicOrdering::Relaxed) != started_generation {
return Err(RetargetApplyError::Stale);
}
let angle_edit = retarget::apply_with_anchors(&state.design, &proposal, anchors);
let applied = proposal.rows.len();
let edit = match material_change {
Some(material) => {
let mut edits = match angle_edit {
Edit::Batch(edits) => edits,
single => vec![single],
};
edits.push(Edit::SetMaterial { material });
Edit::Batch(edits)
}
None => angle_edit,
};
state
.apply(edit)
.map(|()| applied)
.map_err(RetargetApplyError::Edit)
}
pub(in crate::gui::editor) fn setup_retarget_apply_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &view::SolidLastSolved,
) {
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);
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>().on_apply(move || {
stall_guard("retarget_on_apply", || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let mut st = state.borrow_mut();
let optimize_mode = ui.global::<RetargetModel>().get_mode_index() == 1;
let Some(pending) = take_pending(&ui, &mut st, optimize_mode) else {
return;
};
let anchors = match apply_gate(pending.1) {
ApplyGate::Open(anchors) => anchors,
ApplyGate::Checking => {
keep_pending(&mut st, optimize_mode, pending);
show_toast(
&ui,
"Still checking this change -- try again in a moment.",
"info",
);
return;
}
ApplyGate::Blocked(headline) => {
keep_pending(&mut st, optimize_mode, pending);
show_toast(
&ui,
&format!(
"{headline} Change the settings, or use Compare to see what goes wrong."
),
"error",
);
return;
}
};
stale::clear(ResultKind::Retarget);
let custom = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.custom_materials
.as_ref()
.clone();
let combo_index = ui.global::<RetargetModel>().get_target_material_index();
let ri_text = ui.global::<RetargetModel>().get_target_ri_override_text();
let material_selection =
target_material_selection(&st.design, &custom, combo_index, &ri_text);
let target_name = target_display_name(&material_selection);
let material_change =
(material_selection != st.design.material).then_some(material_selection);
let original_design = st.design.clone();
let original_solved =
original_solve_for_snapshot(cached_original_solved(pending.1), &original_design);
match apply_pending_retarget(&mut st, pending, material_change, &anchors) {
Ok(applied) => {
super::snapshot::hold_original_before_retarget(
&ui,
original_design,
original_solved,
&super::snapshot::original_snapshot_label(&target_name),
);
drop(st);
view::refresh_all_now(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&state,
false,
);
ui.global::<RetargetModel>().set_is_open(false);
reset_check(&ui);
forget_results();
release_modal_viewport(&ui, &render_ctx);
show_toast(
&ui,
&format!("Retargeted {applied} tier(s) for {target_name}."),
"success",
);
}
Err(RetargetApplyError::Stale) => {
show_toast(
&ui,
"The design changed since this retarget proposal was built -- \
re-open Retarget for material.",
"error",
);
}
Err(RetargetApplyError::Edit(e)) => {
let text = edit_error_text(&mut st, &e);
show_toast(&ui, &text, "error");
}
}
});
});
}
pub(super) fn original_solve_for_snapshot(
cached: Option<Vec<SolvedTier>>,
design: &indicatrix_cut_core::Design,
) -> Option<Vec<SolvedTier>> {
cached
.filter(|solved| solved.len() == design.tiers.len())
.or_else(|| design.solve().ok())
}
fn take_pending(
ui: &MainWindow,
st: &mut EditorState,
optimize_mode: bool,
) -> Option<(RetargetProposal, u64)> {
let picked = RETARGET_ASYNC.with(|cell| cell.borrow_mut().pending.take());
let pending = if optimize_mode {
picked
} else {
st.pending_retarget.take().or(picked)
};
if pending.is_none() && optimize_mode {
show_toast(
ui,
"Search for options first, then pick one to apply.",
"info",
);
}
pending
}
fn keep_pending(st: &mut EditorState, optimize_mode: bool, pending: (RetargetProposal, u64)) {
if optimize_mode {
RETARGET_ASYNC.with(|cell| cell.borrow_mut().pending = Some(pending));
} else {
st.pending_retarget = Some(pending);
}
}
fn release_modal_viewport(ui: &MainWindow, render_ctx: &Arc<Mutex<RenderContext>>) {
ui.global::<CompareModel>().invoke_close_embedded();
recompute_tab_visible(ui, render_ctx);
}
pub(in crate::gui::editor) fn setup_retarget_close_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &view::SolidLastSolved,
) {
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);
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>().on_close(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
RETARGET_ASYNC.with(|cell| cell.borrow_mut().cancel_and_supersede());
reset_check(&ui);
state.borrow_mut().pending_retarget = None;
stale::clear(ResultKind::Retarget);
ui.global::<RetargetModel>().set_is_open(false);
release_modal_viewport(&ui, &render_ctx);
let st = state.borrow();
revert_ghost_to_live_design(&ui, &render_ctx, &preview_state, &solid_last_solved, &st);
});
}
pub(super) fn revert_ghost_to_live_design(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &view::SolidLastSolved,
st: &EditorState,
) {
view::submit_preview_replan(
ui,
render_ctx,
preview_state,
solid_last_solved,
st,
BTreeSet::new(),
false,
);
}