Tornadofx: Cannot make type-safe CSS work

Created on 3 Oct 2017  路  13Comments  路  Source: edvin/tornadofx

Hi, I am trying out TornadoFX but it's not looking very good.

This is a one-to-one translation from a simple JavaFX app that is themed via css:

class Main : App(HelloWorld::class, Styles::class)

class HelloWorld : View() {
    override val root = stackpane {
        prefWidth = 300.0
        prefHeight = 120.0
        text("Hello world")
    }
}

class Styles : Stylesheet() {
    init {
        root {
            baseColor = Color.DARKSLATEGRAY
        }
        text {
            stroke = Color.WHITE
        }
    }
}

fun main(args: Array<String>) {
    launch<Main>(args)
}

The Java version works and shows the screen with a dark background and white text.

This one doesn't, it just uses the default theme, ignoring the stylesheet. I tried several variants, using importStylsheets(), nothing has worked.

Is there anything wrong with my sample?

Most helpful comment

I found a bug reported under the Scene Builder sub-component. Although, it reports the bug for the tool, it seems to exist in the underlying platform as well - https://bugs.openjdk.java.net/browse/JDK-8088654

All 13 comments

There are actually a couple issues with your code, ~and I'm not sure how it could have worked in Java (any chance you can post your java equivalent?)~. The issues aren't your fault so much as some strange behaviors with JavaFX.

_(edit: I saw a Java version, and noticed it wasn't actually the same as the TornadoFX version, but maybe not entirely obvious why. Hopefully my comment here will clear that up)_

To start with, the base color isn't taking effect because you don't have any controls in your scene (Text is a Shape, not a Control like, say, Label). I'm not sure why they do this, but they do. If you are trying to set the background color, you should use backgroundColor += Color.DARKSLATEGRAY. If you are trying to set the base color (which is great for theming), you will need to add at least one control to the scene graph.

Also, the stroke color isn't being set because Text by default (for some strange reason) does not have the text class applied to it. You can either use your own class, or add the text class to your Text.

Changing your code to the following works:

class Main : App(HelloWorld::class, Styles::class)

class HelloWorld : View() {
    override val root = stackpane {
        text("Stroke and Fill").addClass("text")
    }
}

class Styles : Stylesheet() {
    init {
        root {
            prefWidth = 300.px
            prefHeight = 120.px
            backgroundColor += Color.DARKSLATEGRAY
        }
        text {
            stroke = Color.WHITE
        }
    }
}

fun main(args: Array<String>) {
    launch<Main>(args)
}

(Note: I moved the pref width and height to CSS. I just like it there more, but you can leave it in your View if you prefer.)

Note that in the Styles, the text applies to the CSS class .text, not to the TornadoFX text builder. Here is the same with a custom class to make it a bit clearer:

class Main : App(HelloWorld::class, Styles::class)

class HelloWorld : View() {
    override val root = stackpane {
        text("Stroke and Fill").addClass(Styles.myText)
    }
}

class Styles : Stylesheet() {
    companion object {
        val myText by cssclass()
    }

    init {
        root {
            prefWidth = 300.px
            prefHeight = 120.px
            backgroundColor += Color.DARKSLATEGRAY
        }
        myText {
            stroke = Color.WHITE
        }
    }
}

fun main(args: Array<String>) {
    launch<Main>(args)
}

Also, the stroke color isn't being set because Text by default (for some strange reason) does not have the text class applied to it.

Note that in the Styles, the text applies to the CSS class .text, not to the TornadoFX text builder.

This is just going to cause trouble to everyone... why don't you translate the text() css method to the Text CSS element?

To start with, the base color isn't taking effect because you don't have any controls in your scene

That's ridiculous. You're right, I had a button in my Java UI but I removed it to make the example simpler.

Obviously, my intention was to set the base color, not just the background of the text.

Anyway, thanks for answering... it would be nice to find workarounds for issues like these because that really makes getting started painful.

Otherwise, this is a really cool framework, good job creating it and not just following blindly on the ScalaFX and GroovyFX examples!

Yeah, the base color thing is interesting. I have a hunch that it has something to do with an optimization when there are no Skinnable objects in the graph, but that's a total guess.

As far as the text selector goes, it points to the JavaFX .text class, which is used in many places (such as on labels and stuff (anything that is or contains a LabeledText)). It is incredibly unfortunate that they share the same name without relating to each other, but that's an issue with JavaFX, not TornadoFX. If we had the text selector point at Text objects instead of the .text class, how would users style the .text class?

I don't think there is a workaround for the baseColor issue other than adding a Control to the scene graph. It's a limitation that comes from either JavaFX or modena.css, and we don't have any control over that.

As far as the issues with targeting classes go, I would highly recommend defining and using your own classes (I would suggest this for vanilla JavaFX as well). I find it makes your code cleaner and your intent clearer, and can help avoid unexpected issues when JavaFX uses (or doesn't use) classes in unexpected ways.

If we had the text selector point at Text objects instead of the .text class, how would users style the .text class?

An app developer is not interested in the internals of JavaFX and would expect text to refer to Text, label to refer to Label and so on... I don't think anyone would expect text to affect labels, checkboxes or whatever. Maybe that makes sense if you think in terms of "I want all text in my app to get this color"?! But I think that this is inconsistent with the other css names, isn't it? Perhaps labeled should be translated to .text, and text to Text?! That said, I agree with you that using my own named is better, and I do that normally... Just in certain cases I like to use these generic classes within nested CSS rules (like .my-container Text) and would be nice if this worked in a intuitive way.

But that would be extremely confusing to developers coming from JavaFX who are aware of it's pitfalls, and would make translating existing CSS that much more difficult.

An app developer is not interested in the internals of JavaFX...

I don't think this is true. While it may be true for some casual developers, many developers (regardless of platform) are both interested in and quite familiar with the internals of their chosen platform. This will include any quirks and shortcomings of the platform, so they can exploit and/or avoid them as needed. One of the goals of TornadoFX is to never hide or block access to the underlying JavaFX so developers can do just that.

(I do agree that developers would expect text to point to Text, label to Label, etc., but that's a design decision of JavaFX. There's not much that can be done about that, as changing it now would be a breaking change.)

Defining a baseColor (or -fx-base) doesn't add a background color to the root. The base color is used to derive different colors (background, border, focus etc.) for the rest of the nodes.

On the issue of style-class, none of the nodes in JavaFX have style-class unless they are controls or used by a control. For example, .text style-class is added to a LabeledText when used inside a Label.

@abhinayagarwal Almost. It seems the base color is used to derive colors for the rest of the Controls (or maybe Skinnables), not Nodes, and so it doesn't come into effect if there are no Controls in the scene graph, no matter how many nodes are present, leading to somewhat unexpected results.

While it may make sense to only have style-classes for Controls, the naming is the confusion here. It would have made much more sense to use .labeled-text or .labeled instead of .text, since there is a Text node, and it isn't a .text. But, like I said, it's too late to change now.

it doesn't come into effect if there are no Controls in the scene graph

-fx-base is just a property defined in the root scope of the css file such that it can be referred by all the other class selectors. ~You do not see the changes because you do not have any node on the scene-graph which is using it. You can verify this by adding a class selector for a non-control node and use the base color in them.~

For example, define the background color for root by:

.root {
   -fx-background-color: -fx-base;
}

Or, define a base color using TornadoFX's type safe CSS and then use it to define the fill color of a Text(not a control) :

root {
    baseColor = Color.BLUE
}
.my-text {
   -fx-fill: -fx-base;
} 

It would have made much more sense to use .labeled-text or .labeled instead of .text

Well may be... But, I do not see anything wrong in it as LabeledText extends Text. Nevertheless, it is a naming decision and everyone will have their own opinion :slightly_smiling_face:

You do not see the changes because you do not have any node on the scene-graph which is using it.

That's absolutely incorrect.

If you replace text in the original code I posted with button, you'll see the stackPane that contains it changes its color to the baseColor. With only a text in it, the stackPane does not change color to follow the baseColor. Clearly, JavaFX ignores the baseColor for the stackPane simply because it does not contain any controls, which is a bug if you ask me.

Maybe all of us are _partially_ incorrect and there's nothing absolute about it :)

I found a bug reported under the Scene Builder sub-component. Although, it reports the bug for the tool, it seems to exist in the underlying platform as well - https://bugs.openjdk.java.net/browse/JDK-8088654

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pavan-p picture pavan-p  路  3Comments

AlmasB picture AlmasB  路  8Comments

elliotcm picture elliotcm  路  7Comments

spkingr picture spkingr  路  4Comments

Leo-Thura picture Leo-Thura  路  6Comments