From 1447602d43d550b78135f138a1e35f91e99c2b41 Mon Sep 17 00:00:00 2001 From: r3dbaba <47311570+r3dbaba@users.noreply.github.com> Date: Sun, 17 May 2026 20:54:21 -0400 Subject: [PATCH] Add ability to assign None/Pan/Rotate to mouse buttons (#10736) * Add logic to handle the left, middle and right buttons being assigned to do nothing, pan or rotate * Add entries for setting the drag actions to preferences * Allow the label text in preferences to wrap * Show mouse mappings in Help -> Keyboard Shortcuts * Re-add preferences in updated layout * Add camera mouse options under camera * Change mouse action strings to use L() for localization * Display "None" when it is selected instead of blank in keyboard shortcuts --------- Co-authored-by: Rob O Co-authored-by: Noisyfox --- src/libslic3r/AppConfig.cpp | 10 ++++- src/slic3r/GUI/GLCanvas3D.cpp | 55 ++++++++++++++++++++++------ src/slic3r/GUI/GLCanvas3D.hpp | 7 +++- src/slic3r/GUI/KBShortcutsDialog.cpp | 14 +++++-- src/slic3r/GUI/Preferences.cpp | 11 ++++-- 5 files changed, 74 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 33e25f5d9c..b35a754c2b 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -212,8 +212,14 @@ void AppConfig::set_defaults() if (get("camera_navigation_style").empty()) set("camera_navigation_style", "0"); - if (get("swap_mouse_buttons").empty()) - set_bool("swap_mouse_buttons", false); + if (get("left_mouse_drag_action").empty()) + set("left_mouse_drag_action", "2"); + + if (get("middle_mouse_drag_action").empty()) + set("middle_mouse_drag_action", "1"); + + if (get("right_mouse_drag_action").empty()) + set("right_mouse_drag_action", "1"); if (get("reverse_mouse_wheel_zoom").empty()) set_bool("reverse_mouse_wheel_zoom", false); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b630f46794..3ef5eaf0a9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4272,7 +4272,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } bool any_gizmo_active = m_gizmos.get_current() != nullptr; - bool swap_mouse_buttons = wxGetApp().app_config->get_bool("swap_mouse_buttons"); + + std::map button_mappings; + button_mappings[MouseButton::Left] = static_cast(std::atoi(wxGetApp().app_config->get("left_mouse_drag_action").c_str())); + button_mappings[MouseButton::Middle] = static_cast(std::atoi(wxGetApp().app_config->get("middle_mouse_drag_action").c_str())); + button_mappings[MouseButton::Right] = static_cast(std::atoi(wxGetApp().app_config->get("right_mouse_drag_action").c_str())); if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) { m_mouse.drag.move_requires_threshold = false; @@ -4485,7 +4489,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } } - else if (evt.Dragging() || is_camera_rotate(evt, swap_mouse_buttons) || is_camera_pan(evt, swap_mouse_buttons)) { + else if (evt.Dragging() || is_camera_rotate(evt, button_mappings) || is_camera_pan(evt, button_mappings)) { m_mouse.dragging = true; if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) { @@ -4495,10 +4499,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } // do not process the dragging if the left mouse was set down in another canvas - else if (is_camera_rotate(evt, swap_mouse_buttons)) { + else if (is_camera_rotate(evt, button_mappings)) { // Orca: Sphere rotation for painting view - // if dragging over blank area with left button or button functions swapped then rotate - if ((any_gizmo_active || swap_mouse_buttons || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { + // if dragging over blank area with left button or other button mapped to rotate, then rotate + bool middle_or_right_button_used_as_rotate = (evt.MiddleIsDown() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) || + (evt.RightIsDown() && button_mappings[MouseButton::Right] == MouseAction::Rotation); + if ((any_gizmo_active || middle_or_right_button_used_as_rotate || m_hover_volume_idxs.empty()) && + m_mouse.is_start_position_3D_defined()) { Camera& camera = wxGetApp().plater()->get_camera(); auto mult_pref = wxGetApp().app_config->get("camera_orbit_mult"); const double mult = mult_pref.empty() ? 1.0 : std::stod(mult_pref); @@ -4571,7 +4578,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_camera_movement = true; m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0); } - else if (is_camera_pan(evt, swap_mouse_buttons)) { + else if (is_camera_pan(evt, button_mappings)) { // if dragging with right button or if button functions swapped and dragging with left button over blank area then pan if (m_mouse.is_start_position_2D_defined()) { // get point in model space at Z = 0 @@ -4599,10 +4606,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } else if ((evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) || - (m_camera_movement && !is_camera_rotate(evt, swap_mouse_buttons) && !is_camera_pan(evt, swap_mouse_buttons))) { + (m_camera_movement && !is_camera_rotate(evt, button_mappings) && !is_camera_pan(evt, button_mappings))) { m_mouse.position = pos.cast(); - if (swap_mouse_buttons ? evt.RightUp() : evt.LeftUp()) { + // Check if the button that was released is mapped to rotation + if ((evt.LeftUp() && button_mappings[MouseButton::Left] == MouseAction::Rotation) || + (evt.MiddleUp() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) || + (evt.RightUp() && button_mappings[MouseButton::Right] == MouseAction::Rotation)) { m_rotation_center(0) = m_rotation_center(1) = m_rotation_center(2) = 0.f; } @@ -4795,21 +4805,42 @@ void GLCanvas3D::on_set_focus(wxFocusEvent& evt) m_is_touchpad_navigation = wxGetApp().app_config->get_bool("camera_navigation_style"); } -bool GLCanvas3D::is_camera_rotate(const wxMouseEvent& evt, const bool buttonsSwapped) const +bool GLCanvas3D::clicked_button_matches_action(const wxMouseEvent& evt, const MouseAction action, const std::map& mappings) const +{ + MouseButton clicked = MouseButton::None; + if (evt.LeftIsDown()) { + clicked = MouseButton::Left; + } + if (evt.MiddleIsDown()) { + clicked = MouseButton::Middle; + } + if (evt.RightIsDown()) { + clicked = MouseButton::Right; + } + + auto it = mappings.find(clicked); + if (it == mappings.end()) { + return false; + } + return it->second == action; +} + +bool GLCanvas3D::is_camera_rotate(const wxMouseEvent& evt, const std::map& mappings) const { if (m_is_touchpad_navigation) { return evt.Moving() && evt.AltDown() && !evt.ShiftDown(); } else { - return evt.Dragging() && (buttonsSwapped ? evt.RightIsDown() : evt.LeftIsDown()); + return evt.Dragging() && clicked_button_matches_action(evt, MouseAction::Rotation, mappings); } } -bool GLCanvas3D::is_camera_pan(const wxMouseEvent& evt, const bool buttonsSwapped) const +bool GLCanvas3D::is_camera_pan(const wxMouseEvent& evt, const std::map& mappings) const { if (m_is_touchpad_navigation) { return evt.Moving() && evt.ShiftDown() && !evt.AltDown(); } else { - return evt.Dragging() && (evt.MiddleIsDown() || (buttonsSwapped ? evt.LeftIsDown() : evt.RightIsDown())); + return evt.Dragging() && clicked_button_matches_action(evt, MouseAction::Pan, mappings); + ; } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f6c42bdaa9..9e81e5c4f9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1038,8 +1038,11 @@ public: void on_set_focus(wxFocusEvent& evt); void force_set_focus(); - bool is_camera_rotate(const wxMouseEvent& evt, const bool buttonsSwapped) const; - bool is_camera_pan(const wxMouseEvent& evt, const bool buttonsSwapped) const; + enum class MouseButton { None, Left, Middle, Right }; + enum class MouseAction { None, Pan, Rotation }; + bool clicked_button_matches_action(const wxMouseEvent& evt, MouseAction action, const std::map& mappings) const; + bool is_camera_rotate(const wxMouseEvent& evt, const std::map& mappings) const; + bool is_camera_pan(const wxMouseEvent& evt, const std::map& mappings) const; Size get_canvas_size() const; Vec2d get_local_mouse_position() const; diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 2b845385e7..3e0ef64e55 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -207,13 +207,19 @@ void KBShortcutsDialog::fill_shortcuts() { "?", L("Show keyboard shortcuts list") } }; m_full_shortcuts.push_back({{_L("Global shortcuts"), ""}, global_shortcuts}); + - bool swap_mouse_buttons = wxGetApp().app_config->get_bool("swap_mouse_buttons"); + // Retrieve mouse actions from config and map to MouseAction + std::map mouse_actions; + mouse_actions["0"] = L("None"); + mouse_actions["1"] = L("Pan View"); + mouse_actions["2"] = L("Rotate View"); Shortcuts plater_shortcuts = { - { L("Left mouse button"), swap_mouse_buttons ? L("Pan view") : L("Rotate view") }, - { L("Right mouse button"), swap_mouse_buttons ? L("Rotate view") : L("Pan view") }, - { L("Mouse wheel"), L("Zoom view") }, + { L("Left mouse button"), mouse_actions[wxGetApp().app_config->get("left_mouse_drag_action").c_str()]}, + { L("Middle mouse button"), mouse_actions[wxGetApp().app_config->get("middle_mouse_drag_action").c_str()]}, + { L("Right mouse button"), mouse_actions[wxGetApp().app_config->get("right_mouse_drag_action").c_str()]}, + { L("Mouse wheel"), L("Zoom View") }, { "A", L("Arrange all objects") }, { shift + "A", L("Arrange objects on selected plates") }, diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 2641d73f42..206549cf17 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1500,12 +1500,17 @@ void PreferencesDialog::create_items() auto item_use_free_camera = create_item_checkbox(_L("Use free camera"), _L("If enabled, use free camera. If not enabled, use constrained camera."), "use_free_camera"); g_sizer->Add(item_use_free_camera); - auto swap_pan_rotate = create_item_checkbox(_L("Swap pan and rotate mouse buttons"), _L("If enabled, swaps the left and right mouse buttons pan and rotate functions."), "swap_mouse_buttons"); - g_sizer->Add(swap_pan_rotate); - auto reverse_mouse_zoom = create_item_checkbox(_L("Reverse mouse zoom"), _L("If enabled, reverses the direction of zoom with mouse wheel."), "reverse_mouse_wheel_zoom"); g_sizer->Add(reverse_mouse_zoom); + std::vector ButtonDragActions = {_L("None"), _L("Pan"), _L("Rotate")}; + auto item_left_mouse_drag = create_item_combobox(_L("Left Mouse Drag"), _L("Set the action that dragging the left mouse button should perform."), "left_mouse_drag_action", ButtonDragActions); + g_sizer->Add(item_left_mouse_drag); + auto item_middle_mouse_drag = create_item_combobox(_L("Middle Mouse Drag"), _L("Set the action that dragging the middle mouse button should perform."), "middle_mouse_drag_action", ButtonDragActions); + g_sizer->Add(item_middle_mouse_drag); + auto item_right_mouse_drag = create_item_combobox(_L("Right Mouse Drag"), _L("Set the action that dragging the right mouse button should perform."), "right_mouse_drag_action", ButtonDragActions); + g_sizer->Add(item_right_mouse_drag); + //// CONTROL > Clear my choice on ... g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND);