From f87040b309cc3ea6d18598e122f62b91733cbdbb Mon Sep 17 00:00:00 2001 From: KoSHeroff Date: Sun, 26 Jul 2026 21:03:16 +0500 Subject: [PATCH] fix: render the web page crisp on HiDPI, independent of GUI Scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Size the browser view to the window's physical framebuffer pixels (so the OSR texture draws 1:1 → sharp) and apply CEF page zoom equal to the OS content scale, so the page's CSS viewport stays logical and does not track Minecraft's GUI Scale. Uses only the public setZoomLevel API — no device_scale_factor and no reflection. Bump to 1.6.2. --- CHANGELOG.md | 5 ++ gradle.properties | 2 +- src/main/java/land/webgui/WebHudOverlay.java | 42 +++++++++------- src/main/java/land/webgui/WebViewLayout.java | 53 ++++++++++++++++++++ src/main/java/land/webgui/WebViewScreen.java | 31 ++++++------ 5 files changed, 97 insertions(+), 36 deletions(-) create mode 100644 src/main/java/land/webgui/WebViewLayout.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6473051..2742202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.6.2 - 2026-07-26 + +### Fixed +- The web page now renders crisply on HiDPI/Retina displays and no longer looks mis-scaled or changes size with Minecraft's GUI Scale setting. + ## 1.6.1 - 2026-07-26 ### Fixed diff --git a/gradle.properties b/gradle.properties index 2c9a77c..6b9ec92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true dev.kikugie.stonecutter.hard_mode=true -mod_version=1.6.1 +mod_version=1.6.2 maven_group=land.webgui archives_base_name=webgui diff --git a/src/main/java/land/webgui/WebHudOverlay.java b/src/main/java/land/webgui/WebHudOverlay.java index 163af7d..0f7afab 100644 --- a/src/main/java/land/webgui/WebHudOverlay.java +++ b/src/main/java/land/webgui/WebHudOverlay.java @@ -371,6 +371,7 @@ static void onHudBrowserLoadFinished(MCEFBrowser browser) { } if (WebSession.mode() == WebSession.Mode.HUD_OVERLAY && browser == WebSession.browser()) { hudPageReady = true; + WebViewLayout.applyZoom(browser); } } @@ -478,26 +479,22 @@ public static boolean containsMouse(double mouseX, double mouseY, MinecraftClien //? if fabric { public static int toBrowserLocalX(double mouseX, MinecraftClient client) { + int sw = client.getWindow().getScaledWidth(); //? } else { - /*public static int toBrowserLocalX(double mouseX, Minecraft client) {*/ + /*public static int toBrowserLocalX(double mouseX, Minecraft client) { + int sw = client.getWindow().getGuiScaledWidth();*/ //? } - //? if fabric { - return (int) (mouseX * client.getWindow().getScaleFactor()); - //? } else { - /*return (int) (mouseX * client.getWindow().getGuiScale());*/ - //? } + return (int) Math.round(mouseX * browserViewSize(client)[0] / (double) Math.max(1, sw)); } //? if fabric { public static int toBrowserLocalY(double mouseY, MinecraftClient client) { + int sh = client.getWindow().getScaledHeight(); //? } else { - /*public static int toBrowserLocalY(double mouseY, Minecraft client) {*/ + /*public static int toBrowserLocalY(double mouseY, Minecraft client) { + int sh = client.getWindow().getGuiScaledHeight();*/ //? } - //? if fabric { - return (int) (mouseY * client.getWindow().getScaleFactor()); - //? } else { - /*return (int) (mouseY * client.getWindow().getGuiScale());*/ - //? } + return (int) Math.round(mouseY * browserViewSize(client)[1] / (double) Math.max(1, sh)); } //? if fabric { @@ -509,20 +506,27 @@ static void resizeBrowser(MinecraftClient client) { if (browser == null) { return; } - //? if fabric { - int pxW = client.getWindow().getWidth(); - int pxH = client.getWindow().getHeight(); - //? } else { - /*int pxW = client.getWindow().getWidth(); - int pxH = client.getWindow().getHeight();*/ - //? } + int[] size = browserViewSize(client); + int pxW = size[0]; + int pxH = size[1]; if (pxW != lastPixelW || pxH != lastPixelH) { browser.resize(pxW, pxH); + WebViewLayout.applyZoom(browser); lastPixelW = pxW; lastPixelH = pxH; } } + // Browser view size = physical framebuffer px; page zoom keeps the CSS viewport logical. + //? if fabric { + private static int[] browserViewSize(MinecraftClient client) { + //? } else { + /*private static int[] browserViewSize(Minecraft client) {*/ + //? } + var window = client.getWindow(); + return WebViewLayout.browserSize(window.getWidth(), window.getHeight()); + } + //? if fabric { private static void notifyMcefMissing(MinecraftClient client) { if (client.player != null) { diff --git a/src/main/java/land/webgui/WebViewLayout.java b/src/main/java/land/webgui/WebViewLayout.java new file mode 100644 index 0000000..3f42e31 --- /dev/null +++ b/src/main/java/land/webgui/WebViewLayout.java @@ -0,0 +1,53 @@ +package land.webgui; + +import com.cinemamod.mcef.MCEFBrowser; +import org.lwjgl.glfw.GLFW; + +/** + * Sizing/scale helpers for the embedded browser. The browser view is the physical framebuffer size + * (so its texture draws 1:1 → crisp on HiDPI), and CEF page zoom scales the content up by the OS + * content scale so the page's CSS viewport stays logical and independent of Minecraft's GUI Scale. + * Uses only the public {@link MCEFBrowser#setZoomLevel} API — no device_scale_factor, no reflection. + */ +final class WebViewLayout { + private WebViewLayout() {} + + /** OS content scale of the current window (e.g. 2 on a Retina display), or 1 if unknown. */ + static float contentScale() { + long handle = GLFW.glfwGetCurrentContext(); + if (handle == 0L) { + return 1f; + } + float[] sx = new float[1]; + float[] sy = new float[1]; + GLFW.glfwGetWindowContentScale(handle, sx, sy); + return sx[0] > 0f ? sx[0] : 1f; + } + + /** Browser view size = physical framebuffer pixels, so the OSR texture is drawn 1:1 (crisp). */ + static int[] browserSize(int framebufferWidth, int framebufferHeight) { + return new int[] {Math.max(1, framebufferWidth), Math.max(1, framebufferHeight)}; + } + + /** + * CEF zoom level that scales content by the content scale: {@code zoomFactor = 1.2^level}, so + * {@code level = log(scale) / log(1.2)}. Result: CSS viewport = framebuffer / scale (logical), + * rendered at framebuffer resolution. 0 when no scaling is needed. + */ + static double zoomLevel() { + float scale = contentScale(); + return scale > 1f ? Math.log(scale) / Math.log(1.2) : 0.0; + } + + /** Applies the content-scale zoom. CEF may reset zoom on navigation, so call after load too. */ + static void applyZoom(MCEFBrowser browser) { + if (browser == null) { + return; + } + try { + browser.setZoomLevel(zoomLevel()); + } catch (Throwable t) { + WebGUIMod.LOGGER.warn("[WebGUI] setZoomLevel failed: {}", t.toString()); + } + } +} diff --git a/src/main/java/land/webgui/WebViewScreen.java b/src/main/java/land/webgui/WebViewScreen.java index 5f5d885..dc3c1e5 100644 --- a/src/main/java/land/webgui/WebViewScreen.java +++ b/src/main/java/land/webgui/WebViewScreen.java @@ -105,32 +105,30 @@ private boolean isInBrowserBounds(double x, double y) { return x >= 0 && y >= 0 && x < this.width && y < this.height; } - private int browserLocalMouseX(double x) { + // Browser view size in logical window points, independent of the GUI Scale setting. + private int[] browserViewSize() { //? if fabric { - return (int) (x * this.client.getWindow().getScaleFactor()); + var window = this.client.getWindow(); //? } else { - /*return (int) (x * this.minecraft.getWindow().getGuiScale());*/ + /*var window = this.minecraft.getWindow();*/ //? } + return WebViewLayout.browserSize(window.getWidth(), window.getHeight()); + } + + private int browserLocalMouseX(double x) { + return (int) Math.round(x * browserViewSize()[0] / (double) getBrowserWidth()); } private int browserLocalMouseY(double y) { - //? if fabric { - return (int) (y * this.client.getWindow().getScaleFactor()); - //? } else { - /*return (int) (y * this.minecraft.getWindow().getGuiScale());*/ - //? } + return (int) Math.round(y * browserViewSize()[1] / (double) getBrowserHeight()); } private void resizeBrowser() { - //? if fabric { - if (browser != null && this.client != null) { - browser.resize(this.client.getWindow().getWidth(), this.client.getWindow().getHeight()); + if (browser != null) { + int[] size = browserViewSize(); + browser.resize(size[0], size[1]); + WebViewLayout.applyZoom(browser); } - //? } else { - /*if (browser != null && this.minecraft != null) { - browser.resize(this.minecraft.getWindow().getWidth(), this.minecraft.getWindow().getHeight()); - }*/ - //? } } //? if >=1.21.5 { @@ -194,6 +192,7 @@ static void onGuiBrowserLoadFinished(MCEFBrowser browser) { } if (WebSession.mode() == WebSession.Mode.GUI_SCREEN && browser == WebSession.browser()) { guiPageReady = true; + WebViewLayout.applyZoom(browser); } }