use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex},
};
use indicatrix_cut_core::Edit;
use slint::{ComponentHandle, SharedString};
use super::FIXED_CYLINDER_PREFORM_SIDES;
use crate::{
EditorModel, MainWindow,
bridge::render_thread::RenderContext,
gui::{
editor::{
auto_solve, guide, loading,
stall_guard::stall_guard,
state::EditorState,
view::{SolidLastSolved, refresh_editor_panel_stale, submit_preview_replan},
},
show_toast,
solid_preview::preview_state::SolidPreviewState,
},
};
pub(in crate::gui::editor) fn setup_apply_preform_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_apply_preform(
move |shape_index: i32,
half_width: SharedString,
length_over_width: SharedString,
depth: SharedString| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let mut st = state.borrow_mut();
match loading::parse_preform_form(
shape_index,
&half_width,
&length_over_width,
&depth,
FIXED_CYLINDER_PREFORM_SIDES,
) {
Ok(preform) => {
let _ = st.apply(Edit::SetPreform { preform });
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::new());
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::new(),
false,
);
}
Err(e) => show_toast(&ui, &e, "error"),
}
},
);
}
pub(in crate::gui::editor) fn setup_apply_yield_inputs_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_apply_yield_inputs(
move |girdle_diameter_mm: SharedString,
material_index: i32,
specific_gravity_override: SharedString| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let mut st = state.borrow_mut();
match crate::gui::editor::state::parse_yield_form(
&girdle_diameter_mm,
material_index,
&specific_gravity_override,
&st.design.material,
) {
Ok((girdle_diameter_mm, material)) => {
let _ = st.apply(Edit::Batch(vec![
Edit::SetGirdleDiameterMm { girdle_diameter_mm },
Edit::SetMaterial { material },
]));
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::new());
guide::check_progress(&ui, &st);
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::new(),
false,
);
}
Err(e) => show_toast(&ui, &e, "error"),
}
},
);
}
pub(in crate::gui::editor) fn setup_apply_preform_y_offset_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>()
.on_apply_preform_y_offset(move |mm_text: SharedString| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
stall_guard("on_apply_preform_y_offset", || {
let trimmed = mm_text.trim();
let Ok(mm) = trimmed.parse::<f64>() else {
show_toast(
&ui,
&format!("Y-offset '{trimmed}' is not a number."),
"error",
);
return;
};
if !mm.is_finite() {
show_toast(&ui, "Y-offset must be a finite number.", "error");
return;
}
let mut st = state.borrow_mut();
let Some(solved) = auto_solve::solid_last_solved()
.and_then(|cache| {
cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
})
.filter(|(_, solved)| solved.len() == st.design.tiers.len())
.map(|(_, solved)| solved)
else {
show_toast(&ui, "Solve first, then set a Y-offset.", "error");
return;
};
let Some(mm_per_unit) = st.design.yield_report(&solved).mm_per_unit else {
show_toast(
&ui,
"Set a girdle diameter in the Yield tab before setting a Y-offset in \
millimetres.",
"error",
);
return;
};
let y_offset = mm / mm_per_unit;
if let Err(e) = st.apply(Edit::SetPreformYOffset { y_offset }) {
show_toast(&ui, &e.to_string(), "error");
return;
}
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::new());
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::new(),
false,
);
});
});
}
pub(in crate::gui::editor) fn setup_apply_cheater_offset_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>()
.on_apply_cheater_offset(move |index: i32, text: SharedString| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let Ok(index) = usize::try_from(index) else {
return;
};
let trimmed = text.trim();
let offset_deg = if trimmed.is_empty() {
None
} else {
match trimmed.parse::<f64>() {
Ok(value) if value.is_finite() => Some(value),
_ => {
show_toast(
&ui,
&format!("Cheater offset '{trimmed}' is not a number."),
"error",
);
return;
}
}
};
let mut st = state.borrow_mut();
if let Err(e) = st.apply(Edit::SetCheaterOffset { index, offset_deg }) {
show_toast(&ui, &e.to_string(), "error");
return;
}
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::new());
});
}
pub(in crate::gui::editor) fn setup_apply_tier_note_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>()
.on_apply_tier_note(move |index: i32, text: SharedString| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let Ok(index) = usize::try_from(index) else {
return;
};
let trimmed = text.trim();
let note = if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
};
let mut st = state.borrow_mut();
if let Err(e) = st.apply(Edit::SetTierNote { index, note }) {
show_toast(&ui, &e.to_string(), "error");
return;
}
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::new());
});
}
pub(in crate::gui::editor) fn setup_apply_design_meta_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_apply_design_meta(
move |title: SharedString,
extra_headers: SharedString,
footnotes: SharedString,
gear_ref: SharedString| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let split_lines = |text: &str| -> Vec<String> {
text.split(';')
.map(str::trim)
.filter(|line| !line.is_empty())
.map(str::to_string)
.collect()
};
let trimmed_title = title.trim();
let mut headers = Vec::new();
if !trimmed_title.is_empty() {
headers.push(trimmed_title.to_string());
}
headers.extend(split_lines(&extra_headers));
let footnotes = split_lines(&footnotes);
let gear_ref_trimmed = gear_ref.trim();
let Ok(gear_reference_angle) = gear_ref_trimmed.parse::<f64>() else {
show_toast(
&ui,
&format!("Gear reference angle '{gear_ref_trimmed}' is not a number."),
"error",
);
return;
};
if !gear_reference_angle.is_finite() {
show_toast(
&ui,
"Gear reference angle must be a finite number.",
"error",
);
return;
}
let mut st = state.borrow_mut();
let _ = st.apply(Edit::SetMeta {
headers,
footnotes,
gear_reference_angle,
});
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::new());
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::new(),
false,
);
},
);
}