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())
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);

View File

@@ -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<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)) {
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<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;
}
@@ -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<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) {
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<MouseButton, MouseAction>& 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);
;
}
}

View File

@@ -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<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;
Vec2d get_local_mouse_position() const;

View File

@@ -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<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 = {
{ 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") },

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");
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<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 ...
g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND);