SasQ
SasQ

Reputation: 14699

Scaling a button's text automatically with the button in JavaFX

I made a grid of buttons in JavaFX.
When I resize the window with the grid inside, the buttons resize within the grid as well.
The problem is that the text on those buttons doesn't resize along with them: it stays the same size all the time, so when the buttons grow big, there's a lot of empty space on a button and then a tiny little text in the middle, which looks terrible.
I would rather like the text to automatically resize along with these buttons to fit the empty space, so that when the entire user interface gets bigger, the text on the buttons gets bigger as well.
How can I accomplish that?

I tried setting the -fx-font-size in the CSS stylesheet to percentage values, but it doesn't seem to work the same way as for websites: the text doesn't scale as a percentage of its container, but as a percentage of some predefined text size.

Edit
This is not a duplicate! Stop marking each question out there as duplicate! If it has been answered, I wouldn't have asked it in the first place!

From what I see, the first of those threads was about a situation where someone wanted to set the size/style of the text for newly-created buttons to account for the current size of their container etc. This is not what I need, because I want the buttons which has been already created as well to automatically resize their texts when these buttons resize inside their container in some way.

The other thread was about scaling the text along with the root container / window with a preset font size. This is also different from what I need, because I don't want the text to be scaled with the window, but with the sizes of the buttons themselves. And it has to be scaled in a certain way: to always fit the size of the button. You know: the text stays the same, but stretches so that it always fits the inside of the button (with a little padding, not a huge empty area around the text).

It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself (either the built-in one or a subclassed one), not manually by its encompassing container iterating over all these buttons and updating their text's sizes (which would be dumb way to do it).

Upvotes: 1

Views: 3780

Answers (2)

jewelsea
jewelsea

Reputation: 159546

An alternate approach to get a similar effect could be to subclass com.sun.javafx.scene.control.skin.ButtonSkin and override the layoutLabelInArea(double x, double y, double w, double h, Pos alignment) method from the skin's parent (LabeledSkinBase). You can then explicitly assign the updated skin to your button (either via CSS or via Java API calls).

Doing so would requires the subclassing of com.sun APIs which could change without notice in subsequent JavaFX releases. Also layoutLabelInArea is reasonably complex in its operation so changing the layout logic could be a little tricky. Certainly, James's suggestion of applying a text rescaling operation based upon a listener to the layout bounds property is simpler in this particular case.

I'm not necessarily advocating this approach, just providing a route to something that you could create that would satisfy your goal of: "It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself".

Upvotes: 1

James_D
James_D

Reputation: 209674

This is, liked the linked questions, something of a hack: but consider scaling the text node inside the button instead of changing the font size. This seems to work ok:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class ScaledButtons extends Application {

    @Override
    public void start(Stage primaryStage) {
        GridPane root = new GridPane();
        root.setHgap(5);
        root.setVgap(5);
        for (int i = 1; i <= 9 ; i++) {
            root.add(createScaledButton(Integer.toString(i)), (i-1) % 3, (i-1) / 3);
        }
        root.add(createScaledButton("#"), 0, 3);
        root.add(createScaledButton("0"), 1, 3);
        root.add(createScaledButton("*"), 2, 3);

        primaryStage.setScene(new Scene(root, 250, 400));
        primaryStage.show();
    }

    private Button createScaledButton(String text) {
        Button button = new Button(text);
        GridPane.setFillHeight(button, true);
        GridPane.setFillWidth(button, true);
        GridPane.setHgrow(button, Priority.ALWAYS);
        GridPane.setVgrow(button, Priority.ALWAYS);

        button.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) -> 
            scaleButton(button));

        button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);

        return button ;
    }

    private void scaleButton(Button button) {
        double w = button.getWidth();
        double h = button.getHeight();

        double bw = button.prefWidth(-1);
        double bh = button.prefHeight(-1);

        if (w == 0 || h == 0 || bw == 0 || bh == 0) return ;

        double hScale = w / bw ;
        double vScale = h / bw ;

        double scale = Math.min(hScale, vScale);

        button.lookup(".text").setScaleX(scale);
        button.lookup(".text").setScaleY(scale);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Upvotes: 2

Related Questions