pcbjam/tests/wasm-app/minimal_test.cpp
Viktor Vaczi 3287ec7616 Add GL immediate mode shim for Emscripten compatibility
Create custom JS library (lib/gl_immediate_shim.js) that fixes two
Emscripten LEGACY_GL_EMULATION issues without modifying KiCad source:

1. Color-per-vertex requirement: Emscripten requires glColor before
   each glVertex, but standard OpenGL uses persistent "current color".
   The shim tracks color state and auto-injects before each vertex.

2. Missing double-precision functions: glVertex2d, glVertex3d,
   glColor3d, glColor4d are unimplemented in Emscripten (wontfix).
   The shim provides these as wrappers to float variants.

Update test code to use standard OpenGL patterns (color set once,
multiple vertices) to verify the shim works correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 21:22:09 +01:00

1190 lines
36 KiB
C++

// Comprehensive wxWidgets WASM Test Application
// Purpose: Verify wxWidgets WASM port with full widget coverage and interaction testing
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/notebook.h"
#include "wx/tglbtn.h"
#include "wx/listbox.h"
#include "wx/choice.h"
#include "wx/combobox.h"
#include "wx/slider.h"
#include "wx/gauge.h"
#include "wx/dcbuffer.h"
#include "wx/datetime.h"
#include "wx/glcanvas.h"
// OpenGL headers - using legacy GL with Emscripten's emulation
#ifdef __EMSCRIPTEN__
#include <GL/gl.h>
#include <GL/glu.h>
#include <emscripten/emscripten.h>
#else
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#endif
// Console logging macro for WASM debugging
#ifdef __EMSCRIPTEN__
#define CONSOLE_LOG(msg) EM_ASM({ console.log('[GL-CPP] ' + UTF8ToString($0)); }, msg)
#else
#define CONSOLE_LOG(msg) printf("[GL-CPP] %s\n", msg)
#endif
#include <vector>
// Control IDs
enum {
ID_BTN_TEST = wxID_HIGHEST + 1,
ID_BTN_TOGGLE,
ID_CHK_FEATURE,
ID_RADIO_OPTIONS,
ID_SLIDER,
ID_GAUGE,
ID_TEXT_SINGLE,
ID_TEXT_MULTI,
ID_TEXT_PASSWORD,
ID_COMBO,
ID_LISTBOX,
ID_CHOICE,
ID_BTN_ADD_ITEM,
ID_BTN_REMOVE_ITEM,
ID_BTN_CLEAR,
ID_EVENT_LOG,
ID_DRAWING_PANEL,
ID_GL_CANVAS,
ID_BTN_GL_TEST_IMMEDIATE,
ID_BTN_GL_TEST_MATRIX,
ID_BTN_GL_TEST_VERTEX_ARRAY,
ID_BTN_GL_RUN_ALL,
ID_GL_TEST_SELECT
};
// Forward declarations
class TestFrame;
// Global pointer for logging from child panels
TestFrame* g_frame = nullptr;
//-----------------------------------------------------------------------------
// DrawingPanel - Custom drawing canvas for mouse interaction testing
//-----------------------------------------------------------------------------
class DrawingPanel : public wxPanel
{
public:
DrawingPanel(wxWindow* parent);
void Clear();
private:
std::vector<std::vector<wxPoint>> m_strokes; // Collection of strokes
std::vector<wxPoint> m_currentStroke; // Current stroke being drawn
bool m_drawing;
void OnPaint(wxPaintEvent& evt);
void OnMouseDown(wxMouseEvent& evt);
void OnMouseMove(wxMouseEvent& evt);
void OnMouseUp(wxMouseEvent& evt);
void OnMouseEnter(wxMouseEvent& evt);
void OnMouseLeave(wxMouseEvent& evt);
wxDECLARE_EVENT_TABLE();
};
wxBEGIN_EVENT_TABLE(DrawingPanel, wxPanel)
EVT_PAINT(DrawingPanel::OnPaint)
EVT_LEFT_DOWN(DrawingPanel::OnMouseDown)
EVT_LEFT_UP(DrawingPanel::OnMouseUp)
EVT_MOTION(DrawingPanel::OnMouseMove)
EVT_ENTER_WINDOW(DrawingPanel::OnMouseEnter)
EVT_LEAVE_WINDOW(DrawingPanel::OnMouseLeave)
wxEND_EVENT_TABLE()
DrawingPanel::DrawingPanel(wxWindow* parent)
: wxPanel(parent, ID_DRAWING_PANEL, wxDefaultPosition, wxSize(400, 300),
wxBORDER_SIMPLE | wxFULL_REPAINT_ON_RESIZE)
, m_drawing(false)
{
SetBackgroundColour(*wxWHITE);
SetBackgroundStyle(wxBG_STYLE_PAINT);
}
void DrawingPanel::Clear()
{
m_strokes.clear();
m_currentStroke.clear();
m_drawing = false;
Refresh();
}
void DrawingPanel::OnPaint(wxPaintEvent& WXUNUSED(evt))
{
wxBufferedPaintDC dc(this);
dc.SetBackground(*wxWHITE_BRUSH);
dc.Clear();
// Draw instructions
dc.SetTextForeground(wxColour(150, 150, 150));
dc.DrawText("Draw here with mouse", 10, 10);
// Draw all completed strokes
dc.SetPen(wxPen(*wxBLACK, 2));
for (const auto& stroke : m_strokes) {
if (stroke.size() > 1) {
for (size_t i = 1; i < stroke.size(); ++i) {
dc.DrawLine(stroke[i-1], stroke[i]);
}
}
}
// Draw current stroke
if (m_currentStroke.size() > 1) {
dc.SetPen(wxPen(*wxBLUE, 2));
for (size_t i = 1; i < m_currentStroke.size(); ++i) {
dc.DrawLine(m_currentStroke[i-1], m_currentStroke[i]);
}
}
}
//-----------------------------------------------------------------------------
// GLTestCanvas - OpenGL canvas for testing legacy GL functions (KiCad uses these)
//-----------------------------------------------------------------------------
class GLTestCanvas : public wxGLCanvas
{
public:
GLTestCanvas(wxWindow* parent);
virtual ~GLTestCanvas();
// Test functions matching KiCad's GL usage
void TestImmediateMode(); // glBegin/glEnd, glVertex, glColor
void TestMatrixOperations(); // glMatrixMode, glPushMatrix, glTranslate, etc.
void TestVertexArrays(); // glEnableClientState, glVertexPointer, etc.
void TestStateManagement(); // glEnable/glDisable, glBlendFunc
void TestTexCoords(); // glTexCoord2f
void TestNormals(); // glNormal3f
void RunAllTests();
void SetCurrentTest(int test);
bool IsGLInitialized() const { return m_glInitialized; }
private:
wxGLContext* m_context;
bool m_glInitialized;
int m_currentTest; // Which test pattern to display
void OnPaint(wxPaintEvent& evt);
void OnSize(wxSizeEvent& evt);
void InitGL();
void SetupViewport();
void Render();
wxDECLARE_EVENT_TABLE();
};
wxBEGIN_EVENT_TABLE(GLTestCanvas, wxGLCanvas)
EVT_PAINT(GLTestCanvas::OnPaint)
EVT_SIZE(GLTestCanvas::OnSize)
wxEND_EVENT_TABLE()
// Helper to get GL attributes
static wxGLAttributes GetGLAttributes()
{
wxGLAttributes attrs;
attrs.PlatformDefaults().Defaults().EndList();
return attrs;
}
GLTestCanvas::GLTestCanvas(wxWindow* parent)
: wxGLCanvas(parent, GetGLAttributes(), ID_GL_CANVAS,
wxDefaultPosition, wxSize(400, 300))
, m_context(nullptr)
, m_glInitialized(false)
, m_currentTest(0)
{
CONSOLE_LOG("GLTestCanvas constructor called");
SetBackgroundStyle(wxBG_STYLE_PAINT);
// Check if wxGLCanvas was created successfully
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = GetWebGLContext();
if (ctx > 0) {
EM_ASM({ console.log('[GL-CPP] WebGL context ID: ' + $0); }, ctx);
} else {
CONSOLE_LOG("ERROR: No WebGL context from wxGLCanvas!");
}
}
GLTestCanvas::~GLTestCanvas()
{
delete m_context;
}
void GLTestCanvas::InitGL()
{
CONSOLE_LOG("InitGL called");
if (m_glInitialized) {
CONSOLE_LOG("Already initialized, skipping");
return;
}
CONSOLE_LOG("Creating wxGLContext...");
if (!m_context) {
m_context = new wxGLContext(this);
if (m_context && m_context->IsOK()) {
CONSOLE_LOG("wxGLContext created successfully");
} else {
CONSOLE_LOG("ERROR: wxGLContext creation failed!");
return;
}
}
CONSOLE_LOG("Calling SetCurrent...");
if (!SetCurrent(*m_context)) {
CONSOLE_LOG("ERROR: SetCurrent failed!");
return;
}
CONSOLE_LOG("SetCurrent succeeded");
// Basic GL setup
CONSOLE_LOG("Setting up GL state...");
glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
glEnable(GL_DEPTH_TEST);
m_glInitialized = true;
CONSOLE_LOG("OpenGL initialized successfully");
wxPrintf("[GL] Vendor: %s\n", glGetString(GL_VENDOR));
wxPrintf("[GL] Renderer: %s\n", glGetString(GL_RENDERER));
wxPrintf("[GL] Version: %s\n", glGetString(GL_VERSION));
fflush(stdout);
}
void GLTestCanvas::SetupViewport()
{
wxSize size = GetClientSize();
glViewport(0, 0, size.x, size.y);
// Setup orthographic projection (legacy style)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-2.0, 2.0, -2.0, 2.0, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void GLTestCanvas::OnSize(wxSizeEvent& evt)
{
if (m_glInitialized && m_context) {
SetCurrent(*m_context);
SetupViewport();
}
evt.Skip();
}
void GLTestCanvas::OnPaint(wxPaintEvent& WXUNUSED(evt))
{
CONSOLE_LOG("OnPaint called");
wxPaintDC dc(this); // Required even for GL
// Initialize GL if not done yet (this creates m_context)
if (!m_glInitialized) {
CONSOLE_LOG("GL not initialized, calling InitGL...");
InitGL();
if (!m_glInitialized) {
CONSOLE_LOG("InitGL failed, returning");
return;
}
SetupViewport();
}
if (!m_context) {
CONSOLE_LOG("No context after InitGL, returning");
return;
}
CONSOLE_LOG("Setting context current in OnPaint...");
SetCurrent(*m_context);
CONSOLE_LOG("Calling Render...");
Render();
CONSOLE_LOG("Calling SwapBuffers...");
SwapBuffers();
CONSOLE_LOG("OnPaint complete");
}
void GLTestCanvas::Render()
{
// Set a bright visible color to prove GL is working
glClearColor(0.2f, 0.4f, 0.8f, 1.0f); // Bright blue
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
CONSOLE_LOG("glClear done with blue color");
// Debug GLImmediate state before drawing
EM_ASM({
if (typeof GLImmediate !== 'undefined') {
console.log('[GL-DEBUG] GLImmediate state:');
console.log(' initted:', GLImmediate.initted);
console.log(' enabledClientAttributes:', GLImmediate.enabledClientAttributes);
console.log(' totalEnabledClientAttributes:', GLImmediate.totalEnabledClientAttributes);
if (GLImmediate.TexEnvJIT) {
console.log(' TexEnvJIT.enabled:', GLImmediate.TexEnvJIT.enabled);
}
} else {
console.log('[GL-DEBUG] GLImmediate not defined!');
}
});
// Draw based on current test
switch (m_currentTest) {
case 0: TestImmediateMode(); break;
case 1: TestMatrixOperations(); break;
case 2: TestVertexArrays(); break;
case 3: TestStateManagement(); break;
default: TestImmediateMode(); break;
}
glFlush();
}
// Test 1: Immediate Mode Drawing (glBegin/glEnd) - KiCad uses this heavily
// Using STANDARD OpenGL patterns (color once, multiple vertices)
// This tests the GL shim that handles Emscripten's color-per-vertex requirement
void GLTestCanvas::TestImmediateMode()
{
wxPrintf("[GL TEST] Testing immediate mode (glBegin/glEnd)...\n");
wxPrintf("[GL TEST] Using STANDARD OpenGL patterns (color set once per primitive)\n");
fflush(stdout);
// Test GL_TRIANGLES with glVertex3f and glColor3f
// RGB triangle - each vertex has a different color (smooth shading)
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex3f(-1.0f, -0.5f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex3f(0.0f, 1.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex3f(1.0f, -0.5f, 0.0f);
glEnd();
// Test GL_QUADS with glVertex2f and glColor4f
// STANDARD OPENGL: Set color ONCE, then multiple vertices
// The GL shim should inject color before each vertex automatically
glColor4f(1.0f, 1.0f, 0.0f, 0.8f); // Yellow, semi-transparent - SET ONCE
glBegin(GL_QUADS);
glVertex2f(-1.8f, -1.8f); // All 4 vertices use the same yellow color
glVertex2f(-1.2f, -1.8f);
glVertex2f(-1.2f, -1.2f);
glVertex2f(-1.8f, -1.2f);
glEnd();
// Test GL_LINES with standard pattern
// STANDARD OPENGL: Set color ONCE, then multiple vertices
glColor3f(1.0f, 1.0f, 1.0f); // White - SET ONCE
glBegin(GL_LINES);
glVertex3f(-1.5f, 1.5f, 0.0f);
glVertex3f(1.5f, 1.5f, 0.0f);
glEnd();
// Test GL_LINE_STRIP with standard pattern
glColor3f(0.0f, 1.0f, 1.0f); // Cyan - SET ONCE
glBegin(GL_LINE_STRIP);
glVertex3f(1.2f, -1.8f, 0.0f);
glVertex3f(1.4f, -1.4f, 0.0f);
glVertex3f(1.6f, -1.6f, 0.0f);
glVertex3f(1.8f, -1.2f, 0.0f);
glEnd();
// Test GL_LINE_LOOP with standard pattern
glColor3f(1.0f, 0.0f, 1.0f); // Magenta - SET ONCE
glBegin(GL_LINE_LOOP);
glVertex3f(1.2f, 1.2f, 0.0f);
glVertex3f(1.8f, 1.2f, 0.0f);
glVertex3f(1.8f, 1.8f, 0.0f);
glVertex3f(1.2f, 1.8f, 0.0f);
glEnd();
wxPrintf("[GL TEST] Immediate mode test complete\n");
fflush(stdout);
}
// Test 2: Matrix Operations - KiCad uses glPushMatrix/glPopMatrix extensively
// Using STANDARD OpenGL patterns (color once, multiple vertices)
void GLTestCanvas::TestMatrixOperations()
{
wxPrintf("[GL TEST] Testing matrix operations...\n");
fflush(stdout);
// Draw centered triangle with standard GL pattern
glPushMatrix();
glTranslatef(0.0f, 0.0f, 0.0f);
glColor3f(0.5f, 0.5f, 0.5f); // Gray - SET ONCE
glBegin(GL_TRIANGLES);
glVertex3f(-0.3f, -0.3f, 0.0f);
glVertex3f(0.3f, -0.3f, 0.0f);
glVertex3f(0.0f, 0.3f, 0.0f);
glEnd();
glPopMatrix();
// Draw 4 rotated/translated copies with standard GL pattern
for (int i = 0; i < 4; i++) {
glPushMatrix();
float angle = i * 90.0f;
float tx = 1.2f * ((i % 2) * 2 - 1); // -1.2 or 1.2
float ty = 1.2f * ((i / 2) * 2 - 1); // -1.2 or 1.2
glTranslatef(tx, ty, 0.0f);
glRotatef(angle, 0.0f, 0.0f, 1.0f);
glScalef(0.5f, 0.5f, 1.0f);
// Draw colored square with standard GL pattern
float r = (i == 0 || i == 3) ? 1.0f : 0.3f;
float g = (i == 1 || i == 3) ? 1.0f : 0.3f;
float b = (i == 2 || i == 3) ? 1.0f : 0.3f;
glColor3f(r, g, b); // SET ONCE before glBegin
glBegin(GL_QUADS);
glVertex3f(-0.5f, -0.5f, 0.0f);
glVertex3f(0.5f, -0.5f, 0.0f);
glVertex3f(0.5f, 0.5f, 0.0f);
glVertex3f(-0.5f, 0.5f, 0.0f);
glEnd();
glPopMatrix();
}
wxPrintf("[GL TEST] Matrix operations test complete\n");
fflush(stdout);
}
// Test 3: Legacy Vertex Arrays - KiCad uses glVertexPointer, glColorPointer
void GLTestCanvas::TestVertexArrays()
{
wxPrintf("[GL TEST] Testing legacy vertex arrays...\n");
fflush(stdout);
// Vertex data for a hexagon
static GLfloat vertices[] = {
0.0f, 0.0f, 0.0f, // Center
1.0f, 0.0f, 0.0f, // Right
0.5f, 0.866f, 0.0f, // Upper right
-0.5f, 0.866f, 0.0f, // Upper left
-1.0f, 0.0f, 0.0f, // Left
-0.5f, -0.866f, 0.0f,// Lower left
0.5f, -0.866f, 0.0f // Lower right
};
static GLfloat colors[] = {
1.0f, 1.0f, 1.0f, // White center
1.0f, 0.0f, 0.0f, // Red
1.0f, 0.5f, 0.0f, // Orange
1.0f, 1.0f, 0.0f, // Yellow
0.0f, 1.0f, 0.0f, // Green
0.0f, 0.0f, 1.0f, // Blue
0.5f, 0.0f, 1.0f // Purple
};
static GLubyte indices[] = {
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 5,
0, 5, 6,
0, 6, 1
};
// Enable client state (legacy)
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
// Set up pointers
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(3, GL_FLOAT, 0, colors);
// Draw using vertex arrays
glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_BYTE, indices);
// Disable client state
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
wxPrintf("[GL TEST] Legacy vertex arrays test complete\n");
fflush(stdout);
}
// Test 4: State Management - glEnable/glDisable, blending
// Using STANDARD OpenGL patterns with separate glBegin/glEnd for each color
void GLTestCanvas::TestStateManagement()
{
wxPrintf("[GL TEST] Testing state management...\n");
fflush(stdout);
// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Draw overlapping semi-transparent squares using standard GL pattern
// Each quad has its own color set before glBegin
// Red square
glColor4f(1.0f, 0.0f, 0.0f, 0.5f); // SET ONCE
glBegin(GL_QUADS);
glVertex2f(-1.0f, -1.0f);
glVertex2f(0.5f, -1.0f);
glVertex2f(0.5f, 0.5f);
glVertex2f(-1.0f, 0.5f);
glEnd();
// Green square
glColor4f(0.0f, 1.0f, 0.0f, 0.5f); // SET ONCE
glBegin(GL_QUADS);
glVertex2f(-0.5f, -0.5f);
glVertex2f(1.0f, -0.5f);
glVertex2f(1.0f, 1.0f);
glVertex2f(-0.5f, 1.0f);
glEnd();
// Blue square
glColor4f(0.0f, 0.0f, 1.0f, 0.5f); // SET ONCE
glBegin(GL_QUADS);
glVertex2f(0.0f, 0.0f);
glVertex2f(1.5f, 0.0f);
glVertex2f(1.5f, 1.5f);
glVertex2f(0.0f, 1.5f);
glEnd();
glDisable(GL_BLEND);
wxPrintf("[GL TEST] State management test complete\n");
fflush(stdout);
}
// Test 5: Texture coordinates (no actual texture, just testing the calls)
// Note: glTexCoord is per-vertex anyway, and color is set once before glBegin
void GLTestCanvas::TestTexCoords()
{
wxPrintf("[GL TEST] Testing texture coordinates...\n");
fflush(stdout);
glColor3f(0.8f, 0.8f, 0.8f); // SET ONCE
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f);
glEnd();
wxPrintf("[GL TEST] Texture coordinates test complete\n");
fflush(stdout);
}
// Test 6: Normal vectors (for lighting, which we're not testing but calls should work)
// Note: glNormal is per-vertex, and color is set once before glBegin
void GLTestCanvas::TestNormals()
{
wxPrintf("[GL TEST] Testing normal vectors...\n");
fflush(stdout);
glColor3f(0.7f, 0.7f, 0.9f); // SET ONCE
glBegin(GL_TRIANGLES);
glNormal3f(0.0f, 0.0f, 1.0f);
glVertex3f(-1.0f, -1.0f, 0.0f);
glNormal3f(0.0f, 0.0f, 1.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glNormal3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 1.0f, 0.0f);
glEnd();
wxPrintf("[GL TEST] Normal vectors test complete\n");
fflush(stdout);
}
void GLTestCanvas::RunAllTests()
{
wxPrintf("[GL TEST] Running all legacy GL tests...\n");
fflush(stdout);
m_currentTest = 0;
Refresh();
}
void GLTestCanvas::SetCurrentTest(int test)
{
m_currentTest = test;
Refresh();
}
//-----------------------------------------------------------------------------
// TestFrame - Main application frame
//-----------------------------------------------------------------------------
class TestFrame : public wxFrame
{
public:
TestFrame(const wxString& title);
void LogEvent(const wxString& msg);
private:
wxNotebook* m_notebook;
wxListBox* m_eventLog;
wxGauge* m_gauge;
wxTextCtrl* m_textSingle;
wxTextCtrl* m_textMulti;
DrawingPanel* m_drawingPanel;
wxListBox* m_listBox;
GLTestCanvas* m_glCanvas;
wxChoice* m_glTestChoice;
// Create tab pages
wxPanel* CreateControlsPage(wxNotebook* parent);
wxPanel* CreateTextPage(wxNotebook* parent);
wxPanel* CreateDrawingPage(wxNotebook* parent);
wxPanel* CreateListsPage(wxNotebook* parent);
wxPanel* CreateOpenGLPage(wxNotebook* parent);
// Event handlers
void OnQuit(wxCommandEvent& evt);
void OnAbout(wxCommandEvent& evt);
void OnButtonClick(wxCommandEvent& evt);
void OnToggleButton(wxCommandEvent& evt);
void OnCheckBox(wxCommandEvent& evt);
void OnRadioBox(wxCommandEvent& evt);
void OnSlider(wxCommandEvent& evt);
void OnTextChange(wxCommandEvent& evt);
void OnTextEnter(wxCommandEvent& evt);
void OnComboSelect(wxCommandEvent& evt);
void OnListBoxSelect(wxCommandEvent& evt);
void OnChoiceSelect(wxCommandEvent& evt);
void OnAddItem(wxCommandEvent& evt);
void OnRemoveItem(wxCommandEvent& evt);
void OnClearDrawing(wxCommandEvent& evt);
void OnNotebookPageChanged(wxBookCtrlEvent& evt);
void OnGLTestSelect(wxCommandEvent& evt);
void OnGLRunAll(wxCommandEvent& evt);
wxDECLARE_EVENT_TABLE();
};
wxBEGIN_EVENT_TABLE(TestFrame, wxFrame)
EVT_MENU(wxID_EXIT, TestFrame::OnQuit)
EVT_MENU(wxID_ABOUT, TestFrame::OnAbout)
EVT_BUTTON(ID_BTN_TEST, TestFrame::OnButtonClick)
EVT_TOGGLEBUTTON(ID_BTN_TOGGLE, TestFrame::OnToggleButton)
EVT_CHECKBOX(ID_CHK_FEATURE, TestFrame::OnCheckBox)
EVT_RADIOBOX(ID_RADIO_OPTIONS, TestFrame::OnRadioBox)
EVT_SLIDER(ID_SLIDER, TestFrame::OnSlider)
EVT_TEXT(ID_TEXT_SINGLE, TestFrame::OnTextChange)
EVT_TEXT_ENTER(ID_TEXT_SINGLE, TestFrame::OnTextEnter)
EVT_COMBOBOX(ID_COMBO, TestFrame::OnComboSelect)
EVT_LISTBOX(ID_LISTBOX, TestFrame::OnListBoxSelect)
EVT_CHOICE(ID_CHOICE, TestFrame::OnChoiceSelect)
EVT_BUTTON(ID_BTN_ADD_ITEM, TestFrame::OnAddItem)
EVT_BUTTON(ID_BTN_REMOVE_ITEM, TestFrame::OnRemoveItem)
EVT_BUTTON(ID_BTN_CLEAR, TestFrame::OnClearDrawing)
EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, TestFrame::OnNotebookPageChanged)
EVT_CHOICE(ID_GL_TEST_SELECT, TestFrame::OnGLTestSelect)
EVT_BUTTON(ID_BTN_GL_RUN_ALL, TestFrame::OnGLRunAll)
wxEND_EVENT_TABLE()
TestFrame::TestFrame(const wxString& title)
: wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxSize(640, 480))
{
g_frame = this;
// Menu bar
wxMenu* menuFile = new wxMenu;
menuFile->Append(wxID_EXIT, "E&xit\tAlt-X", "Quit the application");
wxMenu* menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, "&File");
menuBar->Append(menuHelp, "&Help");
SetMenuBar(menuBar);
// Status bar
CreateStatusBar(2);
SetStatusText("Ready");
// Main layout: notebook on top, event log on bottom
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
// Notebook with tabs
m_notebook = new wxNotebook(this, wxID_ANY);
m_notebook->AddPage(CreateControlsPage(m_notebook), "Controls");
m_notebook->AddPage(CreateTextPage(m_notebook), "Text Input");
m_notebook->AddPage(CreateDrawingPage(m_notebook), "Drawing");
m_notebook->AddPage(CreateListsPage(m_notebook), "Lists");
m_notebook->AddPage(CreateOpenGLPage(m_notebook), "OpenGL");
mainSizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5);
// Event log panel
wxStaticBox* logBox = new wxStaticBox(this, wxID_ANY, "Event Log");
wxStaticBoxSizer* logSizer = new wxStaticBoxSizer(logBox, wxVERTICAL);
m_eventLog = new wxListBox(this, ID_EVENT_LOG, wxDefaultPosition, wxSize(-1, 100));
logSizer->Add(m_eventLog, 1, wxEXPAND);
mainSizer->Add(logSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
SetSizer(mainSizer);
LogEvent("Application started");
}
void TestFrame::LogEvent(const wxString& msg)
{
// Get timestamp
wxDateTime now = wxDateTime::Now();
wxString timestamp = now.Format("[%H:%M:%S] ");
wxString fullMsg = timestamp + msg;
// Add to listbox
m_eventLog->Append(fullMsg);
// Keep max 100 entries
while (m_eventLog->GetCount() > 100) {
m_eventLog->Delete(0);
}
// Scroll to bottom
m_eventLog->SetSelection(m_eventLog->GetCount() - 1);
m_eventLog->SetSelection(wxNOT_FOUND);
// Also log to console for Playwright testing
wxPrintf("[EVENT] %s\n", msg);
fflush(stdout);
// Update status bar
SetStatusText(msg, 1);
}
wxPanel* TestFrame::CreateControlsPage(wxNotebook* parent)
{
wxPanel* panel = new wxPanel(parent);
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
// Row 1: Buttons
wxStaticBox* btnBox = new wxStaticBox(panel, wxID_ANY, "Buttons");
wxStaticBoxSizer* btnSizer = new wxStaticBoxSizer(btnBox, wxHORIZONTAL);
wxButton* btnTest = new wxButton(panel, ID_BTN_TEST, "Click Me");
btnSizer->Add(btnTest, 0, wxALL, 5);
wxToggleButton* btnToggle = new wxToggleButton(panel, ID_BTN_TOGGLE, "Toggle");
btnSizer->Add(btnToggle, 0, wxALL, 5);
mainSizer->Add(btnSizer, 0, wxEXPAND | wxALL, 5);
// Row 2: Checkbox and Radio
wxBoxSizer* row2Sizer = new wxBoxSizer(wxHORIZONTAL);
wxCheckBox* chkFeature = new wxCheckBox(panel, ID_CHK_FEATURE, "Enable feature");
row2Sizer->Add(chkFeature, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
wxString radioChoices[] = { "Option A", "Option B", "Option C" };
wxRadioBox* radioBox = new wxRadioBox(panel, ID_RADIO_OPTIONS, "Options",
wxDefaultPosition, wxDefaultSize, 3, radioChoices, 1, wxRA_SPECIFY_ROWS);
row2Sizer->Add(radioBox, 0, wxALL, 5);
mainSizer->Add(row2Sizer, 0, wxEXPAND);
// Row 3: Slider and Gauge
wxStaticBox* rangeBox = new wxStaticBox(panel, wxID_ANY, "Range Controls");
wxStaticBoxSizer* rangeSizer = new wxStaticBoxSizer(rangeBox, wxVERTICAL);
wxBoxSizer* sliderRow = new wxBoxSizer(wxHORIZONTAL);
sliderRow->Add(new wxStaticText(panel, wxID_ANY, "Slider:"), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
wxSlider* slider = new wxSlider(panel, ID_SLIDER, 50, 0, 100,
wxDefaultPosition, wxSize(200, -1));
sliderRow->Add(slider, 1, wxALL, 5);
rangeSizer->Add(sliderRow, 0, wxEXPAND);
wxBoxSizer* gaugeRow = new wxBoxSizer(wxHORIZONTAL);
gaugeRow->Add(new wxStaticText(panel, wxID_ANY, "Gauge:"), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
m_gauge = new wxGauge(panel, ID_GAUGE, 100, wxDefaultPosition, wxSize(200, -1));
m_gauge->SetValue(50);
gaugeRow->Add(m_gauge, 1, wxALL, 5);
rangeSizer->Add(gaugeRow, 0, wxEXPAND);
mainSizer->Add(rangeSizer, 0, wxEXPAND | wxALL, 5);
panel->SetSizer(mainSizer);
return panel;
}
wxPanel* TestFrame::CreateTextPage(wxNotebook* parent)
{
wxPanel* panel = new wxPanel(parent);
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
// Single-line text
wxBoxSizer* singleRow = new wxBoxSizer(wxHORIZONTAL);
singleRow->Add(new wxStaticText(panel, wxID_ANY, "Single-line:"), 0,
wxALL | wxALIGN_CENTER_VERTICAL, 5);
m_textSingle = new wxTextCtrl(panel, ID_TEXT_SINGLE, "",
wxDefaultPosition, wxSize(200, -1), wxTE_PROCESS_ENTER);
singleRow->Add(m_textSingle, 1, wxALL, 5);
mainSizer->Add(singleRow, 0, wxEXPAND);
// Multi-line text
wxStaticBox* multiBox = new wxStaticBox(panel, wxID_ANY, "Multi-line:");
wxStaticBoxSizer* multiSizer = new wxStaticBoxSizer(multiBox, wxVERTICAL);
m_textMulti = new wxTextCtrl(panel, ID_TEXT_MULTI, "",
wxDefaultPosition, wxSize(-1, 100), wxTE_MULTILINE);
multiSizer->Add(m_textMulti, 1, wxEXPAND | wxALL, 5);
mainSizer->Add(multiSizer, 1, wxEXPAND | wxALL, 5);
// Password field
wxBoxSizer* passRow = new wxBoxSizer(wxHORIZONTAL);
passRow->Add(new wxStaticText(panel, wxID_ANY, "Password:"), 0,
wxALL | wxALIGN_CENTER_VERTICAL, 5);
wxTextCtrl* textPass = new wxTextCtrl(panel, ID_TEXT_PASSWORD, "",
wxDefaultPosition, wxSize(200, -1), wxTE_PASSWORD);
passRow->Add(textPass, 0, wxALL, 5);
mainSizer->Add(passRow, 0, wxEXPAND);
// ComboBox
wxBoxSizer* comboRow = new wxBoxSizer(wxHORIZONTAL);
comboRow->Add(new wxStaticText(panel, wxID_ANY, "ComboBox:"), 0,
wxALL | wxALIGN_CENTER_VERTICAL, 5);
wxString comboChoices[] = { "Choice 1", "Choice 2", "Choice 3" };
wxComboBox* combo = new wxComboBox(panel, ID_COMBO, "",
wxDefaultPosition, wxSize(150, -1), 3, comboChoices);
comboRow->Add(combo, 0, wxALL, 5);
mainSizer->Add(comboRow, 0, wxEXPAND);
panel->SetSizer(mainSizer);
return panel;
}
wxPanel* TestFrame::CreateDrawingPage(wxNotebook* parent)
{
wxPanel* panel = new wxPanel(parent);
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
// Instructions
mainSizer->Add(new wxStaticText(panel, wxID_ANY,
"Click and drag to draw. Mouse events are logged."),
0, wxALL, 10);
// Drawing canvas
m_drawingPanel = new DrawingPanel(panel);
mainSizer->Add(m_drawingPanel, 1, wxEXPAND | wxALL, 10);
// Clear button
wxButton* btnClear = new wxButton(panel, ID_BTN_CLEAR, "Clear Canvas");
mainSizer->Add(btnClear, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 10);
panel->SetSizer(mainSizer);
return panel;
}
wxPanel* TestFrame::CreateListsPage(wxNotebook* parent)
{
wxPanel* panel = new wxPanel(parent);
wxBoxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
// ListBox section
wxStaticBox* listBoxGroup = new wxStaticBox(panel, wxID_ANY, "ListBox");
wxStaticBoxSizer* listBoxSizer = new wxStaticBoxSizer(listBoxGroup, wxVERTICAL);
wxString listItems[] = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
m_listBox = new wxListBox(panel, ID_LISTBOX, wxDefaultPosition,
wxSize(150, 150), 5, listItems);
listBoxSizer->Add(m_listBox, 1, wxEXPAND | wxALL, 5);
wxBoxSizer* listBtnSizer = new wxBoxSizer(wxHORIZONTAL);
listBtnSizer->Add(new wxButton(panel, ID_BTN_ADD_ITEM, "Add"), 0, wxALL, 2);
listBtnSizer->Add(new wxButton(panel, ID_BTN_REMOVE_ITEM, "Remove"), 0, wxALL, 2);
listBoxSizer->Add(listBtnSizer, 0, wxALIGN_CENTER);
mainSizer->Add(listBoxSizer, 1, wxEXPAND | wxALL, 10);
// Choice section
wxStaticBox* choiceGroup = new wxStaticBox(panel, wxID_ANY, "Choice");
wxStaticBoxSizer* choiceSizer = new wxStaticBoxSizer(choiceGroup, wxVERTICAL);
wxString choiceItems[] = { "Red", "Green", "Blue", "Yellow", "Purple" };
wxChoice* choice = new wxChoice(panel, ID_CHOICE, wxDefaultPosition,
wxSize(150, -1), 5, choiceItems);
choiceSizer->Add(choice, 0, wxALL, 5);
choiceSizer->Add(new wxStaticText(panel, wxID_ANY,
"Select a color from\nthe dropdown above."), 0, wxALL, 5);
mainSizer->Add(choiceSizer, 1, wxEXPAND | wxALL, 10);
panel->SetSizer(mainSizer);
return panel;
}
wxPanel* TestFrame::CreateOpenGLPage(wxNotebook* parent)
{
wxPanel* panel = new wxPanel(parent);
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
// Description
wxStaticText* desc = new wxStaticText(panel, wxID_ANY,
"OpenGL Legacy Function Tests\n"
"Tests the GL functions KiCad uses (immediate mode, matrix ops, vertex arrays).\n"
"Using Emscripten's -sLEGACY_GL_EMULATION for WebGL compatibility.");
mainSizer->Add(desc, 0, wxALL, 10);
// Test selection row
wxBoxSizer* controlSizer = new wxBoxSizer(wxHORIZONTAL);
controlSizer->Add(new wxStaticText(panel, wxID_ANY, "Test:"), 0,
wxALL | wxALIGN_CENTER_VERTICAL, 5);
wxString testChoices[] = {
"Immediate Mode (glBegin/glEnd)",
"Matrix Operations (glPush/Pop)",
"Vertex Arrays (glVertexPointer)",
"State Management (glEnable/Blend)"
};
m_glTestChoice = new wxChoice(panel, ID_GL_TEST_SELECT, wxDefaultPosition,
wxSize(250, -1), 4, testChoices);
m_glTestChoice->SetSelection(0);
controlSizer->Add(m_glTestChoice, 0, wxALL, 5);
wxButton* btnRunAll = new wxButton(panel, ID_BTN_GL_RUN_ALL, "Run All Tests");
controlSizer->Add(btnRunAll, 0, wxALL, 5);
mainSizer->Add(controlSizer, 0, wxEXPAND);
// GL Canvas
m_glCanvas = new GLTestCanvas(panel);
mainSizer->Add(m_glCanvas, 1, wxEXPAND | wxALL, 10);
// Legend/info
wxStaticBox* legendBox = new wxStaticBox(panel, wxID_ANY, "Test Details");
wxStaticBoxSizer* legendSizer = new wxStaticBoxSizer(legendBox, wxVERTICAL);
wxStaticText* legend = new wxStaticText(panel, wxID_ANY,
"Immediate Mode: glBegin, glEnd, glVertex2f/3f, glColor3f/4f, GL_TRIANGLES/QUADS/LINES\n"
"Matrix Ops: glMatrixMode, glPushMatrix, glPopMatrix, glTranslatef, glRotatef, glScalef\n"
"Vertex Arrays: glEnableClientState, glVertexPointer, glColorPointer, glDrawElements\n"
"State Mgmt: glEnable, glDisable, glBlendFunc, GL_BLEND, GL_DEPTH_TEST");
legendSizer->Add(legend, 0, wxALL, 5);
mainSizer->Add(legendSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
panel->SetSizer(mainSizer);
return panel;
}
// Event handlers
void TestFrame::OnQuit(wxCommandEvent& WXUNUSED(evt))
{
Close(true);
}
void TestFrame::OnAbout(wxCommandEvent& WXUNUSED(evt))
{
wxMessageBox("wxWidgets WASM Comprehensive Test\n\n"
"This application tests various wxWidgets controls\n"
"running in WebAssembly via wxUniversal.",
"About", wxOK | wxICON_INFORMATION, this);
}
void TestFrame::OnButtonClick(wxCommandEvent& WXUNUSED(evt))
{
LogEvent("Button 'Click Me' clicked");
}
void TestFrame::OnToggleButton(wxCommandEvent& evt)
{
bool pressed = evt.IsChecked();
LogEvent(wxString::Format("Toggle button %s", pressed ? "pressed" : "released"));
}
void TestFrame::OnCheckBox(wxCommandEvent& evt)
{
bool checked = evt.IsChecked();
LogEvent(wxString::Format("Checkbox toggled: %s", checked ? "checked" : "unchecked"));
}
void TestFrame::OnRadioBox(wxCommandEvent& evt)
{
int sel = evt.GetSelection();
wxString option = wxString::Format("Option %c", 'A' + sel);
LogEvent(wxString::Format("Radio selection: %s", option));
}
void TestFrame::OnSlider(wxCommandEvent& evt)
{
int value = evt.GetInt();
m_gauge->SetValue(value);
LogEvent(wxString::Format("Slider value: %d", value));
}
void TestFrame::OnTextChange(wxCommandEvent& evt)
{
wxString text = evt.GetString();
LogEvent(wxString::Format("Text changed: \"%s\"", text));
}
void TestFrame::OnTextEnter(wxCommandEvent& evt)
{
wxString text = evt.GetString();
LogEvent(wxString::Format("Text entered (Enter pressed): \"%s\"", text));
}
void TestFrame::OnComboSelect(wxCommandEvent& evt)
{
wxString selection = evt.GetString();
LogEvent(wxString::Format("ComboBox selected: %s", selection));
}
void TestFrame::OnListBoxSelect(wxCommandEvent& evt)
{
wxString selection = evt.GetString();
LogEvent(wxString::Format("ListBox selected: %s", selection));
}
void TestFrame::OnChoiceSelect(wxCommandEvent& evt)
{
wxString selection = evt.GetString();
LogEvent(wxString::Format("Choice selected: %s", selection));
}
void TestFrame::OnAddItem(wxCommandEvent& WXUNUSED(evt))
{
static int itemCount = 5;
wxString newItem = wxString::Format("Item %d", ++itemCount);
m_listBox->Append(newItem);
LogEvent(wxString::Format("Added item: %s", newItem));
}
void TestFrame::OnRemoveItem(wxCommandEvent& WXUNUSED(evt))
{
int sel = m_listBox->GetSelection();
if (sel != wxNOT_FOUND) {
wxString item = m_listBox->GetString(sel);
m_listBox->Delete(sel);
LogEvent(wxString::Format("Removed item: %s", item));
} else {
LogEvent("Remove: No item selected");
}
}
void TestFrame::OnClearDrawing(wxCommandEvent& WXUNUSED(evt))
{
m_drawingPanel->Clear();
LogEvent("Drawing canvas cleared");
}
void TestFrame::OnNotebookPageChanged(wxBookCtrlEvent& evt)
{
int page = evt.GetSelection();
wxString pageName = m_notebook->GetPageText(page);
LogEvent(wxString::Format("Tab changed to: %s", pageName));
evt.Skip();
}
void TestFrame::OnGLTestSelect(wxCommandEvent& evt)
{
int sel = evt.GetSelection();
wxString testNames[] = {
"Immediate Mode",
"Matrix Operations",
"Vertex Arrays",
"State Management"
};
if (sel >= 0 && sel < 4) {
LogEvent(wxString::Format("GL Test selected: %s", testNames[sel]));
m_glCanvas->SetCurrentTest(sel);
}
}
void TestFrame::OnGLRunAll(wxCommandEvent& WXUNUSED(evt))
{
LogEvent("Running all GL tests...");
m_glCanvas->RunAllTests();
LogEvent("All GL tests completed - check console for detailed results");
}
// DrawingPanel event handlers (defined after TestFrame for g_frame access)
void DrawingPanel::OnMouseDown(wxMouseEvent& evt)
{
m_drawing = true;
m_currentStroke.clear();
m_currentStroke.push_back(evt.GetPosition());
CaptureMouse();
if (g_frame) {
g_frame->LogEvent(wxString::Format("Mouse down at (%d, %d)",
evt.GetX(), evt.GetY()));
}
}
void DrawingPanel::OnMouseMove(wxMouseEvent& evt)
{
if (m_drawing) {
m_currentStroke.push_back(evt.GetPosition());
Refresh();
}
}
void DrawingPanel::OnMouseUp(wxMouseEvent& evt)
{
if (m_drawing) {
m_drawing = false;
if (HasCapture()) {
ReleaseMouse();
}
// Save the completed stroke
if (m_currentStroke.size() > 1) {
m_strokes.push_back(m_currentStroke);
}
m_currentStroke.clear();
Refresh();
if (g_frame) {
g_frame->LogEvent(wxString::Format("Mouse up at (%d, %d) - stroke completed",
evt.GetX(), evt.GetY()));
}
}
}
void DrawingPanel::OnMouseEnter(wxMouseEvent& WXUNUSED(evt))
{
if (g_frame) {
g_frame->LogEvent("Mouse entered drawing canvas");
}
}
void DrawingPanel::OnMouseLeave(wxMouseEvent& WXUNUSED(evt))
{
if (g_frame) {
g_frame->LogEvent("Mouse left drawing canvas");
}
}
//-----------------------------------------------------------------------------
// TestApp - Application class
//-----------------------------------------------------------------------------
class TestApp : public wxApp
{
public:
virtual bool OnInit() wxOVERRIDE;
};
wxIMPLEMENT_APP(TestApp);
bool TestApp::OnInit()
{
if (!wxApp::OnInit())
return false;
TestFrame* frame = new TestFrame("wxWidgets WASM Comprehensive Test");
frame->Show(true);
return true;
}