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.
This commit is contained in:
Akumas_Coffin_
2026-05-14 20:42:56 +10:00
parent f4dbe4593c
commit 9bf35fcaaa
2 changed files with 148 additions and 30 deletions

View File

@@ -26,6 +26,7 @@ import com.jidesoft.plaf.LookAndFeelFactory;
import io.github.dsheirer.eventbus.MyEventBus; import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.UserPreferences;
import java.awt.Color;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Window; import java.awt.Window;
import java.lang.ref.WeakReference; 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 //JIDE's Metal-flavored extension overwrites FlatLaf's color/font/border defaults for
//standard Swing component keys (Table.background, TabbedPane.foreground, etc.) which //standard Swing component keys. Re-apply FlatLaf's defaults on top of JIDE's install
//leaves the inner content of the app looking like a Metal LAF island in a FlatLaf //so the inner panels pick up dark backgrounds. JIDE's UI delegate registrations live
//chrome. Re-apply FlatLaf's standard defaults on top of JIDE's install - JIDE's UI //under Jide-prefixed keys that FlatLaf does not define, so they survive untouched.
//delegate registrations live under Jide-prefixed keys that FlatLaf does not define,
//so they survive untouched.
try try
{ {
UIDefaults flatFresh = (darkMode ? new FlatDarkLaf() : new FlatLightLaf()).getDefaults(); UIDefaults flatFresh = (darkMode ? new FlatDarkLaf() : new FlatLightLaf()).getDefaults();
@@ -285,9 +284,6 @@ public class ThemeManager
Object value = entry.getValue(); Object value = entry.getValue();
String keyName = String.valueOf(key); 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")) if(keyName.startsWith("Jide") || keyName.contains(".Jide"))
{ {
continue; continue;
@@ -304,6 +300,14 @@ public class ThemeManager
mLog.warn("Unable to re-apply FlatLaf defaults after JIDE install", t); 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. //Re-rendering any already-realized Swing windows must happen on the EDT.
Runnable updateRealized = () -> { Runnable updateRealized = () -> {
for(Window window: Window.getWindows()) 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) private void pruneDeadReferencesAndCheckPresent(Scene scene)
{ {
Iterator<WeakReference<Scene>> it = mScenes.iterator(); Iterator<WeakReference<Scene>> it = mScenes.iterator();

View File

@@ -29,10 +29,10 @@
-fx-default-button: #4a90d9; -fx-default-button: #4a90d9;
-fx-focus-color: #4a90d9; -fx-focus-color: #4a90d9;
-fx-faint-focus-color: #4a90d922; -fx-faint-focus-color: #4a90d922;
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
-fx-text-base-color: #d6d6d6; -fx-text-base-color: #ececec;
-fx-text-background-color: #d6d6d6; -fx-text-background-color: #ececec;
-fx-mark-color: #d6d6d6; -fx-mark-color: #ececec;
-fx-mark-highlight-color: #ffffff; -fx-mark-highlight-color: #ffffff;
-fx-selection-bar: #214283; -fx-selection-bar: #214283;
-fx-selection-bar-non-focused: #3a3d3f; -fx-selection-bar-non-focused: #3a3d3f;
@@ -41,18 +41,18 @@
.label .label
{ {
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }
.text .text
{ {
-fx-fill: #d6d6d6; -fx-fill: #ececec;
} }
.button, .toggle-button, .menu-button, .split-menu-button, .choice-box, .combo-box-base .button, .toggle-button, .menu-button, .split-menu-button, .choice-box, .combo-box-base
{ {
-fx-background-color: #3c3f41, #4a4d4f, #3c3f41, #3c3f41; -fx-background-color: #3c3f41, #4a4d4f, #3c3f41, #3c3f41;
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }
.button:hover, .toggle-button:hover, .menu-button:hover .button:hover, .toggle-button:hover, .menu-button:hover
@@ -69,7 +69,7 @@
.text-field, .text-area, .password-field .text-field, .text-area, .password-field
{ {
-fx-background-color: #313335; -fx-background-color: #313335;
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
-fx-prompt-text-fill: #888888; -fx-prompt-text-fill: #888888;
-fx-highlight-fill: #214283; -fx-highlight-fill: #214283;
-fx-highlight-text-fill: #ffffff; -fx-highlight-text-fill: #ffffff;
@@ -135,7 +135,7 @@
.menu-item .label .menu-item .label
{ {
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }
.menu-item:focused .label, .menu-item:hover .label .menu-item:focused .label, .menu-item:hover .label
@@ -192,12 +192,12 @@
.table-view .column-header .label, .tree-table-view .column-header .label .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 .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; -fx-background-color: -fx-control-inner-background;
} }
@@ -224,7 +224,7 @@
.titled-pane > .title > .text .titled-pane > .title > .text
{ {
-fx-fill: #d6d6d6; -fx-fill: #ececec;
} }
.titled-pane > .content .titled-pane > .content
@@ -254,7 +254,7 @@
.tooltip .tooltip
{ {
-fx-background-color: #3c3f41; -fx-background-color: #3c3f41;
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
-fx-border-color: #555a5e; -fx-border-color: #555a5e;
} }
@@ -270,7 +270,7 @@
.toggle-switch .toggle-switch
{ {
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }
.toggle-switch > .thumb-area .toggle-switch > .thumb-area
@@ -287,7 +287,7 @@
.toggle-switch > .thumb .toggle-switch > .thumb
{ {
-fx-background-color: #d6d6d6; -fx-background-color: #ececec;
} }
.spinner .spinner
@@ -298,7 +298,7 @@
.spinner .text-field .spinner .text-field
{ {
-fx-background-color: #313335; -fx-background-color: #313335;
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }
.spinner .increment-arrow-button, .spinner .decrement-arrow-button .spinner .increment-arrow-button, .spinner .decrement-arrow-button
@@ -308,7 +308,7 @@
.spinner .increment-arrow, .spinner .decrement-arrow .spinner .increment-arrow, .spinner .decrement-arrow
{ {
-fx-background-color: #d6d6d6; -fx-background-color: #ececec;
} }
.progress-bar > .track .progress-bar > .track
@@ -323,7 +323,7 @@
.combo-box .list-cell, .choice-box .label .combo-box .list-cell, .choice-box .label
{ {
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
-fx-background-color: transparent; -fx-background-color: transparent;
} }
@@ -375,12 +375,12 @@
.axis .axis
{ {
-fx-tick-label-fill: #d6d6d6; -fx-tick-label-fill: #ececec;
} }
.axis-label .axis-label
{ {
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }
.chart-vertical-grid-lines, .chart-horizontal-grid-lines .chart-vertical-grid-lines, .chart-horizontal-grid-lines
@@ -391,10 +391,10 @@
.chart-legend .chart-legend
{ {
-fx-background-color: #313335; -fx-background-color: #313335;
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }
.chart-title .chart-title
{ {
-fx-text-fill: #d6d6d6; -fx-text-fill: #ececec;
} }