use crate::{MainWindow, RetargetCandidateItem, RetargetModel, RetargetRowItem};
use indicatrix_cut_core::{MaterialSelection, ResolvedMaterial};
use indicatrix_editor::retarget::{
search::{CandidateKind, SearchCandidate, SearchReport},
validity::ValidityStatus,
};
use slint::{Color, ComponentHandle, ModelRc, SharedString, VecModel};
#[cfg(test)]
pub(super) use indicatrix_editor::retarget::view::retarget_view;
pub(super) use indicatrix_editor::retarget::view::{CheckView, RetargetView, plan_view};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct CandidateRowView {
pub(super) rank: i32,
pub(super) label: String,
pub(super) score: String,
pub(super) windowing: String,
pub(super) brilliance: String,
pub(super) extinction: String,
pub(super) yield_loss: String,
pub(super) verdict: String,
}
pub(super) fn candidate_row_views(candidates: &[SearchCandidate]) -> Vec<CandidateRowView> {
candidates
.iter()
.enumerate()
.map(|(index, candidate)| {
let label = match candidate.kind {
CandidateKind::Shift => "Shift result".to_string(),
CandidateKind::Partial { percent } => {
format!("Part of the Shift change ({percent} %)")
}
CandidateKind::Optimized => "Searched angles".to_string(),
};
let verdict = if candidate.validity.status == ValidityStatus::Valid {
"Valid"
} else {
"Not valid"
};
CandidateRowView {
rank: i32::try_from(index + 1).unwrap_or(i32::MAX),
label,
score: format!("{:.1}", candidate.numbers.score),
windowing: format!("{:.1} %", candidate.numbers.windowing_pct),
brilliance: format!("{:.1} %", candidate.numbers.brilliance_pct),
extinction: format!("{:.1} %", candidate.numbers.extinction_pct),
yield_loss: format!("{:.1} %", candidate.numbers.yield_loss_pct),
verdict: verdict.to_string(),
}
})
.collect()
}
pub(super) fn search_summary_line(report: &SearchReport) -> String {
let count = report.candidates.len();
if count == 0 {
return "No valid option was found.".to_string();
}
let options = if count == 1 {
"1 valid option".to_string()
} else {
format!("{count} valid options")
};
format!(
"{options}, best score first (lower is better). The search used {} steps.",
report.evaluations
)
}
pub(super) fn push_candidates(ui: &MainWindow, rows: Vec<CandidateRowView>) {
let items: Vec<RetargetCandidateItem> = rows
.into_iter()
.map(|row| RetargetCandidateItem {
rank: row.rank,
label: row.label.into(),
score: row.score.into(),
windowing: row.windowing.into(),
brilliance: row.brilliance.into(),
extinction: row.extinction.into(),
yield_loss: row.yield_loss.into(),
verdict: row.verdict.into(),
})
.collect();
ui.global::<RetargetModel>()
.set_candidates(ModelRc::new(VecModel::from(items)));
}
pub(super) fn string_model<I, S>(items: I) -> ModelRc<SharedString>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
ModelRc::new(VecModel::from(
items
.into_iter()
.map(|item| SharedString::from(item.as_ref()))
.collect::<Vec<_>>(),
))
}
pub(super) fn push_check_view(ui: &MainWindow, view: &CheckView) {
let strings = |lines: &[String]| {
ModelRc::new(VecModel::from(
lines
.iter()
.map(|line| SharedString::from(line.as_str()))
.collect::<Vec<_>>(),
))
};
let model = ui.global::<RetargetModel>();
model.set_validity_state(view.state.code());
model.set_validity_text(view.headline.as_str().into());
model.set_validity_details(strings(view.details.as_slice()));
model.set_strategy_text(view.strategy.as_str().into());
model.set_metrics_cells(strings(view.metrics.as_slice()));
}
pub(super) fn push_retarget_view(ui: &MainWindow, view: RetargetView) {
let rows: Vec<RetargetRowItem> = view
.rows
.into_iter()
.map(|r| RetargetRowItem {
tier_index: r.tier_index as i32,
block: r.block.into(),
name: r.name.into(),
old_angle: r.old_angle.into(),
new_angle: r.new_angle.into(),
margin: r.margin.into(),
risk_label: r.risk_label.into(),
risk_color: Color::from_rgb_u8(r.risk_rgb.0, r.risk_rgb.1, r.risk_rgb.2),
})
.collect();
ui.global::<RetargetModel>()
.set_rows(ModelRc::new(VecModel::from(rows)));
ui.global::<RetargetModel>()
.set_notes(ModelRc::new(VecModel::from(
view.notes
.into_iter()
.map(SharedString::from)
.collect::<Vec<_>>(),
)));
ui.global::<RetargetModel>()
.set_anchored_tier_errors(ModelRc::new(VecModel::from(
view.anchored_errors
.into_iter()
.map(SharedString::from)
.collect::<Vec<_>>(),
)));
ui.global::<RetargetModel>()
.set_solve_error(view.solve_error.into());
}
pub(super) fn push_target_readout(
ui: &MainWindow,
selection: &MaterialSelection,
target: &ResolvedMaterial,
) {
ui.global::<RetargetModel>()
.set_target_material_name(super::material::target_display_name(selection).into());
ui.global::<RetargetModel>()
.set_target_material_ri(format!("{:.4}", target.n_d).into());
ui.global::<RetargetModel>()
.set_target_critical_angle(format!("{:.2}\u{b0}", target.critical_angle_deg).into());
ui.global::<RetargetModel>()
.set_target_material_error("".into());
}
pub(super) fn push_target_error(ui: &MainWindow, message: &str) {
ui.global::<RetargetModel>()
.set_target_material_error(message.into());
super::check_run::reset_check(ui);
push_retarget_view(
ui,
RetargetView {
rows: Vec::new(),
notes: Vec::new(),
anchored_errors: Vec::new(),
solve_error: String::new(),
},
);
}