mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 18:31:11 +00:00
Design tab: move the CAD sources into their own folder
Review request on PR #15238: "Place CAD-related files (e.g. CadDocument/ GeometryEngine) into a separate folder." src/libslic3r/CAD/ the kernel — CadDocument, GeometryEngine, the four Sketch* units, SketchSolver, ThreadStandards src/slic3r/GUI/CAD/ the tab — DesignPanel, DesignCanvas, DesignSketchTool, SketchInlineEditor, McpControl, generated DesignOffer Pure relocation: no line of logic changes. Two include rewrites follow from it — files that moved re-spell their own neighbours against src/ (already on the include path), and files that did not move pick up the new folder. docs and docs/ux/mockups/gen_offer_table.py follow the same paths. Verified: libslic3r, libslic3r_gui and libslic3r_tests all build, CAD suite green at 2518 assertions in 194 test cases, and the sibling fork builds identically — 17 shared sources still byte-identical, 8 diverging by their expected counts.
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
#include "slic3r/GUI/CAD/SketchInlineEditor.hpp"
|
||||
|
||||
#include <wx/display.h>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/toplevel.h>
|
||||
#include <wx/gdicmn.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <gtk/gtk.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace {
|
||||
// Locale-safe value <-> text (wx sets LC_NUMERIC to the user locale, so snprintf may
|
||||
// emit a comma; parsing accepts either separator). Mirrors DesignPanel's en_*.
|
||||
wxString en_format(double v, int digits = 2)
|
||||
{
|
||||
char fmt[16];
|
||||
std::snprintf(fmt, sizeof(fmt), "%%.%df", digits);
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof(buf), fmt, v);
|
||||
for (char* c = buf; *c; ++c) if (*c == ',') *c = '.';
|
||||
return wxString::FromUTF8(buf);
|
||||
}
|
||||
bool en_parse(const wxString& text, double& out)
|
||||
{
|
||||
wxString t(text);
|
||||
t.Replace(wxT(","), wxT("."));
|
||||
return t.ToCDouble(&out);
|
||||
}
|
||||
|
||||
// Present the toplevel with a real X11 server timestamp: wxFrame::Raise() maps to
|
||||
// gtk_window_present() with gtk_get_current_event_time(), which inside a CallAfter is
|
||||
// GDK_CURRENT_TIME (0) and is ignored by mutter's focus-stealing prevention. A server
|
||||
// timestamp lets the compositor grant focus to the re-mapped window.
|
||||
#ifdef __WXGTK__
|
||||
void present_toplevel(wxFrame* frame)
|
||||
{
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
if (frame) {
|
||||
GtkWidget* widget = static_cast<GtkWidget*>(frame->GetHandle());
|
||||
if (widget && GTK_IS_WIDGET(widget)) {
|
||||
GdkWindow* gdkwin = gtk_widget_get_window(widget);
|
||||
if (gdkwin) {
|
||||
gtk_window_present_with_time(GTK_WINDOW(widget),
|
||||
gdk_x11_get_server_time(gdkwin));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (frame) frame->Raise();
|
||||
}
|
||||
#else
|
||||
void present_toplevel(wxFrame* frame)
|
||||
{
|
||||
if (frame) frame->Raise();
|
||||
}
|
||||
#endif
|
||||
|
||||
void trace_inline_focus(wxFrame* frame, const std::string& title)
|
||||
{
|
||||
if (!std::getenv("SNAPORCA_KEYTRACE")) return;
|
||||
#ifdef __WXGTK__
|
||||
GtkWindow* win = nullptr;
|
||||
if (frame) {
|
||||
GtkWidget* widget = static_cast<GtkWidget*>(frame->GetHandle());
|
||||
if (widget && GTK_IS_WIDGET(widget)) win = GTK_WINDOW(widget);
|
||||
}
|
||||
fprintf(stderr, "[INLINE_FOCUS] title=%s active=%d toplevel_focus=%d shown=%d\n",
|
||||
title.c_str(),
|
||||
win ? (int) gtk_window_is_active(win) : -1,
|
||||
win ? (int) gtk_window_has_toplevel_focus(win) : -1,
|
||||
frame ? (int) frame->IsShown() : -1);
|
||||
#else
|
||||
fprintf(stderr, "[INLINE_FOCUS] title=%s shown=%d\n",
|
||||
title.c_str(), frame ? (int) frame->IsShown() : -1);
|
||||
#endif
|
||||
fflush(stderr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SketchInlineEditor::SketchInlineEditor(wxWindow* parent_canvas)
|
||||
{
|
||||
wxWindow* top = parent_canvas ? wxGetTopLevelParent(parent_canvas) : nullptr;
|
||||
// Borderless floating frame: a top-level window so the WM composites it above the
|
||||
// GL canvas (a child widget would be hidden by the GL surface). Floats on its
|
||||
// parent and stays on top so it tracks the main window.
|
||||
// NB: no wxFRAME_FLOAT_ON_PARENT — that maps to a GTK _UTILITY_ window-type hint, which
|
||||
// many WMs (incl. the xrdp/x11vnc session on :10) refuse to give keyboard focus, so the
|
||||
// field opened un-focusable and needed a click before typing. Plain stay-on-top frame is
|
||||
// WM-focusable; we present + SetFocus it explicitly in open().
|
||||
m_frame = new wxFrame(top, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
|
||||
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxSTAY_ON_TOP);
|
||||
m_ctrl = new wxTextCtrl(m_frame, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(82, -1),
|
||||
wxTE_PROCESS_ENTER | wxTE_RIGHT | wxBORDER_SIMPLE);
|
||||
m_frame->SetBackgroundColour(wxColour(40, 42, 46));
|
||||
m_title = new wxStaticText(m_frame, wxID_ANY, wxEmptyString);
|
||||
m_title->SetForegroundColour(wxColour(160, 162, 168));
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_title, 0, wxLEFT | wxRIGHT | wxTOP, 3);
|
||||
sizer->Add(m_ctrl, 1, wxEXPAND | wxALL, 2);
|
||||
m_frame->SetSizerAndFit(sizer);
|
||||
m_frame->Hide();
|
||||
|
||||
m_ctrl->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { do_commit(); });
|
||||
m_ctrl->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& e) {
|
||||
if (e.GetKeyCode() == WXK_ESCAPE) do_cancel();
|
||||
// Tab commits, exactly like Enter — the caller's on_commit is what walks to the next
|
||||
// dimension. Left to wx's default handling it navigated within this one-control popup,
|
||||
// i.e. back to the same field with the text re-selected: typing 60, Tab, 40 looked like
|
||||
// two dimensions entered and silently kept only the 40. Losing typed input with no
|
||||
// visible difference from a committed field is the part that made this worth a key case.
|
||||
else if (e.GetKeyCode() == WXK_TAB) do_commit();
|
||||
else e.Skip();
|
||||
});
|
||||
}
|
||||
|
||||
void SketchInlineEditor::open(const wxPoint& screen_px, double value,
|
||||
const std::string& title,
|
||||
std::function<void(double)> on_commit,
|
||||
std::function<void()> on_cancel)
|
||||
{
|
||||
if (m_frame == nullptr || m_ctrl == nullptr) { if (on_cancel) on_cancel(); return; }
|
||||
// Never close/unmap on the way in: the previous queued dimension left this frame mapped
|
||||
// (see do_commit), and re-mapping a hidden toplevel is exactly what mutter refuses to
|
||||
// focus. Reuse the still-mapped frame and just re-title/re-position it.
|
||||
m_commit = std::move(on_commit);
|
||||
m_cancel = std::move(on_cancel);
|
||||
m_ctrl->ChangeValue(en_format(value));
|
||||
if (m_title) {
|
||||
m_title->SetLabel(wxString::FromUTF8(title.c_str()));
|
||||
m_title->Show(!title.empty());
|
||||
}
|
||||
m_frame->Fit();
|
||||
const wxSize sz = m_frame->GetSize();
|
||||
wxPoint pos(screen_px.x - sz.GetWidth() / 2, screen_px.y - sz.GetHeight() / 2);
|
||||
// Keep the frame fully on-screen: an anchor that maps off the display makes GTK drop
|
||||
// the window at a default corner (top-left) instead of the requested point.
|
||||
// Clamp to the display the anchor is ON, not the primary one: wxGetClientDisplayRect()
|
||||
// only ever describes the primary monitor, so on a multi-head desktop it shoved this
|
||||
// field onto a different screen than the app. It then sat invisible while
|
||||
// m_awaiting_length made the sketch tool eat every mouse event, which read as the
|
||||
// viewport freezing after a sketch with only Enter able to release it.
|
||||
int disp = wxDisplay::GetFromPoint(screen_px);
|
||||
if (disp == wxNOT_FOUND) disp = wxDisplay::GetFromWindow(m_frame);
|
||||
const wxRect area = (disp != wxNOT_FOUND) ? wxDisplay(unsigned(disp)).GetClientArea()
|
||||
: wxGetClientDisplayRect();
|
||||
pos.x = std::max(area.GetLeft(), std::min(pos.x, area.GetRight() - sz.GetWidth()));
|
||||
pos.y = std::max(area.GetTop(), std::min(pos.y, area.GetBottom() - sz.GetHeight()));
|
||||
// Show() BEFORE Move(): GTK ignores a Move() issued before the window is mapped (the
|
||||
// WM places it at its default, i.e. the top-left corner). Move after Show sticks.
|
||||
if (!m_frame->IsShown())
|
||||
m_frame->Show();
|
||||
m_frame->Move(pos);
|
||||
present_toplevel(m_frame); // activate the top-level so SetFocus routes
|
||||
m_frame->SetFocus();
|
||||
m_ctrl->SetFocus();
|
||||
m_ctrl->SelectAll();
|
||||
m_open = true;
|
||||
trace_inline_focus(m_frame, title);
|
||||
// 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, title] {
|
||||
if (m_open && m_ctrl) {
|
||||
present_toplevel(m_frame);
|
||||
m_ctrl->SetFocus();
|
||||
m_ctrl->SelectAll();
|
||||
trace_inline_focus(m_frame, title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SketchInlineEditor::do_commit()
|
||||
{
|
||||
if (!m_open || m_ctrl == nullptr) return;
|
||||
double v = 0.0;
|
||||
if (!en_parse(m_ctrl->GetValue(), v)) { // invalid: keep editing
|
||||
m_ctrl->SetFocus();
|
||||
m_ctrl->SelectAll();
|
||||
return;
|
||||
}
|
||||
auto cb = m_commit;
|
||||
m_open = false; // logically closed: the frame stays MAPPED
|
||||
m_commit = nullptr;
|
||||
m_cancel = nullptr;
|
||||
if (cb) cb(v);
|
||||
// The callback either re-opens us for the next queued dimension (via its own CallAfter,
|
||||
// queued during cb(v), therefore BEFORE the one below) or it does not. Hiding here would
|
||||
// unmap the window and mutter would refuse to focus the re-map; so hide only after the
|
||||
// reopen has had its turn.
|
||||
m_frame->CallAfter([this] { if (!m_open && m_frame) m_frame->Hide(); });
|
||||
}
|
||||
|
||||
void SketchInlineEditor::cancel()
|
||||
{
|
||||
if (m_open) do_cancel();
|
||||
}
|
||||
|
||||
// Accept what is typed and close. Leaving a tool must not silently discard the value the user
|
||||
// just entered — the same rule set_tool already follows for a ready edit-op.
|
||||
void SketchInlineEditor::commit()
|
||||
{
|
||||
if (m_open) do_commit();
|
||||
}
|
||||
|
||||
void SketchInlineEditor::do_cancel()
|
||||
{
|
||||
if (!m_open) return;
|
||||
auto cb = m_cancel;
|
||||
close();
|
||||
if (cb) cb();
|
||||
}
|
||||
|
||||
void SketchInlineEditor::close()
|
||||
{
|
||||
if (m_frame == nullptr || !m_open) return;
|
||||
m_closing = true;
|
||||
m_open = false;
|
||||
m_frame->Hide();
|
||||
m_commit = nullptr;
|
||||
m_cancel = nullptr;
|
||||
m_closing = false;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
Reference in New Issue
Block a user