1

What i have so far

I was trying to implement a multi-line TextInput with javaFx, one that is capable of displaying emojis, I used a VBox of FlowPanes (a FlowPane for each line), split the line by spaces into words, the words are displayed in an HBox and the HBox will contain Text nodes for text and ImageView for Emojis, The current setup is described in the following image

Illustration

The following screenshot shows what i have so far

enter image description here


The problem

The problem i'm facing is with the complex word, when multiple emojis (ImageViews) are displayed in an HBox, the Carret position estimation will be wrong by half a pixel for each image (because i'm assuming the width of the HBox would be equal to the sum of the fitWidths of its children ?, but it's not), as if the HBox has some kind of spacing although the spacing property is set to 0.

the effect gets worse if a lot of emojis are shown together, as shown in the following screenshots

enter image description here enter image description here

It's also not Padding, or Borders, any help would be appreciated, and i'm sorry for the long boring explanation, i had to add it in case it would help anyone help me resolve the issue.

SDIDSA
  • 894
  • 10
  • 19
  • 3
    You might be better off using a [TextFlow](https://openjfx.io/javadoc/16/javafx.graphics/javafx/scene/text/TextFlow.html) and its [caretShape](https://openjfx.io/javadoc/16/javafx.graphics/javafx/scene/text/TextFlow.html#caretShape(int,boolean)) and [hitTest](https://openjfx.io/javadoc/16/javafx.graphics/javafx/scene/text/TextFlow.html#hitTest(javafx.geometry.Point2D)) methods. Note those methods were added in JavaFX 9. Though I can't promise that will solve your problem. If not, could you provide a [mre]? – Slaw Jul 24 '21 at 18:39
  • the TextFlow seems to be close enough to what I'm trying to implement here, but it doesn't extend the TextInputControl, can it be used as a text input and how easy it would be to implement the needed behavior (select, copy, cut, paste, delete... etc)? – SDIDSA Jul 24 '21 at 19:28
  • a minimal reproducible example would be huge because of all the implemented behaviour for my custom node to function, but an example could be an HBox of image views, where the width of the HBox is slightly reater than the sum of the widths of the children, even with 0 padding and spacing, I'll add something asap – SDIDSA Jul 24 '21 at 19:30
  • If you use `TextFlow` then you'll have to implement a lot of the behavior yourself. Though I suppose you could dig into internal API and use the [TextInputConrolBehavior](https://github.com/openjdk/jfx/blob/master/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java) class. Or perhaps something like [RichTextFX](https://github.com/FXMisc/RichTextFX) could be a good starting point (haven't really used that library; not sure if it's appropriate to your scenario). – Slaw Jul 24 '21 at 19:46
  • 1
    And keep in mind my comments assume the problem is with _your_ code, and not that JavaFX is the source of the problem (i.e. doesn't handle emojis correctly). – Slaw Jul 24 '21 at 19:48
  • 1
    aha your assumption is correct and there was something wrong with my code, i will post the found solution as an answer – SDIDSA Jul 24 '21 at 21:02

1 Answers1

3

After a lot of investigation, it turns out to be caused by me setting the fitWidth of the child ImageViews to decimal values (non-integer), the ImageView accepts a double as the fitWidth but it seems to be rounded to the closest greater integer when rendering, calling the getFitWidth() method would still return the double you have set but the Parent will lay them out with the rounded value (because i guess a physical pixel can't display half a pixel right ?).

So calling getWidth() on the HBox parent would return a greater value than the sum of the fitWidths of the child ImageViews ONLY IF the fitWidths of the ImageViews are non-integer.

This can be tested using the following piece of code

ArrayList<Image> images = new ArrayList<Image>();

//Fill the list with N images

StackPane root = new StackPane();
root.setPadding(new Insets(15));

HBox parent = new HBox(0);
for (Image image : images) {
    ImageView view = new ImageView(image);
    view.setPreserveRatio(true);
    view.setFitWidth(fitWidth);
    parent.getChildren().add(view);
}

root.getChildren().add(parent);

ps.setOnShown(event -> {
    double sum = 0;
    for (Node node : parent.getChildren()) {
        sum += ((ImageView) node).getFitWidth();
    }
    System.out.println("fitWidth : " + fitWidth);
    System.out.println("Sum of fitWidths of child ImageViews : " + sum);
    System.out.println("Width of the parent HBox : " + parent.getWidth());
});

ps.setScene(new Scene(root));
ps.show();

Running this with a fitWidth of 31.5 and 32.0 gives the following results

fitWidth : 31.5
Sum of fitWidths of child ImageViews : 315.0
Width of the parent HBox : 320.0

fitWidth : 32.0
Sum of fitWidths of child ImageViews : 320.0
Width of the parent HBox : 320.0

Notice that the width of the parent is the same but the sum of fitWidths isn't, because the value is rounded at some point during layout or render, which caused the issue described in the question.

Solving this would really differ from one context to another, i solved it by casting the double to an int when setting the fitWidth but it's really up to the developer.

SDIDSA
  • 894
  • 10
  • 19
  • 2
    Interesting... there's no documentation about that behavior for `fitWidth` and `fitHeight`. Nice find! – Slaw Jul 25 '21 at 11:40