navigation: selectable mouse navigation style

Options → User Preferences → Navigation style picks which mouse
gestures pan and orbit the model view (#1480):

- CAD (default): middle button pans, Shift + middle button orbits.
- Blender: middle button orbits, Shift + middle button pans.
- CATIA: middle button pans; pressing the left button during it orbits,
  so 3D navigation needs only the mouse.

An armed orbit tool always orbits. The choice is saved with the user
settings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 11:17:13 +03:00
1 parent 74df9ff84a
commit 19ecf78bd9
30 files changed
+183 -1

No files matched your search

+8
View File
@@ -650,6 +650,11 @@ pub(super) struct OpenCADStudio {
polar_increment_deg: f32,
/// Reverse the mouse-wheel zoom direction when true (ZOOMWHEEL = 1).
zoom_wheel_reversed: bool,
/// Which mouse gestures pan and orbit the model view.
navigation_style: crate::app::settings::NavigationStyle,
/// CATIA navigation: the left button went down while the middle one was
/// held, so the middle drag orbits until it is released.
nav_left_with_middle: bool,
/// Scroll pans and Ctrl+scroll zooms (touchpads); see `scroll_intent`.
touchpad_pan: bool,
/// Multi-finger gestures on a touch screen.
@@ -2657,6 +2662,7 @@ pub enum Message {
CommandLineOpacityChanged(u8),
/// Toggle reversing the mouse-wheel zoom direction (ZOOMWHEEL).
ZoomWheelReversedChanged(bool),
NavigationStyleChanged(crate::app::settings::NavigationStyle),
TouchpadPanChanged(bool),
/// Options: show the pull-down menus (MENUBAR).
MenuBarChanged(bool),
@@ -4632,6 +4638,8 @@ impl OpenCADStudio {
polar_mode: false,
polar_increment_deg: 45.0,
zoom_wheel_reversed: false,
navigation_style: Default::default(),
nav_left_with_middle: false,
touchpad_pan: false,
touch: Default::default(),
touch_offset: None,
+29
View File
@@ -204,6 +204,33 @@ pub fn zoom_notch_percent(factor: i32) -> f32 {
(1.0 - ratio) * 100.0
}
/// Which mouse gestures pan and orbit the model view.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NavigationStyle {
/// Middle button pans; Shift + middle button orbits.
#[default]
Cad,
/// Middle button orbits; Shift + middle button pans.
Blender,
/// Middle button pans; middle and left buttons together orbit.
Catia,
}
impl NavigationStyle {
pub const ALL: [NavigationStyle; 3] = [Self::Cad, Self::Blender, Self::Catia];
}
impl std::fmt::Display for NavigationStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&match self {
Self::Cad => crate::t!("CAD: Shift + middle button orbits"),
Self::Blender => crate::t!("Blender: middle button orbits, Shift pans"),
Self::Catia => crate::t!("CATIA: middle + left button orbits"),
})
}
}
/// Active pair of axes while isometric drafting is enabled.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum IsoPlane {
@@ -388,6 +415,7 @@ pub struct UserSettings {
pub polar: bool,
pub polar_increment_deg: f32,
pub zoom_wheel_reversed: bool,
pub navigation_style: NavigationStyle,
/// Scrolling pans and Ctrl+scroll (a touchpad pinch) zooms, for a
/// touchpad that reports two-finger drags as wheel scrolls.
pub touchpad_pan: bool,
@@ -891,6 +919,7 @@ impl Default for UserSettings {
polar: false,
polar_increment_deg: 45.0,
zoom_wheel_reversed: false,
navigation_style: NavigationStyle::Cad,
touchpad_pan: false,
ui_scale_percent: 100,
zoom_factor: 60,
+2
View File
@@ -778,6 +778,7 @@ impl OpenCADStudio {
polar: self.polar_mode,
polar_increment_deg: self.polar_increment_deg,
zoom_wheel_reversed: self.zoom_wheel_reversed,
navigation_style: self.navigation_style,
touchpad_pan: self.touchpad_pan,
ui_scale_percent: self.ui_scale_percent,
zoom_factor: self.zoom_factor,
@@ -913,6 +914,7 @@ impl OpenCADStudio {
self.polar_mode = s.polar;
self.polar_increment_deg = s.polar_increment_deg;
self.zoom_wheel_reversed = s.zoom_wheel_reversed;
self.navigation_style = s.navigation_style;
self.touchpad_pan = s.touchpad_pan;
self.ui_scale_percent = crate::app::settings::clamp_ui_scale(s.ui_scale_percent);
self.zoom_factor = crate::app::settings::clamp_zoom_factor(s.zoom_factor);
+26
View File
@@ -4482,6 +4482,25 @@ impl OpenCADStudio {
Task::none()
}
// CATIA navigation: the left button pressed during a middle drag
// turns the drag into an orbit instead of starting a pick.
Message::ViewportLeftPressAt(_) | Message::ViewportLeftPress | Message::PanePress(_)
if self.navigation_style == crate::app::settings::NavigationStyle::Catia
&& self.tabs[self.active_tab]
.scene
.selection
.borrow()
.input
.middle_down =>
{
self.nav_left_with_middle = true;
Task::none()
}
Message::ViewportLeftRelease | Message::PaneRelease(_) if self.nav_left_with_middle => {
self.nav_left_with_middle = false;
Task::none()
}
Message::ViewportMove(p) => self.on_viewport_move(p),
Message::ViewportExit => self.on_viewport_exit(),
@@ -4686,6 +4705,7 @@ impl OpenCADStudio {
}
Message::ViewportMiddleRelease => {
self.nav_left_with_middle = false;
let i = self.active_tab;
let mut sel = self.tabs[i].scene.selection.borrow_mut();
sel.input.middle_down = false;
@@ -9225,6 +9245,12 @@ impl OpenCADStudio {
Task::none()
}
Message::NavigationStyleChanged(style) => {
self.navigation_style = style;
self.persist_settings_if_changed();
Task::none()
}
Message::MenuBarChanged(shown) => {
self.show_menu_bar = shown;
self.persist_settings_if_changed();
+13 -1
View File
@@ -1416,7 +1416,7 @@ impl OpenCADStudio {
// — the requested Zoom=wheel / Pan=MMB / Rotate=Shift+MMB
// scheme (#229). Floating viewports and paper keep the
// plain MMB pan.
if self.shift_down || self.tabs[i].orbit_mode {
if self.middle_drag_orbits(i) {
if self.tabs[i].scene.active_viewport.is_some() {
// Orbit the floating viewport's own model view.
drop(sel);
@@ -3165,6 +3165,18 @@ impl OpenCADStudio {
Task::none()
}
/// Whether a middle-button drag orbits rather than pans, by the
/// navigation style (an armed orbit tool always orbits).
fn middle_drag_orbits(&self, i: usize) -> bool {
use crate::app::settings::NavigationStyle;
self.tabs[i].orbit_mode
|| match self.navigation_style {
NavigationStyle::Cad => self.shift_down,
NavigationStyle::Blender => !self.shift_down,
NavigationStyle::Catia => self.nav_left_with_middle,
}
}
pub(crate) fn on_viewport_exit(&mut self) -> Task<Message> {
let i = self.active_tab;
self.tabs[i].measurement.live_visible = false;
+1
View File
@@ -312,6 +312,7 @@ impl OpenCADStudio {
show_ribbon: !self.ribbon_closed,
hatch_associative: self.hatch_associative,
zoom_wheel_reversed: self.zoom_wheel_reversed,
navigation_style: self.navigation_style,
touchpad_pan: self.touchpad_pan,
ui_scale_percent: self.ui_scale_percent,
zoom_factor: self.zoom_factor,
+4
View File
@@ -5070,6 +5070,10 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"User Preferences" => Some(("common", "user-preferences")),
"Zoom" => Some(("common", "zoom")),
"Reverse mouse wheel zoom (ZOOMWHEEL)" => Some(("common", "reverse-mouse-wheel-zoom-zoomwheel")),
"Navigation style" => Some(("common", "navigation-style")),
"CAD: Shift + middle button orbits" => Some(("common", "nav-style-cad")),
"Blender: middle button orbits, Shift pans" => Some(("common", "nav-style-blender")),
"CATIA: middle + left button orbits" => Some(("common", "nav-style-catia")),
"Touchpad: scroll pans, Ctrl+scroll or pinch zooms" => Some(("common", "touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms")),
"Interface scale" => Some(("common", "interface-scale")),
"Zoom factor" => Some(("common", "zoom-factor")),
+12
View File
@@ -63,6 +63,8 @@ pub struct AppPrefs {
pub hatch_associative: bool,
/// ZOOMWHEEL: reverse the mouse-wheel zoom direction.
pub zoom_wheel_reversed: bool,
/// Which mouse gestures pan and orbit the model view.
pub navigation_style: crate::app::settings::NavigationStyle,
/// Scroll pans and Ctrl+scroll zooms (touchpads).
pub touchpad_pan: bool,
/// Interface scale in percent.
@@ -1274,6 +1276,16 @@ pub fn view_window<'a>(
Space::new().height(10),
spacemouse,
Space::new().height(12),
text(crate::t!("Navigation style")).size(15),
Space::new().height(10),
iced::widget::pick_list(
Some(prefs.navigation_style),
crate::app::settings::NavigationStyle::ALL,
|style| style.to_string(),
)
.on_select(Message::NavigationStyleChanged)
.text_size(12),
Space::new().height(12),
text(crate::t!("Zoom")).size(15),
Space::new().height(10),
row![