Fix wxChoice/wxComboBox dropdown rendering and add tests
- Fix empty clip region bug in wx.js clipRect function - Add dropdown mouse interaction tests - Add *.log to gitignore - Make build script clean by default (use --no-clean to skip) - Add wxGLCanvas canvas element fix documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3287ec7616
commit
a538954b70
5 changed files with 205 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -39,3 +39,4 @@ wxwidgets-clean/
|
|||
/tests/wasm-app/*.js
|
||||
/tests/wasm-app/minimal_test.wasm
|
||||
/temp/
|
||||
*.log
|
||||
|
|
|
|||
146
docs/06-WXGLCANVAS-CANVAS-ELEMENT-FIX.md
Normal file
146
docs/06-WXGLCANVAS-CANVAS-ELEMENT-FIX.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# 06 - wxGLCanvas WASM Canvas Element Integration
|
||||
|
||||
This document describes how to fix wxGLCanvas to render visibly in WASM by creating its own canvas element.
|
||||
|
||||
## Problem
|
||||
|
||||
wxGLCanvas currently hardcodes `m_canvasTarget = "#canvas"` which creates WebGL context on the main wxUniversal 2D canvas, causing a conflict. The GL rendering doesn't appear because it's overwritten by 2D UI rendering.
|
||||
|
||||
## Key Discovery
|
||||
|
||||
The WASM port already supports **multiple canvas elements per window**:
|
||||
- Main window uses `#canvas` for 2D rendering
|
||||
- Child windows can each get their own canvas element via `createWindow(id, needsCanvas=true)`
|
||||
- Windows are positioned absolutely with z-index layering
|
||||
- JavaScript (wx.js) manages canvas creation, positioning, and context stacking
|
||||
|
||||
## How KiCad Uses wxGLCanvas
|
||||
|
||||
```
|
||||
wxFrame (EDA_DRAW_FRAME)
|
||||
└── EDA_DRAW_PANEL_GAL (wxScrolledCanvas)
|
||||
└── OPENGL_GAL (inherits from wxGLCanvas)
|
||||
- Sized to match parent
|
||||
- Child window positioned within parent
|
||||
- Has its own GL context
|
||||
```
|
||||
|
||||
Key KiCad files:
|
||||
- `kicad/common/gal/opengl/opengl_gal.cpp` - GL rendering implementation
|
||||
- `kicad/common/draw_panel_gal.cpp` - Creates OPENGL_GAL as child: `new OPENGL_GAL(..., this, ...)`
|
||||
- `kicad/include/gal/hidpi_gl_canvas.h` - wxGLCanvas wrapper class
|
||||
|
||||
---
|
||||
|
||||
## Solution
|
||||
|
||||
wxGLCanvas should integrate with the existing WASM window system:
|
||||
|
||||
1. **Create its own canvas element** - Use `createWindow(id, needsCanvas=true)` in JavaScript
|
||||
2. **Create WebGL context on that canvas** - Use selector `#window-{id} canvas`
|
||||
3. **Handle positioning via window system** - `setWindowRect()` positions the canvas
|
||||
|
||||
### Key JavaScript Functions (already exist in wx.js)
|
||||
|
||||
```javascript
|
||||
createWindow(id, needsCanvas, isVisible, classList)
|
||||
setWindowRect(id, x, y, width, height)
|
||||
setWindowVisibility(id, isVisible)
|
||||
destroyWindow(id)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
### Step 1: Modify Create() to create canvas element
|
||||
|
||||
In `wxwidgets/src/wasm/glcanvas.cpp`:
|
||||
|
||||
```cpp
|
||||
bool wxGLCanvas::Create(wxWindow *parent, ...)
|
||||
{
|
||||
if ( !wxWindow::Create(parent, id, pos, size, style, name) )
|
||||
return false;
|
||||
|
||||
// Create a window with canvas element in JavaScript
|
||||
int cssId = GetCSSId();
|
||||
EM_ASM({
|
||||
createWindow($0, true, true, "glcanvas");
|
||||
}, cssId);
|
||||
|
||||
// Position the canvas
|
||||
wxPoint screenPos = GetScreenPosition();
|
||||
wxSize clientSize = GetClientSize();
|
||||
EM_ASM({
|
||||
setWindowRect($0, $1, $2, $3, $4);
|
||||
}, cssId, screenPos.x, screenPos.y, clientSize.GetWidth(), clientSize.GetHeight());
|
||||
|
||||
// Set dynamic canvas selector
|
||||
m_canvasTarget = wxString::Format("#window-%d canvas", cssId).ToStdString();
|
||||
|
||||
return CreateWebGLContext(dispAttrs);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Override DoSetSize for resize handling
|
||||
|
||||
```cpp
|
||||
void wxGLCanvas::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
{
|
||||
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
|
||||
|
||||
wxPoint screenPos = GetScreenPosition();
|
||||
wxSize clientSize = GetClientSize();
|
||||
EM_ASM({
|
||||
setWindowRect($0, $1, $2, $3, $4);
|
||||
}, GetCSSId(), screenPos.x, screenPos.y, clientSize.GetWidth(), clientSize.GetHeight());
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Handle visibility
|
||||
|
||||
```cpp
|
||||
void wxGLCanvas::DoShow(bool show)
|
||||
{
|
||||
wxWindow::DoShow(show);
|
||||
EM_ASM({
|
||||
setWindowVisibility($0, $1);
|
||||
}, GetCSSId(), show);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Clean up in destructor
|
||||
|
||||
```cpp
|
||||
wxGLCanvas::~wxGLCanvas()
|
||||
{
|
||||
if ( m_webglContext > 0 )
|
||||
{
|
||||
emscripten_webgl_destroy_context(m_webglContext);
|
||||
m_webglContext = 0;
|
||||
}
|
||||
EM_ASM({
|
||||
destroyWindow($0);
|
||||
}, GetCSSId());
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `wxwidgets/src/wasm/glcanvas.cpp` | Main implementation - create own canvas, dynamic selector, resize/visibility handling |
|
||||
| `wxwidgets/include/wx/wasm/glcanvas.h` | Add DoSetSize, DoShow declarations if needed |
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
The existing test app in `tests/wasm-app/` with the OpenGL tab will verify:
|
||||
1. Canvas element is created in DOM
|
||||
2. WebGL context is on the correct canvas
|
||||
3. GL rendering appears in the canvas area
|
||||
4. Legacy GL emulation works (via `-sLEGACY_GL_EMULATION`)
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
# ghcr.io/vslavik/bakefile:0.2 bakefile_gen
|
||||
#
|
||||
# Usage:
|
||||
# ./build-wxuniversal-wasm.sh # Build (incremental)
|
||||
# ./build-wxuniversal-wasm.sh --clean # Clean build
|
||||
# ./build-wxuniversal-wasm.sh # Clean build (default)
|
||||
# ./build-wxuniversal-wasm.sh --no-clean # Incremental build
|
||||
|
||||
set -e
|
||||
|
||||
|
|
@ -40,9 +40,9 @@ if [ "$WX_SOURCE/configure.in" -nt "$WX_SOURCE/configure" ]; then
|
|||
(cd "$WX_SOURCE" && autoconf)
|
||||
fi
|
||||
|
||||
# Clean if requested
|
||||
if [ "$1" = "--clean" ]; then
|
||||
echo "Cleaning build directory..."
|
||||
# Clean by default, unless --no-clean is passed
|
||||
if [ "$1" != "--no-clean" ]; then
|
||||
echo "Cleaning build directory (use --no-clean to skip)..."
|
||||
rm -rf "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -194,6 +194,58 @@ test.describe('wxWidgets WASM - Diagnostics', () => {
|
|||
|
||||
await page.screenshot({ path: 'test-results/10-lists-clicked.png', fullPage: true });
|
||||
|
||||
// Test wxChoice dropdown - the Choice control is to the right of the ListBox
|
||||
console.log('--- Testing wxChoice Dropdown ---');
|
||||
|
||||
// Debug: Check all window divs in the DOM before clicking
|
||||
const windowsBefore = await page.evaluate(() => {
|
||||
const windows = document.querySelectorAll('[id^="window-"]');
|
||||
return Array.from(windows).map(w => ({
|
||||
id: w.id,
|
||||
display: (w as HTMLElement).style.display,
|
||||
rect: w.getBoundingClientRect()
|
||||
}));
|
||||
});
|
||||
console.log('Windows before click:', JSON.stringify(windowsBefore));
|
||||
|
||||
// Click on the wxChoice dropdown button (approximately right side of the panel)
|
||||
// The Choice section is in a separate box to the right, starts around x=640
|
||||
// The wxChoice dropdown button (arrow) is at approximately x=800
|
||||
await page.mouse.click(box.x + 800, box.y + 100);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Debug: Check all window divs in the DOM after clicking
|
||||
const windowsAfter = await page.evaluate(() => {
|
||||
const windows = document.querySelectorAll('[id^="window-"]');
|
||||
return Array.from(windows).map(w => ({
|
||||
id: w.id,
|
||||
display: (w as HTMLElement).style.display,
|
||||
width: (w as HTMLElement).style.width,
|
||||
height: (w as HTMLElement).style.height,
|
||||
rect: w.getBoundingClientRect()
|
||||
}));
|
||||
});
|
||||
console.log('Windows after click:', JSON.stringify(windowsAfter));
|
||||
|
||||
await page.screenshot({ path: 'test-results/10a-choice-dropdown-open.png', fullPage: true });
|
||||
|
||||
// Click on the second option in the dropdown (should be "Green")
|
||||
// Dropdown appears below the wxChoice, options are around x=700-800
|
||||
await page.mouse.click(box.x + 700, box.y + 140);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/10b-choice-selected.png', fullPage: true });
|
||||
|
||||
// Try opening the dropdown again to verify it works
|
||||
await page.mouse.click(box.x + 800, box.y + 100);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/10c-choice-dropdown-reopen.png', fullPage: true });
|
||||
|
||||
// Click elsewhere to close
|
||||
await page.mouse.click(box.x + 200, box.y + 300);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// === TAB 5: OpenGL ===
|
||||
console.log('--- Testing OpenGL Tab ---');
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 54beb85efd41ac98c14b810895e69784c948bbae
|
||||
Subproject commit 2b32d5bfab8c4864b7eef5b06c14693375ee919a
|
||||
Loading…
Reference in a new issue