From 4cb6ca7ecbb50d267dce95bb8436e18fbc7daf53 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 5 Oct 2026 02:50:38 +0800 Subject: [PATCH] Let Esc and a click on empty space deselect Design feature and body rows --- docs/HLSD/design-tab.md | 5 +++-- src/slic3r/GUI/CAD/DesignCanvas.cpp | 5 +++++ src/slic3r/GUI/CAD/DesignCanvas.hpp | 1 + src/slic3r/GUI/CAD/DesignPanel.cpp | 24 +++++++++++++++++++++--- src/slic3r/GUI/CAD/DesignPanel.hpp | 1 + src/slic3r/GUI/CAD/DesignSketchTool.cpp | 2 ++ src/slic3r/GUI/CAD/DesignSketchTool.hpp | 3 +++ 7 files changed, 36 insertions(+), 5 deletions(-) diff --git a/docs/HLSD/design-tab.md b/docs/HLSD/design-tab.md index 1f1b4dd860..80531f02a1 100644 --- a/docs/HLSD/design-tab.md +++ b/docs/HLSD/design-tab.md @@ -134,7 +134,7 @@ contract between them is stated in code rather than spread across handlers. | `Transient` | a value field or a popup menu | closes it; the tool stays armed | | `Gesture` | an uncommitted delta — an entity being drawn, a body being dragged | reverts it; committed work is untouched | | `Tool` | a feature card, an armed sketch tool, a constrain session | exits it; drawn entities survive | -| `Idle` | nothing transient | clears the selection; leaves a sketch session only if it is empty | +| `Idle` | nothing transient | clears the selection, a Feature tree or Bodies row included; leaves a sketch session only if it is empty | `cad_escape_level()` is a `constexpr` free function over a POD of four booleans rather than a method on the panel, so the ordering that is the entire contract is checkable without a window, @@ -211,7 +211,8 @@ that makes no face of its own, such as a Boolean union, answers with the bodies replay costs up to a recompute, so the panel finds the faces once per row and topology generation, off the UI thread, and only while no feature card is open. One selection is live at a time: a viewport pick clears the feature row and a feature row clears the viewport pick, as the -Feature tree and Bodies list do between themselves. +Feature tree and Bodies list do between themselves. `Esc`, a click on empty space and an +empty rubber band all let go of it, whichever list or pick made it. ## Following the app diff --git a/src/slic3r/GUI/CAD/DesignCanvas.cpp b/src/slic3r/GUI/CAD/DesignCanvas.cpp index 0ba7b58702..2df2f3edfe 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.cpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.cpp @@ -899,6 +899,11 @@ void DesignCanvas::set_on_solid_selection_changed(std::function cb) +{ + m_sketch_tool.on_empty_pick = std::move(cb); +} + void DesignCanvas::select_body(int body) { m_sketch_tool.select_body(body); diff --git a/src/slic3r/GUI/CAD/DesignCanvas.hpp b/src/slic3r/GUI/CAD/DesignCanvas.hpp index e10ecdb2ba..2a1d613337 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.hpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.hpp @@ -139,6 +139,7 @@ public: const std::vector* visible = nullptr, const std::vector* xform = nullptr); void set_on_solid_selection_changed(std::function cb); + void set_on_empty_pick(std::function cb); // a click or rubber band took nothing std::vector selected_solid_edges() const; // the Shift/Ctrl+click edge set, last-clicked at the end void select_body(int body); // Parts-list -> highlight a whole body by index // Effective display colour of a body: the per-body override (Color tool) when set, diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index 1f8ad16d5c..d84d00325a 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -3826,6 +3826,12 @@ DesignPanel::DesignPanel(wxWindow* parent) : _L("Nothing selected")); m_status->Refresh(); }); + // A click on nothing drops a list row as it drops a pick. Not while a card is open: the + // feature being edited keeps its row. + m_viewport->set_on_empty_pick([this] { + if (m_active == Tool::None && deselect_rows()) + set_status(StatusKind::Info, _L("Nothing selected")); + }); // Visual Extrude gizmo (C5b): dragging/editing the in-canvas depth arrow writes the // matching spin field and re-previews (which re-feeds the gizmo with the new depth). @@ -7526,6 +7532,16 @@ int DesignPanel::tree_body_selection() const return m_parts == nullptr ? -1 : m_parts->selection(); } +// Unselect the Feature tree and Bodies rows; each list's on_select clears what its row lit. True +// if a row was selected. +bool DesignPanel::deselect_rows() +{ + const bool any = (m_tree && tree_selection() != wxNOT_FOUND) || tree_body_selection() >= 0; + if (m_tree) m_tree->unselect(); + if (m_parts) m_parts->unselect(); + return any; +} + void DesignPanel::update_section_flip_btn() { if (m_section_flip_btn) m_section_flip_btn->Enable(m_section_on); @@ -11763,11 +11779,12 @@ void DesignPanel::escape() } return; - case CadLevel::Idle: + case CadLevel::Idle: { // Deselect. In a sketch this is the floor: the session is left through Finish or Cancel, // both of which say which one they are, and never through a key pressed on the way out of - // something else. - if (m_viewport && m_viewport->clear_any_selection()) { + // something else. The Feature tree and Bodies rows count as selections too. + const bool row = deselect_rows(); + if ((m_viewport && m_viewport->clear_any_selection()) || row) { set_status(StatusKind::Info, wxString()); return; } @@ -11783,6 +11800,7 @@ void DesignPanel::escape() } return; } + } } bool DesignPanel::menu_can_undo_redo(bool redo) const diff --git a/src/slic3r/GUI/CAD/DesignPanel.hpp b/src/slic3r/GUI/CAD/DesignPanel.hpp index 772cb1d09f..01c8cbe5b9 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.hpp +++ b/src/slic3r/GUI/CAD/DesignPanel.hpp @@ -916,6 +916,7 @@ private: bool m_hl_pending{false}; void request_feature_highlight(); void update_feature_highlight(); + bool deselect_rows(); // Esc / a click on nothing: drop the tree and Bodies rows // Bodies list under the feature tree: one row per body (parallel to m_doc.bodies). Selecting // one highlights that body and makes it the target for the next op. DesignRowList* m_parts{nullptr}; diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.cpp b/src/slic3r/GUI/CAD/DesignSketchTool.cpp index 7d78cd18b6..b4afdc5081 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.cpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.cpp @@ -3813,6 +3813,7 @@ void DesignSketchTool::pick_bodies_in_rectangle() dp_pick_trace("rubber band -> body=%d (%d samples)", best, best_n); if (on_solid_selection_changed) on_solid_selection_changed(int(m_solid_sel), m_sel_body, m_sel_face, m_sel_edge); + if (best < 0 && on_empty_pick) on_empty_pick(); } // Resolve what a pick at (mx,my) would take. CONST, and it writes only into `out`: the hover @@ -10782,6 +10783,7 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas) return true; } } + if (on_empty_pick) on_empty_pick(); return false; // let the stock canvas orbit / deselect } diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.hpp b/src/slic3r/GUI/CAD/DesignSketchTool.hpp index 388b5b6363..44ee0395ac 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.hpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.hpp @@ -235,6 +235,9 @@ public: std::function on_body_move_changed; // Fired on each cycle change: (level 0=None/1=Whole/2=Face/3=Edge, body index, face id, edge id). std::function on_solid_selection_changed; + // A click or rubber band (no live session) that took nothing, so the host can drop the + // selections the tool does not hold, such as the panel's list rows. + std::function on_empty_pick; // Click a committed sketch overlay (no live session) -> select that loop: the Sketch // feature index + the clicked closed-region index within it (-1 = no specific loop). // entity = the sketch entity index under the cursor when the click landed on a loop