use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex},
time::Duration,
};
use slint::{ComponentHandle, Model};
use super::facet_overlay::resubmit_facet_overlay;
use crate::{
EditorModel, EditorTierItem, MainWindow, SolidPreviewModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
auto_solve,
state::{EditorState, apply_multi_selection, push_multi_selected_count, push_tiers},
view::{SolidLastSolved, push_selected_tier_chips, submit_preview_replan},
},
solid_preview::preview_state::{SolidPickState, SolidPreviewState},
},
};
thread_local! {
static SELECTED_FACET_LABEL: RefCell<String> = const { RefCell::new(String::new()) };
}
fn map_to_pick_coordinates(ui: &MainWindow, x: f32, y: f32) -> (f32, f32) {
let scale = ui.window().scale_factor();
let physical = (x * scale, y * scale);
let Some(render_ctx) = auto_solve::render_ctx() else {
return physical;
};
let view_mode = ui.global::<SolidPreviewModel>().get_view_mode() as u8;
let viewport_physical = (
(ui.global::<SolidPreviewModel>().get_viewport_width() * scale) as u32,
(ui.global::<SolidPreviewModel>().get_viewport_height() * scale) as u32,
);
let render_size = {
let ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
(ctx.width, ctx.height)
};
let contained = crate::gui::render::camera_lighting::contained_request_size(
view_mode,
viewport_physical,
render_size,
);
let (margin_x, margin_y) = letterbox_margin(viewport_physical, contained);
(physical.0 - margin_x, physical.1 - margin_y)
}
pub(super) fn letterbox_margin(
viewport_physical: (u32, u32),
contained_physical: (u32, u32),
) -> (f32, f32) {
(
viewport_physical.0.saturating_sub(contained_physical.0) as f32 / 2.0,
viewport_physical.1.saturating_sub(contained_physical.1) as f32 / 2.0,
)
}
pub(in crate::gui::editor) fn setup_solid_facet_hover_callback(
ui: &MainWindow,
solid_pick_state: &SolidPickState,
) {
let solid_pick = Arc::clone(&solid_pick_state.pick);
let solid_hover_text = Arc::clone(&solid_pick_state.hover_text);
let ui_weak = ui.as_weak();
ui.global::<SolidPreviewModel>()
.on_facet_hover(move |x: f32, y: f32| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let (px, py) = map_to_pick_coordinates(&ui, x, y);
let facet_id = solid_pick
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
.and_then(|pick| pick.facet_at(px.max(0.0) as u32, py.max(0.0) as u32));
let Some(facet_id) = facet_id else {
let selected_label = SELECTED_FACET_LABEL.with(|cell| cell.borrow().clone());
ui.global::<SolidPreviewModel>()
.set_hover_text(selected_label.into());
if let Some(preview_state) = auto_solve::preview_state() {
resubmit_facet_overlay(&preview_state, |overlay| overlay.hovered = None);
}
return;
};
let text = solid_hover_text
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(facet_id as usize)
.cloned()
.unwrap_or_default();
ui.global::<SolidPreviewModel>().set_hover_text(text.into());
if let Some(preview_state) = auto_solve::preview_state() {
resubmit_facet_overlay(&preview_state, |overlay| overlay.hovered = Some(facet_id));
}
});
}
pub(in crate::gui::editor) fn setup_solid_facet_click_callback(
ui: &MainWindow,
solid_pick_state: &SolidPickState,
) {
let solid_pick = Arc::clone(&solid_pick_state.pick);
let solid_facet_tier = Arc::clone(&solid_pick_state.facet_tier);
let solid_hover_text = Arc::clone(&solid_pick_state.hover_text);
let ui_weak = ui.as_weak();
ui.global::<SolidPreviewModel>()
.on_facet_click(move |x: f32, y: f32| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let (px, py) = map_to_pick_coordinates(&ui, x, y);
let Some(facet_id) = solid_pick
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
.and_then(|pick| pick.facet_at(px.max(0.0) as u32, py.max(0.0) as u32))
else {
ui.global::<EditorModel>().set_selected_tier_index(-1);
SELECTED_FACET_LABEL.with(|cell| cell.borrow_mut().clear());
ui.global::<SolidPreviewModel>().set_hover_text("".into());
return;
};
let tier_index = solid_facet_tier
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(facet_id as usize)
.copied()
.flatten();
if let Some(tier_index) = tier_index {
ui.global::<EditorModel>()
.set_selected_tier_index(tier_index as i32);
}
let label = solid_hover_text
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(facet_id as usize)
.cloned()
.unwrap_or_default();
SELECTED_FACET_LABEL.with(|cell| cell.borrow_mut().clone_from(&label));
ui.global::<SolidPreviewModel>()
.set_hover_text(label.into());
if let Some(preview_state) = auto_solve::preview_state() {
resubmit_facet_overlay(&preview_state, |overlay| {
overlay.selected_facet = Some(facet_id);
});
}
});
}
pub(in crate::gui::editor) fn setup_solid_selected_tier_changed_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_selected_tier_changed(move |_index: i32| {
let busy = state.try_borrow_mut().is_err();
if !busy {
if let Some(ui) = ui_weak.upgrade() {
apply_selected_tier_change(
&ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
);
}
return;
}
let ui_weak = ui_weak.clone();
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);
slint::Timer::single_shot(Duration::ZERO, move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
apply_selected_tier_change(
&ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
);
});
});
}
fn apply_selected_tier_change(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let mut st = state.borrow_mut();
st.history.end_coalesce_run();
if !st.multi_selected.is_empty() {
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);
resubmit_facet_overlay(preview_state, |overlay| overlay.multi_selected.clear());
}
push_selected_tier_chips(ui, &st);
submit_preview_replan(
ui,
render_ctx,
preview_state,
solid_last_solved,
&st,
BTreeSet::new(),
false,
);
}