mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Esc is the safe key again: one press, one level, nothing destroyed
Two presses used to discard a live sketch. The key was answered in four places that could not see each other — the inline value field, a sketch branch, a feature-card branch, and the canvas — so a press aimed at one fell through to the next, and request_exit() carried a fourth layer that deliberately let the SECOND consecutive press through to cancel_sketch(). The warning it showed first did not help: the two presses are never one decision, the first is aimed at a field or a tool and the second at whatever was underneath it. The stack is now explicit. CadLevel (DesignInteraction.hpp) is four levels deep, the enum value IS the LIFO depth, and cad_escape_level() is a constexpr function over a POD of four booleans — so the ordering that is the entire contract is checked by static_assert at compile time, with no window, GL context or event loop. DesignPanel::escape() acts on the one level escape_level() names and on no other, and every Esc in the tab routes through it. The destructive layer is gone from request_exit() itself rather than guarded at its callers, so the guarantee cannot be re-opened by adding a route: a session holding geometry is left only through Finish (keep) or Cancel (discard). Cancel now asks before discarding — it used to refuse and tell the user to press the button they had just pressed, which meant a drawn sketch could be kept but never thrown away. Right-click also stops rewarding navigation with a menu: the offer needs BOTH budgets, released within 200 ms and moved no more than 3 px, and the raycast uses the press position, so the menu describes what was pointed at rather than where the camera stopped. Two budgets because drift alone still popped a menu at the end of a slow, careful orbit. docs/ux/interaction-model.md carries the state machine, the routing and the transition table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
This commit is contained in:
co-authored by
Claude Opus 5
parent
bb6a1810f6
commit
324b558747
@@ -225,7 +225,6 @@ void DesignSketchTool::rebuild_features_from_entities()
|
||||
|
||||
void DesignSketchTool::set_tool(Mode mode)
|
||||
{
|
||||
m_exit_refused = false; // any new action re-arms the one-shot exit refusal
|
||||
// A READY edit-op carries the user's typed or dragged value, so switching tools commits it
|
||||
// rather than dropping it — the same rule Tab follows in the dimension editor. Discarding it
|
||||
// here is most of why Fillet looked like it simply did not work: every documented route (type
|
||||
@@ -335,28 +334,44 @@ void DesignSketchTool::cancel()
|
||||
reset_tf();
|
||||
}
|
||||
|
||||
// Esc while active: layered exit (Onshape-like). Abort an in-progress entity first, then
|
||||
// drop a draw tool back to Select; only an idle Select session exits to Feature mode.
|
||||
// CadLevel::Gesture inside a sketch: drop the entity being drawn, keep the tool armed.
|
||||
bool DesignSketchTool::abort_gesture()
|
||||
{
|
||||
if (m_points.empty()) return false;
|
||||
m_points.clear();
|
||||
m_has_cursor = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// CadLevel::Tool inside a sketch: an armed draw/edit tool falls back to Select.
|
||||
// Drop any pending edit-op BEFORE the downgrade: set_tool commits a ready one, and Esc must
|
||||
// cancel it, never apply it. Right-click already discards it through its own branch.
|
||||
bool DesignSketchTool::disarm_tool()
|
||||
{
|
||||
if (m_mode == Mode::Select) return false;
|
||||
reset_op();
|
||||
set_tool(Mode::Select);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Esc / right-click while active: layered exit (Onshape-like), and layered is where it stops.
|
||||
// Abort an in-progress entity first, then drop a draw tool back to Select, then — only if the
|
||||
// session holds NOTHING a user could mourn — leave it.
|
||||
//
|
||||
// It used to have a fourth layer: refuse once, and let the SECOND consecutive request destroy a
|
||||
// drawn sketch. That is the "I pressed Esc twice and my rectangle was gone" report, and no
|
||||
// warning makes it acceptable, because the two presses are never deliberate — the first is aimed
|
||||
// at a value field or a tool and the second at the tool underneath it. A session holding geometry
|
||||
// is now left ONLY through Finish (keep) or Cancel (discard), both of which say which they are.
|
||||
// This is the single decision point for every caller, keyboard and mouse alike, so the guarantee
|
||||
// cannot be re-opened by adding a route.
|
||||
void DesignSketchTool::request_exit()
|
||||
{
|
||||
if (!m_points.empty()) { m_points.clear(); m_has_cursor = false; return; }
|
||||
// Drop any pending edit-op BEFORE the downgrade: set_tool commits a ready one, and Esc must
|
||||
// cancel it, never apply it. Right-click already discards it through its own branch.
|
||||
if (m_mode != Mode::Select) { reset_op(); set_tool(Mode::Select); return; }
|
||||
if (on_exit) {
|
||||
// This is the layer that would destroy a drawn-but-uncommitted sketch. That is the one
|
||||
// thing Esc must not do silently: refuse the FIRST time work exists, and only let a
|
||||
// second consecutive Esc through. The panel reports the refusal; the tool only decides.
|
||||
if (live_sketch_has_work() && !m_exit_refused) {
|
||||
m_exit_refused = true;
|
||||
if (on_exit_refused) on_exit_refused();
|
||||
return;
|
||||
}
|
||||
m_exit_refused = false;
|
||||
on_exit();
|
||||
} else {
|
||||
cancel();
|
||||
}
|
||||
if (abort_gesture()) return;
|
||||
if (disarm_tool()) return;
|
||||
if (live_sketch_has_work()) { if (on_exit_refused) on_exit_refused(); return; }
|
||||
if (on_exit) on_exit();
|
||||
else cancel();
|
||||
}
|
||||
|
||||
void DesignSketchTool::request_undo_redo(bool redo)
|
||||
@@ -375,7 +390,6 @@ void DesignSketchTool::clear_selection()
|
||||
void DesignSketchTool::delete_selected()
|
||||
{
|
||||
if (m_selection.empty()) return;
|
||||
m_exit_refused = false; // deleting is an action; re-arm the exit refusal
|
||||
const int n = int(m_entities.size());
|
||||
std::vector<bool> del(n, false);
|
||||
for (int i : m_selection)
|
||||
@@ -9625,10 +9639,6 @@ bool DesignSketchTool::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
|
||||
bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
{
|
||||
// Re-arm the one-shot exit refusal on a BUTTON press only, never on motion: moving the
|
||||
// mouse between the two Esc presses is what anyone would do, and re-arming there would
|
||||
// make the second Esc refuse again — an Esc that can never exit while the hand moves.
|
||||
if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) m_exit_refused = false;
|
||||
// Track the cursor in canvas client px so the in-canvas value editor can open right
|
||||
// where the user clicked (Onshape places the field at the click, not via a camera
|
||||
// projection — the design canvas's viewport isn't valid outside its own paint).
|
||||
|
||||
Reference in New Issue
Block a user