// 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 #include #include #else #include #include #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 // 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> m_strokes; // Collection of strokes std::vector 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 }; // KiCad uses GL_UNSIGNED_SHORT or GL_UNSIGNED_INT for indices, never GL_UNSIGNED_BYTE // Emscripten's legacy GL emulation only supports GL_UNSIGNED_SHORT for client-side arrays static GLushort 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 (GL_UNSIGNED_SHORT matches KiCad's usage) glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_SHORT, 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; }