use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex},
};
use slint::{ComponentHandle, Model};
use super::facet_overlay::{resubmit_facet_overlay, set_hovered_facet};
use crate::{
EditorModel, EditorTierItem, MainWindow, SolidPreviewModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
auto_solve,
manipulate::{drop_provisional_for_selection, provisional_active},
state::{EditorState, apply_multi_selection, push_multi_selected_count, push_tiers},
view::{SolidLastSolved, push_selected_tier_chips, submit_preview_replan},
},
solid_preview::{
facet_selection,
preview_state::{SolidPickState, SolidPreviewState},
},
tutorial_events::raise,
},
};
use indicatrix_editor::guide::viewing_events as events;
thread_local! {
static SELECTED_FACET_LABEL: RefCell<String> = const { RefCell::new(String::new()) };
}
#[must_use]
pub(in crate::gui::editor) fn selected_facet_id() -> Option<u32> {
facet_selection::selected_facet_id()
}
pub(in crate::gui::editor) fn map_to_pick_coordinates(
ui: &MainWindow,
x: f32,
y: f32,
) -> (f32, f32) {
let scale = ui.window().scale_factor();
logical_to_pick_pixels((x, y), scale, pick_margin(ui))
}
#[must_use]
pub(in crate::gui::editor) fn logical_to_pick_pixels(
logical: (f32, f32),
scale: f32,
margin: (f32, f32),
) -> (f32, f32) {
let physical = (logical.0 * scale, logical.1 * scale);
(physical.0 - margin.0, physical.1 - margin.1)
}
#[must_use]
pub(in crate::gui::editor) fn pick_pixels_to_logical(
pick: (f32, f32),
scale: f32,
margin: (f32, f32),
) -> (f32, f32) {
((pick.0 + margin.0) / scale, (pick.1 + margin.1) / scale)
}
pub(in crate::gui::editor) fn pick_margin(ui: &MainWindow) -> (f32, f32) {
let scale = ui.window().scale_factor();
let Some(render_ctx) = auto_solve::render_ctx() else {
return (0.0, 0.0);
};
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,
);
letterbox_margin(viewport_physical, contained)
}
#[must_use]
pub(in crate::gui::editor) 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() {
set_hovered_facet(&preview_state, 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() {
set_hovered_facet(&preview_state, 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_owners = Arc::clone(&solid_pick_state.facet_owners);
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;
};
if provisional_active() {
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());
if let Some(preview_state) = auto_solve::preview_state() {
facet_selection::clear_clicked_facet(&preview_state);
} else {
facet_selection::set_selected_facet_id(None);
}
ui.global::<SolidPreviewModel>().set_hover_text("".into());
return;
};
let tier_row = {
let owners = solid_facet_owners
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
facet_selection::clicked_tier_row(&ui, &owners, facet_id)
};
if let Some(tier_row) = tier_row {
ui.global::<EditorModel>()
.set_selected_tier_index(tier_row as i32);
raise(&ui, events::FACET_PICKED);
}
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() {
facet_selection::select_clicked_facet(&preview_state, facet_id);
} else {
facet_selection::set_selected_facet_id(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 Some(ui) = ui_weak.upgrade() else {
return;
};
apply_selected_tier_change(
&ui,
&state,
&render_ctx,
&preview_state,
&solid_last_solved,
);
if index >= 0 {
raise(&ui, events::TIER_SELECTED);
}
});
}
fn apply_selected_tier_change(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
drop_provisional_for_selection(ui);
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,
);
}