diff --git a/benches/performance_benchmarks.rs b/benches/performance_benchmarks.rs index 4cfd9f3cd..f51a08aa6 100644 --- a/benches/performance_benchmarks.rs +++ b/benches/performance_benchmarks.rs @@ -1180,15 +1180,18 @@ fn bench_ui_grid_geometry(runner: &mut BenchmarkRunner) { let origin2 = glam::DVec3::new(0.0, 0.0, 0.0); let axes2 = (Vec3::X, Vec3::Y, Vec3::Z); let limits2: Option<(glam::DVec2, glam::DVec2)> = None; + // Both panes use a square grid, so one step per pane plus a shared + // major-line interval covers the new step_x / step_y / major_every args. + let major_every = 5u32; // ── 1. Uncached grid geometry generation ── if runner.should_run("ui_grid_geometry_uncached") { for _ in 0..10 { let _ = black_box(grid_segments( - view_rot1, eye1, bounds1, step1, step1, origin1, axes1, limits1, + view_rot1, eye1, bounds1, step1, step1, major_every, origin1, axes1, limits1, )); let _ = black_box(grid_segments( - view_rot2, eye2, bounds2, step2, step2, origin2, axes2, limits2, + view_rot2, eye2, bounds2, step2, step2, major_every, origin2, axes2, limits2, )); } @@ -1199,8 +1202,8 @@ fn bench_ui_grid_geometry(runner: &mut BenchmarkRunner) { for _ in 0..runs { let t0 = Instant::now(); for _ in 0..n { - let g1 = grid_segments(view_rot1, eye1, bounds1, step1, step1, origin1, axes1, limits1); - let g2 = grid_segments(view_rot2, eye2, bounds2, step2, step2, origin2, axes2, limits2); + let g1 = grid_segments(view_rot1, eye1, bounds1, step1, step1, major_every, origin1, axes1, limits1); + let g2 = grid_segments(view_rot2, eye2, bounds2, step2, step2, major_every, origin2, axes2, limits2); black_box(g1); black_box(g2); } @@ -1227,6 +1230,7 @@ fn bench_ui_grid_geometry(runner: &mut BenchmarkRunner) { bounds: bounds1, step_x: step1, step_y: step1, + major_every, origin: origin1, axes: axes1, limits: limits1, @@ -1237,6 +1241,7 @@ fn bench_ui_grid_geometry(runner: &mut BenchmarkRunner) { bounds: bounds2, step_x: step2, step_y: step2, + major_every, origin: origin2, axes: axes2, limits: limits2, diff --git a/src/app/drafting_settings.rs b/src/app/drafting_settings.rs index ccc372c21..04f5753ed 100644 --- a/src/app/drafting_settings.rs +++ b/src/app/drafting_settings.rs @@ -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); } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 5d4c8cdbc..aff2a2089 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -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, diff --git a/src/app/settings.rs b/src/app/settings.rs index 856e7d2ab..e0590355c 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -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(), diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 3ec81c78f..681626d54 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -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) diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index c88d9ffdf..98e77c62e 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -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; diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 238afa81f..c241e3aac 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -465,6 +465,7 @@ impl OpenCADStudio { bounds, step_x, step_y, + major_every: self.grid_major_every, origin, axes, limits, diff --git a/src/ui/overlay.rs b/src/ui/overlay.rs index db5a1354d..09f4b3019 100644 --- a/src/ui/overlay.rs +++ b/src/ui/overlay.rs @@ -495,6 +495,8 @@ pub struct GridParams { pub step_x: f32, /// World-space Y spacing (GRIDUNIT Y, after adaptive scaling). pub step_y: f32, + /// Draw every Nth line as a brighter major line. < 2 disables majors. + pub major_every: u32, /// Grid origin in absolute world f64 and the active UCS axis directions. /// The grid always lies on the active UCS XY plane. Plain WCS passes /// `(ZERO, X, Y, Z)`. @@ -513,6 +515,8 @@ pub struct GridParams { #[doc(hidden)] pub struct GridGeometry { pub segments: Vec<(Point, Point)>, + /// Brighter every-Nth lines (major grid). Stroked separately in `draw_grid`. + pub major_segments: Vec<(Point, Point)>, pub axis_extent: f32, } @@ -523,7 +527,7 @@ impl GridGeometry { /// the `None` path. #[doc(hidden)] pub fn empty() -> Self { - Self { segments: Vec::new(), axis_extent: 0.0 } + Self { segments: Vec::new(), major_segments: Vec::new(), axis_extent: 0.0 } } } @@ -785,6 +789,7 @@ impl canvas::Program for GridCanvas { gb, g.step_x, g.step_y, + g.major_every, g.origin, g.axes, g.limits, @@ -2078,36 +2083,33 @@ fn draw_grid( bounds: iced::Rectangle, step_x: f32, step_y: f32, + major_every: u32, grid_origin: glam::DVec3, grid_axes: (Vec3, Vec3, Vec3), limits: Option<(glam::DVec2, glam::DVec2)>, style: GridStyle, ) { let alpha = (style.opacity as f32 / 100.0).clamp(0.02, 1.0); - let gc = if style.bg_luminance > 0.5 { - // Light background: subtle dark grid lines - Color { - r: 0.10, - g: 0.10, - b: 0.10, - a: alpha, - } + // Major lines stay subtle: ~1.5x the minor alpha with a slightly heavier + // stroke, matching the reference hierarchy without glaring. + let major_alpha = (alpha * 1.5).clamp(0.03, 1.0); + let (r, g, b) = if style.bg_luminance > 0.5 { + (0.10, 0.10, 0.10) } else { - // Dark background: subtle light grid lines - Color { - r: 0.80, - g: 0.80, - b: 0.80, - a: alpha, - } + (0.80, 0.80, 0.80) }; - let st = canvas::Stroke { + let minor_stroke = canvas::Stroke { width: 0.5, - style: canvas::Style::Solid(gc), + style: canvas::Style::Solid(Color { r, g, b, a: alpha }), + ..Default::default() + }; + let major_stroke = canvas::Stroke { + width: 0.75, + style: canvas::Style::Solid(Color { r, g, b, a: major_alpha }), ..Default::default() }; let geometry = - grid_segments(view_rot, eye, bounds, step_x, step_y, grid_origin, grid_axes, limits); + grid_segments(view_rot, eye, bounds, step_x, step_y, major_every, grid_origin, grid_axes, limits); if !geometry.segments.is_empty() { let path = canvas::Path::new(|builder| { for (p0, p1) in &geometry.segments { @@ -2115,7 +2117,16 @@ fn draw_grid( builder.line_to(*p1); } }); - frame.stroke(&path, st); + frame.stroke(&path, minor_stroke); + } + if !geometry.major_segments.is_empty() { + let path = canvas::Path::new(|builder| { + for (p0, p1) in &geometry.major_segments { + builder.move_to(*p0); + builder.line_to(*p1); + } + }); + frame.stroke(&path, major_stroke); } if geometry.axis_extent > 0.0 { let (gx, gy, gz) = grid_axes; @@ -2139,6 +2150,7 @@ pub fn grid_segments( bounds: iced::Rectangle, step_x: f32, step_y: f32, + major_every: u32, grid_origin: glam::DVec3, grid_axes: (Vec3, Vec3, Vec3), limits: Option<(glam::DVec2, glam::DVec2)>, @@ -2283,6 +2295,9 @@ pub fn grid_segments( return GridGeometry::empty(); } let (sx, sy) = (step_x, step_y); + let is_major = |index: i32| { + major_every >= 2 && (index.rem_euclid(major_every as i32) == 0) + }; // Trace a family-specific visible region around the viewport perimeter. // When a boundary ray points through the horizon, binary-search back toward @@ -2581,13 +2596,15 @@ pub fn grid_segments( let (min1, max1) = coordinate_range(axis1); let (min2, max2) = coordinate_range(axis2); + let mut minor = Vec::new(); + let mut major = Vec::new(); if let Some((_, anchor_world, gap)) = best_anchor(0) { if gap >= MIN_HORIZON_GRID_PX { let anchor = (anchor_world - grid_origin).as_vec3().dot(axis1); let (start, end) = line_range(min1, max1, anchor, sx); for index in start..=end { if let Some(segment) = clip_world_line(0, index as f32 * sx) { - all_segments.push(segment); + if is_major(index) { major.push(segment); } else { minor.push(segment); } } } } @@ -2598,11 +2615,13 @@ pub fn grid_segments( let (start, end) = line_range(min2, max2, anchor, sy); for index in start..=end { if let Some(segment) = clip_world_line(1, index as f32 * sy) { - all_segments.push(segment); + if is_major(index) { major.push(segment); } else { minor.push(segment); } } } } } + all_segments.extend(minor); + let all_major: Vec<(Point, Point)> = major; // LIMITS bounds the grid, not the UCS axes. Size the axes from the // visible grid plane so X/Y/Z still span the viewport even when the @@ -2616,9 +2635,10 @@ pub fn grid_segments( if limits_extent > 0.0 { axis_extent = limits_extent; } - return GridGeometry { segments: all_segments, axis_extent }; + return GridGeometry { segments: all_segments, major_segments: all_major, axis_extent }; } + let mut all_major: Vec<(Point, Point)> = Vec::new(); // Lines parallel to axis2 (varying axis1 position). if let Some((anchor_screen, anchor_world, gap)) = best_anchor(0) { if gap >= MIN_HORIZON_GRID_PX { @@ -2631,7 +2651,8 @@ pub fn grid_segments( for i in start..=end { let value = i as f32 * sx; if let Some((p0, p1)) = project_line(0, value) { - all_segments.extend(trim_line(0, p0, p1)); + let segs = trim_line(0, p0, p1); + if is_major(i) { all_major.extend(segs); } else { all_segments.extend(segs); } } } axis_extent = @@ -2652,7 +2673,8 @@ pub fn grid_segments( for i in start..=end { let value = i as f32 * sy; if let Some((p0, p1)) = project_line(1, value) { - all_segments.extend(trim_line(1, p0, p1)); + let segs = trim_line(1, p0, p1); + if is_major(i) { all_major.extend(segs); } else { all_segments.extend(segs); } } } axis_extent = @@ -2662,7 +2684,7 @@ pub fn grid_segments( } let _ = gz; // gz unused after move; retained for symmetry with `draw_axes` call sites. - GridGeometry { segments: all_segments, axis_extent } + GridGeometry { segments: all_segments, major_segments: all_major, axis_extent } } // ── Coloured UCS axes ────────────────────────────────────────────────────── @@ -3740,6 +3762,7 @@ mod grid_key_tests { }, step_x: 80.0, step_y: 80.0, + major_every: 5, origin: glam::DVec3::new(0.0, 0.0, 0.0), axes: (Vec3::X, Vec3::Y, Vec3::Z), limits: None, @@ -3807,6 +3830,15 @@ mod grid_key_tests { "step_y change must invalidate" ); + // major_every: major-line frequency change + let mut p = baseline_params(); + p.major_every = 10; + assert_ne!( + GridKey::from_grids(&[p], baseline_bounds, GridStyle::default()), + baseline_key, + "major_every change must invalidate" + ); + // origin: translate the UCS origin off-zero let mut p = baseline_params(); p.origin = glam::DVec3::new(100.0, 0.0, 0.0); @@ -3962,11 +3994,39 @@ mod grid_key_tests { let eye = glam::DVec3::new(0.0, 0.0, 500.0); let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: 800.0, height: 600.0 }; let g = grid_segments( - view_rot, eye, bounds, 10.0, 2.0, + view_rot, eye, bounds, 10.0, 2.0, 5, glam::DVec3::ZERO, (Vec3::X, Vec3::Y, Vec3::Z), None, ); assert!(!g.segments.is_empty(), "grid lines expected"); } + + /// Every-Nth line lands in `major_segments`, the rest in `segments`. + #[test] + fn grid_segments_partition_major_lines() { + // Top-down orthographic-ish view over the origin: deterministic lines. + let view_rot = glam::camera::rh::proj::directx::orthographic( + -400.0, 400.0, -300.0, 300.0, 0.1, 2000.0, + ) * glam::camera::rh::view::look_at_mat4( + Vec3::new(0.0, 0.0, 500.0), + Vec3::ZERO, + Vec3::Y, + ); + let eye = glam::DVec3::new(0.0, 0.0, 500.0); + let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: 800.0, height: 600.0 }; + let g = grid_segments( + view_rot, eye, bounds, 10.0, 10.0, 5, + glam::DVec3::ZERO, (Vec3::X, Vec3::Y, Vec3::Z), None, + ); + assert!(!g.segments.is_empty(), "minor lines expected"); + assert!(!g.major_segments.is_empty(), "major lines expected"); + // major_every = 1 disables the split: everything is minor. + let flat = grid_segments( + view_rot, eye, bounds, 10.0, 10.0, 1, + glam::DVec3::ZERO, (Vec3::X, Vec3::Y, Vec3::Z), None, + ); + assert!(flat.major_segments.is_empty()); + assert!(!flat.segments.is_empty()); + } } #[cfg(test)] @@ -4001,6 +4061,7 @@ mod grid_canvas_state_tests { bounds, step_x: 80.0, step_y: 80.0, + major_every: 5, origin: grid_origin, axes: grid_axes, limits, diff --git a/src/ui/window/drafting_settings.rs b/src/ui/window/drafting_settings.rs index 1e216b728..8587f66cb 100644 --- a/src/ui/window/drafting_settings.rs +++ b/src/ui/window/drafting_settings.rs @@ -31,6 +31,11 @@ pub struct DraftingSettingsState { pub snap_x_input: String, pub snap_y_input: String, pub snap_equal: bool, + pub grid_x_input: String, + pub grid_y_input: String, + pub grid_major_input: String, + pub grid_adaptive: bool, + pub grid_beyond_limits: bool, pub isometric: bool, pub iso_plane: IsoPlane, pub snap_angle_deg: f32, @@ -62,6 +67,11 @@ impl DraftingSettingsState { || self.snap_x_input != saved.snap_x_input || self.snap_y_input != saved.snap_y_input || self.snap_equal != saved.snap_equal + || self.grid_x_input != saved.grid_x_input + || self.grid_y_input != saved.grid_y_input + || self.grid_major_input != saved.grid_major_input + || self.grid_adaptive != saved.grid_adaptive + || self.grid_beyond_limits != saved.grid_beyond_limits || self.isometric != saved.isometric || self.iso_plane != saved.iso_plane || (self.snap_angle_deg - saved.snap_angle_deg).abs() > 0.001 @@ -98,6 +108,12 @@ pub fn parse_snap_spacing(s: &str) -> Option { } } +/// Parse a major-line interval. Must be an integer in 2..=100. +pub fn parse_grid_major(s: &str) -> Option { + let v: u32 = s.trim().parse().ok()?; + (2..=100).contains(&v).then_some(v) +} + /// Helper for grouped sub-panels with a light border and header title. fn group<'a>(title: impl Into, body: impl Into>) -> Element<'a, Message> { container( @@ -298,19 +314,31 @@ pub fn view_window<'a>( column![ row![ text(crate::t!("Grid X spacing:")).size(11).width(120), - text("10.00").size(11), + text_input("10", &state.grid_x_input) + .on_input(Message::DraftingSettingsGridXChanged) + .size(11) + .padding([4, 7]) + .width(100), ] .spacing(8) .align_y(iced::Center), row![ text(crate::t!("Grid Y spacing:")).size(11).width(120), - text("10.00").size(11), + text_input("10", &state.grid_y_input) + .on_input(Message::DraftingSettingsGridYChanged) + .size(11) + .padding([4, 7]) + .width(100), ] .spacing(8) .align_y(iced::Center), row![ text(crate::t!("Major line every:")).size(11).width(120), - text("5").size(11), + text_input("5", &state.grid_major_input) + .on_input(Message::DraftingSettingsGridMajorChanged) + .size(11) + .padding([4, 7]) + .width(100), ] .spacing(8) .align_y(iced::Center), @@ -322,13 +350,17 @@ pub fn view_window<'a>( crate::t!("Grid behavior"), column![ row![ - checkbox(true).size(14), + checkbox(state.grid_adaptive) + .on_toggle(|_| Message::DraftingSettingsToggleAdaptiveGrid) + .size(14), text(crate::t!("Adaptive grid")).size(11), ] .spacing(6) .align_y(iced::Center), row![ - checkbox(true).size(14), + checkbox(state.grid_beyond_limits) + .on_toggle(|_| Message::DraftingSettingsToggleBeyondLimits) + .size(14), text(crate::t!("Display grid beyond Limits")).size(11), ] .spacing(6)