use super::{
inspector::refresh_design_settings,
panel_stale::refresh_deep_solve_availability,
state::{
EditorState, ScratchDelta, apply_multi_selection, apply_proposed_angles,
cutting_instructions_rows, design_label_text, girdle_and_ratio_texts,
manufacturability_warnings_tagged, preform_mm_texts, preform_y_offset_mm_text,
proportion_verdicts, proportions_texts, push_multi_selected_count, push_rows, push_tiers,
status_text_and_is_problem, status_text_and_is_problem_from_solved, tier_items,
tier_items_from_solved, yield_report_texts, yield_report_texts_from_solved,
},
};
use crate::{
AngleItem, EditorModel, EditorTierItem, MainWindow, UndoRedoLabels,
bridge::render_thread::RenderContext,
};
use indicatrix::geometry::meet_solver::SolvedTier;
use indicatrix_cut_core::{Design, DesignSolveError, PreformShape};
pub(in crate::gui::editor) use indicatrix_editor::view_model::yield_report::{
girdle_and_ratio_texts_from_solved, preform_mm_texts_from_solved, proportions_texts_from_solved,
};
use slint::{ComponentHandle, SharedString};
use std::sync::{Arc, Mutex, PoisonError, atomic::Ordering as AtomicOrdering};
pub(in crate::gui::editor) fn refresh_editor_panel(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
state: &EditorState,
) -> Option<Vec<SolvedTier>> {
let solved_result = state.design.solve();
refresh_editor_panel_from_solve(ui, render_ctx, state, solved_result)
}
pub(in crate::gui::editor) fn refresh_editor_panel_from_solve(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
state: &EditorState,
solved_result: Result<Vec<SolvedTier>, DesignSolveError>,
) -> Option<Vec<SolvedTier>> {
let delta = state.record_scratch_push();
let n_d = refresh_design_settings(ui, render_ctx, state, &delta);
let facet_count = solved_result.as_ref().ok().map_or(0, |solved| {
i32::try_from(super::solve_results::facet_count_from_solved(
&state.design,
solved,
))
.unwrap_or(i32::MAX)
});
ui.global::<EditorModel>().set_facet_count(facet_count);
let solved = solved_result.as_ref().ok();
let tiers = solved.map_or_else(
|| tier_items(&state.design, n_d),
|solved| tier_items_from_solved(&state.design, solved, n_d),
);
push_tier_list_and_undo_redo(ui, state, tiers);
super::panel_stale::sync_tier_form_with_design(ui, state);
let (status_text, is_problem) = solved.map_or_else(
|| status_text_and_is_problem(&state.design),
|solved| status_text_and_is_problem_from_solved(&state.design, solved),
);
ui.global::<EditorModel>()
.set_status_text(status_text.into());
ui.global::<EditorModel>().set_status_is_problem(is_problem);
ui.global::<EditorModel>()
.set_solve_state(if is_problem { "failed" } else { "solved" }.into());
if super::state::should_open_anchor_explainer(solved_result.is_err()) {
ui.global::<EditorModel>().set_anchor_explainer_open(true);
}
push_solve_dependent_panel_fields(ui, render_ctx, state, &delta, solved, n_d);
ui.global::<EditorModel>()
.set_design_label(design_label_text(state.asc_filename.as_deref()).into());
let rows: Vec<AngleItem> = solved.map_or_else(Vec::new, |solved| {
cutting_instructions_rows(&state.design, solved)
});
push_rows(
&ui.global::<EditorModel>().get_cutting_rows(),
rows,
|model| {
ui.global::<EditorModel>().set_cutting_rows(model);
},
);
refresh_deep_solve_availability(ui, state);
super::panel_stale::refresh_optimize_availability(ui, state);
solved_result.ok()
}
fn push_solve_dependent_panel_fields(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
state: &EditorState,
delta: &ScratchDelta,
solved: Option<&Vec<SolvedTier>>,
n_d: f64,
) {
push_manufacturability_and_preform_scratch(ui, state, delta, solved);
push_yield_and_proportions(ui, render_ctx, state, delta, solved, n_d);
}
fn push_manufacturability_and_preform_scratch(
ui: &MainWindow,
state: &EditorState,
delta: &ScratchDelta,
solved: Option<&Vec<SolvedTier>>,
) {
let tagged_warnings =
manufacturability_warnings_tagged(&state.design, solved.map(Vec::as_slice));
let warning_tiers: Vec<i32> = tagged_warnings
.iter()
.map(|(index, _)| i32::try_from(*index).unwrap_or(i32::MAX))
.collect();
let warnings: Vec<SharedString> = tagged_warnings
.into_iter()
.map(|(_, text)| SharedString::from(text))
.collect();
push_rows(
&ui.global::<EditorModel>().get_manufacturability_warnings(),
warnings,
|model| {
ui.global::<EditorModel>()
.set_manufacturability_warnings(model);
},
);
push_rows(
&ui.global::<EditorModel>()
.get_manufacturability_warning_tiers(),
warning_tiers,
|model| {
ui.global::<EditorModel>()
.set_manufacturability_warning_tiers(model);
},
);
if delta.preform {
let preform = &state.design.preform;
ui.global::<EditorModel>()
.set_preform_shape_index(match preform.shape {
PreformShape::Block => 0,
PreformShape::Cylinder { .. } => 1,
});
ui.global::<EditorModel>()
.set_preform_half_width(format!("{:.4}", preform.half_width).into());
ui.global::<EditorModel>()
.set_preform_length_over_width(format!("{:.4}", preform.length_over_width).into());
ui.global::<EditorModel>()
.set_preform_depth(format!("{:.4}", preform.depth).into());
let (preform_half_width_mm, preform_depth_mm) = solved.map_or_else(
|| preform_mm_texts(&state.design),
|solved| preform_mm_texts_from_solved(&state.design, solved),
);
ui.global::<EditorModel>()
.set_preform_half_width_mm_text(preform_half_width_mm.into());
ui.global::<EditorModel>()
.set_preform_depth_mm_text(preform_depth_mm.into());
let mm_per_unit = solved.and_then(|solved| state.design.yield_report(solved).mm_per_unit);
ui.global::<EditorModel>().set_preform_y_offset_mm(
preform_y_offset_mm_text(state.design.preform_y_offset, mm_per_unit).into(),
);
}
}
fn push_yield_and_proportions(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
state: &EditorState,
delta: &ScratchDelta,
solved: Option<&Vec<SolvedTier>>,
n_d: f64,
) {
push_yield_material_scratch(ui, state, delta);
let custom_sg = Arc::clone(
&render_ctx
.lock()
.unwrap_or_else(PoisonError::into_inner)
.custom_material_specific_gravity,
);
let (vol_yield_text, carat_text, sg_used_text, fit_text) = solved.map_or_else(
|| yield_report_texts(&state.design, &custom_sg),
|solved| yield_report_texts_from_solved(&state.design, solved, &custom_sg),
);
ui.global::<EditorModel>()
.set_volumetric_yield_text(vol_yield_text.into());
ui.global::<EditorModel>()
.set_carat_weight_text(carat_text.into());
ui.global::<EditorModel>()
.set_specific_gravity_used_text(sg_used_text.into());
ui.global::<EditorModel>()
.set_preform_fit_warning(fit_text.into());
let (table_pct, crown_height, pavilion_depth, total_depth, length_to_width) = solved
.map_or_else(
|| proportions_texts(&state.design),
|solved| proportions_texts_from_solved(&state.design, solved),
);
ui.global::<EditorModel>()
.set_proportions_table_pct(table_pct.into());
ui.global::<EditorModel>()
.set_proportions_crown_height(crown_height.into());
ui.global::<EditorModel>()
.set_proportions_pavilion_depth(pavilion_depth.into());
ui.global::<EditorModel>()
.set_proportions_total_depth(total_depth.into());
ui.global::<EditorModel>()
.set_proportions_length_to_width(length_to_width.into());
let (girdle_thickness, crown_to_width, pavilion_to_width, girdle_to_width) = solved
.map_or_else(
|| girdle_and_ratio_texts(&state.design),
|solved| girdle_and_ratio_texts_from_solved(&state.design, solved),
);
ui.global::<EditorModel>()
.set_girdle_thickness_text(girdle_thickness.into());
ui.global::<EditorModel>()
.set_crown_to_width_text(crown_to_width.into());
ui.global::<EditorModel>()
.set_pavilion_to_width_text(pavilion_to_width.into());
ui.global::<EditorModel>()
.set_girdle_to_width_text(girdle_to_width.into());
push_proportion_verdicts_from_solved(ui, &state.design, solved.map(Vec::as_slice), n_d);
}
pub(in crate::gui::editor) fn push_proportion_verdicts_from_solved(
ui: &MainWindow,
design: &Design,
solved: Option<&[SolvedTier]>,
n_d: f64,
) {
crate::gui::editor::verdict::on_solved(ui, design, solved, n_d);
let verdicts = (!design.tiers.is_empty())
.then(|| solved.and_then(|solved| design.stone_proportions(solved)))
.flatten()
.map(|proportions| proportion_verdicts(design, &proportions, n_d));
let push = |level: i32,
reason: &str,
set_level: fn(&MainWindow, i32),
set_reason: fn(&MainWindow, SharedString)| {
set_level(ui, level);
set_reason(ui, reason.into());
};
let (level, reason) = verdicts.as_ref().map_or((-1, String::new()), |v| {
(v.table_pct.level, v.table_pct.reason.clone())
});
push(
level,
&reason,
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_table_level(v);
},
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_table_reason(v);
},
);
let (level, reason) = verdicts.as_ref().map_or((-1, String::new()), |v| {
(v.crown_angle.level, v.crown_angle.reason.clone())
});
push(
level,
&reason,
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_crown_angle_level(v);
},
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_crown_angle_reason(v);
},
);
let (level, reason) = verdicts.as_ref().map_or((-1, String::new()), |v| {
(v.pavilion_angle.level, v.pavilion_angle.reason.clone())
});
push(
level,
&reason,
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_pavilion_angle_level(v);
},
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_pavilion_angle_reason(v);
},
);
let (level, reason) = verdicts.as_ref().map_or((-1, String::new()), |v| {
(v.total_depth_pct.level, v.total_depth_pct.reason.clone())
});
push(
level,
&reason,
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_total_depth_level(v);
},
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_total_depth_reason(v);
},
);
let (level, reason) = verdicts.as_ref().map_or((-1, String::new()), |v| {
(v.girdle_pct.level, v.girdle_pct.reason.clone())
});
push(
level,
&reason,
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_girdle_level(v);
},
|ui, v| {
ui.global::<EditorModel>()
.set_proportion_verdict_girdle_reason(v);
},
);
}
fn apply_pending_optimize_ghost(tiers: &mut [EditorTierItem], state: &EditorState) {
let current_generation = state.generation.load(AtomicOrdering::Relaxed);
let pending = state
.pending_optimize
.lock()
.unwrap_or_else(PoisonError::into_inner);
if let Some((outcome, started_generation)) = pending.as_ref()
&& *started_generation == current_generation
{
apply_proposed_angles(tiers, &outcome.changes);
}
}
pub(super) fn push_tier_list_and_undo_redo(
ui: &MainWindow,
state: &EditorState,
mut tiers: Vec<EditorTierItem>,
) {
apply_multi_selection(&mut tiers, &state.multi_selected);
apply_pending_optimize_ghost(&mut tiers, state);
push_tiers(ui, tiers);
ui.global::<EditorModel>().set_is_dirty(state.is_dirty());
push_multi_selected_count(ui, state.multi_selected.len());
super::inspector::push_selected_tier_chips(ui, state);
ui.global::<EditorModel>()
.set_can_undo(state.history.can_undo());
ui.global::<EditorModel>()
.set_can_redo(state.history.can_redo());
ui.global::<UndoRedoLabels>()
.set_undo_label(state.undo_hint().into());
ui.global::<UndoRedoLabels>()
.set_redo_label(state.redo_hint().into());
}
pub(super) fn push_yield_material_scratch(
ui: &MainWindow,
state: &EditorState,
delta: &ScratchDelta,
) {
if delta.girdle {
ui.global::<EditorModel>().set_girdle_diameter_mm(
state
.design
.girdle_diameter_mm
.map_or_else(String::new, |mm| format!("{mm:.4}"))
.into(),
);
}
if delta.material {
ui.global::<EditorModel>().set_specific_gravity_override(
state
.design
.material
.specific_gravity_override
.map_or_else(String::new, |sg| format!("{sg:.4}"))
.into(),
);
}
}