use super::{
Deps,
actions::{EDITOR_BUSY_TEXT, NO_DESIGN_TEXT, VARIANT_GONE_TEXT, is_busy, set_busy},
diff::{self, DiffView},
identity_of, live_deps, refresh, rows,
rows::Choice,
spawn_job, store, with_db,
};
use crate::{
MainWindow, VariantDiffRow, VariantsModel,
gui::{
editor::compare::{VariantSide, open_variants_compare},
show_toast,
tutorial_events::raise,
},
};
use indicatrix::optics::materials::GemMaterial;
use indicatrix_cut_core::Design;
use indicatrix_editor::guide::solving_events::VARIANTS_COMPARED;
use indicatrix_vault::model::design_variant::VariantSummary;
use slint::{ComponentHandle, ModelRc, SharedString, VecModel};
use std::{rc::Rc, sync::Arc};
#[derive(Clone)]
struct Side {
choice: Choice,
label: String,
}
impl Side {
fn picture_label(&self) -> String {
match self.choice {
Choice::Current => rows::CURRENT_LABEL.to_owned(),
Choice::Variant(_) => format!("Variant \"{}\"", self.label),
}
}
}
fn side_for(choice: Choice, list: &[VariantSummary]) -> Option<Side> {
match choice {
Choice::Current => Some(Side {
choice,
label: rows::CURRENT_LABEL.to_owned(),
}),
Choice::Variant(id) => {
list.iter()
.find(|variant| variant.variant_id == id)
.map(|variant| Side {
choice,
label: variant.name.clone(),
})
}
}
}
fn side_design(
db: &indicatrix_vault::db::sqlite::Database,
uuid: &str,
side: &Side,
current: &Design,
) -> Result<Design, String> {
match side.choice {
Choice::Current => Ok(current.clone()),
Choice::Variant(id) => store::load_design(db, uuid, id)
.map(|(_, design)| design)
.map_err(|problem| format!("\"{}\": {problem}", side.label)),
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum CompareKind {
Pictures,
Text,
}
enum Compared {
Pictures(Box<VariantSide>, Box<VariantSide>),
Text(DiffView),
}
pub(super) fn compare_pictures(ui: &MainWindow, deps: &Rc<Deps>) {
compare(ui, deps, CompareKind::Pictures);
}
pub(super) fn compare_text(ui: &MainWindow, deps: &Rc<Deps>) {
compare(ui, deps, CompareKind::Text);
}
struct CompareStart {
uuid: String,
current: Design,
custom: Vec<GemMaterial>,
}
fn compare(ui: &MainWindow, deps: &Rc<Deps>, kind: CompareKind) {
if is_busy(ui, deps) {
return;
}
let model = ui.global::<VariantsModel>();
let chosen = rows::resolve_pair(
&deps.page.borrow().choices,
model.get_compare_first(),
model.get_compare_second(),
);
let (first, second) = match chosen {
Ok(pair) => pair,
Err(sentence) => return show_toast(ui, sentence, "info"),
};
let sides = {
let page = deps.page.borrow();
(side_for(first, &page.list), side_for(second, &page.list))
};
let (Some(first), Some(second)) = sides else {
show_toast(ui, VARIANT_GONE_TEXT, "info");
return refresh(ui, deps);
};
let Some(start) = deps.with_state(|state| {
identity_of(state).uuid.map(|uuid| CompareStart {
uuid,
current: state.design.clone(),
custom: Vec::new(),
})
}) else {
return show_toast(ui, EDITOR_BUSY_TEXT, "info");
};
let Some(mut start) = start else {
return show_toast(ui, NO_DESIGN_TEXT, "info");
};
start.custom = super::custom_materials(deps);
set_busy(ui, deps, true);
let token = if kind == CompareKind::Text {
open_diff_page(ui, deps, &first.label, &second.label)
} else {
0
};
start_comparison(ui, deps, kind, token, (first, second), start);
}
fn start_comparison(
ui: &MainWindow,
deps: &Rc<Deps>,
kind: CompareKind,
token: u64,
sides: (Side, Side),
start: CompareStart,
) {
let db = Arc::clone(&deps.db);
let started = spawn_job(
ui,
"variant-compare",
move || {
let (first, second) = sides;
let CompareStart {
uuid,
current,
custom,
} = start;
let (before, after) = with_db(&db, |db| {
Ok::<_, String>((
side_design(db, &uuid, &first, ¤t)?,
side_design(db, &uuid, &second, ¤t)?,
))
})?;
Ok(match kind {
CompareKind::Pictures => Compared::Pictures(
Box::new(VariantSide {
design: before,
label: first.picture_label(),
}),
Box::new(VariantSide {
design: after,
label: second.picture_label(),
}),
),
CompareKind::Text => Compared::Text(diff::compare_designs(
(&before, &first.label),
(&after, &second.label),
&custom,
)),
})
},
move |ui, answer| {
if let Some(deps) = live_deps() {
finish_compare(ui, &deps, token, answer);
}
},
);
if let Err(reason) = started {
set_busy(ui, deps, false);
close_diff(ui, deps);
show_toast(ui, &reason, "error");
}
}
fn finish_compare(ui: &MainWindow, deps: &Rc<Deps>, token: u64, answer: Result<Compared, String>) {
set_busy(ui, deps, false);
match answer {
Ok(Compared::Pictures(before, after)) => {
open_variants_compare(ui, *before, *after);
raise(ui, VARIANTS_COMPARED);
}
Ok(Compared::Text(view)) => {
show_diff(ui, deps, token, &view);
raise(ui, VARIANTS_COMPARED);
}
Err(reason) => {
if deps.page.borrow().compare_token == token {
close_diff(ui, deps);
}
show_toast(ui, &reason, "error");
refresh(ui, deps);
}
}
}
fn open_diff_page(ui: &MainWindow, deps: &Deps, first: &str, second: &str) -> u64 {
let token = {
let mut page = deps.page.borrow_mut();
page.compare_token = page.compare_token.wrapping_add(1);
page.compare_token
};
let model = ui.global::<VariantsModel>();
model.set_diff_title(diff::title(first, second).into());
model.set_diff_summary(SharedString::new());
model.set_diff_rows(ModelRc::default());
model.set_diff_busy(true);
model.set_diff_open(true);
token
}
fn show_diff(ui: &MainWindow, deps: &Deps, token: u64, view: &DiffView) {
if deps.page.borrow().compare_token != token {
return;
}
let rows: Vec<VariantDiffRow> = view
.rows
.iter()
.map(|line| VariantDiffRow {
kind: line.kind,
old_line: line.old_line.as_str().into(),
new_line: line.new_line.as_str().into(),
text: line.text.as_str().into(),
})
.collect();
let model = ui.global::<VariantsModel>();
model.set_diff_title(view.title.as_str().into());
model.set_diff_summary(view.summary.as_str().into());
model.set_diff_rows(ModelRc::new(VecModel::from(rows)));
model.set_diff_busy(false);
model.set_diff_open(true);
}
pub(super) fn close_diff(ui: &MainWindow, deps: &Rc<Deps>) {
{
let mut page = deps.page.borrow_mut();
page.compare_token = page.compare_token.wrapping_add(1);
}
let model = ui.global::<VariantsModel>();
model.set_diff_open(false);
model.set_diff_busy(false);
model.set_diff_rows(ModelRc::default());
}