forked from 1b-agent/opencadstudio-upstream
feat: editable grid spacing and major grid lines in Drafting Settings
The Snap and Grid tab showed static grid values with dead checkboxes, and every grid line stroked identically. Grid X/Y spacing and Major-line-every are now editable inputs (validated 2-100 for majors, dirty-tracked, applied with an error message on bad input); Adaptive-grid and Display-beyond-Limits checkboxes are wired to live state. Every Nth line partitions into major_segments and strokes subtly brighter (1.5x alpha, 0.75px) for the reference hierarchy. GRIDMAJOR state persists to UserSettings. Covers partition, invalidation, and parse tests.
This commit is contained in:
1 parent
05f462f294
commit
4aa699e08d
9 files changed
+249
-37
No files matched your search
@@ -16,6 +16,15 @@ impl DraftingSettingsState {
|
||||
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,
|
||||
@@ -61,7 +70,22 @@ impl OpenCADStudio {
|
||||
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;
|
||||
@@ -97,6 +121,11 @@ mod tests {
|
||||
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,
|
||||
@@ -133,6 +162,11 @@ mod tests {
|
||||
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,
|
||||
@@ -220,11 +254,33 @@ mod tests {
|
||||
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_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);
|
||||
@@ -232,5 +288,9 @@ mod tests {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -545,6 +545,8 @@ pub(super) struct OpenCADStudio {
|
||||
/// GRIDUNIT X/Y display spacing backing the DSettings grid-resize inputs.
|
||||
pub grid_spacing_x: f32,
|
||||
pub grid_spacing_y: f32,
|
||||
/// GRIDMAJOR: every Nth line draws as a brighter major line.
|
||||
pub grid_major_every: u32,
|
||||
/// Adaptive grid: scale GRIDUNIT up by 5x steps to stay readable.
|
||||
pub grid_adaptive: bool,
|
||||
/// Display the grid beyond LIMITS (infinite) instead of clipping to them.
|
||||
@@ -2767,6 +2769,11 @@ pub enum Message {
|
||||
DraftingSettingsToggleSnap,
|
||||
DraftingSettingsSnapXChanged(String),
|
||||
DraftingSettingsSnapYChanged(String),
|
||||
DraftingSettingsGridXChanged(String),
|
||||
DraftingSettingsGridYChanged(String),
|
||||
DraftingSettingsGridMajorChanged(String),
|
||||
DraftingSettingsToggleAdaptiveGrid,
|
||||
DraftingSettingsToggleBeyondLimits,
|
||||
DraftingSettingsToggleEqualSnap,
|
||||
DraftingSettingsToggleIsometric,
|
||||
DraftingSettingsSetIsoPlane(crate::app::settings::IsoPlane),
|
||||
@@ -3815,6 +3822,7 @@ impl OpenCADStudio {
|
||||
show_grid: false,
|
||||
grid_spacing_x: 10.0,
|
||||
grid_spacing_y: 10.0,
|
||||
grid_major_every: 5,
|
||||
grid_adaptive: true,
|
||||
grid_beyond_limits: true,
|
||||
dyn_input: true,
|
||||
|
||||
@@ -464,6 +464,9 @@ pub struct UserSettings {
|
||||
pub grid_spacing_x: f32,
|
||||
#[serde(default = "default_snap_spacing")]
|
||||
pub grid_spacing_y: f32,
|
||||
/// GRIDMAJOR: every Nth grid line is a brighter major line.
|
||||
#[serde(default = "default_grid_major")]
|
||||
pub grid_major_every: u32,
|
||||
/// Adaptive grid scaling (default on).
|
||||
#[serde(default = "default_true")]
|
||||
pub grid_adaptive: bool,
|
||||
@@ -496,10 +499,19 @@ fn default_snap_spacing() -> f32 {
|
||||
10.0
|
||||
}
|
||||
|
||||
fn default_grid_major() -> u32 {
|
||||
5
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Clamp a major-line interval to the range the dialog accepts.
|
||||
pub fn sanitize_grid_major(v: u32) -> u32 {
|
||||
v.clamp(2, 100)
|
||||
}
|
||||
|
||||
/// Clamp a snap spacing to the positive range the dialog accepts.
|
||||
pub fn sanitize_snap_spacing(v: f32) -> f32 {
|
||||
if v.is_finite() && v > 0.0 && v <= 1e9 {
|
||||
@@ -616,6 +628,7 @@ impl Default for UserSettings {
|
||||
snap_spacing_y: 10.0,
|
||||
grid_spacing_x: 10.0,
|
||||
grid_spacing_y: 10.0,
|
||||
grid_major_every: 5,
|
||||
grid_adaptive: true,
|
||||
grid_beyond_limits: true,
|
||||
block_mru: Vec::new(),
|
||||
|
||||
@@ -533,6 +533,7 @@ impl OpenCADStudio {
|
||||
snap_spacing_y: self.snapper.snap_spacing_y,
|
||||
grid_spacing_x: self.grid_spacing_x,
|
||||
grid_spacing_y: self.grid_spacing_y,
|
||||
grid_major_every: self.grid_major_every,
|
||||
grid_adaptive: self.grid_adaptive,
|
||||
grid_beyond_limits: self.grid_beyond_limits,
|
||||
block_mru: self.block_mru.clone(),
|
||||
@@ -641,6 +642,7 @@ impl OpenCADStudio {
|
||||
crate::app::settings::sanitize_snap_spacing(s.snap_spacing_y);
|
||||
self.grid_spacing_x = crate::app::settings::sanitize_snap_spacing(s.grid_spacing_x);
|
||||
self.grid_spacing_y = crate::app::settings::sanitize_snap_spacing(s.grid_spacing_y);
|
||||
self.grid_major_every = crate::app::settings::sanitize_grid_major(s.grid_major_every);
|
||||
self.grid_adaptive = s.grid_adaptive;
|
||||
self.grid_beyond_limits = s.grid_beyond_limits;
|
||||
// Block usage: clone but cap to sane sizes (MRU 20, freq map 200)
|
||||
|
||||
@@ -4596,6 +4596,36 @@ impl OpenCADStudio {
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsGridXChanged(value) => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.grid_x_input = value;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsGridYChanged(value) => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.grid_y_input = value;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsGridMajorChanged(value) => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.grid_major_input = value;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleAdaptiveGrid => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.grid_adaptive = !state.grid_adaptive;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleBeyondLimits => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.grid_beyond_limits = !state.grid_beyond_limits;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleEqualSnap => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.snap_equal = !state.snap_equal;
|
||||
|
||||
@@ -465,6 +465,7 @@ impl OpenCADStudio {
|
||||
bounds,
|
||||
step_x,
|
||||
step_y,
|
||||
major_every: self.grid_major_every,
|
||||
origin,
|
||||
axes,
|
||||
limits,
|
||||
|
||||
Reference in new issue
Block a user