pcbjam/tests/apps/standalone/collapse-relayout/collapse-relayout_test.cpp
2026-07-06 09:17:10 +02:00

197 lines
7 KiB
C++

// collapse-relayout test — reproduces the pcbnew "Layer Display Options" bug.
//
// In pcbnew's Appearance sidebar, the Layers notebook page holds a proportion-1
// wxScrolledCanvas layer list above a collapsible "Layer Display Options" pane.
// On toggle, KiCad runs `Freeze(); page->Fit(); outerSizer->Layout(); Thaw();`.
// In the WASM DOM port that permanently collapses the layer list: page->Fit()
// shrinks the notebook page to content-min (a scrolled window reports a tiny
// best height when vertical scrolling is enabled, see wxScrolledT_Helper::
// FilterBestSize), and the following outer Layout() re-asserts the *notebook*
// at its UNCHANGED size — which the DOM port's DoSetSize() treats as a no-op
// that never fires wxEVT_SIZE, so wxNotebook::DoSize() never restores the page.
// The list stays at height 0 (its rows clip-pathed away) until reload.
//
// This app mirrors that exact structure with stock widgets so the failure can
// be reproduced and regression-tested without KiCad.
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/collpane.h"
#include "wx/notebook.h"
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
namespace
{
const char* const kLayerNames[] = {
"top_cu", "bottom_cu", "F.Adhesive", "B.Adhesive",
"F.Paste", "B.Paste", "F.Silkscreen", "B.Silkscreen",
"F.Mask", "B.Mask", "User.Drawings", "User.Comments",
"User.Eco1", "User.Eco2", "Edge.Cuts",
};
const wxColour kSwatchColours[] = {
wxColour(200, 52, 52), wxColour( 77, 127, 196), wxColour(107, 33, 125),
wxColour( 20, 20, 120), wxColour(140, 130, 130), wxColour( 40, 160, 160),
wxColour(224, 224, 122), wxColour(224, 160, 160), wxColour( 96, 40, 112),
wxColour( 32, 112, 112), wxColour(200, 200, 210), wxColour( 90, 140, 200),
wxColour(160, 224, 224), wxColour(200, 190, 60), wxColour(210, 210, 210),
};
}
class CollapseRelayoutApp : public wxApp
{
public:
virtual bool OnInit() override;
};
class CollapseRelayoutFrame : public wxFrame
{
public:
CollapseRelayoutFrame();
private:
wxNotebook* m_notebook = nullptr;
wxPanel* m_page = nullptr; // the "Layers" notebook page
wxScrolledWindow* m_scrolled = nullptr; // the layer list ("layerlist")
wxCollapsiblePane* m_pane = nullptr; // "Layer Display Options"
void BuildLayerRows();
void BuildDisplayOptions();
void OnPaneChanged(wxCollapsiblePaneEvent& evt);
wxDECLARE_EVENT_TABLE();
};
enum { ID_DISPLAY_PANE = wxID_HIGHEST + 1 };
wxBEGIN_EVENT_TABLE(CollapseRelayoutFrame, wxFrame)
EVT_COLLAPSIBLEPANE_CHANGED(ID_DISPLAY_PANE, CollapseRelayoutFrame::OnPaneChanged)
wxEND_EVENT_TABLE()
wxIMPLEMENT_APP(CollapseRelayoutApp);
bool CollapseRelayoutApp::OnInit()
{
if (!wxApp::OnInit())
return false;
(new CollapseRelayoutFrame())->Show(true);
return true;
}
CollapseRelayoutFrame::CollapseRelayoutFrame()
: wxFrame(nullptr, wxID_ANY, "collapse-relayout WASM Test",
wxDefaultPosition, wxSize(420, 820))
{
// Frame -> notebook (prop 1) -> page -> [ scrolled (prop 1), pane (prop 0) ]
wxBoxSizer* frameSizer = new wxBoxSizer(wxVERTICAL);
m_notebook = new wxNotebook(this, wxID_ANY);
m_page = new wxPanel(m_notebook, wxID_ANY);
wxBoxSizer* pageSizer = new wxBoxSizer(wxVERTICAL);
// The layer list: proportion 1, vertical scrolling. Named so the e2e test
// can read its geometry from the element registry.
m_scrolled = new wxScrolledWindow(m_page, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxVSCROLL);
m_scrolled->SetName("layerlist");
m_scrolled->SetScrollRate(0, 5); // vertical scroll -> best height caps to ~0
BuildLayerRows();
pageSizer->Add(m_scrolled, 1, wxEXPAND);
// "Layer Display Options": proportion 0, starts collapsed (like KiCad).
m_pane = new wxCollapsiblePane(m_page, ID_DISPLAY_PANE, "Layer Display Options");
BuildDisplayOptions();
pageSizer->Add(m_pane, 0, wxEXPAND | wxALL, 5);
m_page->SetSizer(pageSizer);
m_notebook->AddPage(m_page, "Layers", true);
frameSizer->Add(m_notebook, 1, wxEXPAND);
SetSizer(frameSizer);
// A plain wxScrolledWindow doesn't appear in the JS element registry (its
// Create() runs during base construction, so it's skipped as a base type),
// so the test can't read its geometry from the registry. Report the height
// directly from C++ instead: once after the initial layout settles...
CallAfter([this]() {
#ifdef __EMSCRIPTEN__
EM_ASM({ console.log('[COLLAPSE_RELAYOUT] initial layerlist height = ' + $0); },
m_scrolled->GetSize().GetHeight());
#endif
});
#ifdef __EMSCRIPTEN__
EM_ASM({ console.log('[COLLAPSE_RELAYOUT] app started'); });
#endif
}
void CollapseRelayoutFrame::BuildLayerRows()
{
wxBoxSizer* listSizer = new wxBoxSizer(wxVERTICAL);
for (size_t i = 0; i < WXSIZEOF(kLayerNames); ++i)
{
wxPanel* row = new wxPanel(m_scrolled, wxID_ANY);
wxBoxSizer* rowSizer = new wxBoxSizer(wxHORIZONTAL);
wxPanel* swatch = new wxPanel(row, wxID_ANY, wxDefaultPosition, wxSize(24, 16));
swatch->SetBackgroundColour(kSwatchColours[i]);
rowSizer->Add(swatch, 0, wxALIGN_CENTER_VERTICAL | wxALL, 4);
rowSizer->Add(new wxStaticText(row, wxID_ANY, kLayerNames[i]),
1, wxALIGN_CENTER_VERTICAL | wxALL, 4);
row->SetSizer(rowSizer);
listSizer->Add(row, 0, wxEXPAND);
}
m_scrolled->SetSizer(listSizer);
m_scrolled->FitInside(); // grow the virtual area to hold all rows
}
void CollapseRelayoutFrame::BuildDisplayOptions()
{
wxWindow* paneWin = m_pane->GetPane();
wxBoxSizer* s = new wxBoxSizer(wxVERTICAL);
s->Add(new wxStaticText(paneWin, wxID_ANY, "Inactive layers (H):"), 0, wxALL, 4);
wxBoxSizer* modes = new wxBoxSizer(wxHORIZONTAL);
modes->Add(new wxRadioButton(paneWin, wxID_ANY, "Normal",
wxDefaultPosition, wxDefaultSize, wxRB_GROUP),
0, wxALL, 4);
modes->Add(new wxRadioButton(paneWin, wxID_ANY, "Dim"), 0, wxALL, 4);
modes->Add(new wxRadioButton(paneWin, wxID_ANY, "Hide"), 0, wxALL, 4);
s->Add(modes, 0, wxEXPAND);
s->Add(new wxCheckBox(paneWin, wxID_ANY, "Flip board view"), 0, wxALL, 4);
paneWin->SetSizer(s);
s->Fit(paneWin);
}
// Replicates KiCad's WX_COLLAPSIBLE_PANE_CHANGED handler in
// pcbnew/widgets/appearance_controls.cpp (the Freeze / page->Fit / outer
// Layout / Thaw idiom) that triggers the bug.
void CollapseRelayoutFrame::OnPaneChanged(wxCollapsiblePaneEvent& WXUNUSED(evt))
{
Freeze();
m_page->Fit(); // shrink the notebook page to content-min
GetSizer()->Layout(); // re-assert the notebook at its UNCHANGED size
Thaw();
#ifdef __EMSCRIPTEN__
// With the bug the list collapses to ~0; after the fix it stays tall.
EM_ASM({ console.log('[COLLAPSE_RELAYOUT] layerlist height after toggle = ' + $0); },
m_scrolled->GetSize().GetHeight());
#endif
}