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
This commit is contained in:
Hakan Seven 2026-08-08 21:53:26 +03:00
commit 8847724127
4 changed files with 63 additions and 20 deletions

View file

@ -448,9 +448,9 @@ pub(super) struct OpenCADStudio {
zoom_wheel_reversed: bool, zoom_wheel_reversed: bool,
/// Mouse-wheel zoom sensitivity, clamped to 3..=100 (ZOOMFACTOR). /// Mouse-wheel zoom sensitivity, clamped to 3..=100 (ZOOMFACTOR).
zoom_factor: i32, zoom_factor: i32,
/// Crosshair reach as a viewport percentage (CURSORSIZE, 1..=100). /// Crosshair size setting (CURSORSIZE, 1..=100).
cursor_size: i32, 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, pick_box: i32,
/// Drawing viewport cursor style (CURSORTYPE). /// Drawing viewport cursor style (CURSORTYPE).
cursor_type: settings::CursorType, cursor_type: settings::CursorType,

View file

@ -156,9 +156,9 @@ pub struct UserSettings {
pub polar_increment_deg: f32, pub polar_increment_deg: f32,
pub zoom_wheel_reversed: bool, pub zoom_wheel_reversed: bool,
pub zoom_factor: i32, 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, 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, pub pick_box: i32,
/// CURSORTYPE: crosshair or the platform pointer over the drawing. /// CURSORTYPE: crosshair or the platform pointer over the drawing.
pub cursor_type: CursorType, pub cursor_type: CursorType,

View file

@ -1778,7 +1778,7 @@ impl OpenCADStudio {
eye, eye,
bounds, bounds,
self.tabs[i].scene.document.header.lineweight_display, 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(|s| Scene::handle_from_wire_name(s))
.or_else(|| { .or_else(|| {
@ -2941,7 +2941,7 @@ impl OpenCADStudio {
view_rot2, view_rot2,
eye2, eye2,
bounds, 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] let include_fills = self.tabs[i]
.active_cmd .active_cmd
@ -2962,7 +2962,7 @@ impl OpenCADStudio {
eye2, eye2,
bounds, bounds,
self.tabs[i].scene.document.header.lineweight_display, 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(|s| Scene::handle_from_wire_name(s))
.or_else(|| { .or_else(|| {
@ -3491,7 +3491,7 @@ impl OpenCADStudio {
view_rot, view_rot,
eye, eye,
bounds, 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] let candidate_handles = self.tabs[i]
.scene .scene
@ -3511,7 +3511,7 @@ impl OpenCADStudio {
eye, eye,
bounds, bounds,
self.tabs[i].scene.document.header.lineweight_display, 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() .into_iter()
.filter_map(|s| Scene::handle_from_wire_name(s)) .filter_map(|s| Scene::handle_from_wire_name(s))
@ -3533,7 +3533,7 @@ impl OpenCADStudio {
eye, eye,
bounds, bounds,
self.tabs[i].scene.document.header.lineweight_display, 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(|s| Scene::handle_from_wire_name(s))
.or_else(|| { .or_else(|| {
@ -3875,7 +3875,7 @@ impl OpenCADStudio {
view_rot, view_rot,
eye, eye,
bounds, 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] let candidate_handles = self.tabs[i]
.scene .scene
@ -3890,7 +3890,7 @@ impl OpenCADStudio {
eye, eye,
bounds, bounds,
self.tabs[i].scene.document.header.lineweight_display, 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(|s| Scene::handle_from_wire_name(s))
.or_else(|| { .or_else(|| {
@ -3995,7 +3995,7 @@ impl OpenCADStudio {
view_rot, view_rot,
eye, eye,
bounds, 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( scene::pick::hit_test::click_hit(
p, p,
@ -4004,7 +4004,7 @@ impl OpenCADStudio {
eye, eye,
bounds, bounds,
self.tabs[i].scene.document.header.lineweight_display, 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(|s| Scene::handle_from_wire_name(s))
.and_then(|h| { .and_then(|h| {
@ -4410,7 +4410,7 @@ impl OpenCADStudio {
view_rot, view_rot,
eye, eye,
bounds, 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_ms = candidate_started.elapsed().as_secs_f64() * 1000.0;
let candidate_count = hover_candidates.len(); let candidate_count = hover_candidates.len();
@ -4430,7 +4430,7 @@ impl OpenCADStudio {
eye, eye,
bounds, bounds,
self.tabs[i].scene.document.header.lineweight_display, 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); .and_then(Scene::handle_from_wire_name);
let wire_ms = wire_started.elapsed().as_secs_f64() * 1000.0; let wire_ms = wire_started.elapsed().as_secs_f64() * 1000.0;

View file

@ -12,6 +12,51 @@ use crate::scene::SelectionState;
use crate::snap::SnapType; 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)] #[derive(Clone, Copy)]
pub struct CrosshairOptions { pub struct CrosshairOptions {
pub size_percent: i32, pub size_percent: i32,
@ -964,10 +1009,8 @@ impl canvas::Program<Message> for SelectionCanvas {
style: canvas::Style::Solid(color), style: canvas::Style::Solid(color),
..Default::default() ..Default::default()
}; };
let sq = self.crosshair.pick_box.clamp(0, 50) as f32; let sq = pick_box_half_px(self.crosshair.pick_box);
let arm = bounds.width.hypot(bounds.height) let arm = crosshair_arm_px(bounds, self.crosshair.size_percent);
* self.crosshair.size_percent.clamp(1, 100) as f32
/ 100.0;
let base_angles: [f64; 2] = if self.crosshair.isometric { let base_angles: [f64; 2] = if self.crosshair.isometric {
self.crosshair.iso_plane.angles() self.crosshair.iso_plane.angles()
} else { } else {