macOS: fixed window "flashing" when switching from a light to a dark theme (or vice versa), especially when using animated theme changer

This commit is contained in:
Karl Tauber
2025-09-09 17:30:43 +02:00
parent 1ed7aeaa45
commit c051ad5f72
3 changed files with 44 additions and 8 deletions

View File

@@ -9,11 +9,12 @@ FlatLaf Change Log
named Java modules, it is no longer necessary to add `opens com.myapp.themes;` named Java modules, it is no longer necessary to add `opens com.myapp.themes;`
to `module-info.java`. (issue #1026) to `module-info.java`. (issue #1026)
#### Fixed bugs #### Fixed bugs
- Tree and List: Fixed painting of rounded drop backgrounds. (issue #1023) - Tree and List: Fixed painting of rounded drop backgrounds. (issue #1023)
- macOS: Fixed window "flashing" when switching from a light to a dark theme (or
vice versa). Especially when using animated theme changer (see
[FlatLaf Extras](flatlaf-extras)).
#### Incompatibilities #### Incompatibilities

View File

@@ -20,6 +20,7 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame; import java.awt.Frame;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
@@ -153,8 +154,28 @@ public class FlatRootPaneUI
Container parent = c.getParent(); Container parent = c.getParent();
if( parent instanceof JFrame || parent instanceof JDialog ) { if( parent instanceof JFrame || parent instanceof JDialog ) {
Color background = parent.getBackground(); Color background = parent.getBackground();
if( background == null || background instanceof UIResource ) if( background == null || background instanceof UIResource ) {
parent.setBackground( UIManager.getColor( "control" ) ); if( SystemInfo.isMacOS ) {
// Setting window background on macOS immediately fills the whole window
// with that color, and slightly delayed, the Swing repaint manager
// repaints the actual window content. This results in some flashing
// when switching from a light to a dark theme (or vice versa).
// --> delay setting window background and immediately repaint window content
Runnable r = () -> {
parent.setBackground( UIManager.getColor( "control" ) );
c.paintImmediately( 0, 0, c.getWidth(), c.getHeight() );
};
// for class FlatAnimatedLafChange:
// if animated Laf change is in progress, set background color when
// animation has finished to avoid/reduce flashing
if( c.getClientProperty( "FlatLaf.internal.animatedLafChange" ) != null )
c.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", r );
else
EventQueue.invokeLater( r );
} else
parent.setBackground( UIManager.getColor( "control" ) );
}
} }
macClearBackgroundForTranslucentWindow( c ); macClearBackgroundForTranslucentWindow( c );

View File

@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLayeredPane; import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.RootPaneContainer; import javax.swing.RootPaneContainer;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.Animator;
@@ -80,7 +81,7 @@ public class FlatAnimatedLafChange
showSnapshot( true, oldUIsnapshots ); showSnapshot( true, oldUIsnapshots );
} }
private static void showSnapshot( boolean useAlpha, Map<JLayeredPane, JComponent> map ) { private static void showSnapshot( boolean old, Map<JLayeredPane, JComponent> map ) {
inShowSnapshot = true; inShowSnapshot = true;
// create snapshots for all shown windows // create snapshots for all shown windows
@@ -107,7 +108,7 @@ public class FlatAnimatedLafChange
if( inShowSnapshot || snapshot.contentsLost() ) if( inShowSnapshot || snapshot.contentsLost() )
return; return;
if( useAlpha ) if( old )
((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) ); ((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
g.drawImage( snapshot, 0, 0, null ); g.drawImage( snapshot, 0, 0, null );
} }
@@ -120,13 +121,17 @@ public class FlatAnimatedLafChange
snapshot.flush(); snapshot.flush();
} }
}; };
if( !useAlpha ) if( !old )
snapshotLayer.setOpaque( true ); snapshotLayer.setOpaque( true );
snapshotLayer.setSize( layeredPane.getSize() ); snapshotLayer.setSize( layeredPane.getSize() );
// add image layer to layered pane // add image layer to layered pane
layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + (useAlpha ? 2 : 1) ) ); layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + (old ? 2 : 1) ) );
map.put( layeredPane, snapshotLayer ); map.put( layeredPane, snapshotLayer );
// let FlatRootPaneUI know that animated Laf change is in progress
if( old )
layeredPane.getRootPane().putClientProperty( "FlatLaf.internal.animatedLafChange", true );
} }
inShowSnapshot = false; inShowSnapshot = false;
@@ -180,6 +185,15 @@ public class FlatAnimatedLafChange
for( Map.Entry<JLayeredPane, JComponent> e : map.entrySet() ) { for( Map.Entry<JLayeredPane, JComponent> e : map.entrySet() ) {
e.getKey().remove( e.getValue() ); e.getKey().remove( e.getValue() );
e.getKey().repaint(); e.getKey().repaint();
// run Runnable that FlatRootPaneUI put into client properties
JRootPane rootPane = e.getKey().getRootPane();
rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange", null );
Runnable r = (Runnable) rootPane.getClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished" );
if( r != null ) {
rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", null );
r.run();
}
} }
map.clear(); map.clear();