diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java index b286ff9a..6ac6ca53 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java @@ -181,7 +181,7 @@ public class FlatButtonUI private AtomicBoolean borderShared; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.canUseSharedUI( c ) + return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c ) ? FlatUIUtils.createSharedUI( FlatButtonUI.class, () -> new FlatButtonUI( true ) ) : new FlatButtonUI( false ); } @@ -193,6 +193,13 @@ public class FlatButtonUI @Override public void installUI( JComponent c ) { + if( FlatUIUtils.needsLightAWTPeer( c ) ) + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) ); + else + installUIImpl( c ); + } + + private void installUIImpl( JComponent c ) { super.installUI( c ); installStyle( (AbstractButton) c ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxUI.java index 354b30b1..f30d07e1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxUI.java @@ -43,7 +43,7 @@ public class FlatCheckBoxUI extends FlatRadioButtonUI { public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.canUseSharedUI( c ) + return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c ) ? FlatUIUtils.createSharedUI( FlatCheckBoxUI.class, () -> new FlatCheckBoxUI( true ) ) : new FlatCheckBoxUI( false ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java index a7b78c2d..155e9523 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java @@ -132,6 +132,7 @@ public class FlatComboBoxUI @Styleable protected String arrowType; protected boolean isIntelliJTheme; + private Color background; @Styleable protected Color editableBackground; @Styleable protected Color focusedBackground; @Styleable protected Color disabledBackground; @@ -165,6 +166,13 @@ public class FlatComboBoxUI @Override public void installUI( JComponent c ) { + if( FlatUIUtils.needsLightAWTPeer( c ) ) + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) ); + else + installUIImpl( c ); + } + + private void installUIImpl( JComponent c ) { super.installUI( c ); installStyle(); @@ -227,6 +235,7 @@ public class FlatComboBoxUI arrowType = UIManager.getString( "Component.arrowType" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); + background = UIManager.getColor( "ComboBox.background" ); editableBackground = UIManager.getColor( "ComboBox.editableBackground" ); focusedBackground = UIManager.getColor( "ComboBox.focusedBackground" ); disabledBackground = UIManager.getColor( "ComboBox.disabledBackground" ); @@ -259,6 +268,7 @@ public class FlatComboBoxUI protected void uninstallDefaults() { super.uninstallDefaults(); + background = null; editableBackground = null; focusedBackground = null; disabledBackground = null; @@ -632,7 +642,7 @@ public class FlatComboBoxUI protected Color getBackground( boolean enabled ) { if( enabled ) { if( FlatUIUtils.isAWTPeer( comboBox ) ) - return UIManager.getColor( "ComboBox.background" ); + return background; Color background = comboBox.getBackground(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java index 9440d2dd..01badb34 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java @@ -90,6 +90,13 @@ public class FlatListUI @Override public void installUI( JComponent c ) { + if( FlatUIUtils.needsLightAWTPeer( c ) ) + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) ); + else + installUIImpl( c ); + } + + private void installUIImpl( JComponent c ) { super.installUI( c ); installStyle(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java index df9304d1..119c08bc 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java @@ -83,7 +83,7 @@ public class FlatRadioButtonUI private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.canUseSharedUI( c ) + return FlatUIUtils.canUseSharedUI( c ) && !FlatUIUtils.needsLightAWTPeer( c ) ? FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, () -> new FlatRadioButtonUI( true ) ) : new FlatRadioButtonUI( false ); } @@ -95,6 +95,13 @@ public class FlatRadioButtonUI @Override public void installUI( JComponent c ) { + if( FlatUIUtils.needsLightAWTPeer( c ) ) + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) ); + else + installUIImpl( c ); + } + + private void installUIImpl( JComponent c ) { super.installUI( c ); if( FlatUIUtils.isAWTPeer( c ) ) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java index f6256ff7..4bb1f824 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java @@ -17,6 +17,7 @@ package com.formdev.flatlaf.ui; import java.awt.Color; +import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; @@ -37,11 +38,13 @@ import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicScrollBarUI; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; +import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; /** @@ -230,6 +233,24 @@ public class FlatScrollBarUI } SwingUtilities.replaceUIInputMap( scrollbar, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap ); break; + + case "ancestor": + // check whether scroll bar is used as AWT peer on macOS and + // dark theme is active --> reinstall using light theme + if( SystemInfo.isMacOS && FlatLaf.isLafDark() ) { + Container p = scrollbar.getParent(); + for( int i = 0; i < 2 && p != null; i++, p = p.getParent() ) { + if( FlatUIUtils.isAWTPeer( p ) ) { + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> { + JScrollBar scrollbar = this.scrollbar; + uninstallUI( scrollbar ); + installUI( scrollbar ); + } ); + break; + } + } + } + break; } }; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index f79e1265..c28efa5d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -87,6 +87,13 @@ public class FlatScrollPaneUI @Override public void installUI( JComponent c ) { + if( FlatUIUtils.needsLightAWTPeer( c ) ) + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) ); + else + installUIImpl( c ); + } + + private void installUIImpl( JComponent c ) { super.installUI( c ); int focusWidth = UIManager.getInt( "Component.focusWidth" ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java index c8724f1e..9d38d5bd 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java @@ -86,6 +86,13 @@ public class FlatTextAreaUI @Override public void installUI( JComponent c ) { + if( FlatUIUtils.needsLightAWTPeer( c ) ) + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) ); + else + installUIImpl( c ); + } + + private void installUIImpl( JComponent c ) { super.installUI( c ); installStyle(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java index 3426b10a..84f41e37 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java @@ -128,6 +128,13 @@ public class FlatTextFieldUI @Override public void installUI( JComponent c ) { + if( FlatUIUtils.needsLightAWTPeer( c ) ) + FlatUIUtils.runWithLightAWTPeerUIDefaults( () -> installUIImpl( c ) ); + else + installUIImpl( c ); + } + + private void installUIImpl( JComponent c ) { super.installUI( c ); leadingIcon = clientProperty( c, TEXT_FIELD_LEADING_ICON, null, Icon.class ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java index df5fd2a8..f0dcd89d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java @@ -33,6 +33,7 @@ import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; +import java.awt.SystemColor; import java.awt.Window; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; @@ -45,16 +46,22 @@ import java.util.WeakHashMap; import java.util.function.Predicate; import java.util.function.Supplier; import javax.swing.JComponent; +import javax.swing.JScrollPane; import javax.swing.JTable; +import javax.swing.JTextField; import javax.swing.LookAndFeel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.FlatIntelliJLaf; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.Graphics2DProxy; @@ -257,13 +264,38 @@ public class FlatUIUtils (window != null && window.getType() == Window.Type.POPUP && window.getOwner() == activeWindow); } - static boolean isAWTPeer( JComponent c ) { + static boolean isAWTPeer( Component c ) { // on macOS, Swing components are used for AWT components if( SystemInfo.isMacOS ) return c.getClass().getName().startsWith( "sun.lwawt.LW" ); return false; } + /** + * Checks whether component is used as peer for AWT (on macOS) and + * whether a dark FlatLaf theme is active, which requires special handling + * because AWT always uses light colors. + */ + static boolean needsLightAWTPeer( JComponent c ) { + return FlatUIUtils.isAWTPeer( c ) && FlatLaf.isLafDark(); + } + + private static UIDefaults lightAWTPeerDefaults; + + static void runWithLightAWTPeerUIDefaults( Runnable runnable ) { + if( lightAWTPeerDefaults == null ) { + FlatLaf lightLaf = UIManager.getInt( "Component.focusWidth" ) >= 2 + ? new FlatIntelliJLaf() + : new FlatLightLaf(); + lightAWTPeerDefaults = lightLaf.getDefaults(); + } + + FlatLaf.runWithUIDefaultsGetter( key -> { + Object value = lightAWTPeerDefaults.get( key ); + return (value != null) ? value : FlatLaf.NULL_VALUE; + }, runnable ); + } + /** * Returns whether the given component is in a window that is in full-screen mode. */ @@ -694,7 +726,17 @@ public class FlatUIUtils // parent.getBackground() may return null // (e.g. for Swing delegate components used for AWT components on macOS) Color background = (parent != null) ? parent.getBackground() : null; - return (background != null) ? background : UIManager.getColor( "Panel.background" ); + if( background != null ) + return background; + + if( isAWTPeer( c ) ) { + // AWT peers usually use component background, except for TextField and ScrollPane + return c instanceof JTextField || c instanceof JScrollPane || c.getBackground() == null + ? SystemColor.window + : c.getBackground(); + } + + return UIManager.getColor( "Panel.background" ); } /** diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAWTTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAWTTest.java index f2eb6dfe..ff56d8e6 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAWTTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAWTTest.java @@ -22,7 +22,7 @@ import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.SwingUtilities; -import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.*; /** * Used to test AWT components on macOS, which internally use Swing. @@ -31,9 +31,14 @@ import com.formdev.flatlaf.FlatLightLaf; */ public class FlatAWTTest { + private static Color oldBackground; + public static void main( String[] args ) { EventQueue.invokeLater( () -> { FlatLightLaf.setup(); +// FlatIntelliJLaf.setup(); +// FlatDarkLaf.setup(); +// FlatDarculaLaf.setup(); Frame frame = new Frame( "FlatAWTTest" ); frame.addWindowListener( new WindowAdapter() { @@ -54,24 +59,37 @@ public class FlatAWTTest frame.add( new Checkbox( "radio 3", false, checkboxGroup ) ); Choice choice = new Choice(); - choice.add( "item 1" ); - choice.add( "item 2" ); - choice.add( "item 3" ); + for( int i = 1; i <= 20; i++ ) + choice.add( "item " + i ); frame.add( choice ); frame.add( new TextField( "text" ) ); - frame.add( new TextArea( "text" ) ); + frame.add( new TextArea( "text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15" ) ); List list = new List(); - list.add( "item 1" ); - list.add( "item 2" ); + for( int i = 1; i <= 10; i++ ) + list.add( "item " + i ); + list.select( 1 ); frame.add( list ); frame.add( new Scrollbar() ); - frame.add( new ScrollPane() ); + frame.add( new ScrollPane( ScrollPane.SCROLLBARS_ALWAYS ) ); frame.add( new Panel() ); frame.add( new Canvas() ); +/* + java.beans.PropertyChangeListener pcl = e -> { + System.out.println( e.getSource().getClass().getName() + + ": " + e.getPropertyName() + " " + e.getOldValue() + " --> " + e.getNewValue() ); + }; + for( Component c : frame.getComponents() ) { + c.addPropertyChangeListener( "background", pcl ); + c.addPropertyChangeListener( "foreground", pcl ); + } + frame.addPropertyChangeListener( "background", pcl ); + frame.addPropertyChangeListener( "foreground", pcl ); +*/ + Panel controlPanel = new Panel(); frame.add( controlPanel ); @@ -89,12 +107,22 @@ public class FlatAWTTest explicitColorsCheckBox.addItemListener( e -> { boolean explicit = explicitColorsCheckBox.getState(); for( Component c : frame.getComponents() ) { - if( c != controlPanel ) + if( c != controlPanel ) { c.setBackground( explicit ? Color.green : null ); + c.setForeground( explicit ? Color.red : null ); + } } } ); controlPanel.add( explicitColorsCheckBox ); + Checkbox backgroundColorsCheckBox = new Checkbox( "background color" ); + backgroundColorsCheckBox.addItemListener( e -> { + if( oldBackground == null ) + oldBackground = frame.getBackground(); + frame.setBackground( backgroundColorsCheckBox.getState() ? Color.orange : oldBackground ); + } ); + controlPanel.add( backgroundColorsCheckBox ); + Menu menu = new Menu( "File" ); menu.add( new MenuItem( "New" ) ); menu.add( new MenuItem( "Open" ) );