wxWidgets icon indicating copy to clipboard operation
wxWidgets copied to clipboard

[gtk] wxNotebook does not display page (panel with opengl content), wxListBooks displays all OK.

Open basiliscos opened this issue 1 year ago • 9 comments

I didn't tried to reproduce as separate example, but I'm allowed to post videos from our app, may be you'll have ideas.

The code is identical but the correct example uses new wxListbook , while incorrect example uses new wxNotebook.

Additionally, the in the buggy case it complains

(granum:26129): Gtk-WARNING **: 15:03:22.839: Drawing a gadget with negative dimensions. Did you forget to allocate a size? (node tab owner GtkNotebook)

I have no idea what can be done.

Platform and version information

  • wxWidgets: 3.2.6.7
  • GTK version: 3-3.24.43
  • X11
  • Desktop environment: awesome
  • linux: 6.6.59

basiliscos avatar Nov 19 '24 12:11 basiliscos

Sorry, I have no idea what the problem is, but in any case we really do need an example reproducing it, if only to be able to test any fixes. Please do try reproducing it in the closest sample (again, no idea which one it would be, as I don't know if it's AUI-specific or not) and submit a patch reproducing it with the explanation of what actually goes wrong.

Thanks!

vadz avatar Nov 19 '24 14:11 vadz

ok, will try

basiliscos avatar Nov 19 '24 15:11 basiliscos

The example goes below. The key change is new wxListbook vs new wxNotebook

The behavior is recorded in videos and attached.

https://github.com/user-attachments/assets/1b0a2aea-d3a0-4ab3-a8b9-ecd5d22b0950

https://github.com/user-attachments/assets/5a5d2128-f179-40ad-a5b9-9cd980771b8c

#include <wx/app.h>
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/listbook.h>
#include <wx/notebook.h>
#include <wx/textctrl.h>
#include <wx/sizer.h>
#include <wx/glcanvas.h>
#include <wx/dcclient.h>

#include <GL/glu.h>
#include <GL/gl.h>

class BasicGLPane : public wxGLCanvas
{
	wxGLContext*	m_context;

public:
	BasicGLPane(wxWindow* parent, int* args, GLfloat bg_r, GLfloat bg_g, GLfloat bg_b);
	virtual ~BasicGLPane();

	void resized(wxSizeEvent& evt);

	int getWidth();
	int getHeight();

	void render(wxPaintEvent& evt);
	void prepare3DViewport(int topleft_x, int topleft_y, int bottomrigth_x, int bottomrigth_y);
	void prepare2DViewport(int topleft_x, int topleft_y, int bottomrigth_x, int bottomrigth_y);

	GLfloat bg_r;
	GLfloat bg_g;
	GLfloat bg_b;
	// events
	DECLARE_EVENT_TABLE()
};

GLfloat v[8][3];
GLint faces[6][4] = {  /* Vertex indices for the 6 faces of a cube. */
	{0, 1, 2, 3}, {3, 2, 6, 7}, {7, 6, 5, 4},
	{4, 5, 1, 0}, {5, 6, 2, 1}, {7, 4, 0, 3} };

BEGIN_EVENT_TABLE(BasicGLPane, wxGLCanvas)
EVT_PAINT(BasicGLPane::render)
END_EVENT_TABLE()

BasicGLPane::BasicGLPane(wxWindow* parent, int* args, GLfloat bg_r_, GLfloat bg_g_, GLfloat bg_b_):
	wxGLCanvas(parent, wxID_ANY, args, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE),
	bg_r{bg_r_}, bg_g{bg_g_}, bg_b{bg_b_}
{
	printf("%f, %f, %f\n", bg_r, bg_g, bg_b);
	m_context = new wxGLContext(this);
	// prepare a simple cube to demonstrate 3D render
	// source: http://www.opengl.org/resources/code/samples/glut_examples/examples/cube.c
	v[0][0] = v[1][0] = v[2][0] = v[3][0] = -1;
	v[4][0] = v[5][0] = v[6][0] = v[7][0] = 1;
	v[0][1] = v[1][1] = v[4][1] = v[5][1] = -1;
	v[2][1] = v[3][1] = v[6][1] = v[7][1] = 1;
	v[0][2] = v[3][2] = v[4][2] = v[7][2] = 1;
	v[1][2] = v[2][2] = v[5][2] = v[6][2] = -1;

	// To avoid flashing on MSW
	SetBackgroundStyle(wxBG_STYLE_CUSTOM);
}

BasicGLPane::~BasicGLPane()
{
	delete m_context;
}

void BasicGLPane::resized(wxSizeEvent& evt)
{
//	wxGLCanvas::OnSize(evt);

	Refresh();
}

void BasicGLPane::prepare3DViewport(int topleft_x, int topleft_y, int bottomrigth_x, int bottomrigth_y)
{

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black Background
	glClearDepth(1.0f);	// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST); // Enables Depth Testing
	glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	glEnable(GL_COLOR_MATERIAL);

	glViewport(topleft_x, topleft_y, bottomrigth_x-topleft_x, bottomrigth_y-topleft_y);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	float ratio_w_h = (float)(bottomrigth_x-topleft_x)/(float)(bottomrigth_y-topleft_y);
	gluPerspective(45 /*view angle*/, ratio_w_h, 0.1 /*clip close*/, 200 /*clip far*/);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

}

/** Inits the OpenGL viewport for drawing in 2D. */
void BasicGLPane::prepare2DViewport(int topleft_x, int topleft_y, int bottomrigth_x, int bottomrigth_y)
{
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black Background
	glEnable(GL_TEXTURE_2D);   // textures
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	glViewport(topleft_x, topleft_y, bottomrigth_x-topleft_x, bottomrigth_y-topleft_y);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluOrtho2D(topleft_x, bottomrigth_x, bottomrigth_y, topleft_y);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

int BasicGLPane::getWidth()
{
	return GetSize().x;
}

int BasicGLPane::getHeight()
{
	return GetSize().y;
}


void BasicGLPane::render( wxPaintEvent& evt )
{
	if(!IsShown()) return;

	wxGLCanvas::SetCurrent(*m_context);
	wxPaintDC(this); // only to be used in paint events. use wxClientDC to paint outside the paint event

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// ------------- draw some 2D ----------------
	prepare2DViewport(0,0,getWidth()/2, getHeight());
	glLoadIdentity();

	// white background
	// glColor4f(bg_r, bg_g, bg_r, 1);
	glBegin(GL_QUADS);
	glVertex3f(0,0,0);
	glVertex3f(getWidth(),0,0);
	glVertex3f(getWidth(),getHeight(),0);
	glVertex3f(0,getHeight(),0);
	glEnd();

	glColor4f(1, 0, 0, 1);
	glBegin(GL_QUADS);
	glVertex3f(getWidth()/8, getHeight()/3, 0);
	glVertex3f(getWidth()*3/8, getHeight()/3, 0);
	glVertex3f(getWidth()*3/8, getHeight()*2/3, 0);
	glVertex3f(getWidth()/8, getHeight()*2/3, 0);
	glEnd();

	// ------------- draw some 3D ----------------
	prepare3DViewport(getWidth()/2,0,getWidth(), getHeight());
	glLoadIdentity();

	glColor4f(0,0,1,1);
	glTranslatef(0,0,-5);
	glRotatef(50.0f, 0.0f, 1.0f, 0.0f);

	glColor4f(bg_r, bg_b, bg_b, 1);
	// glColor4f(bg_r, bg_b, bg_b, 1);
	for (int i = 0; i < 6; i++)
	{
		glBegin(GL_LINE_STRIP);
		glVertex3fv(&v[faces[i][0]][0]);
		glVertex3fv(&v[faces[i][1]][0]);
		glVertex3fv(&v[faces[i][2]][0]);
		glVertex3fv(&v[faces[i][3]][0]);
		glVertex3fv(&v[faces[i][0]][0]);
		glEnd();
	}

	glFlush();
	SwapBuffers();
}

class WxNotebook1Frame : public wxFrame
{
public:
	WxNotebook1Frame(const wxString& title);
};

class WxNotebook1App : public wxApp
{
public:
	virtual bool OnInit();
};

bool WxNotebook1App::OnInit()
{
	WxNotebook1Frame* frame = new WxNotebook1Frame(L"WxNotebook1");
	frame->Show(true);
	return true;
}

WxNotebook1Frame::WxNotebook1Frame(const wxString& title)
	: wxFrame(NULL, wxID_ANY, title)
{
	// Create a top-level panel to hold all the contents of the frame
	wxPanel* panel = new wxPanel(this, wxID_ANY);

	wxBookCtrlBase* tabs = new wxListbook(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);

	// Add 2 pages to the wxNotebook widget
	int args[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0};

	tabs->AddPage(new BasicGLPane(tabs, args, 1., 1., 1.), L"Tab 1");
	tabs->AddPage(new BasicGLPane(tabs, args, 0., 1., 0.), L"Tab 2");
	tabs->AddPage(new BasicGLPane(tabs, args, 1., 0., 0.), L"Tab 3");
	tabs->AddPage(new BasicGLPane(tabs, args, 0., 0., 1.), L"Tab 4");

	// Set up the sizer for the panel
	wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
	panelSizer->Add(tabs, 1, wxEXPAND);
	panel->SetSizer(panelSizer);

	// Set up the sizer for the frame and resize the frame
	// according to its contents
	wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
	topSizer->SetMinSize(250, 100);
	topSizer->Add(panel, 1, wxEXPAND);
	SetSizerAndFit(topSizer);
}

wxIMPLEMENT_APP(WxNotebook1App);

basiliscos avatar Nov 20 '24 10:11 basiliscos

Can you please describe in words what exactly doesn't work? I don't see any obvious difference between the 2 videos...

vadz avatar Nov 20 '24 13:11 vadz

On the 2nd video (notebook) at during the 5th second when I move mouse around "tab 2" header, the tab content starts blinking (black to white background and vise versa). The same for "tab 3".

When I do mouse movement for "listbook" such effect does not appear (video1).

basiliscos avatar Nov 20 '24 13:11 basiliscos

FWIW I don't see this here, the background remains black on tab 2 and red on tab 3, so it must be driver-dependent :-( I have no idea how to debug this, to be honest.

vadz avatar Nov 20 '24 13:11 vadz

Thank you, my colleague tried the sample provided different hardware/OS, and it seems also ok.

However, our app (wx + vtk) still has the initial problem, described above, even in my colleague environment.

Is there sense to try to here minimal application wx + vtk?

basiliscos avatar Nov 20 '24 13:11 basiliscos

I don't know VTK well so even reproducing this in a minimal example using VTK wouldn't make it simple for me to debug this. Reproducing this in the notebook sample with some really minimal changes mirroring whatever VTK does would be helpful, but is probably going to be more difficult.

P.S. I've deleted a duplicate comment.

vadz avatar Nov 20 '24 19:11 vadz

Here is an update with sources and videos attached:

When on the line 151 it uses

    auto tabs = new wxNotebook

then the behavior is buggy.

but when

    auto tabs = new wxListbook

then everything works as expected. The panel with opengl/vtk itself also renderers without any issues. The cmake file is attached to link with VTK.

https://github.com/user-attachments/assets/75e9691d-2229-4f1e-84d7-99ed27183165 https://github.com/user-attachments/assets/302d067b-9255-4f8e-87f6-28ce122068e5

CMakeLists.txt

#include <vtkAnimationCue.h>
#include <vtkAnimationScene.h>
#include <vtkCommand.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSphereSource.h>

#include <wx/wx.h>
#include <wx/slider.h>
#include <wx/evtloop.h>

#include <wx/notebook.h>
#include <wx/listbook.h>

#if !defined(__WXMSW__)
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#endif


/*****************************************************************************/

namespace utils {

// there is a need to get native window id to let it be OpenGL'ized by vtk

void* NativeHandle(wxWindow* window)
{
	auto handle = window->GetHandle();
#if defined(__WXMSW__)
	return reinterpret_cast<void*>(handle);
#elif defined(__WXGTK20__)
	gtk_widget_realize(handle); // otherwise xwindow id will can be null
	//gtk_widget_set_double_buffered(handle, false);
	GdkWindow* gdk = gtk_widget_get_window(GTK_WIDGET(handle));
	return reinterpret_cast<void*>(GDK_WINDOW_XID(gdk));
#else
#error "TODO: add platform support"
#endif
}

/*****************************************************************************/

} // namespace

/*****************************************************************************/

#define DEBUG_MESSAGE(format, ...) fprintf(stderr, format "\n", __VA_ARGS__)

/*****************************************************************************/

class MyApp: public wxApp
{
public:
	using Parent = wxApp;

	bool OnInit() override;
	int OnExit() override;
};

/*****************************************************************************/

struct MyPanel;

/*****************************************************************************/

class MyFrame: public wxFrame
{
public:
	MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);

private:
	wxSlider* slider;
	wxButton* button;
	MyPanel* panel;
	vtkSmartPointer<vtkAnimationScene> scene;
};

/*****************************************************************************/

struct MyInteractor: vtkRenderWindowInteractor
{
	using Parent = vtkRenderWindowInteractor;

	MyInteractor(wxPanel* panel);
	~MyInteractor();
	void Initialize() override;
	void UpdateSize(int x, int y) override;

	private:
	wxPanel* panel; // wx widget to be OpenGL'ized
};

/*****************************************************************************/

// Proxies wx-events to VTK events
struct MyPanel: public wxPanel
{
	using Parent = wxPanel;

    MyPanel(wxWindow* parent, const char* sphere_color);
	~MyPanel();

	void OnRender(wxPaintEvent& event);
    void OnResize(wxSizeEvent& evt);
    void OnTabChange(wxBookCtrlEvent&);

	MyInteractor* interactor;
	vtkRenderer* renderer;

	DECLARE_EVENT_TABLE()
};

/*****************************************************************************/

BEGIN_EVENT_TABLE(MyPanel, wxPanel)
    EVT_PAINT(MyPanel::OnRender)
    EVT_SIZE(MyPanel::OnResize)
END_EVENT_TABLE()

/*****************************************************************************/

bool MyApp::OnInit()
{
	DEBUG_MESSAGE("%s", "MyApp::OnInit");

	MyFrame *frame = new MyFrame( "Hello World", wxPoint(50, 50), wxSize(450, 340) );
	frame->Show( true );
	return true;
}

/*****************************************************************************/

int MyApp::OnExit()
{
	DEBUG_MESSAGE("%s", "MyApp::OnExit");
	return Parent::OnExit();
}

/*****************************************************************************/

MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
		: wxFrame(NULL, wxID_ANY, title, pos, size)
{
    auto sizer = new wxBoxSizer(wxVERTICAL);
    auto tabs = new wxNotebook
    (
        this, wxID_HIGHEST + 1, wxDefaultPosition, wxDefaultSize, wxNB_TOP
    );

    auto page_0 = new MyPanel(tabs, "Green");
    auto page_1 = new MyPanel(tabs, "Red");
    auto page_2 = new MyPanel(tabs, "Cyan");

    tabs->AddPage(page_0, _("page_0"), true);
    tabs->AddPage(page_1, _("page_1"), false);
    tabs->AddPage(page_2, _("page_2"), false);

    sizer->Add(tabs, 1, wxEXPAND);

    SetSizerAndFit(sizer);
    PostSizeEvent();
}

/*****************************************************************************/

MyPanel::MyPanel(wxWindow* parent, const char* sphere_color):
    Parent(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
{
    SetBackgroundStyle(wxBG_STYLE_CUSTOM);

	interactor = new MyInteractor(this);
	renderer = vtkRenderer::New();
	interactor->GetRenderWindow()->AddRenderer(renderer);

    renderer->SetBackground(0, 0, 0);
	renderer->ResetCamera();

    vtkNew<vtkSphereSource> sphereSource;
    sphereSource->SetCenter(0.0, 0.0, 0.0);
    sphereSource->SetRadius(5.0);

    // Make the surface smooth:
    sphereSource->SetPhiResolution(100);
    sphereSource->SetThetaResolution(100);

    vtkNew<vtkPolyDataMapper> mapper;
    mapper->SetInputConnection(sphereSource->GetOutputPort());

    vtkNew<vtkNamedColors> colors;

    vtkNew<vtkActor> actor;
    actor->SetMapper(mapper);
    actor->GetProperty()->SetColor(colors->GetColor3d(sphere_color).GetData());

    renderer->AddActor(actor);
    interactor->GetRenderWindow()->Render();
    interactor->Start();

    parent->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &MyPanel::OnTabChange, this);
    parent->Bind(wxEVT_LISTBOOK_PAGE_CHANGED, &MyPanel::OnTabChange, this);
}

/*****************************************************************************/

MyPanel::~MyPanel()
{
	DEBUG_MESSAGE("%s", "MyPanel::~MyPanel");
	renderer->Delete();
	interactor->GetRenderWindow()->Delete();
	interactor->Delete();
}

/*****************************************************************************/

void MyPanel::OnRender(wxPaintEvent& event)
{
    DEBUG_MESSAGE("%s", "MyPanel::OnRender");
    interactor->GetRenderWindow()->Render();
}

/*****************************************************************************/

void MyPanel::OnResize(wxSizeEvent& evt)
{
    auto w = evt.GetSize().GetWidth();
	auto h = evt.GetSize().GetHeight();
    DEBUG_MESSAGE("MyPanel::OnResize %d x %d", w, h);
	interactor->UpdateSize(w, h);
    Refresh();
}

/*****************************************************************************/

void MyPanel::OnTabChange(wxBookCtrlEvent& event)
{
    DEBUG_MESSAGE("%s %p", "MyFrame::OnTabChange", this);
    // interactor->GetRenderWindow()->Initialize();
    // interactor->Initialize();
    // interactor->GetRenderWindow()->WindowRemap();
}

/*****************************************************************************/

MyInteractor::MyInteractor(wxPanel* panel_): panel{panel_}
{
    this->RenderWindow = NULL;
    this->SetRenderWindow(vtkRenderWindow::New());
    // this->RenderWindow->Delete();
    DEBUG_MESSAGE("%s", "MyInteractor::MyInteractor()");
}

/*****************************************************************************/

MyInteractor::~MyInteractor()
{
	DEBUG_MESSAGE("%s", "MyInteractor::~MyInteractor");
}

/*****************************************************************************/

void MyInteractor::Initialize()
{
	DEBUG_MESSAGE("%s", "MyInteractor::Initialize()");
	Parent::Initialize();
    RenderWindow->SetWindowId(utils::NativeHandle(panel));
	RenderWindow->SetParentId(utils::NativeHandle(panel->GetParent()));
	RenderWindow->SetDisplayId(RenderWindow->GetGenericDisplayId());

	int *size = RenderWindow->GetSize();
	DEBUG_MESSAGE("MyInteractor::Initialize(), sz = %u x %u", size[0], size[1]);

	Size[0] = size[0];
	Size[1] = size[1];
}

/*****************************************************************************/

void MyInteractor::UpdateSize(int w, int h)
{
	if (w != Size[0] || h != Size[1])
	{
		DEBUG_MESSAGE("MyInteractor::UpdateSize(), sz = %u x %u", w, h);
		Size[0] = w;
		Size[1] = h;
		RenderWindow->SetSize(w, h);
		panel->Refresh();
	}
}

/*****************************************************************************/

wxIMPLEMENT_APP(MyApp);

basiliscos avatar May 16 '25 08:05 basiliscos