use super::{
editing::{
commit_pending_base, input_error, push_history_flags, read_inputs, readout_inputs_changed,
resync,
},
host::Host,
inputs::parse_weighed,
shape_worker::{ShapeOutput, set_weight_check},
};
use crate::RoughPlanModel;
use indicatrix_cut_core::yield_metrics::carat_weight;
use slint::ComponentHandle;
use std::rc::Rc;
fn is_typed(host: &Rc<Host>, text: &str) -> bool {
!text.trim().is_empty() && host.session.borrow().carat_shown.as_deref() != Some(text)
}
pub(super) fn typed_carat(host: &Rc<Host>) -> Result<Option<f64>, String> {
let text = host.window.global::<RoughPlanModel>().get_weighed_ct();
if is_typed(host, text.as_str()) {
parse_weighed(&text)
} else {
Ok(None)
}
}
pub(super) fn fit_factor(current: f64, target: f64) -> Option<f64> {
if !(current > 0.0 && target > 0.0) {
return None;
}
let factor = (target / current).cbrt();
(factor.is_finite() && (factor - 1.0).abs() >= 1e-4).then_some(factor)
}
fn model_carat_text(volume_mm3: f64, specific_gravity: f64) -> String {
format!("{:.2}", carat_weight(volume_mm3, specific_gravity))
}
pub(super) fn show(host: &Rc<Host>, model: &RoughPlanModel<'_>, output: &ShapeOutput) {
let text = model.get_weighed_ct();
if is_typed(host, text.as_str()) {
set_weight_check(model, output.check_text.clone(), output.check_level);
return;
}
if output.specific_gravity > 0.0 {
let carat = model_carat_text(output.measure.volume_mm3, output.specific_gravity);
model.set_weighed_ct(carat.clone().into());
host.session.borrow_mut().carat_shown = Some(carat);
}
set_weight_check(model, String::new(), 0);
}
pub(super) fn fit_to_weight(host: &Rc<Host>) {
if commit_pending_base(host).is_err() {
return;
}
let Ok(Some(target)) = typed_carat(host) else {
return;
};
let Some(measure) = host.session.borrow().model_measure else {
return;
};
let text = host
.window
.global::<RoughPlanModel>()
.get_weighed_ct()
.to_string();
let current = carat_weight(measure.volume_mm3, read_inputs(host).specific_gravity);
let Some(factor) = fit_factor(current, target) else {
host.session.borrow_mut().carat_shown = Some(text);
readout_inputs_changed(host);
return;
};
let scaled = host.session.borrow().model.scaled(factor);
if let Err(error) = scaled.base.validate() {
input_error(host, &error.to_string());
return;
}
let recorded = {
let mut session = host.session.borrow_mut();
session.model = scaled;
session.carat_shown = Some(text);
session.commit()
};
if recorded {
push_history_flags(host);
}
resync(host, true);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_fit_factor_is_the_cube_root_of_the_carat_ratio() {
let factor = fit_factor(3.40, 3.80).expect("a change");
assert!((factor - (3.80f64 / 3.40).cbrt()).abs() < 1e-12, "{factor}");
assert!(factor.powi(3).mul_add(3.40, -3.80).abs() < 1e-12);
let shrink = fit_factor(10.0, 1.25).expect("a change");
assert!((shrink - 0.5).abs() < 1e-12, "{shrink}");
}
#[test]
fn a_matching_or_impossible_weight_gives_no_factor() {
assert_eq!(fit_factor(3.40, 3.40), None);
assert_eq!(fit_factor(3.40, 3.40 * (1.0 + 2e-4)), None);
assert_eq!(fit_factor(0.0, 3.40), None);
assert_eq!(fit_factor(3.40, 0.0), None);
assert_eq!(fit_factor(3.40, -1.0), None);
assert_eq!(fit_factor(f64::NAN, 3.40), None);
}
#[test]
fn the_written_carat_has_two_decimals() {
assert_eq!(model_carat_text(256.6, 2.65), "3.40");
assert_eq!(model_carat_text(0.0, 2.65), "0.00");
}
}