Files
OneCAD-Draft/src/app/drafting_settings.rs
T
Hakan SevenandClaude Opus 5.5 ba8aed4fad fix(dynmode): one toggle keeps DYNMODE's sign in step
F12, the drafting dialog and restored settings each re-derived DYNMODE and
lost the mode on the way: F12 off set 0, so on came back as 3 even after
DYNMODE 1. set_dyn_input now makes off negative (temporarily off) and on
restore the stored mode, and the bare query prints the stored value.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-03 15:01:45 +03:00

309 lines
11 KiB
Rust

use super::OpenCADStudio;
use crate::ui::window::drafting_settings::{DraftingSettingsState, DraftingSettingsTab};
impl DraftingSettingsState {
pub(crate) fn from_app(app: &OpenCADStudio) -> Self {
let snap_x = app.snapper.snap_spacing_x;
let snap_y = app.snapper.snap_spacing_y;
// The dialog has no stored "equal" flag, so infer it: equal when
// X and Y match. This keeps the checkbox checked for the common
// case (including fresh defaults of 10/10).
let snap_equal = (snap_x - snap_y).abs() < 1e-6;
Self {
active_tab: DraftingSettingsTab::SnapAndGrid,
snap_on: app.snapper.grid_snap(),
grid_on: app.show_grid,
snap_x_input: crate::ui::window::drafting_settings::format_snap_spacing(snap_x),
snap_y_input: crate::ui::window::drafting_settings::format_snap_spacing(snap_y),
snap_equal,
grid_x_input: crate::ui::window::drafting_settings::format_snap_spacing(
app.grid_spacing_x,
),
grid_y_input: crate::ui::window::drafting_settings::format_snap_spacing(
app.grid_spacing_y,
),
grid_major_input: format!("{}", app.grid_major_every),
grid_adaptive: app.grid_adaptive,
grid_beyond_limits: app.grid_beyond_limits,
isometric: app.isometric_drafting,
iso_plane: app.iso_plane,
snap_angle_deg: app.snap_angle_deg,
polar_on: app.polar_mode,
ortho_on: app.ortho_mode,
polar_increment_deg: app.polar_increment_deg,
osnap_on: app.snapper.snap_enabled,
otrack_on: app.snapper.otrack_enabled,
snap_modes: app.snapper.enabled.clone(),
osnap3d_on: app.snapper.snap3d_enabled,
snap3d_modes: app.snapper.enabled3d.clone(),
dyn_input_on: app.dyn_input,
quick_props_on: app.quick_properties,
selection_cycling_on: app.selection_cycling,
}
}
}
impl OpenCADStudio {
/// Turn Dynamic Input on or off with DYNMODE in step: off keeps the mode
/// negative (temporarily off), on restores it, or 3 when there was none.
pub(in crate::app) fn set_dyn_input(&mut self, on: bool) {
self.dyn_input = on;
let mode = self.dyn_mode.abs();
self.dyn_mode = match (on, mode) {
(true, 0) => 3,
(true, mode) => mode,
(false, mode) => -mode,
};
}
pub(super) fn drafting_settings_dirty(&self) -> bool {
match (&self.drafting_settings_state, &self.drafting_settings_saved) {
(Some(curr), Some(saved)) => curr.is_dirty(saved),
(Some(_), None) => true,
_ => false,
}
}
pub(super) fn apply_drafting_settings(&mut self) -> bool {
let Some(state) = &self.drafting_settings_state.clone() else {
return true;
};
let x = crate::ui::window::drafting_settings::parse_snap_spacing(&state.snap_x_input);
let y = crate::ui::window::drafting_settings::parse_snap_spacing(&state.snap_y_input);
let (Some(sx), Some(sy)) = (x, y) else {
self.command_line
.push_error(crate::t!("Snap X and Y spacings must be positive numbers.").as_ref());
return false;
};
// When locked, Y follows X so the two can never diverge.
let sy = if state.snap_equal { sx } else { sy };
if let Some(live) = &mut self.drafting_settings_state {
if live.snap_equal {
live.snap_y_input = live.snap_x_input.clone();
}
}
let gx = crate::ui::window::drafting_settings::parse_snap_spacing(&state.grid_x_input);
let gy = crate::ui::window::drafting_settings::parse_snap_spacing(&state.grid_y_input);
let gm = crate::ui::window::drafting_settings::parse_grid_major(&state.grid_major_input);
let (Some(gx), Some(gy), Some(gm)) = (gx, gy, gm) else {
self.command_line.push_error(
crate::t!("Grid X/Y spacings must be positive numbers and Major every 2-100.")
.as_ref(),
);
return false;
};
self.show_grid = state.grid_on;
self.grid_spacing_x = gx;
self.grid_spacing_y = gy;
self.grid_major_every = gm;
self.grid_adaptive = state.grid_adaptive;
self.grid_beyond_limits = state.grid_beyond_limits;
self.snapper.grid_snap_on = state.snap_on;
self.snapper.snap_spacing_x = sx;
self.snapper.snap_spacing_y = sy;
self.isometric_drafting = state.isometric;
self.iso_plane = state.iso_plane;
self.snap_angle_deg = state.snap_angle_deg;
self.polar_mode = state.polar_on;
self.ortho_mode = state.ortho_on;
self.polar_increment_deg = state.polar_increment_deg;
self.snapper.snap_enabled = state.osnap_on;
self.snapper.otrack_enabled = state.otrack_on;
self.snapper.enabled = state.snap_modes.clone();
self.snapper.snap3d_enabled = state.osnap3d_on;
self.snapper.enabled3d = state.snap3d_modes.clone();
self.set_dyn_input(state.dyn_input_on);
self.quick_properties = state.quick_props_on;
self.selection_cycling = state.selection_cycling_on;
self.sync_vport_display(self.active_tab);
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drafting_settings_tab_switching_not_dirty() {
let state1 = DraftingSettingsState {
active_tab: DraftingSettingsTab::SnapAndGrid,
snap_on: false,
grid_on: true,
snap_x_input: "10".to_string(),
snap_y_input: "10".to_string(),
snap_equal: true,
grid_x_input: "10".to_string(),
grid_y_input: "10".to_string(),
grid_major_input: "5".to_string(),
grid_adaptive: true,
grid_beyond_limits: true,
isometric: false,
iso_plane: crate::app::settings::IsoPlane::Left,
snap_angle_deg: 0.0,
polar_on: false,
ortho_on: false,
polar_increment_deg: 15.0,
osnap_on: true,
otrack_on: false,
snap_modes: rustc_hash::FxHashSet::default(),
osnap3d_on: false,
snap3d_modes: rustc_hash::FxHashSet::default(),
dyn_input_on: false,
quick_props_on: false,
selection_cycling_on: false,
};
let mut state2 = state1.clone();
assert!(!state2.is_dirty(&state1));
// Switching tabs should NOT mark it dirty
state2.active_tab = DraftingSettingsTab::ObjectSnap;
assert!(!state2.is_dirty(&state1));
// Toggling a setting should mark it dirty
state2.grid_on = false;
assert!(state2.is_dirty(&state1));
}
#[test]
fn test_drafting_settings_dirty_toggles() {
let base = DraftingSettingsState {
active_tab: DraftingSettingsTab::SnapAndGrid,
snap_on: false,
grid_on: true,
snap_x_input: "10".to_string(),
snap_y_input: "10".to_string(),
snap_equal: true,
grid_x_input: "10".to_string(),
grid_y_input: "10".to_string(),
grid_major_input: "5".to_string(),
grid_adaptive: true,
grid_beyond_limits: true,
isometric: false,
iso_plane: crate::app::settings::IsoPlane::Left,
snap_angle_deg: 0.0,
polar_on: false,
ortho_on: false,
polar_increment_deg: 15.0,
osnap_on: true,
otrack_on: false,
snap_modes: rustc_hash::FxHashSet::default(),
osnap3d_on: false,
snap3d_modes: rustc_hash::FxHashSet::default(),
dyn_input_on: false,
quick_props_on: false,
selection_cycling_on: false,
};
let mut modded = base.clone();
modded.snap_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.isometric = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.iso_plane = crate::app::settings::IsoPlane::Top;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.snap_angle_deg = 45.0;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.polar_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.ortho_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.polar_increment_deg = 30.0;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.osnap_on = false;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.otrack_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.snap_modes.insert(crate::snap::SnapType::Endpoint);
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.osnap3d_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.snap3d_modes.insert(crate::snap::SnapType::Vertex);
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.dyn_input_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.quick_props_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.selection_cycling_on = true;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.snap_x_input = "5".to_string();
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.snap_y_input = "5".to_string();
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.snap_equal = false;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.grid_x_input = "5".to_string();
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.grid_y_input = "5".to_string();
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.grid_major_input = "10".to_string();
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.grid_adaptive = false;
assert!(modded.is_dirty(&base));
let mut modded = base.clone();
modded.grid_beyond_limits = false;
assert!(modded.is_dirty(&base));
}
#[test]
fn test_parse_snap_spacing() {
use crate::ui::window::drafting_settings::{
format_snap_spacing, parse_grid_major, parse_snap_spacing,
};
assert_eq!(parse_snap_spacing("10"), Some(10.0));
assert_eq!(parse_snap_spacing(" 2.5 "), Some(2.5));
assert_eq!(parse_snap_spacing("0"), None);
assert_eq!(parse_snap_spacing("-1"), None);
assert_eq!(parse_snap_spacing("abc"), None);
assert_eq!(parse_snap_spacing(""), None);
assert_eq!(format_snap_spacing(10.0), "10");
assert_eq!(parse_grid_major("5"), Some(5));
assert_eq!(parse_grid_major("1"), None);
assert_eq!(parse_grid_major("101"), None);
assert_eq!(parse_grid_major("abc"), None);
}
}