Skip to content

Commit 9c84c77

Browse files
fthevenetkevinrushforth
authored andcommitted
8211294: ScrollPane content is blurry with 125% scaling
Reviewed-by: kcr, arapte
1 parent c197b62 commit 9c84c77

File tree

3 files changed

+149
-25
lines changed

3 files changed

+149
-25
lines changed

modules/javafx.graphics/src/main/java/javafx/scene/layout/Region.java

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -986,20 +986,23 @@ void fireValueChanged() {
986986
private double snappedBottomInset = 0;
987987
private double snappedLeftInset = 0;
988988

989+
/**
990+
* Cached snapScale values, used to determine if snapped cached insets values
991+
* should be invalidated because screen scale has changed.
992+
*/
993+
private double lastUsedSnapScaleY = 0;
994+
private double lastUsedSnapScaleX = 0;
995+
989996
/** Called to update the cached snapped insets */
990997
private void updateSnappedInsets() {
998+
lastUsedSnapScaleX = getSnapScaleX();
999+
lastUsedSnapScaleY = getSnapScaleY();
9911000
final Insets insets = getInsets();
992-
if (_snapToPixel) {
993-
snappedTopInset = Math.ceil(insets.getTop());
994-
snappedRightInset = Math.ceil(insets.getRight());
995-
snappedBottomInset = Math.ceil(insets.getBottom());
996-
snappedLeftInset = Math.ceil(insets.getLeft());
997-
} else {
998-
snappedTopInset = insets.getTop();
999-
snappedRightInset = insets.getRight();
1000-
snappedBottomInset = insets.getBottom();
1001-
snappedLeftInset = insets.getLeft();
1002-
}
1001+
final boolean snap = isSnapToPixel();
1002+
snappedTopInset = snapSpaceY(insets.getTop(), snap);
1003+
snappedRightInset = snapSpaceX(insets.getRight(), snap);
1004+
snappedBottomInset = snapSpaceY(insets.getBottom(), snap);
1005+
snappedLeftInset = snapSpaceX(insets.getLeft(), snap);
10031006
}
10041007

10051008
/**
@@ -1861,6 +1864,11 @@ public double snapPositionY(double value) {
18611864
* @return Rounded up insets top
18621865
*/
18631866
public final double snappedTopInset() {
1867+
// invalidate the cached values for snapped inset dimensions
1868+
// if the screen scale changed since they were last computed.
1869+
if (lastUsedSnapScaleY != getSnapScaleY()) {
1870+
updateSnappedInsets();
1871+
}
18641872
return snappedTopInset;
18651873
}
18661874

@@ -1872,6 +1880,11 @@ public final double snappedTopInset() {
18721880
* @return Rounded up insets bottom
18731881
*/
18741882
public final double snappedBottomInset() {
1883+
// invalidate the cached values for snapped inset dimensions
1884+
// if the screen scale changed since they were last computed.
1885+
if (lastUsedSnapScaleY != getSnapScaleY()) {
1886+
updateSnappedInsets();
1887+
}
18751888
return snappedBottomInset;
18761889
}
18771890

@@ -1883,6 +1896,11 @@ public final double snappedBottomInset() {
18831896
* @return Rounded up insets left
18841897
*/
18851898
public final double snappedLeftInset() {
1899+
// invalidate the cached values for snapped inset dimensions
1900+
// if the screen scale changed since they were last computed.
1901+
if (lastUsedSnapScaleX != getSnapScaleX()) {
1902+
updateSnappedInsets();
1903+
}
18861904
return snappedLeftInset;
18871905
}
18881906

@@ -1894,6 +1912,11 @@ public final double snappedLeftInset() {
18941912
* @return Rounded up insets right
18951913
*/
18961914
public final double snappedRightInset() {
1915+
// invalidate the cached values for snapped inset dimensions
1916+
// if the screen scale changed since they were last computed.
1917+
if (lastUsedSnapScaleX != getSnapScaleX()) {
1918+
updateSnappedInsets();
1919+
}
18971920
return snappedRightInset;
18981921
}
18991922

modules/javafx.graphics/src/main/java/javafx/scene/text/TextFlow.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -612,10 +612,6 @@ public StyleableProperty<Number> getStyleableProperty(TextFlow node) {
612612
}
613613

614614
/* The methods in this section are copied from Region due to package visibility restriction */
615-
private static double snapSpace(double value, boolean snapToPixel) {
616-
return snapToPixel ? Math.round(value) : value;
617-
}
618-
619615
static double boundedSize(double min, double pref, double max) {
620616
double a = pref >= min ? pref : min;
621617
double b = min >= max ? min : max;
@@ -627,11 +623,10 @@ static double boundedSize(double min, double pref, double max) {
627623
}
628624

629625
double computeChildPrefAreaWidth(Node child, Insets margin, double height) {
630-
final boolean snap = isSnapToPixel();
631-
double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
632-
double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
633-
double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
634-
double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
626+
double top = margin != null? snapSpaceY(margin.getTop()) : 0;
627+
double bottom = margin != null? snapSpaceY(margin.getBottom()) : 0;
628+
double left = margin != null? snapSpaceX(margin.getLeft()) : 0;
629+
double right = margin != null? snapSpaceX(margin.getRight()) : 0;
635630
double alt = -1;
636631
if (child.getContentBias() == Orientation.VERTICAL) { // width depends on height
637632
alt = snapSizeY(boundedSize(
@@ -646,11 +641,10 @@ static double boundedSize(double min, double pref, double max) {
646641
}
647642

648643
double computeChildPrefAreaHeight(Node child, Insets margin, double width) {
649-
final boolean snap = isSnapToPixel();
650-
double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
651-
double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
652-
double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
653-
double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
644+
double top = margin != null? snapSpaceY(margin.getTop()) : 0;
645+
double bottom = margin != null? snapSpaceY(margin.getBottom()) : 0;
646+
double left = margin != null? snapSpaceX(margin.getLeft()) : 0;
647+
double right = margin != null? snapSpaceX(margin.getRight()) : 0;
654648
double alt = -1;
655649
if (child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
656650
alt = snapSizeX(boundedSize(
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package test.javafx.scene;
26+
27+
import com.sun.javafx.PlatformUtil;
28+
import javafx.application.Application;
29+
import javafx.application.Platform;
30+
import javafx.scene.Node;
31+
import javafx.scene.Scene;
32+
import javafx.scene.control.Label;
33+
import javafx.scene.control.ScrollPane;
34+
import javafx.scene.layout.VBox;
35+
import javafx.stage.Stage;
36+
import javafx.stage.WindowEvent;
37+
import junit.framework.Assert;
38+
import org.junit.AfterClass;
39+
import org.junit.BeforeClass;
40+
import org.junit.Test;
41+
42+
import java.util.concurrent.CountDownLatch;
43+
import java.util.concurrent.TimeUnit;
44+
45+
import static org.junit.Assert.assertTrue;
46+
import static org.junit.Assume.assumeTrue;
47+
48+
public class UIRenderSnapToPixelTest {
49+
private static final double scale = 1.25;
50+
private static CountDownLatch startupLatch;
51+
private static volatile Stage stage;
52+
private static final double epsilon = 0.00001;
53+
54+
@BeforeClass
55+
public static void setupOnce() throws Exception {
56+
System.setProperty("glass.win.uiScale", String.valueOf(scale));
57+
System.setProperty("glass.gtk.uiScale", String.valueOf(scale));
58+
startupLatch = new CountDownLatch(1);
59+
new Thread(() -> Application.launch(TestApp.class, (String[]) null)).start();
60+
assertTrue("Timeout waiting for FX runtime to start", startupLatch.await(15, TimeUnit.SECONDS));
61+
}
62+
63+
@AfterClass
64+
public static void teardown() {
65+
Platform.runLater(stage::hide);
66+
Platform.exit();
67+
}
68+
69+
@Test
70+
public void testScrollPaneSnapChildrenToPixels() {
71+
assumeTrue(PlatformUtil.isLinux() || PlatformUtil.isWindows());
72+
73+
Assert.assertEquals("Wrong render scale", scale, stage.getRenderScaleY(), 0.0001);
74+
75+
for (Node node : stage.getScene().getRoot().getChildrenUnmodifiable()) {
76+
if (node instanceof ScrollPane) {
77+
var sp = (ScrollPane) node;
78+
Assert.assertEquals("Top inset not snapped to pixel", 0, ((sp.snappedTopInset() * scale) + epsilon) % 1, 0.0001);
79+
Assert.assertEquals("Bottom inset not snapped to pixel", 0, ((sp.snappedBottomInset() * scale) + epsilon) % 1, 0.0001);
80+
Assert.assertEquals("Left inset not snapped to pixel", 0, ((sp.snappedLeftInset() * scale) + epsilon) % 1, 0.0001);
81+
Assert.assertEquals("Right inset not snapped to pixel", 0, ((sp.snappedRightInset() * scale) + epsilon) % 1, 0.0001);
82+
}
83+
}
84+
}
85+
86+
public static class TestApp extends Application {
87+
private static void run() {
88+
startupLatch.countDown();
89+
}
90+
91+
@Override
92+
public void start(Stage primaryStage) throws Exception {
93+
final Label label = new Label("This text may appear blurry at some screen scale without the fix for JDK-8211294");
94+
final ScrollPane scrollpane = new ScrollPane(label);
95+
scrollpane.setSnapToPixel(true);
96+
final VBox root = new VBox();
97+
root.getChildren().add(new Label("This text should be sharp at all screen scale"));
98+
root.getChildren().add(scrollpane);
99+
final Scene scene = new Scene(root);
100+
primaryStage.setScene(scene);
101+
stage = primaryStage;
102+
stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e -> Platform.runLater(TestApp::run));
103+
stage.show();
104+
}
105+
}
106+
107+
}

0 commit comments

Comments
 (0)