mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 01:57:39 -05:00
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
This commit is contained in:
@@ -93,6 +93,7 @@ dependencies {
|
|||||||
implementation 'ch.qos.logback:logback-core:1.5.21'
|
implementation 'ch.qos.logback:logback-core:1.5.21'
|
||||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.1'
|
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.1'
|
||||||
implementation 'com.fazecast:jSerialComm:2.11.4'
|
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-font_awesome:4.7.0.1'
|
||||||
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
||||||
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
rootProject.name = 'sdr-trunk'
|
rootProject.name = 'sdr-trunk'
|
||||||
@@ -35,6 +35,7 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
|||||||
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
|
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
|
||||||
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
||||||
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
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.MessageRecordingViewer;
|
||||||
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
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
|
//JFXPanel has to be populated on the FX event thread
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Scene scene = new Scene(new StatusBox(resourceMonitor));
|
Scene scene = new Scene(new StatusBox(resourceMonitor));
|
||||||
|
ThemeManager.getInstance().register(scene);
|
||||||
mStatusPanel.setScene(scene);
|
mStatusPanel.setScene(scene);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -207,6 +209,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
{
|
{
|
||||||
createJFXPanel();
|
createJFXPanel();
|
||||||
Scene scene = new Scene(getRecordingViewer(), 1100, 800);
|
Scene scene = new Scene(getRecordingViewer(), 1100, 800);
|
||||||
|
ThemeManager.getInstance().register(scene);
|
||||||
mRecordingViewerStage = new Stage();
|
mRecordingViewerStage = new Stage();
|
||||||
mRecordingViewerStage.setTitle("sdrtrunk - Message Recording Viewer (.bits)");
|
mRecordingViewerStage.setTitle("sdrtrunk - Message Recording Viewer (.bits)");
|
||||||
mRecordingViewerStage.setScene(scene);
|
mRecordingViewerStage.setScene(scene);
|
||||||
@@ -232,6 +235,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
{
|
{
|
||||||
createJFXPanel();
|
createJFXPanel();
|
||||||
Scene scene = new Scene(getIconManager(), 500, 500);
|
Scene scene = new Scene(getIconManager(), 500, 500);
|
||||||
|
ThemeManager.getInstance().register(scene);
|
||||||
mIconManagerStage = new Stage();
|
mIconManagerStage = new Stage();
|
||||||
mIconManagerStage.setTitle("sdrtrunk - Icon Manager");
|
mIconManagerStage.setTitle("sdrtrunk - Icon Manager");
|
||||||
mIconManagerStage.setScene(scene);
|
mIconManagerStage.setScene(scene);
|
||||||
@@ -280,6 +284,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
{
|
{
|
||||||
createJFXPanel();
|
createJFXPanel();
|
||||||
Scene scene = new Scene(getJmbeEditor(), 650, 650);
|
Scene scene = new Scene(getJmbeEditor(), 650, 650);
|
||||||
|
ThemeManager.getInstance().register(scene);
|
||||||
mJmbeEditorStage = new Stage();
|
mJmbeEditorStage = new Stage();
|
||||||
mJmbeEditorStage.setTitle("sdrtrunk - JMBE Library Updater");
|
mJmbeEditorStage.setTitle("sdrtrunk - JMBE Library Updater");
|
||||||
mJmbeEditorStage.setScene(scene);
|
mJmbeEditorStage.setScene(scene);
|
||||||
@@ -321,6 +326,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
{
|
{
|
||||||
createJFXPanel();
|
createJFXPanel();
|
||||||
Scene scene = new Scene(getPlaylistEditor(), 1000, 750);
|
Scene scene = new Scene(getPlaylistEditor(), 1000, 750);
|
||||||
|
ThemeManager.getInstance().register(scene);
|
||||||
mPlaylistStage = new Stage();
|
mPlaylistStage = new Stage();
|
||||||
mPlaylistStage.setTitle("sdrtrunk - Playlist Editor");
|
mPlaylistStage.setTitle("sdrtrunk - Playlist Editor");
|
||||||
mPlaylistStage.setScene(scene);
|
mPlaylistStage.setScene(scene);
|
||||||
@@ -371,6 +377,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
{
|
{
|
||||||
createJFXPanel();
|
createJFXPanel();
|
||||||
Scene scene = new Scene(getUserPreferencesEditor(), 900, 500);
|
Scene scene = new Scene(getUserPreferencesEditor(), 900, 500);
|
||||||
|
ThemeManager.getInstance().register(scene);
|
||||||
mUserPreferencesStage = new Stage();
|
mUserPreferencesStage = new Stage();
|
||||||
mUserPreferencesStage.setTitle("sdrtrunk - User Preferences");
|
mUserPreferencesStage.setTitle("sdrtrunk - User Preferences");
|
||||||
mUserPreferencesStage.setScene(scene);
|
mUserPreferencesStage.setScene(scene);
|
||||||
@@ -414,6 +421,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
{
|
{
|
||||||
createJFXPanel();
|
createJFXPanel();
|
||||||
Scene scene = new Scene(getChannelMapEditor(), 500, 500);
|
Scene scene = new Scene(getChannelMapEditor(), 500, 500);
|
||||||
|
ThemeManager.getInstance().register(scene);
|
||||||
mChannelMapStage = new Stage();
|
mChannelMapStage = new Stage();
|
||||||
mChannelMapStage.setTitle("sdrtrunk - Channel Map Editor");
|
mChannelMapStage.setTitle("sdrtrunk - Channel Map Editor");
|
||||||
mChannelMapStage.setScene(scene);
|
mChannelMapStage.setScene(scene);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.gui;
|
package io.github.dsheirer.gui;
|
||||||
|
|
||||||
import com.jidesoft.plaf.LookAndFeelFactory;
|
|
||||||
import com.jidesoft.swing.JideSplitPane;
|
import com.jidesoft.swing.JideSplitPane;
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.DuplicateCallDetector;
|
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.PreferenceEditorType;
|
||||||
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
||||||
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
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.gui.viewer.ViewRecordingViewerRequest;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.log.ApplicationLog;
|
import io.github.dsheirer.log.ApplicationLog;
|
||||||
@@ -81,7 +81,6 @@ import java.nio.file.FileSystems;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -102,10 +101,8 @@ import javax.swing.JMenuItem;
|
|||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JSeparator;
|
import javax.swing.JSeparator;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
import javax.swing.UIManager;
|
|
||||||
import javax.swing.event.MenuEvent;
|
import javax.swing.event.MenuEvent;
|
||||||
import javax.swing.event.MenuListener;
|
import javax.swing.event.MenuListener;
|
||||||
import javax.swing.plaf.metal.MetalLookAndFeel;
|
|
||||||
|
|
||||||
public class SDRTrunk implements Listener<TunerEvent>
|
public class SDRTrunk implements Listener<TunerEvent>
|
||||||
{
|
{
|
||||||
@@ -147,6 +144,9 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
{
|
{
|
||||||
if(!GraphicsEnvironment.isHeadless())
|
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();
|
mMainGui = new JFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,21 +163,6 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
|
|
||||||
mResourceMonitor = new ResourceMonitor(mUserPreferences);
|
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();
|
ThreadPool.logSettings();
|
||||||
|
|
||||||
//Load properties file
|
//Load properties file
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public class ApplicationPreferenceEditor extends HBox
|
|||||||
private Label mAutoStartTimeoutLabel;
|
private Label mAutoStartTimeoutLabel;
|
||||||
private Spinner<Integer> mTimeoutSpinner;
|
private Spinner<Integer> mTimeoutSpinner;
|
||||||
private ToggleSwitch mAutomaticDiagnosticMonitoringToggle;
|
private ToggleSwitch mAutomaticDiagnosticMonitoringToggle;
|
||||||
|
private ToggleSwitch mDarkModeToggle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -74,8 +75,18 @@ public class ApplicationPreferenceEditor extends HBox
|
|||||||
mEditorPane.setHgap(3);
|
mEditorPane.setHgap(3);
|
||||||
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
|
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.");
|
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);
|
GridPane.setHalignment(getAutomaticDiagnosticMonitoringToggle(), HPos.RIGHT);
|
||||||
mEditorPane.add(getAutomaticDiagnosticMonitoringToggle(), 0, ++row);
|
mEditorPane.add(getAutomaticDiagnosticMonitoringToggle(), 0, ++row);
|
||||||
mEditorPane.add(new Label("Enable Diagnostic Monitoring"), 1, row, 2, 1);
|
mEditorPane.add(new Label("Enable Diagnostic Monitoring"), 1, row, 2, 1);
|
||||||
@@ -139,4 +150,20 @@ public class ApplicationPreferenceEditor extends HBox
|
|||||||
|
|
||||||
return mAutomaticDiagnosticMonitoringToggle;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
300
src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java
Normal file
300
src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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<WeakReference<Scene>> 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<WeakReference<Scene>> 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<WeakReference<Scene>> it = mScenes.iterator();
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
Scene existing = it.next().get();
|
||||||
|
if(existing == null)
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
else if(existing == scene)
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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_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_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 final static Logger mLog = LoggerFactory.getLogger(ApplicationPreference.class);
|
||||||
private Preferences mPreferences = Preferences.userNodeForPackage(ApplicationPreference.class);
|
private Preferences mPreferences = Preferences.userNodeForPackage(ApplicationPreference.class);
|
||||||
private Integer mChannelAutoStartTimeout;
|
private Integer mChannelAutoStartTimeout;
|
||||||
private Boolean mAutomaticDiagnosticMonitoring;
|
private Boolean mAutomaticDiagnosticMonitoring;
|
||||||
|
private Boolean mDarkMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -104,4 +106,29 @@ public class ApplicationPreference extends Preference
|
|||||||
mPreferences.putBoolean(PREFERENCE_KEY_CHANNEL_AUTO_DIAGNOSTIC_MONITORING, enabled);
|
mPreferences.putBoolean(PREFERENCE_KEY_CHANNEL_AUTO_DIAGNOSTIC_MONITORING, enabled);
|
||||||
notifyPreferenceUpdated();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
383
src/main/resources/sdrtrunk_dark.css
Normal file
383
src/main/resources/sdrtrunk_dark.css
Normal file
@@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user