Skip to content

Commit

Permalink
feat: Implement cross-platform DPI scaling
Browse files Browse the repository at this point in the history
This replaces the Windows-only code with code that should work on
all the platforms. Tested on Linux/Wayland.

The “Scaling” option is now just treated like a normal UI zoom
control for users who want a larger UI. This means “Native” scaling
option is removed now since that does not mean anything.
  • Loading branch information
csnover committed Jul 17, 2024
1 parent b6f0ee9 commit 66bce41
Show file tree
Hide file tree
Showing 30 changed files with 420 additions and 195 deletions.
1 change: 1 addition & 0 deletions lib/libimhex/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ set(LIBIMHEX_SOURCES
source/providers/memory_provider.cpp
source/providers/undo/stack.cpp

source/ui/glfw_di.cpp
source/ui/imgui_imhex_extensions.cpp
source/ui/view.cpp
source/ui/popup.cpp
Expand Down
19 changes: 12 additions & 7 deletions lib/libimhex/include/hex/api/imhex_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,8 @@ namespace hex {
void setMainDockSpaceId(ImGuiID id);
void setMainWindowHandle(GLFWwindow *window);

void setGlobalScale(float scale);
void setNativeScale(float scale);
void setContentScale(float scale);
void setUserScale(float scale);

void setBorderlessWindowMode(bool enabled);
void setMultiWindowMode(bool enabled);
Expand Down Expand Up @@ -481,17 +481,22 @@ namespace hex {


/**
* @brief Gets the current global scale
* @return The current global scale
* @brief Gets the current UI content scale
* @return The current UI content scale
*/
float getGlobalScale();

/**
* @brief Gets the current native scale
* @return The current native scale
* @brief Gets the current window content scale
* @return The current window content scale
*/
float getNativeScale();
float getContentScale();

/**
* @brief Gets the current user scale
* @return The current user scale
*/
float getUserScale();

/**
* @brief Gets the current main window position
Expand Down
1 change: 0 additions & 1 deletion lib/libimhex/include/hex/helpers/utils_macos.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
void openWebpageMacos(const char *url);
bool isMacosSystemDarkModeEnabled();
bool isMacosFullScreenModeEnabled(GLFWwindow *window);
float getBackingScaleFactor();

void setupMacosWindowStyle(GLFWwindow *window, bool borderlessWindowMode);

Expand Down
35 changes: 35 additions & 0 deletions lib/libimhex/include/hex/ui/glfw_di.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

struct GLFWwindow;
struct GLFWmonitor;
typedef void (* GLFWcursorposfun)(GLFWwindow* window, double diX, double diY);
typedef void (* GLFWframebuffersizefun)(GLFWwindow* window, int width, int height);
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow* window, float xscale, float yscale);
typedef void (* GLFWwindowposfun)(GLFWwindow* window, int diX, int diY);
typedef void (* GLFWwindowsizefun)(GLFWwindow* window, int diWidth, int diHeight);

namespace hex {
/**
* Wrapper GLFW functions that use a device-independent coordinate system
* where the reference pixel is always 1/96in.
*/
namespace glfw {
GLFWwindow *CreateWindow(int diWidth, int diHeight, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
void DestroyWindow(GLFWwindow *window);
void *GetWindowUserPointer(GLFWwindow *window);
void *SetWindowUserPointer(GLFWwindow *window, void *pointer);
void GetMonitorPos(GLFWmonitor *monitor, int *diX, int *diY);
bool GetMonitorSize(GLFWmonitor *monitor, int *diWidth, int *diHeight);
void SetWindowMonitor(GLFWwindow* window, GLFWmonitor* monitor, int diXpos, int diYpos, int diWidth, int diHeight, int refreshRate);
void GetWindowPos(GLFWwindow *window, int *diX, int *diY);
void SetWindowPos(GLFWwindow *window, int diX, int diY);
void GetWindowSize(GLFWwindow *window, int *diWidth, int *diHeight);
void SetWindowSize(GLFWwindow *window, int diWidth, int diHeight);
void SetWindowSizeLimits(GLFWwindow *window, int diMinWidth, int diMinHeight, int diMaxWidth, int diMaxHeight);
GLFWcursorposfun SetCursorPosCallback(GLFWwindow *window, GLFWcursorposfun callback);
GLFWwindowposfun SetFramebufferSizeCallback(GLFWwindow *window, GLFWframebuffersizefun callback);
GLFWwindowcontentscalefun SetWindowContentScaleCallback(GLFWwindow *window, GLFWwindowcontentscalefun callback);
GLFWwindowposfun SetWindowPosCallback(GLFWwindow *window, GLFWwindowposfun callback);
GLFWwindowsizefun SetWindowSizeCallback(GLFWwindow *window, GLFWwindowsizefun callback);
}
}
22 changes: 12 additions & 10 deletions lib/libimhex/source/api/imhex_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -501,17 +501,16 @@ namespace hex {
}


static float s_globalScale = 1.0;
void setGlobalScale(float scale) {
s_globalScale = scale;
static float s_contentScale = 1.0;
void setContentScale(float scale) {
s_contentScale = scale;
}

static float s_nativeScale = 1.0;
void setNativeScale(float scale) {
s_nativeScale = scale;
static float s_userScale = 1.0;
void setUserScale(float scale) {
s_userScale = scale;
}


static bool s_borderlessWindowMode;
void setBorderlessWindowMode(bool enabled) {
s_borderlessWindowMode = enabled;
Expand Down Expand Up @@ -598,13 +597,16 @@ namespace hex {
}

float getGlobalScale() {
return impl::s_globalScale;
return getUserScale();
}

float getNativeScale() {
return impl::s_nativeScale;
float getContentScale() {
return impl::s_contentScale;
}

float getUserScale() {
return impl::s_userScale;
}

ImVec2 getMainWindowPosition() {
if ((ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != ImGuiConfigFlags_None)
Expand Down
4 changes: 0 additions & 4 deletions lib/libimhex/source/helpers/utils_macos.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ bool isMacosSystemDarkModeEnabled(void) {
}
}

float getBackingScaleFactor(void) {
return [[NSScreen mainScreen] backingScaleFactor];
}

void setupMacosWindowStyle(GLFWwindow *window, bool borderlessWindowMode) {
NSWindow* cocoaWindow = glfwGetCocoaWindow(window);

Expand Down
223 changes: 223 additions & 0 deletions lib/libimhex/source/ui/glfw_di.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#include <hex/ui/glfw_di.h>

#include <GLFW/glfw3.h>

#include <new>
#include <stdexcept>

namespace hex::glfw {
struct GlfwData {
void *userPointer = nullptr;
GLFWcursorposfun userCursorPosFn = nullptr;
GLFWframebuffersizefun userFramebufferSizeFn = nullptr;
GLFWwindowcontentscalefun userWindowContentScaleFn = nullptr;
GLFWwindowposfun userWindowPosFn = nullptr;
GLFWwindowsizefun userWindowSizeFn = nullptr;
float scaleX = 1.0F;
float scaleY = 1.0F;
};

static void RecalculateScale(GLFWwindow *window, GlfwData *data) {
int width, height, framebufferWidth, framebufferHeight;
float scaleX, scaleY;
glfwGetWindowSize(window, &width, &height);
glfwGetFramebufferSize(window, &framebufferWidth, &framebufferHeight);
glfwGetWindowContentScale(window, &scaleX, &scaleY);
data->scaleX = scaleX / (float(framebufferWidth) / width);
data->scaleY = scaleY / (float(framebufferHeight) / height);
}

static GlfwData* GetWindowData(GLFWwindow *window) {
if (!window)
return nullptr;

return static_cast<GlfwData *>(glfwGetWindowUserPointer(window));
}

template <typename T>
void FromGLFW(GLFWwindow *window, T *x, T *y) {
auto data = GetWindowData(window);
if (!data)
throw std::runtime_error("Missing window data; did you use glfwCreateWindow directly instead of hex::glfw::CreateWindow?");
if (x)
*x /= data->scaleX;
if (y)
*y /= data->scaleY;
}

template <typename T>
static void FromGLFW(GLFWmonitor *monitor, T *x, T *y) {
float xScale, yScale;
glfwGetMonitorContentScale(monitor, &xScale, &yScale);
if (x)
*x /= xScale;
if (y)
*y /= yScale;
}

template <typename T>
void ToGLFW(GLFWwindow *window, T *x, T *y) {
auto data = GetWindowData(window);
if (!data)
throw std::runtime_error("Missing window data; did you use glfwCreateWindow directly instead of hex::glfw::CreateWindow?");
if (x)
*x *= data->scaleX;
if (y)
*y *= data->scaleY;
}

template <typename T>
static void ToGLFW(GLFWmonitor *monitor, T *x, T *y) {
float xScale, yScale;
glfwGetMonitorContentScale(monitor, &xScale, &yScale);
if (x)
*x *= xScale;
if (y)
*y *= yScale;
}

GLFWwindow* CreateWindow(int diWidth, int diHeight, const char* title, GLFWmonitor* monitor, GLFWwindow* share) {
auto window = glfwCreateWindow(1, 1, title, monitor, share);
if (!window)
return nullptr;

auto *data = new (std::nothrow) GlfwData;
if (!data) {
glfwDestroyWindow(window);
return nullptr;
}

glfwSetWindowUserPointer(window, data);
RecalculateScale(window, data);
glfwSetWindowSize(window, diWidth * data->scaleX, diHeight * data->scaleY);
glfwSetFramebufferSizeCallback(window, [](GLFWwindow *window, int width, int height) {
auto data = GetWindowData(window);
RecalculateScale(window, data);
if (data->userFramebufferSizeFn)
(data->userFramebufferSizeFn)(window, width, height);
});
glfwSetWindowContentScaleCallback(window, [](GLFWwindow *window, float scaleX, float scaleY) {
auto data = GetWindowData(window);
RecalculateScale(window, data);
if (data->userWindowContentScaleFn)
(data->userWindowContentScaleFn)(window, scaleX, scaleY);
});

return window;
}

void DestroyWindow(GLFWwindow *window) {
if (!window)
return;

delete GetWindowData(window);
glfwDestroyWindow(window);
}

void *GetWindowUserPointer(GLFWwindow *window) {
if (!window)
return nullptr;

return GetWindowData(window)->userPointer;
}

void *SetWindowUserPointer(GLFWwindow *window, void *pointer) {
if (!window)
return nullptr;

auto data = GetWindowData(window);
auto prev = data->userPointer;
data->userPointer = pointer;
return prev;
}

bool GetMonitorSize(GLFWmonitor *monitor, int *diWidth, int *diHeight) {
auto mode = glfwGetVideoMode(monitor);
if (!mode)
return false;

if (diWidth)
*diWidth = mode->width;
if (diHeight)
*diHeight = mode->height;
FromGLFW(monitor, diWidth, diHeight);

return true;
}

void GetMonitorPos(GLFWmonitor *monitor, int *diX, int *diY) {
glfwGetMonitorPos(monitor, diX, diY);
FromGLFW(monitor, diX, diY);
}

void SetWindowMonitor(GLFWwindow* window, GLFWmonitor* monitor, int diXpos, int diYpos, int diWidth, int diHeight, int refreshRate) {
if (monitor)
ToGLFW(monitor, &diXpos, &diYpos);
else
ToGLFW(window, &diXpos, &diYpos);

ToGLFW(window, &diWidth, &diHeight);
glfwSetWindowMonitor(window, monitor, diXpos, diYpos, diWidth, diHeight, refreshRate);
}

void GetWindowPos(GLFWwindow *window, int *diX, int *diY) {
glfwGetWindowPos(window, diX, diY);
FromGLFW(window, diX, diY);
}

void SetWindowPos(GLFWwindow *window, int diX, int diY) {
ToGLFW(window, &diX, &diY);
glfwSetWindowPos(window, diX, diY);
}

void GetWindowSize(GLFWwindow *window, int *diWidth, int *diHeight) {
glfwGetWindowSize(window, diWidth, diHeight);
FromGLFW(window, diWidth, diHeight);
}

void SetWindowSize(GLFWwindow *window, int diWidth, int diHeight) {
ToGLFW(window, &diWidth, &diHeight);
glfwSetWindowSize(window, diWidth, diHeight);
}

void SetWindowSizeLimits(GLFWwindow *window, int diMinWidth, int diMinHeight, int diMaxWidth, int diMaxHeight) {
ToGLFW(window,
diMinWidth == GLFW_DONT_CARE ? nullptr : &diMinWidth,
diMinHeight == GLFW_DONT_CARE ? nullptr : &diMinHeight);
ToGLFW(window,
diMaxWidth == GLFW_DONT_CARE ? nullptr : &diMaxWidth,
diMaxHeight == GLFW_DONT_CARE ? nullptr : &diMaxHeight);
glfwSetWindowSizeLimits(window, diMinWidth, diMinHeight, diMaxWidth, diMaxHeight);
}

#define IMHEX_WINDOW_CALLBACK_SCALE(fnName, cbType, paramType) \
static void fnName##Wrapper(GLFWwindow *window, paramType x, paramType y) { \
FromGLFW(window, &x, &y); \
(GetWindowData(window)->user##fnName##Fn)(window, x, y); \
} \
\
cbType Set##fnName##Callback(GLFWwindow *window, cbType callback) { \
auto data = GetWindowData(window); \
auto previous = data->user##fnName##Fn; \
data->user##fnName##Fn = callback; \
glfwSet##fnName##Callback(window, callback ? fnName##Wrapper : nullptr);\
return previous; \
}

IMHEX_WINDOW_CALLBACK_SCALE(CursorPos, GLFWcursorposfun, double)
IMHEX_WINDOW_CALLBACK_SCALE(WindowPos, GLFWwindowposfun, int)
IMHEX_WINDOW_CALLBACK_SCALE(WindowSize, GLFWwindowsizefun, int)
#undef IMHEX_WINDOW_CALLBACK_SCALE

#define IMHEX_WINDOW_CALLBACK_RECALC(fnName, cbType) \
cbType Set##fnName##Callback(GLFWwindow *window, cbType callback) { \
auto data = GetWindowData(window); \
auto previous = data->user##fnName##Fn; \
data->user##fnName##Fn = callback; \
return previous; \
}

IMHEX_WINDOW_CALLBACK_RECALC(FramebufferSize, GLFWframebuffersizefun)
IMHEX_WINDOW_CALLBACK_RECALC(WindowContentScale, GLFWwindowcontentscalefun)
#undef IMHEX_WINDOW_CALLBACK_RECALC
}
Loading

0 comments on commit 66bce41

Please sign in to comment.