Skip to content

Commit

Permalink
Fix graphics flickering/stretching on Windows (calref#367)
Browse files Browse the repository at this point in the history
* DRY, standardized window top offset
* handle_splash_events() handle multiple events per frame
* accurate windows menubar height for multiple rows
* Windows filter a resize event triggered by the menubar
* windows expand small window to fit menubar
* splash screens draw in view rect, not window rect
  • Loading branch information
NQNStudios committed Jul 3, 2024
1 parent d5a198f commit 68de534
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 76 deletions.
30 changes: 9 additions & 21 deletions src/game/boe.graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,9 @@ void adjust_window_mode() {
// 0 - center 1- ul 2 - ur 3 - dl 4 - dr 5 - small win
int mode = get_int_pref("DisplayMode");
if(mode == 5) {
// Increase window height to make room for the menubar
// Increase window height to make room for the menubar on Linux
int winHeight = height;
#ifndef _WIN32
// Not on Windows, for some reason
winHeight += getMenubarHeight();
#endif
winHeight += os_specific_y_offset();

mainPtr.create(sf::VideoMode(width, winHeight, 32), "Blades of Exile", sf::Style::Titlebar | sf::Style::Close, winSettings);

Expand Down Expand Up @@ -179,6 +176,7 @@ void adjust_window_mode() {
init_fileio();
#endif
init_menubar();
adjust_window_for_menubar(mode, width, height);
showMenuBar();
}

Expand All @@ -190,16 +188,6 @@ sf::FloatRect compute_viewport(const sf::RenderWindow& mainPtr, int mode, float
// Dimensions of the OS window.
rectangle windRect { mainPtr };

// This is an additional offset between the "logical" top of the window an the UI.
// On Windows and Mac no offset is needed because the menubar is not a part of the mainPtr, but
// on Linux it is.
int os_specific_y_offset =
#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MAC)
0;
#else
getMenubarHeight();
#endif

// Width and height: how large the viewport is. They seem to be calculated
// in terms of *source* dimensions, with values above 1 resulting in an upscale.
viewport.width = ui_scale * width / windRect.width();
Expand All @@ -215,32 +203,32 @@ sf::FloatRect compute_viewport(const sf::RenderWindow& mainPtr, int mode, float
if(mode == 0) {
// Fullscreen centered
viewport.left = float((windRect.width() - width) / 2) / windRect.width();
viewport.top = float((windRect.height() - height - os_specific_y_offset) / 2)/ windRect.height();
viewport.top = float((windRect.height() - height - os_specific_y_offset()) / 2)/ windRect.height();
} else if(mode == 1) {
// Fullscreen top left
viewport.left = float(extra_horizontal_buffer) / windRect.width();
viewport.top = float(os_specific_y_offset) / windRect.height();
viewport.top = float(os_specific_y_offset()) / windRect.height();
} else if(mode == 2) {
// Fullscreen top right
viewport.left = float(windRect.right - width - extra_horizontal_buffer) / windRect.width();
viewport.top = float(os_specific_y_offset) / windRect.height();
viewport.top = float(os_specific_y_offset()) / windRect.height();
} else if(mode == 3) {
// Fullscreen bottom left
viewport.left = float(extra_horizontal_buffer) / windRect.width();
// DIRTY HACK: windRect in fullscreen modes gives us the entire display, but at the same time
// there could be a windows taskbar / mac os dock / xfce taskbar / etc that consumes a part
// of that display, and that we do not know size of. So we need to account for that somehow,
// so we add 28 more pixels (this was the amount in the previous version of this code).
viewport.top = float(windRect.bottom - height - os_specific_y_offset - 28) / windRect.height();
viewport.top = float(windRect.bottom - height - os_specific_y_offset() - 28) / windRect.height();
} else if(mode == 4) {
// Fullscreen bottom right
viewport.left = float(windRect.right - width - extra_horizontal_buffer) / windRect.width();
// DIRTY HACK: same as for mode 3
viewport.top = float(windRect.bottom - height - os_specific_y_offset - 28) / windRect.height();
viewport.top = float(windRect.bottom - height - os_specific_y_offset() - 28) / windRect.height();
} else if(mode == 5) {
// Small windowed
viewport.left = 0;
viewport.top = float(os_specific_y_offset) / windRect.height();
viewport.top = float(os_specific_y_offset()) / windRect.height();
}

return viewport;
Expand Down
1 change: 1 addition & 0 deletions src/game/boe.graphics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ void draw_startup_stats();
void draw_trim(short q,short r,short which_trim,ter_num_t ground_ter);
sf::FloatRect compute_viewport(const sf::RenderWindow&, int mode, float ui_scale, float width, float height);


void draw_startup_anim(bool advance);


Expand Down
19 changes: 12 additions & 7 deletions src/game/boe.menus.win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,12 @@ void init_menubar() {
if(winHandle == NULL) return;
if(menuHandle == NULL)
menuHandle = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_BLADESOFEXILE));
SetMenu(winHandle, menuHandle);
// Now we have to do a little hack to handle menu messages.
// We replace SFML's window procedure with our own, which checks for menu events and then forwards to SFML's procedure.
mainProc = SetWindowLongPtr(winHandle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&menuProc));
SetMenu(winHandle, menuHandle);

mainPtr.setActive();
// Fix the window's viewport so that everything is drawn correctly
sf::Vector2u sz = mainPtr.getSize();
double menubarHeight = getMenubarHeight();
double usableHeight = sz.y - menubarHeight;
sf::View view(sf::FloatRect(0, 0, sz.x, usableHeight));
mainPtr.setView(view);

// And now initialize the mapping from Windows menu commands to eMenu constants
static bool inited = false;
Expand Down Expand Up @@ -291,6 +286,16 @@ void showMenuBar() {

LRESULT CALLBACK menuProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
MSG msg = {handle, message, wParam, lParam};

// Adding the menubar to the window for the first time triggers an unwanted
// resizing of the window, which we skip processing because it skews our
// viewport:
static bool menubarTriggeredResize = false;
if(message == WM_SIZE && !menubarTriggeredResize) {
menubarTriggeredResize = true;
return true;
}

if(HIWORD(wParam) != 1 || message != WM_COMMAND) {
if(TranslateAccelerator(handle, accel.handle, &msg))
return 0;
Expand Down
20 changes: 13 additions & 7 deletions src/game/boe.startup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,23 @@ bool handle_startup_press(location the_point) {

void handle_splash_events() {
sf::Event event;
if(!mainPtr.pollEvent(event)) return;
if(event.type == sf::Event::GainedFocus || event.type == sf::Event::MouseMoved)
set_cursor(sword_curs);
while(mainPtr.pollEvent(event)) {
if(event.type == sf::Event::GainedFocus || event.type == sf::Event::MouseMoved)
set_cursor(sword_curs);
}
}

rectangle view_rect() {
sf::Vector2f size = mainPtr.getView().getSize();
return rectangle(0, 0, size.y, size.x);
}

void show_logo() {
rectangle whole_window;
rectangle whole_window = view_rect();

if(get_int_pref("DisplayMode") != 5)
hideMenuBar();

whole_window = rectangle(mainPtr);
double ui_scale = get_float_pref("UIScale", 1.0);
if(ui_scale < 1) ui_scale = 1;
rectangle logo_from = {0, 0, int(ui_scale *350), int(ui_scale * 350)};
Expand All @@ -141,11 +146,12 @@ void show_logo() {
}

void plop_fancy_startup() {
rectangle whole_window = view_rect();

float ui_scale = get_float_pref("UIScale", 1.0);
if (ui_scale<1) ui_scale=1;
rectangle whole_window,from_rect;
rectangle from_rect;
rectangle intro_from = {0, 0, int(ui_scale * 480), int(ui_scale * 640)};
whole_window = rectangle(mainPtr);
sf::Time delay = time_in_ticks(220);
intro_from.offset((whole_window.right - intro_from.right) / 2,(whole_window.bottom - intro_from.bottom) / 2);
sf::Texture& pict_to_draw = *ResMgr::graphics.get("startsplash", true);
Expand Down
16 changes: 3 additions & 13 deletions src/pcedit/pc.main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,9 @@ int main(int argc, char* argv[]) {
sf::FloatRect compute_viewport(const sf::RenderWindow& mainPtr, float ui_scale) {

// See compute_viewport() in boe.graphics.cpp
int const os_specific_y_offset =
#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MAC)
0;
#else
getMenubarHeight();
#endif

sf::FloatRect viewport;

viewport.top = float(os_specific_y_offset) / mainPtr.getSize().y;
viewport.top = float(os_specific_y_offset()) / mainPtr.getSize().y;
viewport.left = 0;
viewport.width = ui_scale;
viewport.height = ui_scale;
Expand All @@ -164,11 +157,7 @@ void adjust_window (sf::RenderWindow& mainPtr, sf::View& mainView) {
double ui_scale = get_float_pref("UIScale", 1.0);

int const width = ui_scale * 590;
int const height = ui_scale * 440
#ifndef SFML_SYSTEM_WINDOWS
+ getMenubarHeight()
#endif
;
int const height = ui_scale * 440 + os_specific_y_offset();

mainPtr.create(sf::VideoMode(width, height), "Blades of Exile Character Editor", sf::Style::Titlebar | sf::Style::Close);
sf::VideoMode desktop = sf::VideoMode::getDesktopMode();
Expand All @@ -191,6 +180,7 @@ void adjust_window (sf::RenderWindow& mainPtr, sf::View& mainView) {
#endif

init_menubar();
adjust_window_for_menubar(5, width, height);
}

void handle_events() {
Expand Down
18 changes: 11 additions & 7 deletions src/pcedit/pc.menus.win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,10 @@ void init_menubar() {
if(winHandle == NULL) return;
if(menuHandle == NULL)
menuHandle = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDR_MENU1));
SetMenu(winHandle, menuHandle);
// Now we have to do a little hack to handle menu messages.
// We replace SFML's window procedure with our own, which checks for menu events and then forwards to SFML's procedure.
mainProc = SetWindowLongPtr(winHandle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&menuProc));
// Fix the window's viewport so that everything is drawn correctly
sf::Vector2u sz = mainPtr.getSize();
double menubarHeight = getMenubarHeight();
double usableHeight = sz.y - menubarHeight;
sf::View view(sf::FloatRect(0, 0, sz.x, usableHeight));
mainPtr.setView(view);
SetMenu(winHandle, menuHandle);

// And now initialize the mapping from Windows menu commands to eMenu constants
static bool inited = false;
Expand Down Expand Up @@ -130,6 +124,16 @@ void menu_activate() {

LRESULT CALLBACK menuProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
MSG msg = {handle, message, wParam, lParam};

// Adding the menubar to the window for the first time triggers an unwanted
// resizing of the window, which we skip processing because it skews our
// viewport:
static bool menubarTriggeredResize = false;
if(message == WM_SIZE && !menubarTriggeredResize) {
menubarTriggeredResize = true;
return true;
}

if(HIWORD(wParam) != 1 || message != WM_COMMAND) {
if(TranslateAccelerator(handle, accel.handle, &msg))
return 0;
Expand Down
16 changes: 3 additions & 13 deletions src/scenedit/scen.main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,9 @@ static void init_scrollbars() {
sf::FloatRect compute_viewport(const sf::RenderWindow & mainPtr, float ui_scale) {

// See compute_viewport() in boe.graphics.cpp
const int os_specific_y_offset =
#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MAC)
0;
#else
getMenubarHeight();
#endif

sf::FloatRect viewport;

viewport.top = float(os_specific_y_offset) / mainPtr.getSize().y;
viewport.top = float(os_specific_y_offset()) / mainPtr.getSize().y;
viewport.left = 0;
viewport.width = ui_scale;
viewport.height = ui_scale;
Expand All @@ -169,11 +162,7 @@ void adjust_windows (sf::RenderWindow & mainPtr, sf::View & mainView) {
double ui_scale = get_float_pref("UIScale", 1.0);

const int width = ui_scale * 584;
const int height = ui_scale * 420
#ifndef SFML_SYSTEM_WINDOWS
+ getMenubarHeight()
#endif
;
const int height = ui_scale * 420 + os_specific_y_offset();

mainPtr.create(sf::VideoMode(width, height), "Blades of Exile Scenario Editor", sf::Style::Titlebar | sf::Style::Close);
sf::VideoMode desktop = sf::VideoMode::getDesktopMode();
Expand All @@ -195,6 +184,7 @@ void adjust_windows (sf::RenderWindow & mainPtr, sf::View & mainView) {
mainPtr.setIcon(icon->getSize().x, icon->getSize().y, icon->copyToImage().getPixelsPtr());
#endif
init_menubar();
adjust_window_for_menubar(5, width, height);
}

void process_args(int argc, char* argv[]) {
Expand Down
18 changes: 11 additions & 7 deletions src/scenedit/scen.menus.win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,10 @@ void init_menubar() {
if(winHandle == NULL) return;
if(menuHandle == NULL)
menuHandle = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDR_MENU1));
SetMenu(winHandle, menuHandle);
// Now we have to do a little hack to handle menu messages.
// We replace SFML's window procedure with our own, which checks for menu events and then forwards to SFML's procedure.
mainProc = SetWindowLongPtr(winHandle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&menuProc));
// Fix the window's viewport so that everything is drawn correctly
sf::Vector2u sz = mainPtr.getSize();
double menubarHeight = getMenubarHeight();
double usableHeight = sz.y - menubarHeight;
sf::View view(sf::FloatRect(0, 0, sz.x, usableHeight));
mainPtr.setView(view);
SetMenu(winHandle, menuHandle);

// And now initialize the mapping from Windows menu commands to eMenu constants
static bool inited = false;
Expand Down Expand Up @@ -218,6 +212,16 @@ void update_edit_menu() {

LRESULT CALLBACK menuProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
MSG msg = {handle, message, wParam, lParam};

// Adding the menubar to the window for the first time triggers an unwanted
// resizing of the window, which we skip processing because it skews our
// viewport:
static bool menubarTriggeredResize = false;
if(message == WM_SIZE && !menubarTriggeredResize) {
menubarTriggeredResize = true;
return true;
}

if(HIWORD(wParam) != 1 || message != WM_COMMAND) {
if(TranslateAccelerator(handle, accel.handle, &msg))
return 0;
Expand Down
14 changes: 14 additions & 0 deletions src/tools/winutil.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ void beep();
// Calculates how much of the window is occupied by the menubar
int getMenubarHeight();

// This is an additional offset between the "logical" top of the window an the UI.
// On Windows and Mac no offset is needed because the menubar is not a part of the mainPtr, but
// on Linux it is.
inline int os_specific_y_offset() {
return
#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MAC)
0;
#else
getMenubarHeight();
#endif
}

void adjust_window_for_menubar(int mode, unsigned int width, unsigned int height);

class ModalSession {
void* session;
sf::Window* parent;
Expand Down
3 changes: 3 additions & 0 deletions src/tools/winutil.linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,6 @@ int getMenubarHeight() {
// return MENUBAR_HEIGHT;
return 20;
}

void adjust_window_for_menubar(int mode, unsigned int width, unsigned int height) {
}
3 changes: 3 additions & 0 deletions src/tools/winutil.mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ int getMenubarHeight() {
return 0;
}

void adjust_window_for_menubar(int mode, unsigned int width, unsigned int height) {
}

NSOpenPanel* dlg_get_scen;
NSOpenPanel* dlg_get_game;
NSOpenPanel* dlg_get_rsrc;
Expand Down
27 changes: 26 additions & 1 deletion src/tools/winutil.win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -444,5 +444,30 @@ ModalSession::~ModalSession() {
}

int getMenubarHeight() {
return GetSystemMetrics(SM_CYMENU);
MENUBARINFO info;
info.cbSize = sizeof(MENUBARINFO);
if(GetMenuBarInfo(mainPtr.getSystemHandle(), OBJID_MENU, 0, &info)) {
return info.rcBar.bottom - info.rcBar.top;
} else {
return GetSystemMetrics(SM_CYMENU);
}
}

void adjust_window_for_menubar(int mode, unsigned int width, unsigned int height) {
// On Windows, the menubar DOES take up space in the window,
// but this is not handled with os_specific_y_offset() because
// y = 0 still refers to the bottom of the menubar on Windows.

// getMenuBarHeight() has to be called again AFTER init_menubar() for this,
// because different combinations of OS and BOE scaling options can
// result in a menubar with more than one row, which can only be measured
// after it is placed in the window
if(mode != 5) {
sf::VideoMode desktop = sf::VideoMode::getDesktopMode();
width = desktop.width;
height = desktop.height;
}

height += getMenubarHeight();
mainPtr.setSize({ width, height });
}

0 comments on commit 68de534

Please sign in to comment.