JavaFX HiDPI layout bugs
Sam Hutchins
sam.hutchins at screamingfrog.co.uk
Fri Jun 19 13:49:02 UTC 2020
Hi,
I've been doing some investigation into a layout bug in JavaFX on Windows with non-integer scaling values. I think it's related to JDK-8199592, and I've put a small example that will reproduce these layout bugs at the end of this email. The most obvious layout error is truncation of labels in many controls. I've used CheckBoxes in this example, but the truncation errors are apparent in any labeled control. I suspect the issue affects other controls too (and probably the entire layout), but in more subtle ways. With scaling values of 1.25 or 1.5, the label truncation only happens in the dialog box. At 1.75 the issue is apparent in both windows.
I _think_ the issue is a combination of caching and invalid calculations. I stepped through the `layoutLabelInArea(x, y, w, h, alignment)` method in LabeledSkinBase and found that 3 layout passes take place.
In the first and second passes, all the layout calculations appear to be done without taking scaling into account.
The 3rd pass appears to be when a scene is involved, and the snap* methods start having an impact. It looks like some values are scaled appropriately, but others are still using the pre-scene cached values. In particular, the call to `leftLabelPadding()` returns 5 for the first 2 passes, but 5.6 in the 3rd (at 1.25 scaling). The value of `w` doesn't change though, and that .6 increase in padding causes the label to be truncated. When you interact with the CheckBox to trigger a 4th layout, the value of `w` is increased by .6, and the layout appears to work as expected.
I've hacked some of the caching out of `Parent` by adding a call to clearSizeCache() to the start of the `prefWidth(height)` and `prefHeight(width)` methods, which forces JavaFX to re-compute everything on every call. This resolves the issues for simple layouts at 1.25 and 1.5 scaling (but not 1.75).
This doesn't resolve issues in more complicated layouts, however, as many subclasses of `Parent` have caching of their own. For example, controls in GridPanes will still exhibit this behaviour. I've hacked some caching out of that by removing the early return in the `computePrefHeights(widths)` and `computePrefWidths(heights)` methods, which resolves it for the GridPane case. I imagine there are many other instances where these cached values aren't correctly invalidated.
I think there's also a fundamental issue with the layout calculations with non-integer scaling, as the layout is _always_ wrong at 1.75. I suspect that the layout calculations are always wrong, but the error at lower scaling values isn't enough to cause visible issues after pixel snapping.
I'm not really sure where to go from here, as I'm not at all familiar with how and when JavaFX invalidates its layout calculations. If anyone can point me at some other threads to pull, I'd be grateful.
Thanks,
Sam
Code to reproduce:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class LayoutBug
{
public static void main(String[] args)
{
System.setProperty("glass.win.uiScale", "1.5");
Application.launch(MainWindow.class);
}
public static class MainWindow extends Application
{
public void start(final Stage primaryStage)
{
final var button = new Button("Show Dialog");
button.setOnAction(e ->
{
final var alert = new Alert(Alert.AlertType.NONE);
alert.initOwner(primaryStage);
alert.getButtonTypes().add(ButtonType.OK);
alert.getDialogPane().setContent(createTestUi());
alert.show();
});
final var root = createTestUi();
root.getChildren().add(button);
primaryStage.setScene(new Scene(root, 400, 600));
primaryStage.show();
}
private VBox createTestUi()
{
final var gridPane = new GridPane();
gridPane.addRow(0, new CheckBox("Checkbox in gridpane"));
return new VBox(10,
gridPane,
new CheckBox("Checkbox outside gridpane"));
}
}
}
More information about the openjfx-dev
mailing list