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();