use super::session::{CompareOrigin, SideInput, resolve_metrics_material, resolve_side_material};
use crate::{
MainWindow,
bridge::render_thread::RenderContext,
gui::editor::{
callbacks::{self, RetargetCompareInputs, RetargetKeepGuard},
state::EditorState,
view::build_optimize_preview_design,
},
};
use indicatrix::optics::materials::GemMaterial;
use indicatrix_cut_core::{Design, OptimizeOutcome};
use std::sync::{Arc, Mutex, PoisonError, atomic::Ordering as AtomicOrdering};
#[derive(Debug, Clone, PartialEq)]
pub(super) enum KeepGuard {
Retarget(Box<RetargetKeepGuard>),
Optimize {
outcome: Box<OptimizeOutcome>,
generation: u64,
},
Snapshot,
Variants,
}
pub(in crate::gui::editor) struct VariantSide {
pub(in crate::gui::editor) design: Design,
pub(in crate::gui::editor) label: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(super) enum Reference {
#[default]
Current,
Original,
}
impl Reference {
pub(super) const fn index(self) -> i32 {
match self {
Self::Current => 0,
Self::Original => 1,
}
}
pub(super) const fn from_index(index: i32) -> Self {
if index == 1 {
Self::Original
} else {
Self::Current
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(super) struct ReferenceOffer {
pub(super) selected: Reference,
pub(super) original_label: Option<String>,
}
#[must_use]
pub(super) fn reference_options(offer: &ReferenceOffer) -> Vec<String> {
let mut options = vec!["Current design".to_string()];
if let Some(label) = &offer.original_label {
options.push(format!("Original ({label})"));
}
options
}
pub(super) struct OpenRequest {
pub(super) reference: ReferenceOffer,
pub(super) origin: CompareOrigin,
pub(super) guard: KeepGuard,
pub(super) before: SideInput,
pub(super) after: SideInput,
}
fn custom_materials(render_ctx: &Arc<Mutex<RenderContext>>) -> Vec<GemMaterial> {
render_ctx
.lock()
.unwrap_or_else(PoisonError::into_inner)
.custom_materials
.as_ref()
.clone()
}
fn side(design: Design, label: String, custom: &[GemMaterial]) -> SideInput {
let material = resolve_side_material(&design, custom);
let metrics_material = resolve_metrics_material(&design, custom);
SideInput {
design,
label,
material,
metrics_material,
}
}
pub(super) fn retarget_request(
ui: &MainWindow,
st: &EditorState,
render_ctx: &Arc<Mutex<RenderContext>>,
) -> Result<OpenRequest, String> {
let inputs: RetargetCompareInputs = callbacks::retarget_compare_inputs(ui, st, render_ctx)?;
let custom = custom_materials(render_ctx);
Ok(OpenRequest {
reference: ReferenceOffer::default(),
origin: CompareOrigin::Retarget,
guard: KeepGuard::Retarget(Box::new(inputs.guard)),
before: side(inputs.current, "Current design".to_string(), &custom),
after: side(inputs.candidate, inputs.after_label, &custom),
})
}
pub(super) fn pick_before(
current: &Design,
original: Option<(Design, String)>,
wanted: Reference,
) -> (Design, String, ReferenceOffer) {
match original {
Some((design, label)) => {
let offer = ReferenceOffer {
selected: wanted,
original_label: Some(label.clone()),
};
match wanted {
Reference::Original => (design, format!("Original \"{label}\""), offer),
Reference::Current => (current.clone(), "Current design".to_string(), offer),
}
}
None => (
current.clone(),
"Current design".to_string(),
ReferenceOffer::default(),
),
}
}
pub(super) fn optimize_request(
st: &EditorState,
render_ctx: &Arc<Mutex<RenderContext>>,
reference: Reference,
) -> Result<OpenRequest, String> {
let pending = st
.pending_optimize
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone();
let (outcome, generation) =
pending.ok_or_else(|| "No Optimize result to compare yet.".to_string())?;
if generation != st.generation.load(AtomicOrdering::Relaxed) {
return Err("The design changed since Optimize ran -- re-run Optimize first.".to_string());
}
let candidate = build_optimize_preview_design(&st.design, &outcome);
let custom = custom_materials(render_ctx);
let (before_design, before_label, offer) =
pick_before(&st.design, callbacks::original_for_compare(), reference);
Ok(OpenRequest {
reference: offer,
origin: CompareOrigin::Optimize,
guard: KeepGuard::Optimize {
outcome: Box::new(outcome),
generation,
},
before: side(before_design, before_label, &custom),
after: side(candidate, "Optimize candidate".to_string(), &custom),
})
}
pub(super) fn snapshot_request(
st: &EditorState,
render_ctx: &Arc<Mutex<RenderContext>>,
) -> Result<OpenRequest, String> {
let (snapshot, label) = callbacks::snapshot_for_compare()
.ok_or_else(|| "No snapshot taken yet -- use Snapshot Design first.".to_string())?;
let custom = custom_materials(render_ctx);
Ok(OpenRequest {
reference: ReferenceOffer::default(),
origin: CompareOrigin::Snapshot,
guard: KeepGuard::Snapshot,
before: side(snapshot, format!("Snapshot \"{label}\""), &custom),
after: side(st.design.clone(), "Current design".to_string(), &custom),
})
}
pub(super) fn variants_request(
render_ctx: &Arc<Mutex<RenderContext>>,
first: VariantSide,
second: VariantSide,
) -> OpenRequest {
let custom = custom_materials(render_ctx);
OpenRequest {
reference: ReferenceOffer::default(),
origin: CompareOrigin::Variants,
guard: KeepGuard::Variants,
before: side(first.design, first.label, &custom),
after: side(second.design, second.label, &custom),
}
}
#[must_use]
pub(super) fn guard_still_matches(
guard: &KeepGuard,
ui: &MainWindow,
st: &EditorState,
render_ctx: &Arc<Mutex<RenderContext>>,
) -> bool {
match guard {
KeepGuard::Retarget(expected) => {
callbacks::current_retarget_guard(ui, st, render_ctx).as_ref() == Some(&**expected)
}
KeepGuard::Optimize {
outcome,
generation,
} => {
let pending = st
.pending_optimize
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone();
pending.is_some_and(|(held, held_generation)| {
held == **outcome && held_generation == *generation
})
}
KeepGuard::Snapshot | KeepGuard::Variants => false,
}
}