use egui::DragValue;
use egui::emath::format_with_decimals_in_range;
use std::ops::RangeInclusive;
const MM: f64 = 0.001;
pub(crate) fn millimetres(metres: &mut f32) -> DragValue<'_> {
DragValue::new(metres)
.speed(MM)
.suffix(" mm")
.custom_formatter(shown_in_mm)
.custom_parser(typed_in_mm)
}
fn shown_in_mm(metres: f64, decimals: RangeInclusive<usize>) -> String {
let decimals = decimals.start().saturating_sub(3)..=decimals.end().saturating_sub(3);
format_with_decimals_in_range(metres / MM, decimals)
}
fn typed_in_mm(text: &str) -> Option<f64> {
let text: String = text
.chars()
.filter(|c| !c.is_whitespace())
.map(|c| if c == '−' { '-' } else { c })
.collect();
let number = text.strip_suffix("mm").unwrap_or(&text);
number.parse::<f64>().ok().map(|mm| mm * MM)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_length_is_shown_in_millimetres() {
assert_eq!(shown_in_mm(0.0167, 3..=4), "16.7");
assert_eq!(shown_in_mm(0.25, 3..=5), "250");
assert_eq!(shown_in_mm(-0.1, 0..=3), "-100");
}
#[test]
fn a_length_is_typed_in_millimetres() {
let typed = |text| typed_in_mm(text).map(|metres| (metres * 1e6).round() / 1e6);
assert_eq!(typed("16.7"), Some(0.0167));
assert_eq!(typed(" 250 mm"), Some(0.25));
assert_eq!(typed("−5"), Some(-0.005));
assert_eq!(typed("lots"), None);
}
}