diff --git a/docs/CAD/design/SKETCHER_TOOL_ARCHITECTURE.md b/docs/CAD/design/SKETCHER_TOOL_ARCHITECTURE.md index a9659ac4b0..b450835daa 100644 --- a/docs/CAD/design/SKETCHER_TOOL_ARCHITECTURE.md +++ b/docs/CAD/design/SKETCHER_TOOL_ARCHITECTURE.md @@ -262,9 +262,9 @@ and a band is not a "gesture" in the `CadLevel::Gesture` sense because nothing h |---|---|---|---|---|---| | `Move` | — | `update_hover` / `update_solid_hover` (**non-consuming**, returns false) | — | passthrough | hover only asks for a repaint, `:10022-10025` | | `LeftDown` | orbit may begin | latch press, **return false** | — | consume-or-orbit is the canvas's call | `:10058-10063`; consuming here killed orbit once already | -| `LeftDrag` > 8 px | — | start + drive band, **consume** | — | no longer orbits in this canvas | middle-drag orbits, right-drag pans (`:10013-10016`) | +| `LeftDrag` > 8 px | — | start + drive band, **consume**, when Shift is held or the left button has no camera action | — | otherwise the left button's drag action (Preferences > Control) | the band is Prepare's Shift+left-drag rectangle selection | | `LeftUp` | — | commit pick **or** resolve band | — | — | `:10035-10041` | -| `MiddleDrag` / `RightDrag` | — | must not see it | — | orbit / pan | camera gestures never reach the FSM | +| `MiddleDrag` / `RightDrag` | — | must not see it | — | the button's drag action (Preferences > Control) | camera gestures never reach the FSM; a right press reaches the tool only once its release shows it was a click | | `RightClick` | — | — | offer menu | — | `snaporca-xmh6` open: a right-click that only clears the sketch selection eats the offer | | `Esc` | — | — | `escape()` ladder | — | one route whatever holds focus (`:4228-4231`) | | `Del` / `Backspace` | — | `delete_selected_or_last_sketch_entity()` | char hook | — | `:4260` | diff --git a/docs/HLSD/design-tab.md b/docs/HLSD/design-tab.md index 996447f764..d73c245718 100644 --- a/docs/HLSD/design-tab.md +++ b/docs/HLSD/design-tab.md @@ -154,15 +154,18 @@ rather than to its end points: a press that wandered past the budget at any mome navigation, even if it comes back to where it started, which is what stops a slow, careful orbit from ending in a menu. There is no time budget — a gesture that means something different when it is slow is exactly what the interaction charter rules out. The raycast uses the press -position, not the release. An armed sketch tool that already consumed the right -button (to terminate a chain, say) declines to also open a menu, through a read-and-clear flag. -Past either budget the event is navigation, and navigation does not transition the state -machine. +position, not the release. The sketch tool sees a right press only once the release has shown it +was a click: the press itself goes to the camera, which may pan or orbit with that button, and the +canvas replays it to the tool on a stationary release. A tool that uses the click (to terminate a +chain, say) keeps the menu closed. Past either budget the event is navigation, and navigation +does not transition the state machine. Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control -for each button. The left button is shared with picking, so a whole body is swept with a -rectangle on plain left-drag only while no camera action is assigned to it, and with -Shift+left-drag otherwise — Prepare's own rectangle selection. +for each button, and in the Touchpad camera style a move with Alt held orbits and one with Shift +held pans, whatever tool is armed. The left button is shared with picking and drawing, so a tool +handle or a press that draws takes it first, as a gizmo does in Prepare; a whole body is swept +with a rectangle on plain left-drag only while no camera action is assigned to the left button, +and with Shift+left-drag otherwise — Prepare's own rectangle selection. Entering a sketch changes three things at once so the mode is legible: a banner above the canvas (a sibling of the canvas, not a child over it — on GTK a child window over a diff --git a/src/slic3r/GUI/CAD/DesignCanvas.cpp b/src/slic3r/GUI/CAD/DesignCanvas.cpp index 6af0ee7ae1..a108221e34 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.cpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.cpp @@ -1051,15 +1051,18 @@ void DesignCanvas::set_on_context_menu(std::function cb) return; m_ctx_bound = true; // Bound AFTER GLCanvas3D's own handlers, so this runs first and can consume the event. - // It only consumes when it actually opens the offer; every other right-click still falls - // through to the polyline-chain end and the move gizmo, which were there first. - // Right-drag pans. Without remembering where the press landed, every pan ended by popping - // the offer over wherever the camera stopped — the menu appearing as the reward for moving - // the view. The offer is the release of a STATIONARY right-click (kCadRightClickDriftPx). + // It only consumes when it actually opens the offer. + // Right-drag may pan or orbit (Preferences > Control). Without remembering where the press + // landed, every such drag ended by popping the offer over wherever the camera stopped — the + // menu appearing as the reward for moving the view. A right-click is the release of a + // STATIONARY press (kCadRightClickDriftPx); only that reaches the sketch tool or the offer. m_canvas_widget->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) { m_ctx_press = e.GetPosition(); m_ctx_travelled = false; - e.Skip(); // the canvas still needs the press to seed the orbit + // Drop any press the tool still keeps (only a click's release takes it, so a pan's stays) + // before the canvas offers it this one, which ImGui may take instead. + m_sketch_tool.drop_right_click(); + e.Skip(); // the canvas still needs the press to seed a pan or an orbit }); m_canvas_widget->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { if (e.RightIsDown()) { @@ -1070,12 +1073,13 @@ void DesignCanvas::set_on_context_menu(std::function cb) }); m_canvas_widget->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent& e) { const wxPoint d = e.GetPosition() - m_ctx_press; - // Always read-and-clear, even when another guard already rules the offer out, or a - // terminator recorded under one condition would still be pending under the next. - const bool terminated = m_sketch_tool.take_right_consumed(); - // Click, or navigation? A press that travelled orbited; one that did not, did not. + // Click, or navigation? A press that travelled panned or orbited; one that did not, did not. const bool is_click = !m_ctx_travelled && std::max(std::abs(d.x), std::abs(d.y)) <= kCadRightClickDriftPx; - if (m_on_context_menu && !terminated && !inline_busy() && is_click) { + // Ending a chain or abandoning an anchor uses the click up. + const bool terminated = is_click && m_canvas && m_sketch_tool.take_right_click(*m_canvas); + if (terminated) + m_canvas->set_as_dirty(); // drawn by the canvas's own RightUp (e.Skip below) or at idle + else if (m_on_context_menu && !inline_busy() && is_click) { // The menu belongs to what you POINTED AT — and pointing happened at the PRESS, not // at the release, so the raycast uses the press position. Within a 3 px budget the // two are the same pixel in practice; using the press is what makes that a diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.cpp b/src/slic3r/GUI/CAD/DesignSketchTool.cpp index f27e09be5e..35992344d7 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.cpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.cpp @@ -10315,14 +10315,14 @@ std::vector DesignSketchTool::connected_loop(int seed) const // the offer was excluded in sketch mode wholesale so a right-click could end a polyline chain, // abandon an anchor or exit a tool. That made every sketch row in the atlas unreachable. // The honest test is not "which mode are we in" but "did the tool actually USE this right-click", -// and only the tool knows. Wrapping on_mouse records that once, for every terminator, instead of -// threading a flag through the twenty-odd sites that consume a RightDown. +// and only the tool knows: take_right_click returns it for every terminator, from the +// twenty-odd sites that consume a RightDown. // Right-click abandons the anchor a draw tool has down. With NOTHING down there is nothing to // abandon — and consuming the click anyway made the offer unreachable from every armed draw tool: -// on_mouse records the consumption in m_right_consumed and DesignCanvas's RIGHT_UP handler -// suppresses the menu whenever it is set, so right-click became a no-op that also hid the one door -// to half the vocabulary (47 of 86 verbs have no shortcut). Measured on the rig: with Line armed, -// two right-clicks in a row produced no menu and no tool change; only Escape freed it. +// DesignCanvas's RIGHT_UP handler suppresses the menu whenever the tool used the click, so +// right-click became a no-op that also hid the one door to half the vocabulary (47 of 86 verbs +// have no shortcut). Measured on the rig: with Line armed, two right-clicks in a row produced no +// menu and no tool change; only Escape freed it. // Same rule as xmh6, which said it for the selection: clearing nothing is not a gesture // terminator. ghcz. bool DesignSketchTool::right_abandon() @@ -10334,12 +10334,24 @@ bool DesignSketchTool::right_abandon() return true; } +// The camera follows Preferences > Control here as in Prepare, so no tool may take a gesture the +// camera owns. A right press may start whatever drag action the right button is given, and +// whether it did is known only at the release: ending a chain on the press made every pan or +// orbit started there end the chain too. The press goes to the camera and is kept; +// DesignCanvas's RIGHT_UP handler replays it through take_right_click when it was a click. bool DesignSketchTool::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { - const bool consumed = on_mouse_impl(evt, canvas); - if (evt.RightDown()) - m_right_consumed = consumed; - return consumed; + if (evt.RightDown()) { + m_right_press = evt; + return false; + } + return on_mouse_impl(evt, canvas); +} + +bool DesignSketchTool::take_right_click(GLCanvas3D& canvas) +{ + auto press = std::exchange(m_right_press, {}); + return press && on_mouse_impl(*press, canvas); } bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas) @@ -11288,9 +11300,9 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas) return true; } if (evt.RightDown()) { - // Hand the click back (return false) so the offer opens: the m_right_consumed flag - // this return value feeds means "the tool USED this right-click", and a plain - // right-click in Select mode is not a gesture terminator. + // Hand the click back (return false) so the offer opens: take_right_click returns + // this value as "the tool USED this right-click", and a plain right-click in Select + // mode is not a gesture terminator. // // But do NOT drop the selection on the way out. The offer menu describes WHAT IS // SELECTED, so clearing first guaranteed it could only ever describe nothing: select diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.hpp b/src/slic3r/GUI/CAD/DesignSketchTool.hpp index 7e1c4a629d..85693dba27 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.hpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.hpp @@ -9,14 +9,15 @@ #include "slic3r/GUI/GLSelectionRectangle.hpp" // left-drag rubber band over the committed bodies #include #include +#include #include +#include #include "libslic3r/Color.hpp" #include #include #include #include -class wxMouseEvent; class wxPoint; namespace Slic3r { @@ -149,10 +150,10 @@ public: // Right-click on a draw tool: true when an in-progress anchor was abandoned, false when // there was nothing to abandon — and false is what lets the offer menu open. ghcz. bool right_abandon(); - // True if the LAST right-press was consumed as a gesture terminator (end a polyline chain, - // abandon an anchor, exit a tool). Read-and-clear: the canvas asks on the matching release to - // decide whether that right-click was the user's, in which case it opens the offer. - bool take_right_consumed() { const bool b = m_right_consumed; m_right_consumed = false; return b; } + // Replays and clears the right press on_mouse kept for the camera: true if the tool used it as + // a gesture terminator (end a polyline chain, abandon an anchor), so no offer opens. + bool take_right_click(GLCanvas3D& canvas); + void drop_right_click() { m_right_press.reset(); } void render(GLCanvas3D& canvas); // The in-canvas value field, drawn by render() before any early return. Owned by // DesignCanvas; null until it sets it. Not a window — see SketchInlineEditor.hpp. @@ -1274,7 +1275,7 @@ private: void hit_display_sketch(const DisplaySketch& d, const Vec2d& p, double tol, int& edge_feat, int& edge_reg, int& edge_ent, double& edge_d, int& face_feat, int& face_reg) const; - bool m_right_consumed{false}; // last RightDown was a gesture terminator, not a menu + std::optional m_right_press; // right press not yet known to be a click bool m_escalate_repick{true}; // re-picking the same sub-element takes the whole body void render_solid_highlight(); // The above's edge and vertex highlight, from explicit arguments, so the committed selection diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 36f632096c..c1267e8d31 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4311,7 +4311,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } if (evt.LeftDown() && m_canvas != nullptr) m_canvas->SetFocus(); // grab keyboard focus so Delete/keys reach this canvas - if (m_design_sketch_tool->on_mouse(evt, *this)) { + // A Touchpad-style orbit or pan (a plain move with Alt or Shift held) is the camera's, + // whatever the tool would make of the move. Only a drag consults the button mappings. + const bool camera_move = evt.Moving() && (is_camera_rotate(evt, {}) || is_camera_pan(evt, {})); + if (!camera_move && m_design_sketch_tool->on_mouse(evt, *this)) { m_dirty = true; render(); // force an immediate redraw so the sketch overlay updates live return;