From d82c8b59c236dad4c607832a104cfa53b38262d7 Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Sat, 5 Sep 2026 15:56:10 +0200 Subject: [PATCH] The field now owns the keyboard; what it does not yet own is the characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured, not reasoned. A per-frame trace of the ImGui state is what finally named the mechanism, and it is a deadlock, not a focus problem: [UX] frame want_text=0 want_kb=0 active=0 buf=158.74 <- frame 1: the widget is not active yet [UX] frame want_text=0 want_kb=0 active=1 buf=158.74 <- frame 2: now it is (nothing further) <- and the canvas stops This canvas repaints ON DEMAND. ImGui decides whether it wants the keyboard at the END of a frame, from the active item; GLCanvas3D::on_char only calls render() when update_key_data() says ImGui wants it. So: no frames -> WantTextInput never turns on -> no render on a keystroke -> still no frames. The characters sit in ImGui's input queue and the field is exactly as deaf as the window it replaced, for a completely different reason. request_frame breaks the circle, and the same trace says so: [UX] frame want_text=1 want_kb=1 active=1 That is the first time in this file's history that the value field has owned the keyboard without asking a window manager for it. STILL OPEN: the typed characters do not reach the buffer (buf stays at the prefill) and the frames stop after nine. The pump is the suspect — on software GL request_repaint() calls m_canvas->render() SYNCHRONOUSLY, so this asks for a render from inside a render; it needs to schedule one instead. That is the next thing to measure, not to guess. The deployed binary on behemoth is restored to 00d6c191dc (md5 0eeb9a58cef5), byte-identical to the last good build. Nothing from this branch is installed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA --- src/slic3r/GUI/CAD/DesignCanvas.cpp | 1 + src/slic3r/GUI/CAD/SketchInlineEditor.cpp | 16 ++++++++++++++++ src/slic3r/GUI/CAD/SketchInlineEditor.hpp | 15 +++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/slic3r/GUI/CAD/DesignCanvas.cpp b/src/slic3r/GUI/CAD/DesignCanvas.cpp index 204086eac5..e8b370eb6b 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.cpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.cpp @@ -87,6 +87,7 @@ DesignCanvas::DesignCanvas(wxWindow* parent) // The tool draws it: it owns the frame's ImGui pass and the render scale. Handing it a raw // pointer rather than the unique_ptr keeps the ownership where it was. m_sketch_tool.inline_editor = m_inline_editor.get(); + m_inline_editor->request_frame = [this] { request_repaint(); }; m_sketch_tool.on_inline_edit = [this](wxPoint screen_px, double current, const std::string& title, std::function commit, diff --git a/src/slic3r/GUI/CAD/SketchInlineEditor.cpp b/src/slic3r/GUI/CAD/SketchInlineEditor.cpp index 81e04c5305..620b8f43de 100644 --- a/src/slic3r/GUI/CAD/SketchInlineEditor.cpp +++ b/src/slic3r/GUI/CAD/SketchInlineEditor.cpp @@ -170,11 +170,27 @@ bool SketchInlineEditor::render(ImGuiWrapper& imgui, float scale) ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_CharsDecimal); + // MEASUREMENT, not a fix: one line per frame saying whether ImGui believes it owns the + // keyboard and whether our widget is the active one. "Typing does not arrive" has two very + // different causes — no FRAMES (this canvas repaints on demand only, so an idle canvas never + // processes ImGui's queued characters) versus frames that run while the input is not active — + // and they are indistinguishable from outside. + if (std::getenv("SNAPORCA_UXTRACE")) { + const ImGuiIO& io = ImGui::GetIO(); + std::fprintf(stderr, "[UX] frame title=%s want_text=%d want_kb=%d active=%d buf=%s\n", + m_title.c_str(), (int) io.WantTextInput, (int) io.WantCaptureKeyboard, + (int) ImGui::IsItemActive(), m_buf); + std::fflush(stderr); + } ImGui::PopItemWidth(); imgui.end(); ImGui::PopStyleVar(); ImGuiWrapper::pop_common_window_style(); + // Keep the frames coming while the field is up — see request_frame's note in the header. + if (m_open && request_frame) + request_frame(); + // Act AFTER end(): do_commit can reopen the field for the next queued dimension, and that // must not happen inside this frame's window. if (entered) diff --git a/src/slic3r/GUI/CAD/SketchInlineEditor.hpp b/src/slic3r/GUI/CAD/SketchInlineEditor.hpp index e91e96d21a..162d157864 100644 --- a/src/slic3r/GUI/CAD/SketchInlineEditor.hpp +++ b/src/slic3r/GUI/CAD/SketchInlineEditor.hpp @@ -54,6 +54,21 @@ public: // frame's ImGui pass; `scale` is the tool's m_render_scale. Returns true if it drew. bool render(ImGuiWrapper& imgui, float scale); + // Ask for another frame. THE FIELD DOES NOT WORK WITHOUT THIS, and the reason is a deadlock + // that only a per-frame trace shows: + // + // [UX] frame want_text=0 want_kb=0 active=0 <- frame 1: the widget is not active yet + // [UX] frame want_text=0 want_kb=0 active=1 <- frame 2: it is now + // (nothing further) <- the canvas has nothing to redraw, so it stops + // + // This canvas repaints ON DEMAND. ImGui decides whether it wants the keyboard at the END of a + // frame, from the active item, and GLCanvas3D::on_char only calls render() when + // update_key_data() says ImGui wants it. No frames -> WantTextInput never turns on -> no + // render on a keystroke -> still no frames. The characters sit in ImGui's queue and the field + // looks exactly as deaf as the window it replaced. One repaint per frame while it is open + // breaks the circle. + std::function request_frame; + // Kept because callers ask them, but there is no longer any difference to report: with no // window there is no state where the field is on screen but logically closed, and no state // where it is open but somebody else holds the keyboard.