mod drag;
mod handles;
mod slice;
#[cfg(test)]
mod tests;
use crate::{
MainWindow, ManipulateModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
edit_intent::{EditIntent, EditIntentQueue},
state::EditorState,
view::SolidLastSolved,
},
solid_preview::{
facet_map::FacetMap,
preview_state::{FrameGeometry, SolidPickState, SolidPreviewState},
},
},
};
use indicatrix::geometry::meet_solver::SolvedTier;
use indicatrix_cut_core::ConstraintTier;
use indicatrix_editor::manipulate::{FacetFrame, HandleKind, HandleLayout, SnapMode};
use slint::ComponentHandle as _;
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, Mutex},
};
pub(super) const PROVISIONAL_GENERATION: u64 = u64::MAX - 1;
pub(super) const fn frame_updates_mast_cache(generation: u64) -> bool {
generation != PROVISIONAL_GENERATION
}
pub(super) fn provisional_active() -> bool {
slice::is_active()
}
pub(super) fn note_provisional_masts(masts: Vec<SolvedTier>) {
slice::note_masts(masts);
}
pub(super) fn note_committed_replan_submitted() {
slice::clear_awaiting();
}
pub(super) fn note_provisional_planes(planes: &[(glam::Vec3, f32)]) {
slice::note_planes(planes);
}
pub(super) fn drop_provisional_for_selection(ui: &MainWindow) {
slice::drop_for_selection(ui);
}
#[derive(Clone)]
struct Shared {
state: Rc<RefCell<EditorState>>,
render_ctx: Arc<Mutex<RenderContext>>,
preview_state: Arc<SolidPreviewState>,
solid_last_solved: SolidLastSolved,
geometry: Arc<Mutex<Option<FrameGeometry>>>,
}
#[derive(Clone)]
struct Target {
tier: usize,
frame: FacetFrame,
layout: HandleLayout,
label: String,
provisional: bool,
}
struct CachedFacetMap {
design_generation: u64,
solved_generation: u64,
map: Rc<FacetMap>,
}
struct HandleSession {
target: Option<Target>,
facet_map: Option<CachedFacetMap>,
hovered: Option<HandleKind>,
hover_hint_active: bool,
drag: Option<drag::ActiveDrag>,
slice: slice::SliceState,
}
impl HandleSession {
const fn new() -> Self {
Self {
target: None,
facet_map: None,
hovered: None,
hover_hint_active: false,
drag: None,
slice: slice::SliceState::new(),
}
}
}
thread_local! {
static SESSION: RefCell<HandleSession> = const { RefCell::new(HandleSession::new()) };
static CTX: RefCell<Option<Shared>> = const { RefCell::new(None) };
}
const fn kind_from_int(kind: i32) -> Option<HandleKind> {
match kind {
0 => Some(HandleKind::Angle),
1 => Some(HandleKind::Depth),
2 => Some(HandleKind::Index),
_ => None,
}
}
const fn kind_to_int(kind: HandleKind) -> i32 {
match kind {
HandleKind::Angle => 0,
HandleKind::Depth => 1,
HandleKind::Index => 2,
}
}
const fn snap_mode(shift: bool, snap_off: bool) -> SnapMode {
if snap_off {
SnapMode::Off
} else if shift {
SnapMode::Fine
} else {
SnapMode::Coarse
}
}
fn tier_label(tier: &ConstraintTier, index: usize) -> String {
if tier.name.is_empty() {
format!("tier {}", index + 1)
} else {
tier.name.clone()
}
}
fn set_hint(ui: &MainWindow, text: &str) {
ui.global::<ManipulateModel>().set_hint_text(text.into());
}
pub(super) fn setup_manipulate_callbacks(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
solid_pick_state: &SolidPickState,
) {
let ctx = Shared {
state: Rc::clone(state),
render_ctx: Arc::clone(render_ctx),
preview_state: Arc::clone(preview_state),
solid_last_solved: Arc::clone(solid_last_solved),
geometry: Arc::clone(&solid_pick_state.geometry),
};
CTX.with(|cell| *cell.borrow_mut() = Some(ctx.clone()));
wire_pointer_callbacks(ui, &ctx);
wire_drag_callbacks(ui, &ctx);
wire_slice_callbacks(ui, &ctx);
}
fn wire_slice_callbacks(ui: &MainWindow, ctx: &Shared) {
let model = ui.global::<ManipulateModel>();
let ui_weak = ui.as_weak();
let toggle_ctx = ctx.clone();
model.on_slice_toggled(move || {
if let Some(ui) = ui_weak.upgrade() {
slice::toggled(&ui, &toggle_ctx);
}
});
let ui_weak = ui.as_weak();
model.on_slice_begin(move |x: f32, y: f32| {
if let Some(ui) = ui_weak.upgrade() {
slice::begin(&ui, x, y);
}
});
let ui_weak = ui.as_weak();
model.on_slice_move(move |x: f32, y: f32| {
if let Some(ui) = ui_weak.upgrade() {
slice::move_to(&ui, x, y);
}
});
let ui_weak = ui.as_weak();
let end_ctx = ctx.clone();
model.on_slice_end(move |x: f32, y: f32| {
if let Some(ui) = ui_weak.upgrade() {
slice::end(&ui, &end_ctx, x, y);
}
});
let ui_weak = ui.as_weak();
model.on_slice_cancel(move || {
if let Some(ui) = ui_weak.upgrade() {
slice::cancel_gesture(&ui);
}
});
wire_provisional_buttons(ui, ctx);
}
fn wire_provisional_buttons(ui: &MainWindow, ctx: &Shared) {
let model = ui.global::<ManipulateModel>();
let ui_weak = ui.as_weak();
let flip_ctx = ctx.clone();
model.on_slice_flip(move || {
if let Some(ui) = ui_weak.upgrade() {
slice::flip(&ui, &flip_ctx);
}
});
let ui_weak = ui.as_weak();
let keep_ctx = ctx.clone();
model.on_slice_keep(move || {
if let Some(ui) = ui_weak.upgrade() {
slice::keep(&ui, &keep_ctx);
}
});
let ui_weak = ui.as_weak();
let discard_ctx = ctx.clone();
model.on_slice_discard(move || {
if let Some(ui) = ui_weak.upgrade() {
slice::discard(&ui, &discard_ctx, slice::DiscardReason::User);
}
});
let ui_weak = ui.as_weak();
let symmetric_ctx = ctx.clone();
model.on_slice_symmetric_toggled(move || {
if let Some(ui) = ui_weak.upgrade() {
slice::symmetric_toggled(&ui, &symmetric_ctx);
}
});
}
fn wire_pointer_callbacks(ui: &MainWindow, ctx: &Shared) {
let model = ui.global::<ManipulateModel>();
let ui_weak = ui.as_weak();
model.on_handle_hit_test(move |x: f32, y: f32| {
ui_weak
.upgrade()
.map_or(-1, |ui| handles::hit_test_int(&ui, x, y))
});
let ui_weak = ui.as_weak();
let hover_ctx = ctx.clone();
model.on_handle_hover(move |x: f32, y: f32| {
if let Some(ui) = ui_weak.upgrade() {
handles::hover(&ui, &hover_ctx, x, y);
}
});
let ui_weak = ui.as_weak();
let snap_ctx = ctx.clone();
model.on_snap_toggled(move || {
if let Some(ui) = ui_weak.upgrade() {
handles::refresh_hover_hint(&ui, &snap_ctx);
}
});
}
fn wire_drag_callbacks(ui: &MainWindow, ctx: &Shared) {
let model = ui.global::<ManipulateModel>();
let queue = {
let ctx = ctx.clone();
let ui_weak = ui.as_weak();
EditIntentQueue::new(move |intent: EditIntent| {
if let Some(ui) = ui_weak.upgrade() {
drag::apply_intent(&ui, &ctx, &intent);
}
})
};
let ui_weak = ui.as_weak();
let begin_ctx = ctx.clone();
model.on_drag_begin(move |kind: i32, x: f32, y: f32| {
if let Some(ui) = ui_weak.upgrade() {
drag::begin(&ui, &begin_ctx, kind, x, y);
}
});
let ui_weak = ui.as_weak();
model.on_drag_move(move |x: f32, y: f32, shift: bool| {
if let Some(ui) = ui_weak.upgrade() {
drag::move_to(&ui, &queue, x, y, shift);
}
});
let ui_weak = ui.as_weak();
let end_ctx = ctx.clone();
model.on_drag_end(move || {
if let Some(ui) = ui_weak.upgrade() {
drag::end(&ui, &end_ctx);
}
});
let ui_weak = ui.as_weak();
let cancel_ctx = ctx.clone();
model.on_drag_cancel(move || {
if let Some(ui) = ui_weak.upgrade() {
drag::cancel(&ui, &cancel_ctx);
}
});
}
pub(super) fn on_frame_landed(ui: &MainWindow, generation: u64) {
let Some(ctx) = CTX.with(|cell| cell.borrow().clone()) else {
return;
};
if slice::on_landed(ui, &ctx, generation) {
return;
}
slice::sync_outline(ui, &ctx);
if ui.global::<ManipulateModel>().get_dragging() {
drag::update_feedback(ui, &ctx);
} else {
handles::refresh_handles(ui, &ctx);
}
}