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); } }