From 9bf35fcaaac479e412314cfb863d591e86a32345 Mon Sep 17 00:00:00 2001 From: Akumas_Coffin_ <68540569+AkumasCoffin@users.noreply.github.com> Date: Thu, 14 May 2026 20:42:56 +1000 Subject: [PATCH] Force-override Swing foreground colors via UIManager user-defaults layer Symptom: With dark mode enabled the inner Swing content area now had dark panels (good) but tab labels, table cells, status-bar labels and button text were rendering nearly black-on-dark (bad). Root cause: UIManager.getLookAndFeelDefaults() lookups for FlatLaf's component foreground keys still resolve to Metal-era dark values after the JIDE install + FlatLaf re-apply, because some FlatLaf keys are derived / LazyValue and recompute against state that JIDE seeded. Fix: After the bulk FlatLaf-defaults copy, explicitly UIManager.put the foreground / background / selection / disabled / border colors for every standard Swing component family. UIManager.put writes to the user-defaults layer that takes precedence over the LAF defaults, so these overrides win regardless of what JIDE or derived-value resolution produces. Also bump the JavaFX dark stylesheet text colour from #d6d6d6 to #ececec so labels read closer to white on the dark panels. --- .../dsheirer/gui/theme/ThemeManager.java | 134 ++++++++++++++++-- src/main/resources/sdrtrunk_dark.css | 44 +++--- 2 files changed, 148 insertions(+), 30 deletions(-) diff --git a/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java b/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java index 3664f36c..46a36128 100644 --- a/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java +++ b/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java @@ -26,6 +26,7 @@ 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.Color; import java.awt.EventQueue; import java.awt.Window; import java.lang.ref.WeakReference; @@ -270,11 +271,9 @@ public class ThemeManager } //JIDE's Metal-flavored extension overwrites FlatLaf's color/font/border defaults for - //standard Swing component keys (Table.background, TabbedPane.foreground, etc.) which - //leaves the inner content of the app looking like a Metal LAF island in a FlatLaf - //chrome. Re-apply FlatLaf's standard defaults on top of JIDE's install - JIDE's UI - //delegate registrations live under Jide-prefixed keys that FlatLaf does not define, - //so they survive untouched. + //standard Swing component keys. Re-apply FlatLaf's defaults on top of JIDE's install + //so the inner panels pick up dark backgrounds. JIDE's UI delegate registrations live + //under Jide-prefixed keys that FlatLaf does not define, so they survive untouched. try { UIDefaults flatFresh = (darkMode ? new FlatDarkLaf() : new FlatLightLaf()).getDefaults(); @@ -285,9 +284,6 @@ public class ThemeManager Object value = entry.getValue(); String keyName = String.valueOf(key); - //Do not overwrite JIDE-specific keys, even though FlatLaf would not normally - //define them - belt-and-braces in case a future FlatLaf version adds keys - //whose names happen to start with "Jide". if(keyName.startsWith("Jide") || keyName.contains(".Jide")) { continue; @@ -304,6 +300,14 @@ public class ThemeManager mLog.warn("Unable to re-apply FlatLaf defaults after JIDE install", t); } + //FlatLaf's per-component foreground/background lookups go through derived/lazy values + //that JIDE's Metal initializer was also feeding from, so even after the defaults copy + //above some labels and buttons end up with Metal-era dark foregrounds on a dark + //background. Brute-force the standard component colour keys via UIManager.put which + //writes to the user-defaults layer that takes precedence over the LAF defaults. Use + //near-white rather than FlatLaf's #bbbbbb so text reads sharply on the dark panels. + applyExplicitOverrides(darkMode); + //Re-rendering any already-realized Swing windows must happen on the EDT. Runnable updateRealized = () -> { for(Window window: Window.getWindows()) @@ -322,6 +326,120 @@ public class ThemeManager } } + private void applyExplicitOverrides(boolean darkMode) + { + Color bgPanel; + Color bgRaised; + Color bgInput; + Color bgSelection; + Color fgPrimary; + Color fgSelection; + Color fgDisabled; + Color border; + + if(darkMode) + { + bgPanel = new Color(0x2b2b2b); + bgRaised = new Color(0x3c3f41); + bgInput = new Color(0x313335); + bgSelection = new Color(0x214283); + fgPrimary = new Color(0xe6e6e6); + fgSelection = Color.WHITE; + fgDisabled = new Color(0x6a6a6a); + border = new Color(0x4f5356); + } + else + { + //Light mode mirrors FlatLightLaf's palette. + bgPanel = new Color(0xf2f2f2); + bgRaised = new Color(0xffffff); + bgInput = new Color(0xffffff); + bgSelection = new Color(0x2675bf); + fgPrimary = new Color(0x1e1e1e); + fgSelection = Color.WHITE; + fgDisabled = new Color(0x8c8c8c); + border = new Color(0xc4c4c4); + } + + String[] simpleComponents = { + "Button", "ToggleButton", "CheckBox", "RadioButton", + "Label", "Menu", "MenuItem", "CheckBoxMenuItem", "RadioButtonMenuItem", + "MenuBar", "PopupMenu", "Panel", "Viewport", "ScrollPane", + "Separator", "ToolBar", "ToolTip", "FormattedTextField", + "TitledBorder", "TabbedPane", "OptionPane", "ProgressBar", + "Spinner", "Slider", "ComboBox", "EditorPane", "PasswordField" + }; + + for(String c: simpleComponents) + { + UIManager.put(c + ".background", bgPanel); + UIManager.put(c + ".foreground", fgPrimary); + UIManager.put(c + ".disabledForeground", fgDisabled); + UIManager.put(c + ".disabledText", fgDisabled); + } + + //Buttons sit on a slightly raised tone than the panel they live on. + for(String c: new String[]{"Button", "ToggleButton", "ComboBox", "Spinner"}) + { + UIManager.put(c + ".background", bgRaised); + } + + //Text inputs use their own background tone for affordance. + for(String c: new String[]{"TextField", "TextArea", "TextPane", "EditorPane", + "FormattedTextField", "PasswordField"}) + { + UIManager.put(c + ".background", bgInput); + UIManager.put(c + ".foreground", fgPrimary); + UIManager.put(c + ".caretForeground", fgPrimary); + UIManager.put(c + ".inactiveForeground", fgDisabled); + UIManager.put(c + ".selectionBackground", bgSelection); + UIManager.put(c + ".selectionForeground", fgSelection); + } + + //Tables, trees and lists. + for(String c: new String[]{"Table", "Tree", "List"}) + { + UIManager.put(c + ".background", bgInput); + UIManager.put(c + ".foreground", fgPrimary); + UIManager.put(c + ".selectionBackground", bgSelection); + UIManager.put(c + ".selectionForeground", fgSelection); + } + + UIManager.put("Table.gridColor", border); + UIManager.put("Table.alternateRowColor", new Color(darkMode ? 0x353739 : 0xfafafa)); + UIManager.put("TableHeader.background", bgRaised); + UIManager.put("TableHeader.foreground", fgPrimary); + UIManager.put("TableHeader.cellBorder", javax.swing.BorderFactory.createMatteBorder(0, 0, 1, 1, border)); + + //Tab labels read black-on-dark with the JIDE-flavoured TabbedPane otherwise. + UIManager.put("TabbedPane.foreground", fgPrimary); + UIManager.put("TabbedPane.background", bgPanel); + UIManager.put("TabbedPane.selectedForeground", fgSelection); + UIManager.put("TabbedPane.selected", bgRaised); + UIManager.put("TabbedPane.contentAreaColor", bgPanel); + UIManager.put("TabbedPane.darkShadow", border); + UIManager.put("TabbedPane.shadow", border); + + //Menu hover and selected states. + UIManager.put("MenuItem.selectionBackground", bgSelection); + UIManager.put("MenuItem.selectionForeground", fgSelection); + UIManager.put("Menu.selectionBackground", bgSelection); + UIManager.put("Menu.selectionForeground", fgSelection); + + //Borders and split-pane divider colour. + UIManager.put("SplitPane.background", bgPanel); + UIManager.put("SplitPane.dividerFocusColor", bgSelection); + UIManager.put("SplitPaneDivider.draggingColor", bgSelection); + UIManager.put("Component.borderColor", border); + UIManager.put("Component.disabledBorderColor", border); + UIManager.put("Separator.foreground", border); + UIManager.put("Separator.background", bgPanel); + + //ToolTip needs to stand out from the panel. + UIManager.put("ToolTip.background", bgRaised); + UIManager.put("ToolTip.foreground", fgPrimary); + } + private void pruneDeadReferencesAndCheckPresent(Scene scene) { Iterator> it = mScenes.iterator(); diff --git a/src/main/resources/sdrtrunk_dark.css b/src/main/resources/sdrtrunk_dark.css index b3e6e35f..61183a07 100644 --- a/src/main/resources/sdrtrunk_dark.css +++ b/src/main/resources/sdrtrunk_dark.css @@ -29,10 +29,10 @@ -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-text-fill: #ececec; + -fx-text-base-color: #ececec; + -fx-text-background-color: #ececec; + -fx-mark-color: #ececec; -fx-mark-highlight-color: #ffffff; -fx-selection-bar: #214283; -fx-selection-bar-non-focused: #3a3d3f; @@ -41,18 +41,18 @@ .label { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .text { - -fx-fill: #d6d6d6; + -fx-fill: #ececec; } .button, .toggle-button, .menu-button, .split-menu-button, .choice-box, .combo-box-base { -fx-background-color: #3c3f41, #4a4d4f, #3c3f41, #3c3f41; - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .button:hover, .toggle-button:hover, .menu-button:hover @@ -69,7 +69,7 @@ .text-field, .text-area, .password-field { -fx-background-color: #313335; - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; -fx-prompt-text-fill: #888888; -fx-highlight-fill: #214283; -fx-highlight-text-fill: #ffffff; @@ -135,7 +135,7 @@ .menu-item .label { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .menu-item:focused .label, .menu-item:hover .label @@ -192,12 +192,12 @@ .table-view .column-header .label, .tree-table-view .column-header .label { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .table-row-cell, .tree-table-row-cell, .list-cell, .tree-cell { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; -fx-background-color: -fx-control-inner-background; } @@ -224,7 +224,7 @@ .titled-pane > .title > .text { - -fx-fill: #d6d6d6; + -fx-fill: #ececec; } .titled-pane > .content @@ -254,7 +254,7 @@ .tooltip { -fx-background-color: #3c3f41; - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; -fx-border-color: #555a5e; } @@ -270,7 +270,7 @@ .toggle-switch { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .toggle-switch > .thumb-area @@ -287,7 +287,7 @@ .toggle-switch > .thumb { - -fx-background-color: #d6d6d6; + -fx-background-color: #ececec; } .spinner @@ -298,7 +298,7 @@ .spinner .text-field { -fx-background-color: #313335; - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .spinner .increment-arrow-button, .spinner .decrement-arrow-button @@ -308,7 +308,7 @@ .spinner .increment-arrow, .spinner .decrement-arrow { - -fx-background-color: #d6d6d6; + -fx-background-color: #ececec; } .progress-bar > .track @@ -323,7 +323,7 @@ .combo-box .list-cell, .choice-box .label { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; -fx-background-color: transparent; } @@ -375,12 +375,12 @@ .axis { - -fx-tick-label-fill: #d6d6d6; + -fx-tick-label-fill: #ececec; } .axis-label { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .chart-vertical-grid-lines, .chart-horizontal-grid-lines @@ -391,10 +391,10 @@ .chart-legend { -fx-background-color: #313335; - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; } .chart-title { - -fx-text-fill: #d6d6d6; + -fx-text-fill: #ececec; }