use super::{
RETARGET_ASYNC, apply_ghost_preview_or_revert,
check_run::{begin_check, reset_check},
material::{initial_target_index, resolve_target_selection, resolved_material_from_selection},
optimize_run::{
cancel_optimize_run, prepare_for_plan, push_options, refresh_estimate, reset_search_ui,
select_candidate, start_search,
},
proposal_view::{plan_view, push_retarget_view, push_target_error, push_target_readout},
sync_embedded_comparison,
};
use crate::{
MainWindow, RetargetModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
edit_intent::{EditIntent, EditIntentQueue},
retarget::{CrownShift, RetargetPlan, RetargetProposal, build_plan},
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::optics::materials::GemMaterial;
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},
};
const NOTHING_TO_RETARGET: &str =
"Nothing to retarget: no crown or pavilion facet has a slope to shift.";
const SEED_NOTE: &str = "These are the Shift angles the search starts from. Press Search to look for better ones; the options it finds are listed below.";
fn resolve_plan(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
design: &Design,
crown: CrownShift,
) -> Option<(RetargetPlan, Vec<GemMaterial>)> {
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 plan = build_plan(design, &target, crown, &custom_materials);
Some((plan, custom_materials))
}
pub(super) fn rebuild_and_push(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
design: &Design,
generation: u64,
crown: CrownShift,
) -> Option<RetargetProposal> {
let (plan, custom_materials) = resolve_plan(ui, render_ctx, design, crown)?;
let mut view = plan_view(&plan).with_tier_names(design);
if plan.is_empty() {
view.rows.clear();
view.notes.push(NOTHING_TO_RETARGET.to_string());
push_retarget_view(ui, view);
reset_check(ui);
return None;
}
push_retarget_view(ui, view);
let proposal = plan.proposal();
begin_check(ui, design, plan, &custom_materials, generation);
Some(proposal)
}
fn rebuild_seed(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
design: &Design,
generation: u64,
crown: CrownShift,
) {
let Some((plan, _)) = resolve_plan(ui, render_ctx, design, crown) else {
return;
};
reset_check(ui);
let mut view = plan_view(&plan).with_tier_names(design);
if plan.is_empty() {
view.rows.clear();
view.notes.push(NOTHING_TO_RETARGET.to_string());
push_retarget_view(ui, view);
return;
}
view.notes.insert(0, SEED_NOTE.to_string());
push_retarget_view(ui, view);
prepare_for_plan(ui, design, &plan, generation);
}
pub(in crate::gui::editor) fn setup_retarget_open_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
) {
super::advanced_in_use::setup_advanced_in_use_callback(ui);
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_crown_follows_pavilion(true);
ui.global::<RetargetModel>().set_keep_look(true);
ui.global::<RetargetModel>().set_girdle_allowance(true);
reset_search_ui(&ui);
ui.global::<RetargetModel>().set_free_tier_count(0);
push_options(&ui);
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,
generation,
CrownShift::default(),
);
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_intent(&ui, &state, &render_ctx, &preview_state, &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());
reset_search_ui(&ui);
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);
});
}
{
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>().on_search(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
start_search(&ui, &state, &render_ctx);
});
}
{
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>()
.on_select_candidate(move |index| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
if let Ok(index) = usize::try_from(index) {
select_candidate(&ui, index);
}
});
}
{
let ui_weak = ui.as_weak();
ui.global::<RetargetModel>().on_settings_changed(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
refresh_estimate(&ui);
});
}
}
fn apply_retarget_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(),
follow_pavilion: ui.global::<RetargetModel>().get_crown_follows_pavilion(),
};
ui.global::<RetargetModel>().set_is_busy(false);
let generation = st.generation.load(AtomicOrdering::Relaxed);
if ui.global::<RetargetModel>().get_mode_index() == 1 {
st.pending_retarget = None;
stale::clear(ResultKind::Retarget);
rebuild_seed(ui, render_ctx, &st.design, generation, crown);
drop(st);
sync_embedded_comparison(ui, false);
return;
}
let proposal = rebuild_and_push(ui, render_ctx, &st.design, generation, crown);
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);
}