use crate::{
gui::editor::{deep_solve, optimize_solve, retarget},
settings::SettingsPersister,
};
use indicatrix_cut_core::{FreshDesignSpec, OptimizeOutcome, RemapRounding};
use indicatrix_editor::{EditorSession, material::MaterialComboCache, scratch::PushedScratch};
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
sync::{
Arc, Mutex,
atomic::{AtomicU64, Ordering as AtomicOrdering},
},
};
use tracing::warn;
use super::file_extras::DesignFileExtras;
pub(in crate::gui::editor) use indicatrix_editor::session::ANGLE_NUDGE_COALESCE_WINDOW;
const ANCHOR_EXPLAINER_SUPPRESS_KEY: &str = "anchor_explainer";
thread_local! {
static ANCHOR_EXPLAINER_DECIDED_THIS_SESSION: std::cell::Cell<bool> =
const { std::cell::Cell::new(false) };
}
fn anchor_explainer_is_suppressed() -> bool {
SettingsPersister::installed_for_this_thread().is_some_and(|persister| {
persister
.snapshot()
.settings
.is_confirm_suppressed(ANCHOR_EXPLAINER_SUPPRESS_KEY)
})
}
pub(in crate::gui::editor) fn anchor_explainer_suppress_permanently() {
let Some(persister) = SettingsPersister::installed_for_this_thread() else {
warn!("No settings persister is installed; not suppressing the anchor explainer");
return;
};
persister.update(|file| {
file.settings
.suppress_confirm(ANCHOR_EXPLAINER_SUPPRESS_KEY);
});
}
#[must_use]
pub(in crate::gui::editor) fn should_open_anchor_explainer(
design_has_missing_anchor: bool,
) -> bool {
if !design_has_missing_anchor {
return false;
}
if ANCHOR_EXPLAINER_DECIDED_THIS_SESSION.with(std::cell::Cell::get) {
return false;
}
ANCHOR_EXPLAINER_DECIDED_THIS_SESSION.with(|decided| decided.set(true));
!anchor_explainer_is_suppressed()
}
pub(in crate::gui::editor) struct PendingGearRemap {
pub(in crate::gui::editor) from_gear: i32,
pub(in crate::gui::editor) to_gear: i32,
pub(in crate::gui::editor) symmetry_order: u32,
pub(in crate::gui::editor) mirror: bool,
pub(in crate::gui::editor) rounding: RemapRounding,
}
pub(in crate::gui) enum PendingUnsavedAction {
New {
spec: FreshDesignSpec,
template_index: i32,
},
LoadSelected,
OpenNative,
}
pub(in crate::gui) enum AfterSave {
CloseWindow,
Resume(PendingUnsavedAction),
}
pub(in crate::gui) struct EditorState {
pub(in crate::gui::editor) session: EditorSession,
pub(in crate::gui::editor) printed_proportions:
Option<indicatrix::geometry::stone_metrics::ExternalProportions>,
pub(in crate::gui::editor) design_epoch: Arc<AtomicU64>,
pub(in crate::gui::editor) pending_unsaved_action: Option<PendingUnsavedAction>,
pub(in crate::gui) after_save: Option<AfterSave>,
pub(in crate::gui::editor) deep_solve: Option<deep_solve::DeepSolveHandle>,
pub(in crate::gui::editor) optimize: Option<optimize_solve::OptimizeSolveHandle>,
pub(in crate::gui::editor) pending_optimize: Arc<Mutex<Option<(OptimizeOutcome, u64)>>>,
pub(in crate::gui::editor) deep_solve_result_generation: Option<u64>,
pub(in crate::gui::editor) asc_filename: Option<String>,
pub(in crate::gui::editor) original_asc_text: Option<String>,
pub(in crate::gui::editor) source_entry_id: Option<i64>,
pub(in crate::gui::editor) used_placeholder: bool,
pub(in crate::gui::editor) file_extras: DesignFileExtras,
pub(in crate::gui::editor) pending_gear_remap: Option<PendingGearRemap>,
pub(in crate::gui::editor) pending_retarget: Option<(retarget::RetargetProposal, u64)>,
pub(in crate::gui::editor) last_pushed_scratch: RefCell<PushedScratch>,
pub(in crate::gui::editor) material_combo_cache: RefCell<MaterialComboCache>,
pub(in crate::gui::editor) has_design: bool,
}
impl EditorState {
pub(in crate::gui::editor) fn fresh() -> Self {
Self::wrapping(EditorSession::fresh(), false)
}
pub(in crate::gui::editor) fn fresh_from_template(
spec: FreshDesignSpec,
template_index: i32,
) -> Self {
Self::wrapping(EditorSession::from_template(spec, template_index), true)
}
pub(in crate::gui::editor) fn fresh_from_session(session: EditorSession) -> Self {
Self::wrapping(session, true)
}
fn wrapping(session: EditorSession, has_design: bool) -> Self {
Self {
session,
printed_proportions: None,
design_epoch: Arc::new(AtomicU64::new(0)),
pending_unsaved_action: None,
after_save: None,
deep_solve: None,
optimize: None,
pending_optimize: Arc::new(Mutex::new(None)),
deep_solve_result_generation: None,
asc_filename: None,
original_asc_text: None,
pending_gear_remap: None,
pending_retarget: None,
source_entry_id: None,
used_placeholder: false,
file_extras: DesignFileExtras::default().with_design_uuid_assigned(None),
last_pushed_scratch: RefCell::new(PushedScratch::default()),
material_combo_cache: RefCell::new(MaterialComboCache::default()),
has_design,
}
}
pub(in crate::gui) fn design_uuid(&self) -> &str {
&self.file_extras.metadata.id
}
pub(in crate::gui::editor) fn replace_wholesale(&mut self, mut replacement: Self) {
if let Some(handle) = self.deep_solve.take() {
handle.cancel();
}
if let Some(handle) = self.optimize.take() {
handle.cancel();
}
replacement.session.continue_generation_from(&self.session);
replacement.design_epoch = Arc::clone(&self.design_epoch);
replacement
.design_epoch
.fetch_add(1, AtomicOrdering::Relaxed);
*self = replacement;
}
}
impl Deref for EditorState {
type Target = EditorSession;
fn deref(&self) -> &EditorSession {
&self.session
}
}
impl DerefMut for EditorState {
fn deref_mut(&mut self) -> &mut EditorSession {
&mut self.session
}
}