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 <robertolabode@gmail.com>
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
This commit is contained in:
r3dbaba
2026-05-17 20:54:21 -04:00
committed by GitHub
parent 6cf7db1ded
commit 1447602d43
5 changed files with 74 additions and 23 deletions

View File

@@ -212,8 +212,14 @@ void AppConfig::set_defaults()
if (get("camera_navigation_style").empty()) if (get("camera_navigation_style").empty())
set("camera_navigation_style", "0"); set("camera_navigation_style", "0");
if (get("swap_mouse_buttons").empty()) if (get("left_mouse_drag_action").empty())
set_bool("swap_mouse_buttons", false); 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()) if (get("reverse_mouse_wheel_zoom").empty())
set_bool("reverse_mouse_wheel_zoom", false); set_bool("reverse_mouse_wheel_zoom", false);

View File

@@ -4272,7 +4272,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
} }
bool any_gizmo_active = m_gizmos.get_current() != nullptr; bool any_gizmo_active = m_gizmos.get_current() != nullptr;
bool swap_mouse_buttons = wxGetApp().app_config->get_bool("swap_mouse_buttons");
std::map<MouseButton, MouseAction> button_mappings;
button_mappings[MouseButton::Left] = static_cast<MouseAction>(std::atoi(wxGetApp().app_config->get("left_mouse_drag_action").c_str()));
button_mappings[MouseButton::Middle] = static_cast<MouseAction>(std::atoi(wxGetApp().app_config->get("middle_mouse_drag_action").c_str()));
button_mappings[MouseButton::Right] = static_cast<MouseAction>(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)) { 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; m_mouse.drag.move_requires_threshold = false;
@@ -4485,7 +4489,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true; 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; m_mouse.dragging = true;
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) { 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 // 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 // Orca: Sphere rotation for painting view
// if dragging over blank area with left button or button functions swapped then rotate // if dragging over blank area with left button or other button mapped to rotate, then rotate
if ((any_gizmo_active || swap_mouse_buttons || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { 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(); Camera& camera = wxGetApp().plater()->get_camera();
auto mult_pref = wxGetApp().app_config->get("camera_orbit_mult"); auto mult_pref = wxGetApp().app_config->get("camera_orbit_mult");
const double mult = mult_pref.empty() ? 1.0 : std::stod(mult_pref); 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_camera_movement = true;
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0); 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 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()) { if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0 // 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()) || 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<double>(); m_mouse.position = pos.cast<double>();
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; 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"); 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<MouseButton, MouseAction>& 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<MouseButton, MouseAction>& mappings) const
{ {
if (m_is_touchpad_navigation) { if (m_is_touchpad_navigation) {
return evt.Moving() && evt.AltDown() && !evt.ShiftDown(); return evt.Moving() && evt.AltDown() && !evt.ShiftDown();
} else { } 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<MouseButton, MouseAction>& mappings) const
{ {
if (m_is_touchpad_navigation) { if (m_is_touchpad_navigation) {
return evt.Moving() && evt.ShiftDown() && !evt.AltDown(); return evt.Moving() && evt.ShiftDown() && !evt.AltDown();
} else { } else {
return evt.Dragging() && (evt.MiddleIsDown() || (buttonsSwapped ? evt.LeftIsDown() : evt.RightIsDown())); return evt.Dragging() && clicked_button_matches_action(evt, MouseAction::Pan, mappings);
;
} }
} }

View File

@@ -1038,8 +1038,11 @@ public:
void on_set_focus(wxFocusEvent& evt); void on_set_focus(wxFocusEvent& evt);
void force_set_focus(); void force_set_focus();
bool is_camera_rotate(const wxMouseEvent& evt, const bool buttonsSwapped) const; enum class MouseButton { None, Left, Middle, Right };
bool is_camera_pan(const wxMouseEvent& evt, const bool buttonsSwapped) const; enum class MouseAction { None, Pan, Rotation };
bool clicked_button_matches_action(const wxMouseEvent& evt, MouseAction action, const std::map<MouseButton, MouseAction>& mappings) const;
bool is_camera_rotate(const wxMouseEvent& evt, const std::map<MouseButton, MouseAction>& mappings) const;
bool is_camera_pan(const wxMouseEvent& evt, const std::map<MouseButton, MouseAction>& mappings) const;
Size get_canvas_size() const; Size get_canvas_size() const;
Vec2d get_local_mouse_position() const; Vec2d get_local_mouse_position() const;

View File

@@ -207,13 +207,19 @@ void KBShortcutsDialog::fill_shortcuts()
{ "?", L("Show keyboard shortcuts list") } { "?", L("Show keyboard shortcuts list") }
}; };
m_full_shortcuts.push_back({{_L("Global shortcuts"), ""}, global_shortcuts}); 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<std::string, std::string> mouse_actions;
mouse_actions["0"] = L("None");
mouse_actions["1"] = L("Pan View");
mouse_actions["2"] = L("Rotate View");
Shortcuts plater_shortcuts = { Shortcuts plater_shortcuts = {
{ L("Left mouse button"), swap_mouse_buttons ? L("Pan view") : L("Rotate view") }, { L("Left mouse button"), mouse_actions[wxGetApp().app_config->get("left_mouse_drag_action").c_str()]},
{ L("Right mouse button"), swap_mouse_buttons ? L("Rotate view") : L("Pan view") }, { L("Middle mouse button"), mouse_actions[wxGetApp().app_config->get("middle_mouse_drag_action").c_str()]},
{ L("Mouse wheel"), L("Zoom view") }, { 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") }, { "A", L("Arrange all objects") },
{ shift + "A", L("Arrange objects on selected plates") }, { shift + "A", L("Arrange objects on selected plates") },

View File

@@ -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"); 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); 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"); 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); g_sizer->Add(reverse_mouse_zoom);
std::vector<wxString> 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 ... //// CONTROL > Clear my choice on ...
g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND); g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND);