mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-15 10:54:36 +00:00
Qt: Get window sizes from OS instead of scaling Qt size
Qt's device independent sizes are a lossy transformation. This stops our sizes from being potentially off by one with the "real" window size. Too bad it can't be done properly on Linux, see comment. Don't care.
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#ifndef __APPLE__
|
||||
#error This file should only be included when compiling for MacOS.
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
class Error;
|
||||
|
||||
@@ -40,4 +45,7 @@ std::optional<std::string> GetNonTranslocatedBundlePath();
|
||||
|
||||
/// Launch the given application once this one quits
|
||||
bool DelayedLaunch(std::string_view file, std::span<const std::string_view> args = {});
|
||||
|
||||
/// Returns the size of a NSView in pixels.
|
||||
std::optional<std::pair<int, int>> GetViewSizeInPixels(const void* view);
|
||||
} // namespace CocoaTools
|
||||
|
||||
@@ -145,6 +145,19 @@ bool CocoaTools::DelayedLaunch(std::string_view file, std::span<const std::strin
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<int, int>> CocoaTools::GetViewSizeInPixels(const void* view)
|
||||
{
|
||||
std::optional<std::pair<int, int>> ret;
|
||||
if (view)
|
||||
{
|
||||
NSView* nsview = (__bridge NSView*)view;
|
||||
const NSSize size = [nsview convertSizeToBacking:nsview.frame.size];
|
||||
ret = std::make_pair(static_cast<int>(size.width), static_cast<int>(size.height));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine)
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
|
||||
#include "threading.h"
|
||||
#include "assert.h"
|
||||
#include "cocoa_tools.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "cocoa_tools.h"
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -49,25 +49,12 @@ DisplayWidget::DisplayWidget(QWidget* parent) : QWidget(parent)
|
||||
|
||||
DisplayWidget::~DisplayWidget() = default;
|
||||
|
||||
int DisplayWidget::scaledWindowWidth() const
|
||||
{
|
||||
return std::max(
|
||||
static_cast<int>(std::ceil(static_cast<qreal>(width()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1);
|
||||
}
|
||||
|
||||
int DisplayWidget::scaledWindowHeight() const
|
||||
{
|
||||
return std::max(
|
||||
static_cast<int>(std::ceil(static_cast<qreal>(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> DisplayWidget::getWindowInfo(RenderAPI render_api, Error* error)
|
||||
{
|
||||
std::optional<WindowInfo> ret = QtUtils::GetWindowInfoForWidget(this, render_api, error);
|
||||
if (ret.has_value())
|
||||
{
|
||||
m_last_window_width = ret->surface_width;
|
||||
m_last_window_height = ret->surface_height;
|
||||
m_last_window_size = QSize(ret->surface_width, ret->surface_height);
|
||||
m_last_window_scale = ret->surface_scale;
|
||||
}
|
||||
return ret;
|
||||
@@ -363,18 +350,14 @@ bool DisplayWidget::event(QEvent* event)
|
||||
QWidget::event(event);
|
||||
|
||||
const qreal dpr = QtUtils::GetDevicePixelRatioForWidget(this);
|
||||
const u32 scaled_width =
|
||||
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
|
||||
const u32 scaled_height =
|
||||
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * dpr)), 1));
|
||||
const QSize size = QtUtils::GetPixelSizeForWidget(this, dpr);
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
if (m_last_window_width != scaled_width || m_last_window_height != scaled_height || m_last_window_scale != dpr)
|
||||
if (m_last_window_size != size || m_last_window_scale != dpr)
|
||||
{
|
||||
m_last_window_width = scaled_width;
|
||||
m_last_window_height = scaled_height;
|
||||
m_last_window_size = size;
|
||||
m_last_window_scale = dpr;
|
||||
emit windowResizedEvent(scaled_width, scaled_height, static_cast<float>(dpr));
|
||||
emit windowResizedEvent(size.width(), size.height(), static_cast<float>(dpr));
|
||||
}
|
||||
|
||||
updateCenterPos();
|
||||
@@ -607,21 +590,17 @@ bool AuxiliaryDisplayWidget::event(QEvent* event)
|
||||
QWidget::event(event);
|
||||
|
||||
const qreal dpr = QtUtils::GetDevicePixelRatioForWidget(this);
|
||||
const u32 scaled_width =
|
||||
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
|
||||
const u32 scaled_height =
|
||||
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * dpr)), 1));
|
||||
const QSize size = QtUtils::GetPixelSizeForWidget(this, dpr);
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
if (m_last_window_width != scaled_width || m_last_window_height != scaled_height || m_last_window_scale != dpr)
|
||||
if (m_last_window_size != size || m_last_window_scale != dpr)
|
||||
{
|
||||
m_last_window_width = scaled_width;
|
||||
m_last_window_height = scaled_height;
|
||||
m_last_window_size = size;
|
||||
m_last_window_scale = dpr;
|
||||
g_emu_thread->queueAuxiliaryRenderWindowInputEvent(
|
||||
m_userdata, Host::AuxiliaryRenderWindowEvent::Resized,
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = scaled_width},
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = scaled_height},
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = static_cast<u32>(size.width())},
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = static_cast<u32>(size.height())},
|
||||
Host::AuxiliaryRenderWindowEventParam{.float_param = static_cast<float>(dpr)});
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,6 @@ public:
|
||||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo(RenderAPI render_api, Error* error);
|
||||
|
||||
void updateRelativeMode(bool enabled);
|
||||
@@ -67,7 +64,7 @@ private:
|
||||
|
||||
std::vector<int> m_keys_pressed_with_modifiers;
|
||||
|
||||
u32 m_last_window_width = 0;
|
||||
QSize m_last_window_size;
|
||||
u32 m_last_window_height = 0;
|
||||
qreal m_last_window_scale = 1;
|
||||
|
||||
@@ -109,8 +106,7 @@ protected:
|
||||
|
||||
private:
|
||||
void* m_userdata = nullptr;
|
||||
u32 m_last_window_width = 0;
|
||||
u32 m_last_window_height = 0;
|
||||
QSize m_last_window_size;
|
||||
qreal m_last_window_scale = 1;
|
||||
bool m_destroying = false;
|
||||
};
|
||||
|
||||
@@ -458,7 +458,7 @@ void GameListModel::createPlaceholderImage(QImage& image, const QImage& placehol
|
||||
painter.setFont(font);
|
||||
|
||||
const int margin = static_cast<int>(30.0f * scale);
|
||||
const QSize unscaled_size = image.deviceIndependentSize().toSize();
|
||||
const QSize unscaled_size = QtUtils::GetDeviceIndependentSize(image.size(), image.devicePixelRatio());
|
||||
const QRect text_rc(margin, margin, static_cast<int>(static_cast<float>(unscaled_size.width() - margin - margin)),
|
||||
static_cast<int>(static_cast<float>(unscaled_size.height() - margin - margin)));
|
||||
|
||||
@@ -1383,7 +1383,7 @@ public:
|
||||
|
||||
const QRect& r = option.rect;
|
||||
const QPixmap pix = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
|
||||
const QSize pix_size = pix.deviceIndependentSize().toSize();
|
||||
const QSize pix_size = QtUtils::GetDeviceIndependentSize(pix.size(), pix.devicePixelRatio());
|
||||
|
||||
// draw pixmap at center of item
|
||||
const QPoint p((r.width() - pix_size.width()) / 2, (r.height() - pix_size.height()) / 2);
|
||||
@@ -1435,7 +1435,7 @@ public:
|
||||
const QPixmap& icon = (num_achievements > 0) ? (mastered ? m_model->getMasteredAchievementsPixmap() :
|
||||
m_model->getHasAchievementsPixmap()) :
|
||||
m_model->getNoAchievementsPixmap();
|
||||
const QSize icon_size = icon.deviceIndependentSize().toSize();
|
||||
const QSize icon_size = QtUtils::GetDeviceIndependentSize(icon.size(), icon.devicePixelRatio());
|
||||
painter->drawPixmap(r.topLeft() + QPoint(5, (r.height() - icon_size.height() + 2) / 2), icon);
|
||||
r.setLeft(r.left() + 12 + icon_size.width());
|
||||
|
||||
@@ -1515,7 +1515,7 @@ public:
|
||||
|
||||
const QRect& r = option.rect;
|
||||
const QPixmap pix = m_frame_pixmaps[m_current_frame];
|
||||
const QSize pix_size = pix.deviceIndependentSize().toSize();
|
||||
const QSize pix_size = QtUtils::GetDeviceIndependentSize(pix.size(), pix.devicePixelRatio());
|
||||
|
||||
// draw pixmap at center of item
|
||||
const QPoint p((r.width() - pix_size.width()) / 2, (r.height() - pix_size.height()) / 2);
|
||||
@@ -1633,7 +1633,7 @@ public:
|
||||
return;
|
||||
|
||||
const QPixmap& pix = m_model->getCoverForEntry(ge);
|
||||
const QSize pix_size = pix.deviceIndependentSize().toSize();
|
||||
const QSize pix_size = QtUtils::GetDeviceIndependentSize(pix.size(), pix.devicePixelRatio());
|
||||
const QSize cover_size = m_model->getCoverArtSize();
|
||||
|
||||
// draw pixmap at center of item
|
||||
@@ -1920,7 +1920,7 @@ void GameListWidget::updateBackground(bool reload_image)
|
||||
}
|
||||
|
||||
QImage scaled_image = m_background_image;
|
||||
resizeAndPadImage(&scaled_image, QSizeF(m_ui.stack->size() * devicePixelRatio()).toSize(), true, true);
|
||||
resizeAndPadImage(&scaled_image, QtUtils::ApplyDevicePixelRatioToSize(m_ui.stack->size(), m_model->getDevicePixelRatio()), true, true);
|
||||
|
||||
QPalette new_palette = qApp->palette(m_ui.stack);
|
||||
new_palette.setBrush(QPalette::Window, QPixmap::fromImage(scaled_image));
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#if defined(_WIN32)
|
||||
#include "common/windows_headers.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include "common/cocoa_tools.h"
|
||||
#include "common/thirdparty/usb_key_code_data.h"
|
||||
#else
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
@@ -434,6 +435,18 @@ void QtUtils::ResizeSharpBilinear(QImage& pm, int size, int base_size)
|
||||
ResizeSharpBilinearT(pm, size, base_size);
|
||||
}
|
||||
|
||||
QSize QtUtils::ApplyDevicePixelRatioToSize(const QSize& size, qreal device_pixel_ratio)
|
||||
{
|
||||
return QSize(static_cast<int>(std::ceil(static_cast<qreal>(size.width()) * device_pixel_ratio)),
|
||||
static_cast<int>(std::ceil(static_cast<qreal>(size.height()) * device_pixel_ratio)));
|
||||
}
|
||||
|
||||
QSize QtUtils::GetDeviceIndependentSize(const QSize& size, qreal device_pixel_ratio)
|
||||
{
|
||||
return QSize(std::max(static_cast<int>(std::ceil(static_cast<qreal>(size.width()) / device_pixel_ratio)), 1),
|
||||
std::max(static_cast<int>(std::ceil(static_cast<qreal>(size.height()) / device_pixel_ratio)), 1));
|
||||
}
|
||||
|
||||
qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget)
|
||||
{
|
||||
const QScreen* screen_for_ratio = widget->screen();
|
||||
@@ -443,6 +456,27 @@ qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget)
|
||||
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
|
||||
}
|
||||
|
||||
QSize QtUtils::GetPixelSizeForWidget(const QWidget* widget, qreal device_pixel_ratio)
|
||||
{
|
||||
// Why this nonsense? Qt's device independent sizes are integer, and fractional scaling is lossy.
|
||||
// We can't get back the "real" size of the window. So we have to platform natively query the actual client size.
|
||||
#if defined(_WIN32)
|
||||
if (RECT rc; GetClientRect(reinterpret_cast<HWND>(widget->winId()), &rc))
|
||||
return QSize(static_cast<int>(rc.right - rc.left), static_cast<int>(rc.bottom - rc.top));
|
||||
#elif defined(__APPLE__)
|
||||
if (std::optional<std::pair<int, int>> size =
|
||||
CocoaTools::GetViewSizeInPixels(reinterpret_cast<void*>(widget->winId())))
|
||||
{
|
||||
return QSize(size->first, size->second);
|
||||
}
|
||||
#endif
|
||||
|
||||
// On Linux, fuck you, enjoy round trip to the X server, and on Wayland you can't query it in the first place...
|
||||
// I ain't dealing with this crap OS. Enjoy your mismatched sizes and shit experience.
|
||||
return ApplyDevicePixelRatioToSize(widget->size(), (device_pixel_ratio < 1) ? GetDevicePixelRatioForWidget(widget) :
|
||||
device_pixel_ratio);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error)
|
||||
{
|
||||
WindowInfo wi;
|
||||
@@ -489,8 +523,9 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Rende
|
||||
#endif
|
||||
|
||||
const qreal dpr = GetDevicePixelRatioForWidget(widget);
|
||||
wi.surface_width = static_cast<u16>(static_cast<qreal>(widget->width()) * dpr);
|
||||
wi.surface_height = static_cast<u16>(static_cast<qreal>(widget->height()) * dpr);
|
||||
const QSize size = GetPixelSizeForWidget(widget, dpr);
|
||||
wi.surface_width = static_cast<u16>(size.width());
|
||||
wi.surface_height = static_cast<u16>(size.height());
|
||||
wi.surface_scale = static_cast<float>(dpr);
|
||||
|
||||
// Query refresh rate, we need it for sync.
|
||||
|
||||
@@ -144,9 +144,18 @@ QIcon GetIconForLanguage(std::string_view language_name);
|
||||
void ResizeSharpBilinear(QPixmap& pm, int size, int base_size);
|
||||
void ResizeSharpBilinear(QImage& pm, int size, int base_size);
|
||||
|
||||
/// Applies the device pixel ratio to the given size, giving the size in pixels.
|
||||
QSize ApplyDevicePixelRatioToSize(const QSize& size, qreal device_pixel_ratio);
|
||||
|
||||
/// Removes the device pixel ratio from the given size, giving the size in device-independent units.
|
||||
QSize GetDeviceIndependentSize(const QSize& size, qreal device_pixel_ratio);
|
||||
|
||||
/// Returns the pixel ratio/scaling factor for a widget.
|
||||
qreal GetDevicePixelRatioForWidget(const QWidget* widget);
|
||||
|
||||
/// Returns the pixel size (real geometry) for a widget. DPR can be passed to avoid re-querying when needed.
|
||||
QSize GetPixelSizeForWidget(const QWidget* widget, qreal device_pixel_ratio = -1);
|
||||
|
||||
/// Returns the common window info structure for a Qt widget.
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error = nullptr);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user