From 8847724127fe87523d3a1be0ee01197ec170473d Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 8 Aug 2026 21:53:26 +0300 Subject: [PATCH] fix(cursor): restore default geometry Keep CURSORSIZE 5 and PICKBOX 3 mapped to the pre-option 60 px arms, 15 px box, and 8 px selection aperture.\n\nRefs #413 --- src/app/mod.rs | 4 +-- src/app/settings.rs | 4 +-- src/app/update/viewport.rs | 24 +++++++++--------- src/ui/overlay.rs | 51 +++++++++++++++++++++++++++++++++++--- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index bec19b348..e582987e1 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -448,9 +448,9 @@ pub(super) struct OpenCADStudio { zoom_wheel_reversed: bool, /// Mouse-wheel zoom sensitivity, clamped to 3..=100 (ZOOMFACTOR). zoom_factor: i32, - /// Crosshair reach as a viewport percentage (CURSORSIZE, 1..=100). + /// Crosshair size setting (CURSORSIZE, 1..=100). cursor_size: i32, - /// Selection-box half-size and click aperture in pixels (PICKBOX, 0..=50). + /// Selection-box size setting (PICKBOX, 0..=50). pick_box: i32, /// Drawing viewport cursor style (CURSORTYPE). cursor_type: settings::CursorType, diff --git a/src/app/settings.rs b/src/app/settings.rs index 25cf6f8bb..4e4ad329c 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -156,9 +156,9 @@ pub struct UserSettings { pub polar_increment_deg: f32, pub zoom_wheel_reversed: bool, pub zoom_factor: i32, - /// CURSORSIZE: crosshair arm reach as a percentage of the viewport. + /// CURSORSIZE: normalized crosshair reach; 5 retains the original 60 px arms. pub cursor_size: i32, - /// PICKBOX: visible selection-box half-size and click aperture in pixels. + /// PICKBOX: normalized visible-box and click-aperture size. pub pick_box: i32, /// CURSORTYPE: crosshair or the platform pointer over the drawing. pub cursor_type: CursorType, diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index c567cc622..37219dd69 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -1778,7 +1778,7 @@ impl OpenCADStudio { eye, bounds, self.tabs[i].scene.document.header.lineweight_display, - self.pick_box.max(1) as f32, + crate::ui::overlay::pick_box_aperture_px(self.pick_box), ) .and_then(|s| Scene::handle_from_wire_name(s)) .or_else(|| { @@ -2941,7 +2941,7 @@ impl OpenCADStudio { view_rot2, eye2, bounds, - self.pick_box.max(1) as f32 * 2.0, + crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0, ); let include_fills = self.tabs[i] .active_cmd @@ -2962,7 +2962,7 @@ impl OpenCADStudio { eye2, bounds, self.tabs[i].scene.document.header.lineweight_display, - self.pick_box.max(1) as f32, + crate::ui::overlay::pick_box_aperture_px(self.pick_box), ) .and_then(|s| Scene::handle_from_wire_name(s)) .or_else(|| { @@ -3491,7 +3491,7 @@ impl OpenCADStudio { view_rot, eye, bounds, - self.pick_box.max(1) as f32 * 2.0, + crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0, ); let candidate_handles = self.tabs[i] .scene @@ -3511,7 +3511,7 @@ impl OpenCADStudio { eye, bounds, self.tabs[i].scene.document.header.lineweight_display, - self.pick_box.max(1) as f32, + crate::ui::overlay::pick_box_aperture_px(self.pick_box), ) .into_iter() .filter_map(|s| Scene::handle_from_wire_name(s)) @@ -3533,7 +3533,7 @@ impl OpenCADStudio { eye, bounds, self.tabs[i].scene.document.header.lineweight_display, - self.pick_box.max(1) as f32, + crate::ui::overlay::pick_box_aperture_px(self.pick_box), ) .and_then(|s| Scene::handle_from_wire_name(s)) .or_else(|| { @@ -3875,7 +3875,7 @@ impl OpenCADStudio { view_rot, eye, bounds, - self.pick_box.max(1) as f32 * 2.0, + crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0, ); let candidate_handles = self.tabs[i] .scene @@ -3890,7 +3890,7 @@ impl OpenCADStudio { eye, bounds, self.tabs[i].scene.document.header.lineweight_display, - self.pick_box.max(1) as f32, + crate::ui::overlay::pick_box_aperture_px(self.pick_box), ) .and_then(|s| Scene::handle_from_wire_name(s)) .or_else(|| { @@ -3995,7 +3995,7 @@ impl OpenCADStudio { view_rot, eye, bounds, - self.pick_box.max(1) as f32 * 2.0, + crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0, ); scene::pick::hit_test::click_hit( p, @@ -4004,7 +4004,7 @@ impl OpenCADStudio { eye, bounds, self.tabs[i].scene.document.header.lineweight_display, - self.pick_box.max(1) as f32, + crate::ui::overlay::pick_box_aperture_px(self.pick_box), ) .and_then(|s| Scene::handle_from_wire_name(s)) .and_then(|h| { @@ -4410,7 +4410,7 @@ impl OpenCADStudio { view_rot, eye, bounds, - self.pick_box.max(1) as f32 * 2.0, + crate::ui::overlay::pick_box_aperture_px(self.pick_box) * 2.0, ); let candidate_ms = candidate_started.elapsed().as_secs_f64() * 1000.0; let candidate_count = hover_candidates.len(); @@ -4430,7 +4430,7 @@ impl OpenCADStudio { eye, bounds, self.tabs[i].scene.document.header.lineweight_display, - self.pick_box.max(1) as f32, + crate::ui::overlay::pick_box_aperture_px(self.pick_box), ) .and_then(Scene::handle_from_wire_name); let wire_ms = wire_started.elapsed().as_secs_f64() * 1000.0; diff --git a/src/ui/overlay.rs b/src/ui/overlay.rs index a92324536..c0eef63c1 100644 --- a/src/ui/overlay.rs +++ b/src/ui/overlay.rs @@ -12,6 +12,51 @@ use crate::scene::SelectionState; use crate::snap::SnapType; +/// Original crosshair geometry retained at the default setting values. +pub const CROSSHAIR_SQ: f32 = 7.5; +pub const CROSSHAIR_ARM: f32 = 60.0; +const DEFAULT_CURSOR_SIZE: i32 = 5; +const DEFAULT_PICK_BOX: i32 = 3; +const DEFAULT_PICK_APERTURE: f32 = 8.0; + +/// Convert CURSORSIZE to a screen-space arm length while keeping the original +/// 60 px cursor at the default value and the full-viewport result at 100. +pub(crate) fn crosshair_arm_px(bounds: iced::Rectangle, value: i32) -> f32 { + let value = value.clamp(1, 100); + if value <= DEFAULT_CURSOR_SIZE { + return CROSSHAIR_ARM * value as f32 / DEFAULT_CURSOR_SIZE as f32; + } + + let full = bounds.width.hypot(bounds.height).max(CROSSHAIR_ARM); + let scale = (value - DEFAULT_CURSOR_SIZE) as f32 / (100 - DEFAULT_CURSOR_SIZE) as f32; + CROSSHAIR_ARM + (full - CROSSHAIR_ARM) * scale +} + +/// Convert PICKBOX to the visible half-size while retaining the original +/// 15 x 15 px center box at the default value. +pub(crate) fn pick_box_half_px(value: i32) -> f32 { + let value = value.clamp(0, 50); + if value <= DEFAULT_PICK_BOX { + return CROSSHAIR_SQ * value as f32 / DEFAULT_PICK_BOX as f32; + } + + let scale = (value - DEFAULT_PICK_BOX) as f32 / (50 - DEFAULT_PICK_BOX) as f32; + CROSSHAIR_SQ + (50.0 - CROSSHAIR_SQ) * scale +} + +/// Convert PICKBOX to the real click/hover aperture. A zero setting hides the +/// box but retains the one-pixel minimum needed for direct hits. +pub(crate) fn pick_box_aperture_px(value: i32) -> f32 { + let value = value.clamp(0, 50); + let aperture = if value <= DEFAULT_PICK_BOX { + DEFAULT_PICK_APERTURE * value as f32 / DEFAULT_PICK_BOX as f32 + } else { + let scale = (value - DEFAULT_PICK_BOX) as f32 / (50 - DEFAULT_PICK_BOX) as f32; + DEFAULT_PICK_APERTURE + (50.0 - DEFAULT_PICK_APERTURE) * scale + }; + aperture.max(1.0) +} + #[derive(Clone, Copy)] pub struct CrosshairOptions { pub size_percent: i32, @@ -964,10 +1009,8 @@ impl canvas::Program for SelectionCanvas { style: canvas::Style::Solid(color), ..Default::default() }; - let sq = self.crosshair.pick_box.clamp(0, 50) as f32; - let arm = bounds.width.hypot(bounds.height) - * self.crosshair.size_percent.clamp(1, 100) as f32 - / 100.0; + let sq = pick_box_half_px(self.crosshair.pick_box); + let arm = crosshair_arm_px(bounds, self.crosshair.size_percent); let base_angles: [f64; 2] = if self.crosshair.isometric { self.crosshair.iso_plane.angles() } else {