use super::{
RETARGET_ASYNC, apply_ghost_preview_or_revert,
material::{initial_target_index, resolve_target_selection, resolved_material_from_selection},
optimize_run::start_optimize_run,
proposal_view::{
RetargetView, push_retarget_view, push_target_error, push_target_readout, retarget_view,
},
sync_embedded_comparison,
};
use crate::{
MainWindow, RetargetModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
edit_intent::{EditIntent, EditIntentQueue},
retarget::{CrownShift, RetargetMode, RetargetProposal},
stale::{self, ResultKind},
stall_guard::stall_guard,
state::{EditorState, design_material_options},
view,
},
render::render_visibility::recompute_tab_visible,
solid_preview::preview_state::SolidPreviewState,
},
};
use indicatrix_cut_core::Design;
use slint::{ComponentHandle, ModelRc, SharedString, VecModel};
use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex, atomic::Ordering as AtomicOrdering},
};
pub(super) fn rebuild_and_push(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
design: &Design,
crown: CrownShift,
mode: RetargetMode,
) -> Option<RetargetProposal> {
let combo_index = ui.global::<RetargetModel>().get_target_material_index();
let ri_text = ui.global::<RetargetModel>().get_target_ri_override_text();
let (selection, target, custom_materials) = {
let ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match resolve_target_selection(design, &ctx.custom_materials, combo_index, &ri_text) {
Ok(selection) => {
let target = resolved_material_from_selection(&selection, &ctx.custom_materials);
let custom_materials = ctx.custom_materials.as_ref().clone();
(selection, target, custom_materials)
}
Err(message) => {
drop(ctx);
push_target_error(ui, &message);
return None;
}
}
};
push_target_readout(ui, &selection, &target);
let (view, proposal) = retarget_view(design, &target, crown, mode, &custom_materials);
push_retarget_view(ui, view);
proposal
}
pub(in crate::gui::editor) fn setup_retarget_open_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>().on_open(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
RETARGET_ASYNC.with(|cell| cell.borrow_mut().cancel_and_supersede());
stale::clear(ResultKind::Retarget);
let mut st = state.borrow_mut();
ui.global::<RetargetModel>().set_mode_index(0);
ui.global::<RetargetModel>().set_crown_fraction(0.0);
ui.global::<RetargetModel>().set_scale_crown_by_ratio(false);
ui.global::<RetargetModel>().set_is_busy(false);
ui.global::<RetargetModel>().set_optimize_evaluations(0);
ui.global::<RetargetModel>().set_optimize_max_evaluations(0);
ui.global::<RetargetModel>().set_preview_enabled(false);
let options = {
let ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
design_material_options(&ctx.custom_materials)
};
ui.global::<RetargetModel>()
.set_target_material_index(initial_target_index(&st.design.material, &options));
ui.global::<RetargetModel>().set_target_ri_override_text(
st.design
.material
.refractive_index_override
.map(|v| format!("{v}"))
.unwrap_or_default()
.into(),
);
ui.global::<RetargetModel>()
.set_material_options(ModelRc::new(VecModel::from(
options
.into_iter()
.map(SharedString::from)
.collect::<Vec<_>>(),
)));
let generation = st.generation.load(AtomicOrdering::Relaxed);
let proposal = rebuild_and_push(
&ui,
&render_ctx,
&st.design,
CrownShift::default(),
RetargetMode::Shift,
);
let solved = proposal.is_some();
if let Some(proposal) = proposal {
stale::stamp(ResultKind::Retarget, generation);
st.pending_retarget = Some((proposal, generation));
} else {
st.pending_retarget = None;
}
drop(st);
ui.global::<RetargetModel>().set_is_open(true);
recompute_tab_visible(&ui, &render_ctx);
sync_embedded_comparison(&ui, solved);
});
}
pub(in crate::gui::editor) fn setup_retarget_proposal_changed_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &view::SolidLastSolved,
) {
{
let intent_queue = {
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();
EditIntentQueue::new(move |_intent| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
apply_retarget_shift_intent(
&ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
);
})
};
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_proposal_changed(move || {
stall_guard("retarget_on_proposal_changed", || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
RETARGET_ASYNC.with(|cell| cell.borrow_mut().cancel_and_supersede());
if ui.global::<RetargetModel>().get_mode_index() == 1 {
let mut st = state.borrow_mut();
let crown = CrownShift {
fraction: f64::from(ui.global::<RetargetModel>().get_crown_fraction()),
scale_by_ratio: ui.global::<RetargetModel>().get_scale_crown_by_ratio(),
};
st.pending_retarget = None;
start_optimize_run(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&mut st,
crown,
);
} else {
intent_queue.post(EditIntent::RetargetCrown);
}
});
});
}
{
let state = Rc::clone(state);
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>().on_cancel_optimize(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
state.borrow_mut().pending_retarget = None;
cancel_optimize_run(&ui);
});
}
}
pub(super) fn cancel_optimize_run(ui: &MainWindow) {
RETARGET_ASYNC.with(|cell| cell.borrow_mut().cancel_and_supersede());
stale::clear(ResultKind::Retarget);
ui.global::<RetargetModel>().set_is_busy(false);
push_retarget_view(
ui,
RetargetView {
rows: Vec::new(),
notes: vec!["Optimize cancelled.".to_string()],
anchored_errors: Vec::new(),
solve_error: String::new(),
},
);
sync_embedded_comparison(ui, false);
}
fn apply_retarget_shift_intent(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &view::SolidLastSolved,
) {
let mut st = state.borrow_mut();
let crown = CrownShift {
fraction: f64::from(ui.global::<RetargetModel>().get_crown_fraction()),
scale_by_ratio: ui.global::<RetargetModel>().get_scale_crown_by_ratio(),
};
ui.global::<RetargetModel>().set_is_busy(false);
let generation = st.generation.load(AtomicOrdering::Relaxed);
let proposal = rebuild_and_push(ui, render_ctx, &st.design, crown, RetargetMode::Shift);
if !apply_ghost_preview_or_revert(ui, render_ctx, preview_state, &st.design, proposal.as_ref())
{
view::submit_preview_replan(
ui,
render_ctx,
preview_state,
solid_last_solved,
&st,
BTreeSet::new(),
false,
);
}
let solved = proposal.is_some();
if let Some(proposal) = proposal {
stale::stamp(ResultKind::Retarget, generation);
st.pending_retarget = Some((proposal, generation));
} else {
stale::clear(ResultKind::Retarget);
st.pending_retarget = None;
}
drop(st);
sync_embedded_comparison(ui, solved);
}