Hello everyone.
I started to write an utility application that needs a menu and status bar, so I went with a native win32 main window (as I don't need portability) and I'm trying to use sf::RenderWindow as a renderer.
I also tried using SFML window, but recieving WM_COMMAND from it seemed a bit hacky.
I followed this tutorial:
https://www.sfml-dev.org/tutorials/1.6/graphics-win32.php, and succesfuly got myself a RenderWindow, it renders ok, but when I resize my window, strange things happen.
When I don't process WM_SIZE at all, the image is rendered correctly in the window (on the static control that's SFML's RenderWindow's "base"), but it stays the same when resizing the window, as expected.
Here's my onSize function:
LRESULT Application::onSize(UINT width, UINT height)
{
main_window.width = width;
main_window.height = height;
SetWindowPos(main_window.renderer_parent, nullptr, 0, 0, width, height, SWP_NOZORDER);
//renderer.setView(sf::View(sf::FloatRect(0.f, 0.f, width, height)));
return 0;
}
UINT width and height are taken from lParam of WM_SIZE message (using LOWORD and HIWORD macros) and are correct. SetWindowPos is used to resize the static control to dimensions of the client area, while renderer.setView sets the view for SFML RenderWindow.
Strange behavoiur goes like this:
When I just resize the static control (HWND renderer_parent), I get a drawing of the default view, as expected, but when changing height of the window, the drawn "picture" follows bottom border and hides behind the top border. When I drag the bottom border, drawing goes up/down accordingly and even goes up under the menu/title bar; whe I drag the top border, I get more/less space between it and the drawing, and I can hide the drawing behind the menu/title bar. Changing width works a s expected: the drawing sticks to the left border, I don't get anything more than drawn at the beggining when I stretch the window, and it hides behind the right border when I shrink the window.
When I uncomment the last line and set the view for the renderer, everything works just like above
and I get reverse scaling of the drawing - it shrinks when I stretch the window and vice versa, in both axes. So for example, when I stretch the window in x axis, dragging right border, the drawin get squished in x axis (y axis unaffected). It also doesn't draw anything more than the initial painting (with the default view) that gets transformed in that inverse way.
I tried to create the sf::RenderWindow using my main window's handle (renderer.create(main_window.handle)) and got the exact same results, except I don't get the gray static background when resizing.
I compiled a small code snippet from the SFML and VS tutorial to see if it behaves properly, and it does. I added sf::Event::Resized case to that code as well, and just setting the view of the window makes the green circle behave properly (not get stretched with the window). So SFML library seem to work as intended.
I tried to browse SFML sources to figure out what's going on under the hood, but couldn't find anything fancy that could break when combined with WinAPI code. WindowImplWin32 instance just pushes proper SFML event to the queue and calls my own window procedure (as its pointer is stored in a class member). In theory I should be able to set the view of the RenderWindow as I would do with pure SFML code and it should work just fine.
I found a possible culprit of the wierd behaviour of the y axis resizing in the RenderTarget.cpp file:
void RenderTarget::applyCurrentView()
{
// Set the viewport
IntRect viewport = getViewport(m_view);
int top = getSize().y - (viewport.top + viewport.height);
glCheck(glViewport(viewport.left, top, viewport.width, viewport.height));
// Set the projection matrix
glCheck(glMatrixMode(GL_PROJECTION));
glCheck(glLoadMatrixf(m_view.getTransform().getMatrix()));
// Go back to model-view mode
glCheck(glMatrixMode(GL_MODELVIEW));
m_cache.viewChanged = false;
}
Calculating "top" variable seems off here. It shouldn't matter with the default viewport (0,0,1,1), but why there's no "left" calculated as well?
My actual question is: Is there anything else that goes inside SFML that makes the code produce such odd results?
I may try compiling SFML without that suspicious line in applyCurrentView(), if I feel sane enough for that. This problem bothers me for more than a week now and got really into my head, but I feel like I'm making circles without any progress.
Also I thought a simple workaround could be to add sf::Event::Command triggered on WM_COMMAND and recompile SFML with that, so I can use native Win32 menu with SFML window. I really don't want to drop SFML as any alternative would require implementing lots of stuff "by hand".
My initialization function:
bool Application::init(HINSTANCE instance, int cmdShow)
{
instHandle = instance;
//prepare and register window class
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = Application::wndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = instance;
wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(ID_APP_ICON));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wcex.lpszMenuName = MAKEINTRESOURCEW(ID_MAIN_MENU);
wcex.lpszClassName = WNDCLASS_NAME;
wcex.hIconSm = LoadIcon(instance, MAKEINTRESOURCE(ID_APP_ICON_SMALL));
RegisterClassExW(&wcex);
//create the main window
main_window.handle = CreateWindow(WNDCLASS_NAME, CFG::APP_NAME, WS_OVERLAPPEDWINDOW,
40, 40, 800, 450,
nullptr, nullptr, instance, nullptr);
if (!main_window.handle) {
MessageBox(nullptr, L"Creation of the main window failed.", nullptr, MB_OK);
return false;
}
//cache size of the client area
{
RECT rc;
GetClientRect(main_window.handle, &rc);
main_window.width = rc.right - rc.left;
main_window.height = rc.bottom - rc.top;
}
//create a static control as a base for sfml renderer
main_window.renderer_parent = CreateWindow(L"STATIC", nullptr, WS_CHILD | WS_CLIPSIBLINGS,
0, 0, main_window.width, main_window.height,
main_window.handle, nullptr, instance, nullptr);
if (!main_window.renderer_parent) {
MessageBox(nullptr, L"Creation of the view parent failed.", nullptr, MB_OK);
return false;
}
renderer.create(main_window.renderer_parent);
//show and update the window
ShowWindow(main_window.handle, cmdShow);
UpdateWindow(main_window.handle);
//load accelerators
accelerators = LoadAccelerators(instance, MAKEINTRESOURCE(ID_ACCELERATORS));
//load resources
ResourceManager::get().loadFont(instance, MAKEINTRESOURCE(ID_APP_FONT));
return true;
}
My message loop:
int Application::run()
{
MSG msg;
msg.message = ~(WM_QUIT);
while (msg.message != WM_QUIT){
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateAccelerator(main_window.handle, accelerators, &msg);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
renderer.clear();
renderer.draw(data_bank);
renderer.display();
}
}
return (int)msg.wParam;
}
Application class just stores thinkgs like HINSTANCE/HACCEL/HWND stuff, proveides static window procedure function and non-static function for handling messages (onSize, onClose etc). Instance of that class is a singleton static object, and I get it like this:
Application& Application::get()
{
static Application app_instance;
return app_instance;
}
So in my window proc I can do this:
LRESULT Application::wndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT ret = 0;
switch (message) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_SIZE:
ret = Application::get().onSize(LOWORD(lParam), HIWORD(lParam));
break;
case WM_CLOSE:
ret = Application::get().onClose();
break;
case WM_GETMINMAXINFO:
ret = Application::get().onMinmaxinfo((MINMAXINFO*)lParam);
break;
case WM_COMMAND:
ret = Application::get().onCommand(wParam, lParam);
break;
default:
ret = DefWindowProc(window, message, wParam, lParam);
}
return ret;
}
Any hint I could research would be very appreciated.
Stay safe!