Fix UI hanging on Mac

This commit is contained in:
SoftFever
2026-08-23 18:43:41 +08:00
parent 6f32d59997
commit ff35dacf4c
5 changed files with 74 additions and 20 deletions

View File

@@ -85,6 +85,14 @@ GradientCurveEditor::GradientCurveEditor(wxWindow* parent,
});
}
GradientCurveEditor::~GradientCurveEditor()
{
// See MixedFilamentDialog::~MixedFilamentDialog: a widget destroyed while it
// still holds the capture wedges mouse input for the whole application.
if (HasCapture())
ReleaseMouse();
}
void GradientCurveEditor::set_points(const PointList& pts)
{
m_points = pts;

View File

@@ -36,6 +36,8 @@ public:
const wxColour& color_low = wxColour(217, 217, 217),
const wxColour& color_high = wxColour(217, 217, 217));
~GradientCurveEditor() override;
// Replace the entire point list. The widget enforces x in [0,1], y in [0,1],
// sorts by x, and clamps the first / last x to 0 / 1. Tangent overrides are
// preserved as-is (NaN entries continue to use PCHIP defaults).

View File

@@ -154,6 +154,18 @@ MixedFilamentDialog::MixedFilamentDialog(wxWindow* parent,
m_preview_bmp_three = wxBitmap(img);
}
MixedFilamentDialog::~MixedFilamentDialog()
{
// Backstop: a child must never be destroyed while it still holds the mouse
// capture. wxWidgets only asserts about this (compiled out in release), and
// the macOS port never unwinds its capture stack, so the stale entry would
// make wxNSWindow::sendEvent swallow every mouse event in the application.
if (m_ratio_bar && m_ratio_bar->HasCapture())
m_ratio_bar->ReleaseMouse();
if (m_triangle_panel && m_triangle_panel->HasCapture())
m_triangle_panel->ReleaseMouse();
}
MixedFilamentDialog::MixedFilamentDialog(wxWindow* parent,
const MixedFilamentResult& existing,
const std::vector<std::string>& physical_colors,
@@ -892,24 +904,30 @@ wxBoxSizer* MixedFilamentDialog::create_ratio_slider()
m_ratio_bar->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) {
if (m_ratio_editor_panel && m_ratio_editor_panel->IsShown())
commit_ratio_editor(true);
m_dragging = true;
m_ratio_bar->CaptureMouse();
m_ratio_dragging = true;
if (!m_ratio_bar->HasCapture())
m_ratio_bar->CaptureMouse();
int new_ratio = 100 - (int)(e.GetX() * 100.0 / m_ratio_bar->GetClientSize().GetWidth() + 0.5);
on_ratio_changed(std::max(MIN_COMPONENT_RATIO, std::min(100 - MIN_COMPONENT_RATIO, new_ratio)));
});
m_ratio_bar->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) {
if (!m_dragging) return;
if (!m_ratio_dragging) return;
int new_ratio = 100 - (int)(e.GetX() * 100.0 / m_ratio_bar->GetClientSize().GetWidth() + 0.5);
on_ratio_changed(std::max(MIN_COMPONENT_RATIO, std::min(100 - MIN_COMPONENT_RATIO, new_ratio)));
});
// Release whenever the capture is held, not only when the drag flag is set:
// the flag can be cleared behind our back, and a capture that outlives the
// widget wedges mouse input for the whole application.
m_ratio_bar->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent&) {
if (m_dragging) {
m_dragging = false;
if (m_ratio_bar->HasCapture())
m_ratio_bar->ReleaseMouse();
}
m_ratio_dragging = false;
if (m_ratio_bar->HasCapture())
m_ratio_bar->ReleaseMouse();
});
m_ratio_bar->Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) {
m_ratio_dragging = false;
});
sizer->Add(m_ratio_bar, 0, wxEXPAND);
@@ -1122,11 +1140,12 @@ wxBoxSizer* MixedFilamentDialog::create_triangle_picker()
// clicks outside the triangle must not change the mix ratio.
if (!tri_contains(p, v0, v1, v2))
return;
m_dragging = true;
m_triangle_panel->CaptureMouse();
m_tri_dragging = true;
if (!m_triangle_panel->HasCapture())
m_triangle_panel->CaptureMouse();
}
if (!m_dragging) return;
if (!m_tri_dragging) return;
TriPoint clamped = tri_clamp(p, v0, v1, v2);
tri_barycentric(clamped, v0, v1, v2, m_tri_wx, m_tri_wy, m_tri_wz);
@@ -1161,15 +1180,16 @@ wxBoxSizer* MixedFilamentDialog::create_triangle_picker()
handle_mouse(e, true);
});
m_triangle_panel->Bind(wxEVT_MOTION, [this, handle_mouse](wxMouseEvent& e) {
if (m_dragging)
if (m_tri_dragging)
handle_mouse(e, false);
});
m_triangle_panel->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent&) {
if (m_dragging) {
m_dragging = false;
if (m_triangle_panel->HasCapture())
m_triangle_panel->ReleaseMouse();
}
m_tri_dragging = false;
if (m_triangle_panel->HasCapture())
m_triangle_panel->ReleaseMouse();
});
m_triangle_panel->Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) {
m_tri_dragging = false;
});
sizer->Add(m_triangle_panel, 0);

View File

@@ -53,6 +53,8 @@ public:
const std::vector<std::string>& physical_names,
const std::vector<std::string>& physical_types = {});
~MixedFilamentDialog();
MixedFilamentResult get_result() const { return m_result; }
protected:
@@ -160,8 +162,11 @@ private:
wxBitmap m_preview_bmp_two;
wxBitmap m_preview_bmp_three;
// Drag state
bool m_dragging{false};
// Drag state. The ratio bar and the triangle picker capture the mouse
// independently, so they must not share a flag: a mouse-up on one would
// otherwise clear the other's flag and skip its ReleaseMouse().
bool m_ratio_dragging{false};
bool m_tri_dragging{false};
std::vector<size_t> m_ratio_manual_order;
size_t m_ratio_editor_idx{0};
bool m_ratio_editor_committing{false};

View File

@@ -184,6 +184,7 @@ public:
GreenSlider(wxWindow* parent, int value, int minVal, int maxVal,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize);
~GreenSlider() override;
int GetValue() const;
void SetValue(int val);
bool Enable(bool enable = true) override;
@@ -213,6 +214,15 @@ GreenSlider::GreenSlider(wxWindow* parent, int value, int minVal, int maxVal,
Bind(wxEVT_LEFT_DOWN, &GreenSlider::OnMouse, this);
Bind(wxEVT_LEFT_UP, &GreenSlider::OnMouse, this);
Bind(wxEVT_MOTION, &GreenSlider::OnMouse, this);
Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) { m_dragging = false; });
}
GreenSlider::~GreenSlider()
{
// See MixedFilamentDialog::~MixedFilamentDialog: a widget destroyed while it
// still holds the capture wedges mouse input for the whole application.
if (HasCapture())
ReleaseMouse();
}
int GreenSlider::GetValue() const { return m_value; }
@@ -302,7 +312,7 @@ void GreenSlider::OnMouse(wxMouseEvent& evt)
if (evt.LeftDown()) {
m_dragging = true;
CaptureMouse();
if (!HasCapture()) CaptureMouse();
update(evt.GetX());
} else if (evt.LeftUp()) {
m_dragging = false;
@@ -1019,11 +1029,20 @@ TexturePreviewCanvas::TexturePreviewCanvas(wxWindow* parent, const wxGLAttribute
Bind(wxEVT_MIDDLE_DOWN, &TexturePreviewCanvas::on_mouse, this);
Bind(wxEVT_MIDDLE_UP, &TexturePreviewCanvas::on_mouse, this);
Bind(wxEVT_MOTION, &TexturePreviewCanvas::on_mouse, this);
Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) {
m_drag_mode = DragMode::None;
m_reset_overlay_pressed = false;
});
Bind(wxEVT_LEAVE_WINDOW, &TexturePreviewCanvas::on_mouse, this);
}
TexturePreviewCanvas::~TexturePreviewCanvas()
{
// See MixedFilamentDialog::~MixedFilamentDialog: a widget destroyed while it
// still holds the capture wedges mouse input for the whole application.
if (HasCapture())
ReleaseMouse();
if (m_context) {
SetCurrent(*m_context);
if (m_tex_id)