fix: hollow sheet rooms wiped subsheets on entry (ysync 0011) + hierarchy pane icon sizing/selection colours
Collab: a save-all's layout-only sync into a never-entered sheet room left a doc with layout but zero items; the first entry adopted it and removed every item on screen. seed() now file-seeds a hollow doc, syncLayoutFromSave skips never-seeded empty rooms, and the ydoc-boot / sibling-restage paths fall back to the file. Tree: new tree-hier standalone app + e2e spec covering the wxwidgets bundle-size and unfocused-selection fixes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0189z2siCsezE39ugcDKNBMs
This commit is contained in:
parent
767f2abfc0
commit
8fa6bae084
12 changed files with 266 additions and 7 deletions
|
|
@ -180,6 +180,7 @@ all: minimal_test.html \
|
|||
$(S)/dialog/dialog_test.html \
|
||||
$(S)/timer/timer_test.html \
|
||||
$(S)/tree/tree_test.html \
|
||||
$(S)/tree-hier/tree_hier_test.html \
|
||||
$(S)/radiogroups/radiogroups_test.html \
|
||||
$(S)/dataview/dataview_test.html \
|
||||
$(S)/htmlwin/htmlwin_test.html \
|
||||
|
|
@ -400,6 +401,13 @@ $(S)/tree/tree_test.o: $(S)/tree/tree_test.cpp
|
|||
$(S)/tree/tree_test.html: $(S)/tree/tree_test.o $(WX_CORE_LIB) $(JS_FILES)
|
||||
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
|
||||
|
||||
# Hierarchy-pane-like tree test (no GL): hidden root + images + bold item
|
||||
$(S)/tree-hier/tree_hier_test.o: $(S)/tree-hier/tree_hier_test.cpp
|
||||
$(CXX) -c $(CXXFLAGS) $< -o $@
|
||||
|
||||
$(S)/tree-hier/tree_hier_test.html: $(S)/tree-hier/tree_hier_test.o $(WX_CORE_LIB) $(JS_FILES)
|
||||
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
|
||||
|
||||
# Radio button groups test (no GL) - multiple wxRB_GROUP groups in one window
|
||||
$(S)/radiogroups/radiogroups_test.o: $(S)/radiogroups/radiogroups_test.cpp
|
||||
$(CXX) -c $(CXXFLAGS) $< -o $@
|
||||
|
|
@ -808,6 +816,7 @@ grid: $(S)/grid/grid_test.html
|
|||
dialog: $(S)/dialog/dialog_test.html
|
||||
timer: $(S)/timer/timer_test.html
|
||||
tree: $(S)/tree/tree_test.html
|
||||
tree-hier: $(S)/tree-hier/tree_hier_test.html
|
||||
radiogroups: $(S)/radiogroups/radiogroups_test.html
|
||||
dataview: $(S)/dataview/dataview_test.html
|
||||
htmlwin: $(S)/htmlwin/htmlwin_test.html
|
||||
|
|
|
|||
80
tests/apps/standalone/tree-hier/tree_hier_test.cpp
Normal file
80
tests/apps/standalone/tree-hier/tree_hier_test.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// wxTreeCtrl configured like eeschema's HIERARCHY_PANE: hidden root, buttons,
|
||||
// per-item images (wxBitmapBundle), bold "current sheet" item, no explicit lines.
|
||||
// Repro for the mangled hierarchy pane in the wasm port (icon/text misaligned,
|
||||
// oversized rows, black text on the selection highlight).
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
#include <wx/treectrl.h>
|
||||
#include <wx/bmpbndl.h>
|
||||
#include <wx/dcmemory.h>
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
static wxBitmap MakeDot(int size, const wxColour& fill)
|
||||
{
|
||||
wxBitmap bmp(size, size, 32);
|
||||
bmp.UseAlpha();
|
||||
wxMemoryDC dc(bmp);
|
||||
dc.SetBackground(*wxTRANSPARENT_BRUSH);
|
||||
dc.Clear();
|
||||
dc.SetPen(wxPen(fill));
|
||||
dc.SetBrush(wxBrush(fill));
|
||||
dc.DrawEllipse(1, 1, size - 2, size - 2);
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
class HierFrame : public wxFrame
|
||||
{
|
||||
public:
|
||||
HierFrame() : wxFrame(nullptr, wxID_ANY, "Hierarchy tree test", wxDefaultPosition, wxSize(420, 420))
|
||||
{
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_tree = new wxTreeCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
|
||||
wxTR_HAS_BUTTONS | wxTR_EDIT_LABELS | wxTR_HIDE_ROOT);
|
||||
wxVector<wxBitmapBundle> images;
|
||||
// KiCad's tree_nosel/tree_sel are 16px dots (bundle carries 16 + 32 variants).
|
||||
images.push_back(wxBitmapBundle::FromBitmaps(MakeDot(16, wxColour(160, 160, 160)),
|
||||
MakeDot(32, wxColour(160, 160, 160))));
|
||||
images.push_back(wxBitmapBundle::FromBitmaps(MakeDot(16, wxColour(40, 40, 40)),
|
||||
MakeDot(32, wxColour(40, 40, 40))));
|
||||
m_tree->SetImages(images);
|
||||
|
||||
wxTreeItemId root = m_tree->AddRoot("root");
|
||||
wxTreeItemId top = m_tree->AppendItem(root, "Arduino Leonardo (page 1)", 0, 1);
|
||||
m_tree->SetItemBold(top, true);
|
||||
m_tree->AppendItem(top, "Headers (page 2)", 0, 1);
|
||||
m_tree->AppendItem(top, "Power (page 3)", 0, 1);
|
||||
wxTreeItemId mcu = m_tree->AppendItem(top, "ATMEGA32U4-AU (page 4)", 0, 1);
|
||||
m_tree->AppendItem(mcu, "USB (page 5)", 0, 1);
|
||||
m_tree->ExpandAll();
|
||||
m_tree->SelectItem(top);
|
||||
sizer->Add(m_tree, 1, wxEXPAND);
|
||||
|
||||
wxBoxSizer* btns = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxButton* focusBtn = new wxButton(this, wxID_ANY, "Take focus");
|
||||
btns->Add(focusBtn, 0, wxALL, 4);
|
||||
sizer->Add(btns, 0);
|
||||
SetSizer(sizer);
|
||||
Layout();
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM({ console.log('[TREE_HIER_TEST] started'); });
|
||||
#endif
|
||||
}
|
||||
private:
|
||||
wxTreeCtrl* m_tree;
|
||||
};
|
||||
|
||||
class HierApp : public wxApp
|
||||
{
|
||||
public:
|
||||
bool OnInit() override
|
||||
{
|
||||
(new HierFrame())->Show();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
wxIMPLEMENT_APP(HierApp);
|
||||
59
tests/e2e/tree-hier.spec.ts
Normal file
59
tests/e2e/tree-hier.spec.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { test, expect } from './utils/fixtures';
|
||||
import { findAllTreeItems, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
/**
|
||||
* eeschema's Schematic Hierarchy pane = wxTreeCtrl with a hidden root, a
|
||||
* per-item wxBitmapBundle (16px dots) and a bold "current sheet". Regression
|
||||
* for two port bugs: the bundle's preferred LOGICAL size was reported at the
|
||||
* device-pixel size (32 on retina) so every row reserved twice the icon
|
||||
* height with the icon pinned to the row top; and an unfocused selection was
|
||||
* drawn black-on-highlight (wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT defaulted to the
|
||||
* window text colour while the generic renderer always paints HIGHLIGHT).
|
||||
*/
|
||||
test.describe('wxTreeCtrl hierarchy-pane configuration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/tree-hier/tree_hier_test.html');
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('rows with 16px bundle icons stay text-height, not icon@2x-height', async ({ page }) => {
|
||||
const items = await findAllTreeItems(page);
|
||||
expect(items.length).toBe(5);
|
||||
const byY = [...items].sort((a, b) => a.screenY - b.screenY);
|
||||
// 10pt text ≈ 16px + 2px padding + 2px spacing; a 32px "logical" icon made this ~35.
|
||||
for (const it of byY) expect(it.height, `row "${it.label}"`).toBeLessThanOrEqual(24);
|
||||
const pitches = byY.slice(1).map((it, i) => it.screenY - byY[i].screenY);
|
||||
for (const p of pitches) expect(p).toBeLessThanOrEqual(24);
|
||||
await stableShot(page, 'tree-hier-01-loaded.png');
|
||||
});
|
||||
|
||||
test('an unfocused selection is drawn light-on-highlight, not black-on-blue', async ({ page }) => {
|
||||
const items = await findAllTreeItems(page);
|
||||
const sel = items.find(it => it.label.startsWith('Arduino Leonardo'))!;
|
||||
expect(sel).toBeDefined();
|
||||
// Sample the label row at device resolution: the highlight (navy) must be
|
||||
// present and the glyph pixels on it must be light, never near-black.
|
||||
const dpr = await page.evaluate(() => window.devicePixelRatio);
|
||||
const px = await page.evaluate(
|
||||
({ x, y, w, h, dpr }) => {
|
||||
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
const r = canvas.getBoundingClientRect();
|
||||
const d = ctx.getImageData((x - r.x) * dpr, (y - r.y) * dpr, w * dpr, h * dpr).data;
|
||||
let navy = 0, dark = 0, light = 0;
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
const [R, G, B] = [d[i], d[i + 1], d[i + 2]];
|
||||
if (B > 90 && R < 40 && G < 40) navy++;
|
||||
else if (R < 60 && G < 60 && B < 60) dark++;
|
||||
else if (R > 200 && G > 200 && B > 200) light++;
|
||||
}
|
||||
return { navy, dark, light, total: d.length / 4 };
|
||||
},
|
||||
// Skip the leading 16px item icon (the "selected" dot is dark by design).
|
||||
{ x: sel.screenX + 22, y: sel.screenY, w: sel.width - 22, h: sel.height, dpr }
|
||||
);
|
||||
expect(px.navy, 'highlight painted under the selected label').toBeGreaterThan(px.total * 0.2);
|
||||
expect(px.light, 'light glyph pixels on the highlight').toBeGreaterThan(px.total * 0.02);
|
||||
expect(px.dark, 'no black glyphs on the highlight').toBeLessThan(px.total * 0.01);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue