ui: fix frame maximized bounds with dpi scaling

Due to JDK-4737788, maximizing an undecorated frame incorrectly covers the
taskbar. Substance attempts to correct this by computing its own maximized
bounds, but it does not account for DPI scaling. Since Substance automatically
calls setMaximizedBounds when the frame is resized to do this we simply
override it and replace the bounds with our own which includes the scaling of
the display.

This also fixes the frame maximizing to the incorrect display on 11.0.8 due to
JDK-8231564, which changes the coordinates setMaximizedBounds() expects to be
relative to the primary display instead of the current display. Previously,
Substance was always setting the bounds x/y to 0 due to this.

Co-authored-by: Runemoro <runemoro1@gmail.com>
This commit is contained in:
Adam
2020-09-17 13:41:48 -04:00
parent 04ae212e8b
commit f7606c7aca

View File

@@ -25,11 +25,21 @@
package net.runelite.client.ui;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.util.Arrays;
import java.util.Comparator;
import javax.swing.JFrame;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.config.ExpandResizeType;
import net.runelite.client.util.OSType;
@Slf4j
public class ContainableFrame extends JFrame
{
public enum Mode
@@ -39,6 +49,23 @@ public class ContainableFrame extends JFrame
NEVER;
}
private static boolean jdk8231564;
static
{
try
{
String javaVersion = System.getProperty("java.version");
String[] s = javaVersion.split("\\.");
int major = Integer.parseInt(s[0]), minor = Integer.parseInt(s[1]), patch = Integer.parseInt(s[2]);
jdk8231564 = major > 11 || (major == 11 && minor > 0) || (major == 11 && minor == 0 && patch >= 8);
}
catch (Exception ex)
{
log.error("error checking java version", ex);
}
}
private static final int SCREEN_EDGE_CLOSE_DISTANCE = 40;
@Setter
@@ -186,6 +213,97 @@ public class ContainableFrame extends JFrame
expandedClientOppositeDirection = false;
}
/**
* Due to Java bug JDK-4737788, maximizing an undecorated frame causes it to cover the taskbar.
* As a workaround, Substance calls this method when the window is maximized to manually set the
* bounds, but its calculation ignores high-DPI scaling. We're overriding it to correctly calculate
* the maximized bounds.
*/
@Override
public void setMaximizedBounds(Rectangle bounds)
{
if (OSType.getOSType() == OSType.MacOS)
{
// OSX seems to correctly handle DPI scaling already
super.setMaximizedBounds(bounds);
}
else
{
super.setMaximizedBounds(getWindowAreaBounds());
}
}
/**
* Finds the {@link GraphicsConfiguration} of the display the window is currently on. If it's on more than
* one screen, returns the one it's most on (largest area of intersection)
*/
private GraphicsConfiguration getCurrentDisplayConfiguration()
{
return Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
.map(GraphicsDevice::getDefaultConfiguration)
.max(Comparator.comparing(config ->
{
Rectangle intersection = config.getBounds().intersection(getBounds());
return intersection.width * intersection.height;
}))
.orElseGet(this::getGraphicsConfiguration);
}
/**
* Calculates the bounds of the window area of the screen.
* <p>
* The bounds returned by {@link GraphicsEnvironment#getMaximumWindowBounds} are incorrectly calculated on
* high-DPI screens.
*/
private Rectangle getWindowAreaBounds()
{
log.trace("Current bounds: {}", getBounds());
for (GraphicsDevice device : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
{
log.trace("Device: {} bounds {}", device, device.getDefaultConfiguration().getBounds());
}
GraphicsConfiguration config = getCurrentDisplayConfiguration();
// get screen bounds
Rectangle bounds = config.getBounds();
log.trace("Chosen device: {} bounds {}", config, bounds);
// transform bounds to dpi-independent coordinates
if (!jdk8231564)
{
// JDK-8231564 fixed setMaximizedBounds to scale the bounds, so this must only be done on <11.0.8
bounds = config.getDefaultTransform().createTransformedShape(bounds).getBounds();
log.trace("Transformed bounds {}", bounds);
}
// subtract insets (taskbar, etc.)
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(config);
if (!jdk8231564)
{
// Prior to JDK-8231564, WFramePeer expects the bounds to be relative to the current monitor instead of the
// primary display.
bounds.x = bounds.y = 0;
}
else
{
// The insets from getScreenInsets are not scaled, we must convert them to DPI scaled pixels on 11.0.8 due
// to JDK-8231564 which expects the bounds to be in DPI-aware pixels.
double scaleX = config.getDefaultTransform().getScaleX();
double scaleY = config.getDefaultTransform().getScaleY();
insets.top /= scaleY;
insets.bottom /= scaleY;
insets.left /= scaleX;
insets.right /= scaleX;
}
bounds.x += insets.left;
bounds.y += insets.top;
bounds.height -= (insets.bottom + insets.top);
bounds.width -= (insets.right + insets.left);
log.trace("Final bounds: {}", bounds);
return bounds;
}
/**
* Force minimum size of frame to be it's layout manager's minimum size
*/