Search code examples
javaswinglook-and-feel

Update components border/insets on user scale change


I'm currently using FlatLaf UI as look&feel for my Swing application.
FlatLaf offers a user scale mechanism based on the font size, e.g.:

12pt: 1.0 | 100%
16pt: 1.3 | 130%

This means all the borders and insets are calculated considering this factor:

BorderFactory.createEmptyBorder(UIScale.scale(10), ... // Which means 10 * 1.3, for example

The problem arises when the user scale is changed and components are shown on screen.
Given I add and customize components on class constructors...

public class ProtocolSearchBar extends Box {
  public ProtocolSearchBar() {
    ...
    add(protocolLabel);
    add(Box.createHorizontalStrut(UIScale.scale(10)));
    add(protocol);
    add(Box.createHorizontalStrut(UIScale.scale(10)));
    add(fetchButton);
  }
  ...

...how can I efficiently update their insets each time the UI scale is changed by the user?

FlatLaf offers a callback to be notified.

UIScale.addPropertyChangeListener(event -> {
  if ("userScaleFactor".equals(event.getPropertyName())) {
    ...
  }
});

Solution

  • A neat solution is extending the standard Swing components, e.g.

    public class ScaledFiller extends Filler {
      public enum Axis {
        X_AXIS,
        Y_AXIS
      }
    
      public ScaledFiller(final int size, final Axis axis) {
        super(
            new Dimension(axis == X_AXIS ? size : 0, axis == Y_AXIS ? size : 0),
            new Dimension(axis == X_AXIS ? size : 0, axis == Y_AXIS ? size : 0),
            new Dimension(axis == X_AXIS ? size : Short.MAX_VALUE, axis == Y_AXIS ? size : Short.MAX_VALUE)
        );
      }
    
      public ScaledFiller(final Dimension min, final Dimension pref, final Dimension max) {
        super(min, pref, max);
      }
    
      @Override
      public Dimension getMinimumSize() {
        return new ScaledDimension(super.getMinimumSize());
      }
    
      @Override
      public Dimension getPreferredSize() {
        return new ScaledDimension(super.getPreferredSize());
      }
    
      @Override
      public Dimension getMaximumSize() {
        return new ScaledDimension(super.getMaximumSize());
      }
    }
    

    And used as

    @NotNull
    public static Filler horizontalStrut(final int size) {
      return new ScaledFiller(size, ScaledFiller.Axis.X_AXIS);
    }
    
    @NotNull
    public static Filler verticalStrut(final int size) {
      return new ScaledFiller(size, ScaledFiller.Axis.Y_AXIS);
    }