use crate::{
RoughPlanModel, RoughPlanResult,
gui::rough_plan::{
host::Host,
saved::{convert::CandidateSource, dto::DesignShape},
},
};
use indicatrix_cut_core::rough_plan::{PlanSettings, RoughLayout, RoughModel};
use slint::{ModelRc, SharedString};
use std::{collections::BTreeMap, rc::Rc};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::gui::rough_plan) enum DesignStatus {
Unchanged,
Changed,
Deleted,
MatchedByTitle {
resolved_entry_id: i64,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(in crate::gui::rough_plan) enum ResultsSource {
#[default]
Planned,
Loaded {
plan_id: i64,
name: String,
created_at: i64,
},
}
#[derive(Default)]
pub(in crate::gui::rough_plan) struct Screen {
pub rows: ModelRc<RoughPlanResult>,
pub summary: SharedString,
pub skipped: i32,
pub selected_result: i32,
pub selected_group: i32,
pub expanded_index: i32,
pub keep_count: i32,
pub banner: SharedString,
}
impl Screen {
pub(in crate::gui::rough_plan) fn capture(model: &RoughPlanModel<'_>) -> Self {
Self {
rows: model.get_results(),
summary: model.get_summary(),
skipped: model.get_skipped_count(),
selected_result: model.get_selected_result(),
selected_group: model.get_selected_group(),
expanded_index: model.get_expanded_index(),
keep_count: model.get_keep_count(),
banner: model.get_loaded_banner(),
}
}
pub(in crate::gui::rough_plan) fn show(self, model: &RoughPlanModel<'_>) {
model.set_results(self.rows);
model.set_summary(self.summary);
model.set_skipped_count(self.skipped);
model.set_selected_result(self.selected_result);
model.set_selected_group(self.selected_group);
model.set_expanded_index(self.expanded_index);
model.set_keep_count(self.keep_count);
model.set_loaded_banner(self.banner);
}
}
pub(in crate::gui::rough_plan) struct Replaced {
state: RunState,
screen: Screen,
}
pub(in crate::gui::rough_plan) type Proceed = Box<dyn FnOnce(&Rc<Host>)>;
#[derive(Default)]
pub(in crate::gui::rough_plan) struct RunState {
pub(in crate::gui::rough_plan) layouts: Vec<RoughLayout>,
pub(in crate::gui::rough_plan) plan_model: Option<RoughModel>,
pub(in crate::gui::rough_plan) plan_settings: Option<PlanSettings>,
pub(in crate::gui::rough_plan) material_name: String,
pub(in crate::gui::rough_plan) weighed_ct: Option<f64>,
pub(in crate::gui::rough_plan) candidate_source: CandidateSource,
pub(in crate::gui::rough_plan) titles: BTreeMap<i64, String>,
pub(in crate::gui::rough_plan) statuses: BTreeMap<i64, DesignStatus>,
pub(in crate::gui::rough_plan) shapes: BTreeMap<i64, DesignShape>,
pub(in crate::gui::rough_plan) keep: Vec<bool>,
pub(in crate::gui::rough_plan) source: ResultsSource,
pub(in crate::gui::rough_plan) results_unsaved: bool,
pub(super) replaced: Option<Box<Replaced>>,
pub(super) pending: Option<Proceed>,
pub(super) generation: u64,
pub(super) summary: Option<String>,
}
impl RunState {
pub(in crate::gui::rough_plan) fn clear(&mut self) {
let generation = self.generation + 1;
*self = Self {
generation,
..Self::default()
};
}
pub(in crate::gui::rough_plan) fn begin_run(&mut self, screen: Screen) {
let generation = self.generation + 1;
let mut state = std::mem::take(self);
state.replaced = None;
state.pending = None;
*self = Self {
generation,
replaced: Some(Box::new(Replaced { state, screen })),
..Self::default()
};
}
pub(in crate::gui::rough_plan) fn restore_replaced(&mut self) -> Option<Screen> {
let Replaced { state, screen } = *self.replaced.take()?;
let generation = self.generation + 1;
*self = Self {
generation,
..state
};
Some(screen)
}
#[must_use]
pub(in crate::gui::rough_plan) fn kept(&self) -> usize {
self.keep.iter().filter(|&&kept| kept).count()
}
}
pub(in crate::gui::rough_plan) struct ShownLayouts {
pub layouts: Vec<RoughLayout>,
pub plan_model: Option<RoughModel>,
pub plan_settings: Option<PlanSettings>,
pub material_name: String,
pub weighed_ct: Option<f64>,
pub candidate_source: CandidateSource,
pub titles: BTreeMap<i64, String>,
pub statuses: BTreeMap<i64, DesignStatus>,
pub shapes: BTreeMap<i64, DesignShape>,
pub source: ResultsSource,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clearing_forgets_everything_but_moves_the_generation_on() {
let mut state = RunState {
plan_settings: Some(PlanSettings::default()),
material_name: "Quartz".to_string(),
keep: vec![true, false, true],
source: ResultsSource::Loaded {
plan_id: 3,
name: "Aqua".to_string(),
created_at: 0,
},
results_unsaved: true,
generation: 4,
summary: Some("x".to_string()),
..RunState::default()
};
assert_eq!(state.kept(), 2);
state.clear();
assert_eq!(state.generation, 5);
assert!(state.plan_settings.is_none() && state.summary.is_none());
assert!(state.material_name.is_empty() && state.keep.is_empty());
assert_eq!(state.source, ResultsSource::Planned);
assert!(!state.results_unsaved);
assert_eq!(state.kept(), 0);
}
#[test]
fn a_run_keeps_the_previous_results_aside_and_a_cancel_brings_them_back() {
let mut state = RunState {
material_name: "Quartz".to_string(),
keep: vec![true, false],
results_unsaved: true,
generation: 4,
..RunState::default()
};
let screen = Screen {
skipped: 7,
selected_result: 1,
..Screen::default()
};
state.begin_run(screen);
assert_eq!(state.generation, 5, "a push in flight is superseded");
assert!(state.material_name.is_empty() && state.keep.is_empty());
assert!(!state.results_unsaved, "the run starts with nothing shown");
assert!(state.replaced.is_some());
let back = state.restore_replaced().expect("the old results were kept");
assert_eq!((back.skipped, back.selected_result), (7, 1));
assert_eq!(state.material_name, "Quartz");
assert_eq!(state.keep, vec![true, false]);
assert!(state.results_unsaved, "the old results were still unsaved");
assert_eq!(state.generation, 6, "restoring supersedes pushes as well");
assert!(state.replaced.is_none());
assert!(
state.restore_replaced().is_none(),
"there is nothing left to put back"
);
}
#[test]
fn publishing_a_new_result_drops_the_previous_one_for_good() {
let mut state = RunState {
material_name: "Quartz".to_string(),
..RunState::default()
};
state.begin_run(Screen::default());
state.replaced = None;
state.material_name = "Beryl".to_string();
assert!(state.restore_replaced().is_none());
assert_eq!(state.material_name, "Beryl");
}
#[test]
fn beginning_a_run_twice_never_nests_the_kept_state() {
let mut state = RunState {
material_name: "Quartz".to_string(),
..RunState::default()
};
state.begin_run(Screen::default());
state.material_name = "Beryl".to_string();
state.begin_run(Screen::default());
let _ = state.restore_replaced().expect("kept");
assert_eq!(state.material_name, "Beryl");
assert!(state.replaced.is_none(), "only the latest state is kept");
}
}