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.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<WeakReference<Scene>> it = mScenes.iterator();

View File

@@ -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;
}