use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, Mutex},
};
use indicatrix_cut_core::FreshDesignSpec;
use indicatrix_editor::{
EditorSession,
templates::{NewDesignChoice, create_from_template, template_spec},
};
use slint::{ComponentHandle, SharedString};
use super::{FIXED_CYLINDER_PREFORM_SIDES, misc::bump_form_reset_pulse};
use crate::{
EditorModel, MainWindow, TemplateGalleryModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
callbacks::solve_actions::clear_analysis_results,
guide, loading,
stall_guard::stall_guard,
state::{EditorState, PendingUnsavedAction, gear_choice_to_teeth},
templates::GalleryDialog,
view::{SolidLastSolved, push_has_design, refresh_all_now},
},
show_toast,
solid_preview::preview_state::SolidPreviewState,
},
};
thread_local! {
static TEMPLATE_CHOICE: RefCell<Option<NewDesignChoice>> = const { RefCell::new(None) };
}
fn stash_template_choice(choice: Option<NewDesignChoice>) {
TEMPLATE_CHOICE.with(|slot| *slot.borrow_mut() = choice);
}
fn take_template_choice(template_index: i32) -> Option<NewDesignChoice> {
TEMPLATE_CHOICE
.with(|slot| slot.borrow_mut().take())
.filter(|choice| choice.template_index == template_index)
}
fn create_or_ask(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
spec: FreshDesignSpec,
template_index: i32,
) {
if state.borrow().is_dirty() {
state.borrow_mut().pending_unsaved_action = Some(PendingUnsavedAction::New {
spec,
template_index,
});
ui.global::<EditorModel>().set_unsaved_dialog_message(
"Starting a new design will discard the current one's unsaved changes.".into(),
);
ui.global::<EditorModel>().set_unsaved_dialog_open(true);
} else {
do_new_design_create(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
spec,
template_index,
);
}
}
fn setup_template_dialog_callbacks(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let dialog = Rc::new(GalleryDialog::new(render_ctx));
let gallery = ui.global::<TemplateGalleryModel>();
let ui_weak = ui.as_weak();
let shown = Rc::clone(&dialog);
gallery.on_dialog_shown(move || {
stall_guard("on_template_dialog_shown", || {
if let Some(ui) = ui_weak.upgrade() {
shown.shown(&ui);
}
});
});
let ui_weak = ui.as_weak();
let chosen = Rc::clone(&dialog);
gallery.on_template_chosen(move |template_index: i32| {
stall_guard("on_template_chosen", || {
if let Some(ui) = ui_weak.upgrade() {
chosen.template_chosen(&ui, template_index);
}
});
});
let ui_weak = ui.as_weak();
let material = Rc::clone(&dialog);
gallery.on_material_chosen(move |material_index: i32| {
stall_guard("on_template_material_chosen", || {
if let Some(ui) = ui_weak.upgrade() {
material.material_chosen(&ui, material_index);
}
});
});
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();
gallery.on_create(
move |template_index: i32,
gear_index: i32,
material_index: i32,
width_text: SharedString| {
stall_guard("on_template_create", || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let choice =
match dialog.choice(template_index, gear_index, material_index, &width_text) {
Ok(choice) => choice,
Err(message) => {
show_toast(&ui, &message, "error");
return;
}
};
let Some(template) = template_spec(choice.template_index) else {
return;
};
let spec = template.fresh_spec(choice.material.clone());
let template_index = choice.template_index;
stash_template_choice(Some(choice));
create_or_ask(
&ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
spec,
template_index,
);
});
},
);
}
pub(in crate::gui::editor) fn setup_new_design_create_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
setup_template_dialog_callbacks(ui, state, render_ctx, preview_state, solid_last_solved);
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_new_design_create(
move |shape_index: i32,
half_width: SharedString,
length_over_width: SharedString,
depth: SharedString,
gear_preset_index: i32,
gear_custom_text: SharedString,
symmetry_order_text: SharedString,
mirror: bool,
material_index: i32,
template_index: i32| {
stall_guard("on_new_design_create", || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
stash_template_choice(None);
let gear_teeth = match gear_choice_to_teeth(gear_preset_index, &gear_custom_text) {
Ok(t) => t,
Err(e) => {
show_toast(&ui, &e, "error");
return;
}
};
let preform = match loading::parse_preform_form(
shape_index,
&half_width,
&length_over_width,
&depth,
FIXED_CYLINDER_PREFORM_SIDES,
) {
Ok(p) => p,
Err(e) => {
show_toast(&ui, &e, "error");
return;
}
};
match loading::parse_new_design_form(
gear_teeth,
preform,
&symmetry_order_text,
mirror,
material_index,
) {
Ok(spec) => create_or_ask(
&ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
spec,
template_index,
),
Err(e) => show_toast(&ui, &e, "error"),
}
});
},
);
}
pub(super) fn do_new_design_create(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
spec: FreshDesignSpec,
template_index: i32,
) {
let prepared = match take_template_choice(template_index) {
Some(choice) => match create_from_template(&choice) {
Ok(created) => Some(created),
Err(message) => {
show_toast(ui, &message, "error");
return;
}
},
None => None,
};
let adaptation = if let Some(created) = prepared {
install_prepared_design(&mut state.borrow_mut(), created.session);
Some(created.adaptation)
} else {
install_new_design(&mut state.borrow_mut(), spec, template_index);
None
};
clear_analysis_results(ui);
ui.global::<EditorModel>()
.set_material_suggestion_name("".into());
ui.global::<EditorModel>()
.set_material_suggestion_text("".into());
refresh_all_now(
ui,
render_ctx,
preview_state,
solid_last_solved,
state,
true,
);
ui.global::<EditorModel>().set_selected_tier_index(-1);
bump_form_reset_pulse(ui);
ui.global::<EditorModel>().set_new_dialog_open(false);
ui.global::<EditorModel>().set_is_dirty(false);
ui.set_loaded_design_name(SharedString::new());
push_has_design(ui, &state.borrow());
guide::notify(ui, guide::NEW_DESIGN_CREATED);
if let Some(adaptation) = adaptation
&& let Some(message) = adaptation.message()
{
let kind = if adaptation.adapted() {
"info"
} else {
"warning"
};
show_toast(ui, &message, kind);
}
}
pub(super) fn install_prepared_design(st: &mut EditorState, session: EditorSession) {
st.replace_wholesale(EditorState::fresh_from_session(session));
st.has_design = true;
}
pub(super) fn install_new_design(st: &mut EditorState, spec: FreshDesignSpec, template_index: i32) {
st.replace_wholesale(EditorState::fresh_from_template(spec, template_index));
st.has_design = true;
}