fixed AWT components on macOS (issue #583)

- fixed missing focus indicator
- fixed round corners
- fixed java.awt.Button background
- fixed java.awt.Choice background
- fixed java.awt.Checkbox hover
This commit is contained in:
Karl Tauber
2022-09-05 14:47:17 +02:00
parent 30132aa6b0
commit 7858e42e37
6 changed files with 160 additions and 8 deletions

View File

@@ -18,6 +18,8 @@ FlatLaf Change Log
- Fixed missing UI value `MenuItem.acceleratorDelimiter` on macOS. (was `null`,
is now an empty string)
- Fixed possible exception in `FlatUIUtils.resetRenderingHints()`. (issue #575)
- Fixed AWT components on macOS, which use Swing components internally. (issue
#583)
## 2.4

View File

@@ -629,6 +629,9 @@ public class FlatButtonUI
}
protected Color getBackgroundBase( JComponent c, boolean def ) {
if( FlatUIUtils.isAWTPeer( c ) )
return background;
// use component background if explicitly set
Color bg = c.getBackground();
if( isCustomBackground( bg ) )

View File

@@ -631,6 +631,9 @@ public class FlatComboBoxUI
protected Color getBackground( boolean enabled ) {
if( enabled ) {
if( FlatUIUtils.isAWTPeer( comboBox ) )
return UIManager.getColor( "ComboBox.background" );
Color background = comboBox.getBackground();
// always use explicitly set color

View File

@@ -18,12 +18,16 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Objects;
import javax.swing.AbstractButton;
@@ -31,6 +35,7 @@ import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener;
@@ -92,9 +97,20 @@ public class FlatRadioButtonUI
public void installUI( JComponent c ) {
super.installUI( c );
if( FlatUIUtils.isAWTPeer( c ) )
AWTPeerMouseExitedFix.install( c );
installStyle( (AbstractButton) c );
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
if( FlatUIUtils.isAWTPeer( c ) )
AWTPeerMouseExitedFix.uninstall( c );
}
@Override
public void installDefaults( AbstractButton b ) {
super.installDefaults( b );
@@ -321,4 +337,69 @@ public class FlatRadioButtonUI
FlatRadioButtonUI.this.propertyChange( b, e );
}
}
//---- class AWTPeerMouseExitedFix ----------------------------------------
/**
* Hack for missing mouse-exited event for java.awt.Checkbox on macOS (to fix hover effect).
*
* On macOS, AWT components internally use Swing components.
* This is implemented in class sun.lwawt.LWCheckboxPeer, which uses
* a container component CheckboxDelegate that has a JCheckBox and a JRadioButton
* as children. Only one of them is visible.
*
* The reason that mouse-exited event is not sent to the JCheckBox or JRadioButton
* is that sun.lwawt.LWComponentPeer.createDelegateEvent() uses
* SwingUtilities.getDeepestComponentAt() to find the event target,
* which finds the container component CheckboxDelegate,
* which receives the mouse-exited event.
*
* This class adds listeners and forwards the mouse-exited event
* from CheckboxDelegate to JCheckBox or JRadioButton.
*/
private static class AWTPeerMouseExitedFix
extends MouseAdapter
implements PropertyChangeListener
{
private final JComponent button;
static void install( JComponent button ) {
AWTPeerMouseExitedFix l = new AWTPeerMouseExitedFix( button );
button.addPropertyChangeListener( "ancestor", l );
Container parent = button.getParent();
if( parent != null )
parent.addMouseListener( l );
}
static void uninstall( JComponent button ) {
for( PropertyChangeListener l : button.getPropertyChangeListeners( "ancestor" ) ) {
if( l instanceof AWTPeerMouseExitedFix ) {
button.removePropertyChangeListener( "ancestor", l );
Container parent = button.getParent();
if( parent != null )
parent.removeMouseListener( (AWTPeerMouseExitedFix) l );
break;
}
}
}
AWTPeerMouseExitedFix( JComponent button ) {
this.button = button;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( e.getOldValue() instanceof Component )
((Component)e.getOldValue()).removeMouseListener( this );
if( e.getNewValue() instanceof Component ) {
((Component)e.getNewValue()).removeMouseListener( this ); // avoid duplicate listeners
((Component)e.getNewValue()).addMouseListener( this );
}
}
@Override
public void mouseExited( MouseEvent e ) {
button.dispatchEvent( SwingUtilities.convertMouseEvent( e.getComponent(), e, button ) );
}
}
}

View File

@@ -59,6 +59,7 @@ import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -241,6 +242,11 @@ public class FlatUIUtils
}
}
// invoke hasFocus() here because components may have overridden this method
// (e.g. Swing delegate components used for AWT components on macOS)
if( c.hasFocus() )
return true;
return keyboardFocusManager.getPermanentFocusOwner() == c &&
isInActiveWindow( c, keyboardFocusManager.getActiveWindow() );
}
@@ -251,6 +257,13 @@ public class FlatUIUtils
(window != null && window.getType() == Window.Type.POPUP && window.getOwner() == activeWindow);
}
static boolean isAWTPeer( JComponent c ) {
// on macOS, Swing components are used for AWT components
if( SystemInfo.isMacOS )
return c.getClass().getName().startsWith( "sun.lwawt.LW" );
return false;
}
/**
* Returns whether the given component is in a window that is in full-screen mode.
*/
@@ -335,7 +348,7 @@ public class FlatUIUtils
*/
public static Object[] setRenderingHints( Graphics g ) {
Graphics2D g2 = (Graphics2D) g;
Object[] oldRenderingHints = new Object[] {
Object[] oldRenderingHints = {
g2.getRenderingHint( RenderingHints.KEY_ANTIALIASING ),
g2.getRenderingHint( RenderingHints.KEY_STROKE_CONTROL ),
};
@@ -378,7 +391,7 @@ public class FlatUIUtils
}
Graphics2D g2 = (Graphics2D) g;
Object[] oldRenderingHints2 = new Object[] {
Object[] oldRenderingHints2 = {
g2.getRenderingHint( RenderingHints.KEY_ANTIALIASING ),
g2.getRenderingHint( RenderingHints.KEY_STROKE_CONTROL ),
};
@@ -666,9 +679,9 @@ public class FlatUIUtils
* is smaller than its bounds (for the focus decoration).
*/
public static void paintParentBackground( Graphics g, JComponent c ) {
Container parent = findOpaqueParent( c );
if( parent != null ) {
g.setColor( parent.getBackground() );
Color background = getParentBackground( c );
if( background != null ) {
g.setColor( background );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
}
}
@@ -678,9 +691,10 @@ public class FlatUIUtils
*/
public static Color getParentBackground( JComponent c ) {
Container parent = findOpaqueParent( c );
return (parent != null)
? parent.getBackground()
: UIManager.getColor( "Panel.background" ); // fallback, probably never used
// 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" );
}
/**

View File

@@ -17,8 +17,11 @@
package com.formdev.flatlaf.testing;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.FlatLightLaf;
/**
@@ -69,6 +72,52 @@ public class FlatAWTTest
frame.add( new Panel() );
frame.add( new Canvas() );
Panel controlPanel = new Panel();
frame.add( controlPanel );
Checkbox enabledCheckBox = new Checkbox( "enabled", true );
enabledCheckBox.addItemListener( e -> {
boolean enabled = enabledCheckBox.getState();
for( Component c : frame.getComponents() ) {
if( c != controlPanel )
c.setEnabled( enabled );
}
} );
controlPanel.add( enabledCheckBox );
Checkbox explicitColorsCheckBox = new Checkbox( "explicit colors" );
explicitColorsCheckBox.addItemListener( e -> {
boolean explicit = explicitColorsCheckBox.getState();
for( Component c : frame.getComponents() ) {
if( c != controlPanel )
c.setBackground( explicit ? Color.green : null );
}
} );
controlPanel.add( explicitColorsCheckBox );
Menu menu = new Menu( "File" );
menu.add( new MenuItem( "New" ) );
menu.add( new MenuItem( "Open" ) );
menu.add( new MenuItem( "Save" ) );
MenuBar menuBar = new MenuBar();
menuBar.add( menu );
frame.setMenuBar( menuBar );
PopupMenu popupMenu = new PopupMenu();
popupMenu.add( new MenuItem( "item 1" ) );
popupMenu.add( new MenuItem( "item 2" ) );
popupMenu.add( new MenuItem( "item 3" ) );
list.add( popupMenu );
list.addMouseListener( new MouseAdapter() {
@Override
public
void mousePressed( MouseEvent e ) {
if( SwingUtilities.isRightMouseButton( e ) )
popupMenu.show( list, 0, 0 );
}
} );
frame.setSize( 800, 600 );
frame.setVisible( true );
});