Compare commits

...
Author SHA1 Message Date
Hanif Koh 5d8a81a14e Defer Plugin Dock Panes Until the Plater Is Shown
A plugin enabled at startup loads before the main frame exists, so a
dock panel it opens from on_load was dropped by the one-shot CallAfter
that found no plater. Opened a moment later, before the frame was laid
out, the pane was sized against the unsized frame and track_docked_size
kept that width. Poll until the plater is shown on screen, then build
the pane; release the reserved id instead when the app is closing.
2026-10-07 20:47:56 +08:00
+43 -5
View File
@@ -28,6 +28,7 @@
#include <wx/app.h>
#include <wx/defs.h>
#include <wx/thread.h>
#include <wx/timer.h>
#include <wx/toplevel.h>
#include <wx/event.h>
#include <wx/progdlg.h>
@@ -415,6 +416,44 @@ struct UiDockPanelHandle
int id{0};
};
// Polls until the plater is shown on screen, then runs the callback with it (null when the app
// is closing instead) and deletes itself. A plugin loaded at startup opens its panes from
// on_load, before the main window exists, and a pane added before that window is laid out is
// sized against the unsized frame and keeps that width (AuiMgr::track_docked_size).
class PlaterShownWaiter : public wxTimer
{
public:
explicit PlaterShownWaiter(std::function<void(GUI::Plater*)> fn) : m_fn(std::move(fn)) {}
// True once the callback ran.
bool try_run()
{
const bool closing = GUI::wxGetApp().is_closing();
GUI::Plater* plater = closing ? nullptr : GUI::wxGetApp().plater();
if (!closing && (plater == nullptr || !plater->IsShownOnScreen()))
return false;
Stop();
m_fn(plater);
// Off the timer callback's stack: wxGTK's timeout callback still reads the timer after Notify().
GUI::wxGetApp().CallAfter([this]() { delete this; });
return true;
}
void Notify() override { try_run(); }
private:
std::function<void(GUI::Plater*)> m_fn;
};
void run_when_plater_shown(std::function<void(GUI::Plater*)> fn)
{
GUI::wxGetApp().CallAfter([fn = std::move(fn)]() mutable {
auto* waiter = new PlaterShownWaiter(std::move(fn));
if (!waiter->try_run())
waiter->Start(100);
});
}
py::object ui_create_dock_panel(const std::string& html, const std::string& title, int width, int height,
py::object on_message, py::object on_close, const std::string& dock)
{
@@ -434,14 +473,13 @@ py::object ui_create_dock_panel(const std::string& html, const std::string& titl
const int new_id = UiRegistry::instance().reserve_id();
UiRegistry::instance().bind(new_id, nullptr, plugin_key);
GUI::wxGetApp().CallAfter([new_id, plugin_key, html, title, dock, w, h,
msg_adapter = std::move(msg_adapter),
close_holder = std::move(close_holder)]() mutable {
run_when_plater_shown([new_id, plugin_key, html, title, dock, w, h,
msg_adapter = std::move(msg_adapter),
close_holder = std::move(close_holder)](GUI::Plater* plater) mutable {
if (!UiRegistry::instance().is_open(new_id))
return;
GUI::Plater* plater = GUI::wxGetApp().plater();
if (plater == nullptr || GUI::wxGetApp().is_closing()) {
if (plater == nullptr) {
UiRegistry::instance().remove(new_id);
return;
}