use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex},
};
use indicatrix_editor::session::InlineAngle;
use slint::{ComponentHandle, SharedString};
use crate::{
EditorModel, MainWindow,
bridge::render_thread::RenderContext,
gui::{
editor::{
edit_intent,
relation_ui::{
DRIVEN_ANGLE_HINT, edit_error_text, refresh_with_followers, split_driven_targets,
with_followers,
},
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_inline_set_angle_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_inline_set_angle(move |index: i32, text: SharedString| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let Ok(index) = usize::try_from(index) else {
return;
};
let mut st = state.borrow_mut();
match st.set_tier_angle_from_text(index, &text) {
Ok(InlineAngle::Missing) => {}
Ok(InlineAngle::NoChange) => show_toast(&ui, "No change.", "info"),
Ok(InlineAngle::Applied(_)) => refresh_with_followers(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
[index],
),
Err(e) => show_toast(&ui, &e, "error"),
}
});
}
pub(super) use indicatrix_editor::session::tier_nudge_label;
pub(in crate::gui::editor) fn setup_nudge_angle_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let intent_queue = {
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();
edit_intent::EditIntentQueue::new(move |intent| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let edit_intent::EditIntent::NudgeAngle { targets, delta_deg } = intent else {
return;
};
apply_nudge_intent(
&ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
&targets,
delta_deg,
);
})
};
let state = Rc::clone(state);
ui.global::<EditorModel>()
.on_nudge_angle(move |anchor_index: i32, delta_deg: f32| {
stall_guard("on_nudge_angle", || {
let Ok(anchor_index) = usize::try_from(anchor_index) else {
return;
};
let st = state.borrow();
let is_multi_target =
st.multi_selected.len() > 1 && st.multi_selected.contains(&anchor_index);
let targets: Vec<usize> = if is_multi_target {
st.multi_selected.iter().copied().collect()
} else {
vec![anchor_index]
};
drop(st);
intent_queue.post(edit_intent::EditIntent::NudgeAngle {
targets,
delta_deg: f64::from(delta_deg),
});
});
});
}
fn apply_nudge_intent(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
targets: &[usize],
delta_deg: f64,
) {
let mut st = state.borrow_mut();
let (driven, targets) = split_driven_targets(&st.design, targets);
let targets = targets.as_slice();
if targets.is_empty() {
drop(st);
if !driven.is_empty() {
show_toast(ui, DRIVEN_ANGLE_HINT, "info");
}
return;
}
match st.nudge_angles(targets, delta_deg) {
Ok(None) => {}
Ok(Some(outcome)) => {
let dirty: BTreeSet<usize> = with_followers(&st.design, targets.iter().copied());
refresh_editor_panel_stale(ui, render_ctx, &st, &dirty);
submit_preview_replan(
ui,
render_ctx,
preview_state,
solid_last_solved,
&st,
dirty,
false,
);
if !outcome.clamped_labels.is_empty() {
show_toast(
ui,
&format!(
"{} stopped at 0° -- nudging further would move it into the \
other block. Type the angle directly (e.g. \"-0\") to cross \
blocks on purpose.",
outcome.clamped_labels.join(", ")
),
"info",
);
}
if !driven.is_empty() {
show_toast(ui, DRIVEN_ANGLE_HINT, "info");
}
}
Err(e) => {
let message = edit_error_text(&mut st, &e);
show_toast(ui, &message, "error");
}
}
}