use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex},
};
use indicatrix_cut_core::Edit;
use slint::{ComponentHandle, Model};
use super::{
adopt::{setup_adopt_all_callback, setup_adopt_selected_callback, setup_pin_to_mast_callback},
facet_editing::{
setup_facet_add_callback, setup_facet_remove_callback, setup_facet_toggle_detach_callback,
setup_highlight_tooth_callback, setup_tier_mirror_indices_callback,
setup_tier_rotate_indices_callback,
},
facet_overlay::resubmit_facet_overlay,
misc::{adjust_selection_after_remove, bump_form_reset_pulse},
nudge::tier_nudge_label,
tier_generation::{
setup_generate_step_series_callback, setup_mirror_tier_to_other_block_callback,
unique_duplicate_name,
},
};
use crate::{
EditorModel, EditorTierItem, MainWindow,
bridge::render_thread::RenderContext,
gui::{
editor::{
auto_solve,
state::{EditorState, apply_multi_selection, push_multi_selected_count, push_tiers},
view::{SolidLastSolved, refresh_editor_panel_stale, submit_preview_replan},
},
show_toast,
solid_preview::preview_state::SolidPreviewState,
},
};
pub(in crate::gui::editor) fn setup_remove_tier_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_remove_tier(move |index: i32| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
if index < 0 {
return;
}
let mut st = state.borrow_mut();
let removed_summary = st.design.tiers.get(index as usize).map(|tier| {
let name = if tier.name.is_empty() {
"(unnamed)".to_string()
} else {
tier.name.clone()
};
(name, tier.indices.len())
});
match st.apply(Edit::RemoveTier {
index: index as usize,
}) {
Ok(()) => {
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::new());
adjust_selection_after_remove(&ui, index);
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::new(),
false,
);
drop(st);
if let Some((name, facet_count)) = removed_summary {
let plural = if facet_count == 1 { "" } else { "s" };
show_toast(
&ui,
&format!("Removed {name} ({facet_count} facet{plural}), Undo"),
"info",
);
}
}
Err(e) => show_toast(&ui, &e.to_string(), "error"),
}
});
}
pub(in crate::gui::editor) fn setup_duplicate_tier_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_duplicate_tier(move |index: i32| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let Ok(index) = usize::try_from(index) else {
return;
};
let mut st = state.borrow_mut();
let Some(source) = st.design.tiers.get(index) else {
return;
};
let mut duplicate = source.clone();
let source_label = if source.name.is_empty() {
"(unnamed)".to_string()
} else {
source.name.clone()
};
let existing_names: Vec<String> =
st.design.tiers.iter().map(|t| t.name.clone()).collect();
duplicate.name = unique_duplicate_name(&source.name, &existing_names);
let duplicate_label = duplicate.name.clone();
duplicate.imported_meet = None;
let new_index = index + 1;
match st.apply(Edit::AddTier {
index: new_index,
tier: duplicate,
}) {
Ok(()) => {
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::from([new_index]));
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::from([new_index]),
false,
);
drop(st);
ui.global::<EditorModel>()
.set_selected_tier_index(new_index as i32);
show_toast(
&ui,
&format!("Duplicated {source_label} as {duplicate_label}"),
"info",
);
}
Err(e) => show_toast(&ui, &e.to_string(), "error"),
}
});
}
pub(in crate::gui::editor) fn setup_toggle_detach_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
auto_solve::stash_render_ctx(render_ctx);
let state_toggle = Rc::clone(state);
let render_ctx_toggle = Arc::clone(render_ctx);
let preview_state_toggle = Arc::clone(preview_state);
let solid_last_solved_toggle = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>()
.on_toggle_detach(move |index: i32| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
if index < 0 {
return;
}
let mut st = state_toggle.borrow_mut();
let index = index as usize;
let Some(tier) = st.design.tiers.get(index) else {
return;
};
let edit_result = if tier.detached.is_empty() {
st.design.detach_all_in_tier(index)
} else {
st.design.reattach_all_in_tier(index)
};
match edit_result.and_then(|edit| st.apply(edit)) {
Ok(()) => {
refresh_editor_panel_stale(
&ui,
&render_ctx_toggle,
&st,
&BTreeSet::from([index]),
);
submit_preview_replan(
&ui,
&render_ctx_toggle,
&preview_state_toggle,
&solid_last_solved_toggle,
&st,
BTreeSet::from([index]),
false,
);
let now_detached = st
.design
.tiers
.get(index)
.is_some_and(|tier| !tier.detached.is_empty());
let label = st
.design
.tiers
.get(index)
.map(|tier| tier_nudge_label(tier, index));
drop(st);
if let Some(label) = label {
let verb = if now_detached {
"Detached"
} else {
"Reattached"
};
show_toast(&ui, &format!("{verb} {label}"), "info");
}
}
Err(e) => show_toast(&ui, &e.to_string(), "error"),
}
});
setup_move_tier_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_complete_orbit_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_clear_multi_select_callback(ui, state);
setup_remove_multi_selected_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_facet_remove_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_facet_toggle_detach_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_facet_add_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_tier_rotate_indices_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_tier_mirror_indices_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_adopt_all_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_adopt_selected_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_pin_to_mast_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_highlight_tooth_callback(ui, state);
setup_generate_step_series_callback(ui, state, render_ctx, preview_state, solid_last_solved);
setup_mirror_tier_to_other_block_callback(
ui,
state,
render_ctx,
preview_state,
solid_last_solved,
);
}
fn setup_move_tier_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_move_tier(move |index: i32, direction: i32| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let Ok(index) = usize::try_from(index) else {
return;
};
let mut st = state.borrow_mut();
let tier_count = st.design.tiers.len();
let target = if direction < 0 {
index.checked_sub(1)
} else {
index.checked_add(1).filter(|&t| t < tier_count)
};
let Some(target) = target else {
return;
};
match st.apply(Edit::MoveTier {
from: index,
to: target,
}) {
Ok(()) => {
refresh_editor_panel_stale(
&ui,
&render_ctx,
&st,
&(0..st.design.tiers.len()).collect(),
);
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::new(),
true,
);
drop(st);
ui.global::<EditorModel>()
.set_selected_tier_index(target as i32);
}
Err(e) => show_toast(&ui, &e.to_string(), "error"),
}
});
}
fn setup_complete_orbit_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_complete_orbit(move |index: i32| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let Ok(index) = usize::try_from(index) else {
return;
};
let mut st = state.borrow_mut();
let Ok(units) = st.design.orbit_units(index) else {
return;
};
let anchors: Vec<f64> = units
.iter()
.filter(|unit| !unit.is_complete())
.filter_map(|unit| unit.members.first().copied())
.collect();
if anchors.is_empty() {
return;
}
let mut last_err = None;
for position in anchors {
let outcome = st
.design
.add_orbit_member(index, position)
.and_then(|edit| st.apply(edit));
if let Err(e) = outcome {
last_err = Some(e);
}
}
match last_err {
None => {
refresh_editor_panel_stale(&ui, &render_ctx, &st, &BTreeSet::from([index]));
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::from([index]),
false,
);
}
Some(e) => show_toast(&ui, &e.to_string(), "error"),
}
});
}
fn setup_clear_multi_select_callback(ui: &MainWindow, state: &Rc<RefCell<EditorState>>) {
let state = Rc::clone(state);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_clear_multi_select(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let mut st = state.borrow_mut();
if st.multi_selected.is_empty() {
return;
}
st.multi_selected.clear();
let mut rows: Vec<EditorTierItem> = ui.global::<EditorModel>().get_tiers().iter().collect();
apply_multi_selection(&mut rows, &st.multi_selected);
push_tiers(&ui, rows);
push_multi_selected_count(&ui, 0);
if let Some(preview_state) = auto_solve::preview_state() {
resubmit_facet_overlay(&preview_state, |overlay| overlay.multi_selected.clear());
}
});
}
fn setup_remove_multi_selected_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_remove_multi_selected(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let mut st = state.borrow_mut();
let targets: Vec<usize> = st.multi_selected.iter().rev().copied().collect();
if targets.is_empty() {
return;
}
let removed_count = targets.len();
let mut last_err = None;
for index in targets {
if let Err(e) = st.apply(Edit::RemoveTier { index }) {
last_err = Some(e);
}
}
match last_err {
None => {
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,
);
drop(st);
ui.global::<EditorModel>().set_selected_tier_index(-1);
bump_form_reset_pulse(&ui);
show_toast(&ui, &format!("Removed {removed_count} tier(s)."), "info");
}
Some(e) => show_toast(&ui, &e.to_string(), "error"),
}
});
}