Search code examples
cssjavafxfxmltornadofx

How to prevent JavaFX FXML menu-item's text-fill from inheriting from menu


I'm playing around with JavaFx and seem to have a problem getting the menu-bar to look exactly how I want using css.

The main menu bar seems to be okay. When I hover or select the background becomes darker and the font becomes blue ("Edit" in the example). The same with the menu-item below ("Delete" in the example) but it seems that the other non-hovered, non-selected menu-items also get blue text instead of remaining white.

I've tried looking at https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html and other StackOverflow articles, and just a bunch of trial and error, but nothing seems to do it. Any tips?

Source code is at https://github.com/pnogas/TornadoTest, but I'll have snippets below in case I ever delete it from github for future people reading this.

example of output

FXML generated from scene builder then auto-formatted from intelliJ IDE

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox prefHeight="400.0" prefWidth="640.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
  fx:controller="com.paulnogas.log.analyzer.MainView">
<MenuBar stylesheets="@test.css" VBox.vgrow="NEVER">
    <Menu mnemonicParsing="false" text="Edit">
        <MenuItem mnemonicParsing="false" text="Undo"/>
        <MenuItem mnemonicParsing="false" text="Redo"/>
        <SeparatorMenuItem mnemonicParsing="false"/>
        <MenuItem mnemonicParsing="false" text="Cut"/>
        <MenuItem mnemonicParsing="false" text="Copy"/>
        <MenuItem mnemonicParsing="false" text="Paste"/>
        <MenuItem mnemonicParsing="false" text="Delete"/>
        <SeparatorMenuItem mnemonicParsing="false"/>
        <MenuItem mnemonicParsing="false" text="Select All"/>
        <MenuItem mnemonicParsing="false" text="Unselect All"/>
    </Menu>

and here's the relevant parts of test.css

.menu-bar,
.menu,
.menu-item {
    -fx-background-color: #222222;
}

.menu-bar .label,
.menu .label,
.menu-item .label {
    -fx-text-fill: white;
}

.menu:hover,
.menu:showing,
.menu-item:hover {
    -fx-background-color: #111111;
}

.menu:hover .label,
.menu:showing .label,
.menu-item:hover .label {
    -fx-text-fill: #80CBC4;
}

(I know TornadoFX prefers type-safe CSS in Kotlin to a normal *.css file, but I think I want to do it in *.css for now. I want to use Scene Builder occasionally. I also like the strict forcing of separation of concerns. When working in Kotlin only I tend to lose discipline and end up with spaghetti code mixing structure, style, functionality in one ugly Kotlin class. Maybe I'll move it over to pure Kotlin later).


Solution

  • The selector (as an example)

    .menu:showing .label
    

    will select all labels that are descendants of a showing menu. So this includes labels which are child nodes of a menu item which is a child node of a showing menu, etc. Here you want to only select labels which are immediate children of a showing menu. The correct selector syntax for that is

    .menu:showing > .label
    

    The CSS you need is

    .menu-bar,
    .menu,
    .menu-item {
        -fx-background-color: #222222;
    }
    
    .menu-bar > .label,
    .menu > .label,
    .menu-item > .label {
        -fx-text-fill: white;
    }
    
    .menu:hover,
    .menu:showing,
    .menu-item:hover {
        -fx-background-color: #111111;
    }
    
    .menu:hover > .label,
    .menu:showing > .label,
    .menu-item:hover > .label {
        -fx-text-fill: #80CBC4;
    }
    

    Another solution would be to explicitly set the text color of labels in menu items in showing menus with and without the :hover psuedoclass:

    .menu-bar,
    .menu,
    .menu-item {
        -fx-background-color: #222222;
    }
    
    
    /* Note added .menu:showing .menu-item .label selector */
    
    .menu-bar .label,
    .menu .label,
    .menu-item .label,
    .menu:showing .menu-item .label {
        -fx-text-fill: white;
    }
    
    .menu:hover,
    .menu:showing,
    .menu-item:hover {
        -fx-background-color: #111111;
    }
    
    .menu:hover .label,
    .menu:showing .label,
    .menu:showing .menu-item:hover .label {
        -fx-text-fill: #80CBC4;
    }