diff --git a/src/slic3r/GUI/DesignCanvas.cpp b/src/slic3r/GUI/DesignCanvas.cpp index 8fa0278ecb..579652bad3 100644 --- a/src/slic3r/GUI/DesignCanvas.cpp +++ b/src/slic3r/GUI/DesignCanvas.cpp @@ -1185,6 +1185,11 @@ bool DesignCanvas::inline_busy() const return m_sketch_tool.inline_busy(); } +bool DesignCanvas::feed_inline_key(wxKeyEvent& evt) +{ + return m_inline_editor && m_inline_editor->is_open() && m_inline_editor->feed_key(evt); +} + bool DesignCanvas::live_sketch_has_work() const { return m_sketch_tool.live_sketch_has_work(); diff --git a/src/slic3r/GUI/DesignCanvas.hpp b/src/slic3r/GUI/DesignCanvas.hpp index cc6d1b2184..57508d7dec 100644 --- a/src/slic3r/GUI/DesignCanvas.hpp +++ b/src/slic3r/GUI/DesignCanvas.hpp @@ -217,7 +217,8 @@ public: // taking those over would break two working interactions in order to add a third. void set_on_context_menu(std::function cb); void delete_selected_sketch_entities(); - bool inline_busy() const; // a sketch value field is open (guard keys) + bool inline_busy() const; + bool feed_inline_key(wxKeyEvent& evt); // type into the in-canvas value field without focus // a sketch value field is open (guard keys) bool live_sketch_has_work() const; // the live sketch holds entities a cancel would destroy bool undo_last_sketch_entity(); // Ctrl+Z in a sketch: drop the last entity bool delete_selected_or_last_sketch_entity(); // Delete in a sketch: selected, else last diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index 3d7128d560..dcd114aa50 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -3796,6 +3796,18 @@ DesignPanel::DesignPanel(wxWindow* parent) // Delete/Ctrl+Z there must edit the text, not the model. const bool in_text = (dynamic_cast(wxWindow::FindFocus()) != nullptr) || (m_viewport && m_viewport->inline_busy()); + // The in-canvas value field cannot take the keyboard focus on a Wayland session (mutter + // refuses a self-focus request without an activation token), so typing went nowhere and + // the value had to be clicked into first. Feed it from here instead: the canvas keeps + // focus, we hand it the keys, and the behaviour is identical on every compositor. Does + // nothing when the field genuinely does hold focus, so X11 keeps its normal routing. + // Gate on the EDITOR being open, not on inline_busy(). inline_busy is a freeze flag for + // the sketch tool: it is cleared on commit and only re-set when the next queued field + // opens, so across a rectangle's Width -> Height handover there is a window where the + // field is on screen and the flag is false. Typing worked for the first dimension and + // not the second, which is exactly that gap. feed_inline_key() already checks is_open(). + if (m_viewport && m_viewport->feed_inline_key(e)) + return; if (getenv("SNAPORCA_KEYTRACE")) { wxWindow* fw = wxWindow::FindFocus(); diff --git a/src/slic3r/GUI/SketchInlineEditor.cpp b/src/slic3r/GUI/SketchInlineEditor.cpp index ce4cda616d..42c308c506 100644 --- a/src/slic3r/GUI/SketchInlineEditor.cpp +++ b/src/slic3r/GUI/SketchInlineEditor.cpp @@ -111,6 +111,7 @@ void SketchInlineEditor::open(const wxPoint& screen_px, double value, m_ctrl->SetFocus(); m_ctrl->SelectAll(); m_open = true; + m_fresh = true; // the field opens with its value selected: the first digit replaces it // Re-assert on the next tick too: the GL canvas can reclaim focus while it finishes // handling the click/render that opened us, so a single immediate SetFocus may be stolen. m_ctrl->CallAfter([this] { @@ -163,4 +164,36 @@ void SketchInlineEditor::close() m_closing = false; } + +bool SketchInlineEditor::feed_key(wxKeyEvent& evt) +{ + if (!m_open || m_ctrl == nullptr) return false; + // If the field really does hold the focus (X11 sessions, where Raise+SetFocus works), let + // wx route the key normally — forwarding it here as well would type every character twice. + if (wxWindow::FindFocus() == m_ctrl) return false; + + const int key = evt.GetKeyCode(); + if (key == WXK_RETURN || key == WXK_NUMPAD_ENTER) { do_commit(); return true; } + if (key == WXK_ESCAPE) { do_cancel(); return true; } + + wxString v = m_ctrl->GetValue(); + if (key == WXK_BACK) { + if (m_fresh) { v.clear(); m_fresh = false; } + else if (!v.empty()) v.RemoveLast(); + } else if (key == WXK_DELETE) { + v.clear(); m_fresh = false; + } else { + const wxChar c = wxChar(evt.GetUnicodeKey()); + // A numeric field: digits, a sign, and either decimal separator. Everything else is + // left for the canvas — a stray letter must not silently vanish into the field. + const bool numeric = (c >= '0' && c <= '9') || c == '-' || c == '.' || c == ','; + if (!numeric) return false; + if (m_fresh) { v.clear(); m_fresh = false; } + v += c; + } + m_ctrl->ChangeValue(v); // ChangeValue: no EVT_TEXT feedback loop + m_ctrl->SetInsertionPointEnd(); + return true; +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SketchInlineEditor.hpp b/src/slic3r/GUI/SketchInlineEditor.hpp index e3f7670e5e..6e1350a86f 100644 --- a/src/slic3r/GUI/SketchInlineEditor.hpp +++ b/src/slic3r/GUI/SketchInlineEditor.hpp @@ -9,6 +9,7 @@ class wxFrame; class wxTextCtrl; class wxStaticText; class wxPoint; +class wxKeyEvent; namespace Slic3r { namespace GUI { @@ -33,6 +34,13 @@ public: void cancel(); // if open, run the registered cancel (keep-as-drawn) void commit(); // if open, run the registered commit (accept the typed value) bool is_open() const { return m_open; } + // Type into the field WITHOUT owning the keyboard focus. Under Wayland a client cannot + // focus itself: mutter ignores gtk_window_present() without an activation token, so the + // Show/Raise/SetFocus dance in open() is refused and the first keystrokes went nowhere — + // "keyboard focus does not go on the labels and some clicks are wasted to focus them". + // The canvas keeps focus and feeds us instead, which behaves the same on every compositor. + // Returns true if the key was consumed. + bool feed_key(wxKeyEvent& evt); private: void do_commit(); @@ -43,6 +51,7 @@ private: wxStaticText* m_title{nullptr}; std::function m_commit; std::function m_cancel; + bool m_fresh{true}; // next printable key replaces the prefill bool m_open{false}; bool m_closing{false}; };