From c051ad5f72cb026772d05f999e67012c5cc69b9a Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 9 Sep 2025 17:30:43 +0200 Subject: [PATCH] macOS: fixed window "flashing" when switching from a light to a dark theme (or vice versa), especially when using animated theme changer --- CHANGELOG.md | 5 ++-- .../formdev/flatlaf/ui/FlatRootPaneUI.java | 25 +++++++++++++++++-- .../flatlaf/extras/FlatAnimatedLafChange.java | 22 +++++++++++++--- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f2609b9..86cb7812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,12 @@ FlatLaf Change Log named Java modules, it is no longer necessary to add `opens com.myapp.themes;` to `module-info.java`. (issue #1026) - #### Fixed bugs - 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 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java index 6ec58389..b007bb7c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java @@ -20,6 +20,7 @@ import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; @@ -153,8 +154,28 @@ public class FlatRootPaneUI Container parent = c.getParent(); if( parent instanceof JFrame || parent instanceof JDialog ) { Color background = parent.getBackground(); - if( background == null || background instanceof UIResource ) - parent.setBackground( UIManager.getColor( "control" ) ); + if( background == null || background instanceof UIResource ) { + 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 ); diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatAnimatedLafChange.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatAnimatedLafChange.java index bc0a6fae..a7160d88 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatAnimatedLafChange.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatAnimatedLafChange.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.WeakHashMap; import javax.swing.JComponent; import javax.swing.JLayeredPane; +import javax.swing.JRootPane; import javax.swing.RootPaneContainer; import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.util.Animator; @@ -80,7 +81,7 @@ public class FlatAnimatedLafChange showSnapshot( true, oldUIsnapshots ); } - private static void showSnapshot( boolean useAlpha, Map map ) { + private static void showSnapshot( boolean old, Map map ) { inShowSnapshot = true; // create snapshots for all shown windows @@ -107,7 +108,7 @@ public class FlatAnimatedLafChange if( inShowSnapshot || snapshot.contentsLost() ) return; - if( useAlpha ) + if( old ) ((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) ); g.drawImage( snapshot, 0, 0, null ); } @@ -120,13 +121,17 @@ public class FlatAnimatedLafChange snapshot.flush(); } }; - if( !useAlpha ) + if( !old ) snapshotLayer.setOpaque( true ); snapshotLayer.setSize( layeredPane.getSize() ); // 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 ); + + // let FlatRootPaneUI know that animated Laf change is in progress + if( old ) + layeredPane.getRootPane().putClientProperty( "FlatLaf.internal.animatedLafChange", true ); } inShowSnapshot = false; @@ -180,6 +185,15 @@ public class FlatAnimatedLafChange for( Map.Entry e : map.entrySet() ) { e.getKey().remove( e.getValue() ); 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();