From 61f7330cd2c6fbc369bc76c385ecc219bedc0756 Mon Sep 17 00:00:00 2001 From: Akumas_Coffin_ <68540569+AkumasCoffin@users.noreply.github.com> Date: Thu, 14 May 2026 18:58:50 +1000 Subject: [PATCH] Add dark mode toggle under User Preferences > Application - Add darkMode boolean preference to ApplicationPreference (persisted via java.util.prefs) - Add ToggleSwitch labeled "Dark Mode" to ApplicationPreferenceEditor - Add ThemeManager singleton that listens for preference updates and applies/removes the dark stylesheet to all registered JavaFX Scenes and swaps the Swing LAF - Add sdrtrunk_dark.css with dark styling for controls, tables, menus, dialogs, charts - Add FlatLaf dependency for full Swing dark theming (replaces previous OS-specific MetalLookAndFeel setup; FlatLaf works cross-platform) - Register all Stages created in JavaFxWindowManager with the ThemeManager and walk Window.getWindows() at apply-time so unregistered dialogs/alerts also pick up the theme - Add Foojay toolchain resolver to settings.gradle so Gradle can auto-provision JDK 25 --- build.gradle | 1 + settings.gradle | 6 +- .../dsheirer/gui/JavaFxWindowManager.java | 8 + .../java/io/github/dsheirer/gui/SDRTrunk.java | 23 +- .../ApplicationPreferenceEditor.java | 29 +- .../dsheirer/gui/theme/ThemeManager.java | 300 ++++++++++++++ .../application/ApplicationPreference.java | 27 ++ src/main/resources/sdrtrunk_dark.css | 383 ++++++++++++++++++ 8 files changed, 756 insertions(+), 21 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java create mode 100644 src/main/resources/sdrtrunk_dark.css diff --git a/build.gradle b/build.gradle index 3dfe491e..ddf30202 100644 --- a/build.gradle +++ b/build.gradle @@ -93,6 +93,7 @@ dependencies { implementation 'ch.qos.logback:logback-core:1.5.21' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.1' implementation 'com.fazecast:jSerialComm:2.11.4' + implementation 'com.formdev:flatlaf:3.5.4' implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1' implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0' implementation 'com.github.jiconfont:jiconfont-swing:1.0.1' diff --git a/settings.gradle b/settings.gradle index e592e50e..734f9d7c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,5 @@ -rootProject.name = 'sdr-trunk' \ No newline at end of file +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + +rootProject.name = 'sdr-trunk' diff --git a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java index f7b13a17..8d44606d 100644 --- a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java +++ b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java @@ -35,6 +35,7 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.gui.preference.UserPreferencesEditor; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; import io.github.dsheirer.gui.preference.calibration.CalibrationDialog; +import io.github.dsheirer.gui.theme.ThemeManager; import io.github.dsheirer.gui.viewer.MessageRecordingViewer; import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest; import io.github.dsheirer.icon.IconModel; @@ -135,6 +136,7 @@ public class JavaFxWindowManager extends Application //JFXPanel has to be populated on the FX event thread Platform.runLater(() -> { Scene scene = new Scene(new StatusBox(resourceMonitor)); + ThemeManager.getInstance().register(scene); mStatusPanel.setScene(scene); }); } @@ -207,6 +209,7 @@ public class JavaFxWindowManager extends Application { createJFXPanel(); Scene scene = new Scene(getRecordingViewer(), 1100, 800); + ThemeManager.getInstance().register(scene); mRecordingViewerStage = new Stage(); mRecordingViewerStage.setTitle("sdrtrunk - Message Recording Viewer (.bits)"); mRecordingViewerStage.setScene(scene); @@ -232,6 +235,7 @@ public class JavaFxWindowManager extends Application { createJFXPanel(); Scene scene = new Scene(getIconManager(), 500, 500); + ThemeManager.getInstance().register(scene); mIconManagerStage = new Stage(); mIconManagerStage.setTitle("sdrtrunk - Icon Manager"); mIconManagerStage.setScene(scene); @@ -280,6 +284,7 @@ public class JavaFxWindowManager extends Application { createJFXPanel(); Scene scene = new Scene(getJmbeEditor(), 650, 650); + ThemeManager.getInstance().register(scene); mJmbeEditorStage = new Stage(); mJmbeEditorStage.setTitle("sdrtrunk - JMBE Library Updater"); mJmbeEditorStage.setScene(scene); @@ -321,6 +326,7 @@ public class JavaFxWindowManager extends Application { createJFXPanel(); Scene scene = new Scene(getPlaylistEditor(), 1000, 750); + ThemeManager.getInstance().register(scene); mPlaylistStage = new Stage(); mPlaylistStage.setTitle("sdrtrunk - Playlist Editor"); mPlaylistStage.setScene(scene); @@ -371,6 +377,7 @@ public class JavaFxWindowManager extends Application { createJFXPanel(); Scene scene = new Scene(getUserPreferencesEditor(), 900, 500); + ThemeManager.getInstance().register(scene); mUserPreferencesStage = new Stage(); mUserPreferencesStage.setTitle("sdrtrunk - User Preferences"); mUserPreferencesStage.setScene(scene); @@ -414,6 +421,7 @@ public class JavaFxWindowManager extends Application { createJFXPanel(); Scene scene = new Scene(getChannelMapEditor(), 500, 500); + ThemeManager.getInstance().register(scene); mChannelMapStage = new Stage(); mChannelMapStage.setTitle("sdrtrunk - Channel Map Editor"); mChannelMapStage.setScene(scene); diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index 0c8808c2..1585ec12 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -18,7 +18,6 @@ */ package io.github.dsheirer.gui; -import com.jidesoft.plaf.LookAndFeelFactory; import com.jidesoft.swing.JideSplitPane; import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.DuplicateCallDetector; @@ -38,6 +37,7 @@ import io.github.dsheirer.gui.preference.CalibrateRequest; import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; import io.github.dsheirer.gui.preference.calibration.CalibrationDialog; +import io.github.dsheirer.gui.theme.ThemeManager; import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.log.ApplicationLog; @@ -81,7 +81,6 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Locale; import java.util.Optional; import java.util.prefs.Preferences; import javafx.application.Platform; @@ -102,10 +101,8 @@ import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JSeparator; import javax.swing.KeyStroke; -import javax.swing.UIManager; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; -import javax.swing.plaf.metal.MetalLookAndFeel; public class SDRTrunk implements Listener { @@ -147,6 +144,9 @@ public class SDRTrunk implements Listener { if(!GraphicsEnvironment.isHeadless()) { + //Initialize the theme manager before any Swing components are realized so the chosen + //look-and-feel applies to the very first window that is created. + ThemeManager.getInstance().initialize(mUserPreferences); mMainGui = new JFrame(); } @@ -163,21 +163,6 @@ public class SDRTrunk implements Listener mResourceMonitor = new ResourceMonitor(mUserPreferences); - String operatingSystem = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); - - if(operatingSystem.contains("mac") || operatingSystem.contains("nux")) - { - try - { - UIManager.setLookAndFeel(MetalLookAndFeel.class.getName()); - LookAndFeelFactory.installJideExtension(); - } - catch(Exception e) - { - mLog.error("Error trying to set Metal look and feel for OS [" + operatingSystem + "]"); - } - } - ThreadPool.logSettings(); //Load properties file diff --git a/src/main/java/io/github/dsheirer/gui/preference/application/ApplicationPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/application/ApplicationPreferenceEditor.java index 65d984a8..1b5b815e 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/application/ApplicationPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/application/ApplicationPreferenceEditor.java @@ -45,6 +45,7 @@ public class ApplicationPreferenceEditor extends HBox private Label mAutoStartTimeoutLabel; private Spinner mTimeoutSpinner; private ToggleSwitch mAutomaticDiagnosticMonitoringToggle; + private ToggleSwitch mDarkModeToggle; /** * Constructs an instance @@ -74,8 +75,18 @@ public class ApplicationPreferenceEditor extends HBox mEditorPane.setHgap(3); mEditorPane.setPadding(new Insets(10, 10, 10, 10)); + Label appearanceLabel = new Label("Appearance"); + mEditorPane.add(appearanceLabel, 0, row, 2, 1); + GridPane.setHalignment(getDarkModeToggle(), HPos.RIGHT); + mEditorPane.add(getDarkModeToggle(), 0, ++row); + mEditorPane.add(new Label("Dark Mode"), 1, row, 2, 1); + + Separator appearanceSeparator = new Separator(Orientation.HORIZONTAL); + GridPane.setHgrow(appearanceSeparator, Priority.ALWAYS); + mEditorPane.add(appearanceSeparator, 0, ++row, 3, 1); + Label monitoringLabel = new Label("Application Health and Diagnostic Monitoring."); - mEditorPane.add(monitoringLabel, 0, row, 2, 1); + mEditorPane.add(monitoringLabel, 0, ++row, 2, 1); GridPane.setHalignment(getAutomaticDiagnosticMonitoringToggle(), HPos.RIGHT); mEditorPane.add(getAutomaticDiagnosticMonitoringToggle(), 0, ++row); mEditorPane.add(new Label("Enable Diagnostic Monitoring"), 1, row, 2, 1); @@ -139,4 +150,20 @@ public class ApplicationPreferenceEditor extends HBox return mAutomaticDiagnosticMonitoringToggle; } + + /** + * Toggle switch to enable/disable dark mode. + */ + private ToggleSwitch getDarkModeToggle() + { + if(mDarkModeToggle == null) + { + mDarkModeToggle = new ToggleSwitch(); + mDarkModeToggle.setSelected(mApplicationPreference.isDarkMode()); + mDarkModeToggle.selectedProperty().addListener((observable, oldValue, enabled) -> + mApplicationPreference.setDarkMode(enabled)); + } + + return mDarkModeToggle; + } } diff --git a/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java b/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java new file mode 100644 index 00000000..1c1837e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java @@ -0,0 +1,300 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.gui.theme; + +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatLightLaf; +import com.google.common.eventbus.Subscribe; +import com.jidesoft.plaf.LookAndFeelFactory; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.UserPreferences; +import java.awt.EventQueue; +import java.awt.Window; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import javafx.application.Platform; +import javafx.scene.Scene; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Centralized light/dark theme manager. + * + *

Tracks every JavaFX {@link Scene} that opts in and applies (or removes) the dark stylesheet + * when the user toggles the {@code Dark Mode} preference. Also drives Swing look-and-feel + * via {@link FlatDarkLaf} / {@link FlatLightLaf} so the main Swing window and all child windows + * follow the same setting. + * + *

Subscribes to the global event bus for {@link PreferenceType#APPLICATION} updates. + */ +public class ThemeManager +{ + private final static Logger mLog = LoggerFactory.getLogger(ThemeManager.class); + private static final String DARK_STYLESHEET = "/sdrtrunk_dark.css"; + private static final ThemeManager INSTANCE = new ThemeManager(); + + private final Set> mScenes = new LinkedHashSet<>(); + private final String mDarkStylesheetUrl; + private volatile UserPreferences mUserPreferences; + private volatile boolean mDarkMode; + + private ThemeManager() + { + java.net.URL url = ThemeManager.class.getResource(DARK_STYLESHEET); + mDarkStylesheetUrl = (url != null) ? url.toExternalForm() : null; + + if(mDarkStylesheetUrl == null) + { + mLog.warn("Dark stylesheet [{}] not found on classpath - dark mode will only affect Swing.", + DARK_STYLESHEET); + } + } + + /** + * @return the singleton instance. + */ + public static ThemeManager getInstance() + { + return INSTANCE; + } + + /** + * Bind the theme manager to the user preferences and apply the current preference immediately. + * Safe to call multiple times - subsequent calls update the reference and re-apply. + * + * @param userPreferences source of the dark-mode preference. + */ + public void initialize(UserPreferences userPreferences) + { + if(userPreferences == null) + { + return; + } + + boolean firstBind = (mUserPreferences == null); + mUserPreferences = userPreferences; + mDarkMode = userPreferences.getApplicationPreference().isDarkMode(); + + if(firstBind) + { + MyEventBus.getGlobalEventBus().register(this); + } + + applySwingLookAndFeel(mDarkMode); + } + + /** + * @return current dark-mode state. + */ + public boolean isDarkMode() + { + return mDarkMode; + } + + /** + * Register a Scene to receive theme updates. The Scene is held via {@link WeakReference} + * so callers do not need to unregister - garbage-collected Scenes are pruned on the next pass. + * + * @param scene to track. + */ + public void register(Scene scene) + { + if(scene == null) + { + return; + } + + synchronized(mScenes) + { + pruneDeadReferencesAndCheckPresent(scene); + mScenes.add(new WeakReference<>(scene)); + } + + applyToScene(scene, mDarkMode); + } + + /** + * Receives notification when any preference changes. Re-reads the dark-mode setting and + * propagates if it differs from the last known value. + */ + @Subscribe + public void onPreferenceUpdated(PreferenceType type) + { + if(type != PreferenceType.APPLICATION || mUserPreferences == null) + { + return; + } + + boolean updated = mUserPreferences.getApplicationPreference().isDarkMode(); + + if(updated == mDarkMode) + { + return; + } + + mDarkMode = updated; + applyAll(updated); + } + + private void applyAll(boolean darkMode) + { + applySwingLookAndFeel(darkMode); + + Runnable fxApply = () -> { + synchronized(mScenes) + { + Iterator> it = mScenes.iterator(); + while(it.hasNext()) + { + Scene scene = it.next().get(); + if(scene == null) + { + it.remove(); + } + else + { + applyToScene(scene, darkMode); + } + } + } + + //Also walk every currently-shown JavaFX window so that dialogs, alerts and + //popups that were not explicitly registered still pick up the theme. + for(javafx.stage.Window window: javafx.stage.Window.getWindows()) + { + Scene scene = window.getScene(); + if(scene != null) + { + applyToScene(scene, darkMode); + } + } + }; + + if(Platform.isFxApplicationThread()) + { + fxApply.run(); + } + else + { + try + { + Platform.runLater(fxApply); + } + catch(IllegalStateException ignored) + { + //FX toolkit not started yet - scenes will be themed at registration time instead. + } + } + } + + private void applyToScene(Scene scene, boolean darkMode) + { + if(mDarkStylesheetUrl == null) + { + return; + } + + Runnable r = () -> { + scene.getStylesheets().remove(mDarkStylesheetUrl); + if(darkMode) + { + scene.getStylesheets().add(mDarkStylesheetUrl); + } + }; + + if(Platform.isFxApplicationThread()) + { + r.run(); + } + else + { + Platform.runLater(r); + } + } + + private void applySwingLookAndFeel(boolean darkMode) + { + Runnable r = () -> { + try + { + if(darkMode) + { + UIManager.setLookAndFeel(new FlatDarkLaf()); + } + else + { + UIManager.setLookAndFeel(new FlatLightLaf()); + } + } + catch(Exception e) + { + mLog.error("Unable to apply Swing look-and-feel for dark mode = " + darkMode, e); + return; + } + + try + { + //JIDE components (split panes, docking) need their UI delegates re-registered + //after every LAF change. + LookAndFeelFactory.installJideExtension(); + } + catch(Exception e) + { + mLog.warn("Unable to install JIDE LAF extension after theme change", e); + } + + for(Window window: Window.getWindows()) + { + SwingUtilities.updateComponentTreeUI(window); + } + }; + + if(EventQueue.isDispatchThread()) + { + r.run(); + } + else + { + EventQueue.invokeLater(r); + } + } + + private void pruneDeadReferencesAndCheckPresent(Scene scene) + { + Iterator> it = mScenes.iterator(); + while(it.hasNext()) + { + Scene existing = it.next().get(); + if(existing == null) + { + it.remove(); + } + else if(existing == scene) + { + it.remove(); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/preference/application/ApplicationPreference.java b/src/main/java/io/github/dsheirer/preference/application/ApplicationPreference.java index 58d16914..64bfc576 100644 --- a/src/main/java/io/github/dsheirer/preference/application/ApplicationPreference.java +++ b/src/main/java/io/github/dsheirer/preference/application/ApplicationPreference.java @@ -33,11 +33,13 @@ public class ApplicationPreference extends Preference { private static final String PREFERENCE_KEY_CHANNEL_AUTO_DIAGNOSTIC_MONITORING = "automatic.diagnostic.monitoring"; private static final String PREFERENCE_KEY_CHANNEL_AUTO_START_TIMEOUT = "channel.auto.start.timeout"; + private static final String PREFERENCE_KEY_DARK_MODE = "dark.mode"; private final static Logger mLog = LoggerFactory.getLogger(ApplicationPreference.class); private Preferences mPreferences = Preferences.userNodeForPackage(ApplicationPreference.class); private Integer mChannelAutoStartTimeout; private Boolean mAutomaticDiagnosticMonitoring; + private Boolean mDarkMode; /** * Constructs an instance @@ -104,4 +106,29 @@ public class ApplicationPreference extends Preference mPreferences.putBoolean(PREFERENCE_KEY_CHANNEL_AUTO_DIAGNOSTIC_MONITORING, enabled); notifyPreferenceUpdated(); } + + /** + * Indicates whether dark mode is enabled for the application UI. + * @return enabled. + */ + public boolean isDarkMode() + { + if(mDarkMode == null) + { + mDarkMode = mPreferences.getBoolean(PREFERENCE_KEY_DARK_MODE, false); + } + + return mDarkMode; + } + + /** + * Sets the dark mode preference. + * @param enabled true to enable dark mode. + */ + public void setDarkMode(boolean enabled) + { + mDarkMode = enabled; + mPreferences.putBoolean(PREFERENCE_KEY_DARK_MODE, enabled); + notifyPreferenceUpdated(); + } } diff --git a/src/main/resources/sdrtrunk_dark.css b/src/main/resources/sdrtrunk_dark.css new file mode 100644 index 00000000..61c94983 --- /dev/null +++ b/src/main/resources/sdrtrunk_dark.css @@ -0,0 +1,383 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +/* sdrtrunk dark theme - applied on top of the default Modena stylesheet. */ + +.root +{ + -fx-base: #2b2b2b; + -fx-background: #2b2b2b; + -fx-control-inner-background: #313335; + -fx-control-inner-background-alt: #3c3f41; + -fx-accent: #4a90d9; + -fx-default-button: #4a90d9; + -fx-focus-color: #4a90d9; + -fx-faint-focus-color: #4a90d922; + -fx-text-fill: #d6d6d6; + -fx-text-base-color: #d6d6d6; + -fx-text-background-color: #d6d6d6; + -fx-mark-color: #d6d6d6; + -fx-mark-highlight-color: #ffffff; + -fx-selection-bar: #214283; + -fx-selection-bar-non-focused: #3a3d3f; + -fx-cell-hover-color: #3a3d3f; +} + +.label +{ + -fx-text-fill: #d6d6d6; +} + +.text +{ + -fx-fill: #d6d6d6; +} + +.button, .toggle-button, .menu-button, .split-menu-button, .choice-box, .combo-box-base +{ + -fx-background-color: #3c3f41, #4a4d4f, #3c3f41, #3c3f41; + -fx-text-fill: #d6d6d6; +} + +.button:hover, .toggle-button:hover, .menu-button:hover +{ + -fx-background-color: #4a90d9, #5aa0e9, #4a90d9, #3c3f41; +} + +.button:armed, .toggle-button:selected +{ + -fx-background-color: #214283, #3672c4, #214283, #214283; + -fx-text-fill: #ffffff; +} + +.text-field, .text-area, .password-field +{ + -fx-background-color: #313335; + -fx-text-fill: #d6d6d6; + -fx-prompt-text-fill: #888888; + -fx-highlight-fill: #214283; + -fx-highlight-text-fill: #ffffff; +} + +.text-area .content +{ + -fx-background-color: #313335; +} + +.scroll-pane, .scroll-pane > .viewport +{ + -fx-background-color: #2b2b2b; +} + +.scroll-bar +{ + -fx-background-color: #2b2b2b; +} + +.scroll-bar .thumb +{ + -fx-background-color: #555a5e; + -fx-background-radius: 3; +} + +.scroll-bar .thumb:hover +{ + -fx-background-color: #6a7075; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button +{ + -fx-background-color: #2b2b2b; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow +{ + -fx-background-color: #c0c0c0; +} + +.menu-bar +{ + -fx-background-color: #3c3f41; +} + +.menu, .menu-item, .context-menu, .menu-bar > .container > .menu-button +{ + -fx-background-color: transparent; +} + +.menu-item +{ + -fx-background-color: #3c3f41; +} + +.menu-item:focused, .menu-item:hover +{ + -fx-background-color: #4a90d9; +} + +.menu-item .label +{ + -fx-text-fill: #d6d6d6; +} + +.menu-item:focused .label, .menu-item:hover .label +{ + -fx-text-fill: #ffffff; +} + +.context-menu +{ + -fx-background-color: #3c3f41; + -fx-border-color: #555a5e; +} + +.tab-pane > .tab-header-area > .headers-region > .tab +{ + -fx-background-color: #3c3f41; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected +{ + -fx-background-color: #2b2b2b; +} + +.tab-pane > .tab-header-area > .headers-region > .tab .tab-label +{ + -fx-text-fill: #b0b0b0; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .tab-label +{ + -fx-text-fill: #ffffff; +} + +.tab-pane > .tab-header-area +{ + -fx-background-color: #2b2b2b; +} + +.table-view, .tree-view, .list-view, .tree-table-view +{ + -fx-background-color: #2b2b2b; + -fx-control-inner-background: #313335; + -fx-control-inner-background-alt: #353739; + -fx-table-cell-border-color: #3c3f41; +} + +.table-view .column-header, .table-view .filler, .table-view .column-header-background, +.tree-table-view .column-header, .tree-table-view .filler, .tree-table-view .column-header-background +{ + -fx-background-color: #3c3f41; +} + +.table-view .column-header .label, .tree-table-view .column-header .label +{ + -fx-text-fill: #d6d6d6; +} + +.table-row-cell, .tree-table-row-cell, .list-cell, .tree-cell +{ + -fx-text-fill: #d6d6d6; + -fx-background-color: -fx-control-inner-background; +} + +.table-row-cell:odd, .tree-table-row-cell:odd, .list-cell:odd, .tree-cell:odd +{ + -fx-background-color: -fx-control-inner-background-alt; +} + +.table-row-cell:selected, .tree-table-row-cell:selected, .list-cell:selected, .tree-cell:selected +{ + -fx-background-color: #214283; + -fx-text-fill: #ffffff; +} + +.table-row-cell:filled:hover, .tree-table-row-cell:filled:hover, .list-cell:filled:hover, .tree-cell:filled:hover +{ + -fx-background-color: #3a3d3f; +} + +.titled-pane > .title +{ + -fx-background-color: #3c3f41; +} + +.titled-pane > .title > .text +{ + -fx-fill: #d6d6d6; +} + +.titled-pane > .content +{ + -fx-background-color: #2b2b2b; + -fx-border-color: #3c3f41; +} + +.separator .line +{ + -fx-border-color: #444444; +} + +.tooltip +{ + -fx-background-color: #3c3f41; + -fx-text-fill: #d6d6d6; + -fx-border-color: #555a5e; +} + +.check-box .box, .radio-button .radio +{ + -fx-background-color: #3c3f41, #4a4d4f; +} + +.check-box:selected .mark, .radio-button:selected .dot +{ + -fx-background-color: #ffffff; +} + +.toggle-switch +{ + -fx-text-fill: #d6d6d6; +} + +.toggle-switch > .thumb-area +{ + -fx-background-color: #3c3f41; + -fx-border-color: #555a5e; +} + +.toggle-switch:selected > .thumb-area +{ + -fx-background-color: #4a90d9; + -fx-border-color: #4a90d9; +} + +.toggle-switch > .thumb +{ + -fx-background-color: #d6d6d6; +} + +.spinner +{ + -fx-background-color: #313335; +} + +.spinner .text-field +{ + -fx-background-color: #313335; + -fx-text-fill: #d6d6d6; +} + +.spinner .increment-arrow-button, .spinner .decrement-arrow-button +{ + -fx-background-color: #3c3f41; +} + +.spinner .increment-arrow, .spinner .decrement-arrow +{ + -fx-background-color: #d6d6d6; +} + +.progress-bar > .track +{ + -fx-background-color: #3c3f41; +} + +.progress-bar > .bar +{ + -fx-background-color: #4a90d9; +} + +.combo-box .list-cell, .choice-box .label +{ + -fx-text-fill: #d6d6d6; + -fx-background-color: transparent; +} + +.combo-box-popup .list-view +{ + -fx-background-color: #3c3f41; + -fx-border-color: #555a5e; +} + +.hyperlink +{ + -fx-text-fill: #6ab0ff; +} + +.hyperlink:visited +{ + -fx-text-fill: #9683c2; +} + +.dialog-pane +{ + -fx-background-color: #2b2b2b; +} + +.dialog-pane > .button-bar > .container +{ + -fx-background-color: #2b2b2b; +} + +.dialog-pane .header-panel +{ + -fx-background-color: #3c3f41; +} + +.dialog-pane .header-panel .label +{ + -fx-text-fill: #ffffff; +} + +.chart +{ + -fx-background-color: #2b2b2b; +} + +.chart-plot-background +{ + -fx-background-color: #313335; +} + +.axis +{ + -fx-tick-label-fill: #d6d6d6; +} + +.axis-label +{ + -fx-text-fill: #d6d6d6; +} + +.chart-vertical-grid-lines, .chart-horizontal-grid-lines +{ + -fx-stroke: #3c3f41; +} + +.chart-legend +{ + -fx-background-color: #313335; + -fx-text-fill: #d6d6d6; +} + +.chart-title +{ + -fx-text-fill: #d6d6d6; +}