Extras: FlatAnimatedLafChange: made transition smoother:

- use a single component in layered pane to paint new and old UI snapshots (previously used two components)
- the snapshot layer component is now opaque, which avoids that window component hierarchy is involved when painting snapshots
- snapshots are now painted immediately, which should result in a smoother transition
- changed animation resolution from 30ms to 16ms
This commit is contained in:
Karl Tauber
2025-09-09 18:54:11 +02:00
parent c051ad5f72
commit d079741f94
2 changed files with 84 additions and 61 deletions

View File

@@ -8,6 +8,7 @@ FlatLaf Change Log
- If using `FlatLaf.registerCustomDefaultsSource( "com.myapp.themes" )` and - If using `FlatLaf.registerCustomDefaultsSource( "com.myapp.themes" )` and
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)
- Extras: Made animated theme change (class `FlatAnimatedLafChange`) smoother.
#### Fixed bugs #### Fixed bugs

View File

@@ -53,15 +53,13 @@ public class FlatAnimatedLafChange
public static int duration = 160; public static int duration = 160;
/** /**
* The resolution of the animation in milliseconds. Default is 30 ms. * The resolution of the animation in milliseconds. Default is 16 ms.
*/ */
public static int resolution = 30; public static int resolution = 16;
private static Animator animator; private static Animator animator;
private static final Map<JLayeredPane, JComponent> oldUIsnapshots = new WeakHashMap<>(); private static final Map<JLayeredPane, SnapshotLayer> snapshots = new WeakHashMap<>();
private static final Map<JLayeredPane, JComponent> newUIsnapshots = new WeakHashMap<>();
private static float alpha; private static float alpha;
private static boolean inShowSnapshot;
/** /**
* Create a snapshot of the old UI and shows it on top of the UI. * Create a snapshot of the old UI and shows it on top of the UI.
@@ -78,63 +76,52 @@ public class FlatAnimatedLafChange
alpha = 1; alpha = 1;
// show snapshot of old UI // show snapshot of old UI
showSnapshot( true, oldUIsnapshots ); showSnapshot( true );
} }
private static void showSnapshot( boolean old, Map<JLayeredPane, JComponent> map ) { private static void showSnapshot( boolean old ) {
inShowSnapshot = true;
// create snapshots for all shown windows // create snapshots for all shown windows
Window[] windows = Window.getWindows(); Window[] windows = Window.getWindows();
for( Window window : windows ) { for( Window window : windows ) {
if( !(window instanceof RootPaneContainer) || !window.isShowing() ) if( !(window instanceof RootPaneContainer) || !window.isShowing() )
continue; continue;
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane();
// create snapshot image // create snapshot image
// (using volatile image to have correct sub-pixel text rendering on Java 9+) // (using volatile image to have correct sub-pixel text rendering on Java 9+)
VolatileImage snapshot = window.createVolatileImage( window.getWidth(), window.getHeight() ); VolatileImage snapshotImage = layeredPane.createVolatileImage( layeredPane.getWidth(), layeredPane.getHeight() );
if( snapshot == null ) if( snapshotImage == null )
continue; continue;
// paint window to snapshot image // paint window to snapshot image
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane(); layeredPane.paint( snapshotImage.getGraphics() );
layeredPane.paint( snapshot.getGraphics() );
// create snapshot layer, which is added to layered pane and paints if( old ) {
// snapshot with animated alpha // create snapshot layer, which is added to layered pane and paints
JComponent snapshotLayer = new JComponent() { // snapshot with animated alpha
@Override SnapshotLayer snapshotLayer = new SnapshotLayer();
public void paint( Graphics g ) {
if( inShowSnapshot || snapshot.contentsLost() )
return;
if( old )
((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
g.drawImage( snapshot, 0, 0, null );
}
@Override
public void removeNotify() {
super.removeNotify();
// release system resources used by volatile image
snapshot.flush();
}
};
if( !old )
snapshotLayer.setOpaque( true ); snapshotLayer.setOpaque( true );
snapshotLayer.setSize( layeredPane.getSize() ); snapshotLayer.setSize( layeredPane.getSize() );
snapshotLayer.oldSnapshotImage = snapshotImage;
// add image layer to layered pane snapshots.put( layeredPane, snapshotLayer );
layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + (old ? 2 : 1) ) ); } else {
map.put( layeredPane, snapshotLayer ); SnapshotLayer snapshotLayer = snapshots.get( layeredPane );
if( snapshotLayer == null ) {
snapshotImage.flush();
continue;
}
// let FlatRootPaneUI know that animated Laf change is in progress snapshotLayer.newSnapshotImage = snapshotImage;
if( old )
// add snapshot layer to layered pane
layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + 1 ) );
// let FlatRootPaneUI know that animated Laf change is in progress
layeredPane.getRootPane().putClientProperty( "FlatLaf.internal.animatedLafChange", true ); layeredPane.getRootPane().putClientProperty( "FlatLaf.internal.animatedLafChange", true );
}
} }
inShowSnapshot = false;
} }
/** /**
@@ -146,23 +133,22 @@ public class FlatAnimatedLafChange
if( !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) ) if( !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) )
return; return;
if( oldUIsnapshots.isEmpty() ) if( snapshots.isEmpty() )
return; return;
// show snapshot of new UI // show snapshot of new UI
showSnapshot( false, newUIsnapshots ); showSnapshot( false );
// create animator // create animator
animator = new Animator( duration, fraction -> { animator = new Animator( duration, fraction -> {
if( fraction < 0.1 || fraction > 0.9 )
return; // ignore initial and last events
alpha = 1f - fraction; alpha = 1f - fraction;
// repaint snapshots // repaint snapshots
for( Map.Entry<JLayeredPane, JComponent> e : oldUIsnapshots.entrySet() ) { for( Map.Entry<JLayeredPane, SnapshotLayer> e : snapshots.entrySet() ) {
if( e.getKey().isShowing() ) if( e.getKey().isShowing() ) {
e.getValue().repaint(); SnapshotLayer snapshotLayer = e.getValue();
snapshotLayer.paintImmediately( 0, 0, snapshotLayer.getWidth(),snapshotLayer.getHeight() );
}
} }
Toolkit.getDefaultToolkit().sync(); Toolkit.getDefaultToolkit().sync();
@@ -176,18 +162,18 @@ public class FlatAnimatedLafChange
} }
private static void hideSnapshot() { private static void hideSnapshot() {
hideSnapshot( oldUIsnapshots );
hideSnapshot( newUIsnapshots );
}
private static void hideSnapshot( Map<JLayeredPane, JComponent> map ) {
// remove snapshots // remove snapshots
for( Map.Entry<JLayeredPane, JComponent> e : map.entrySet() ) { for( Map.Entry<JLayeredPane, SnapshotLayer> e : snapshots.entrySet() ) {
e.getKey().remove( e.getValue() ); JLayeredPane layeredPane = e.getKey();
e.getKey().repaint(); SnapshotLayer snapshotLayer = e.getValue();
layeredPane.remove( snapshotLayer );
layeredPane.repaint();
snapshotLayer.flushSnapshotImages();
// run Runnable that FlatRootPaneUI put into client properties // run Runnable that FlatRootPaneUI put into client properties
JRootPane rootPane = e.getKey().getRootPane(); JRootPane rootPane = layeredPane.getRootPane();
rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange", null ); rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange", null );
Runnable r = (Runnable) rootPane.getClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished" ); Runnable r = (Runnable) rootPane.getClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished" );
if( r != null ) { if( r != null ) {
@@ -196,7 +182,7 @@ public class FlatAnimatedLafChange
} }
} }
map.clear(); snapshots.clear();
} }
/** /**
@@ -208,4 +194,40 @@ public class FlatAnimatedLafChange
else else
hideSnapshot(); hideSnapshot();
} }
//---- class SnapshotLayer ------------------------------------------------
private static class SnapshotLayer
extends JComponent
{
VolatileImage oldSnapshotImage;
VolatileImage newSnapshotImage;
@Override
public void paint( Graphics g ) {
if( oldSnapshotImage.contentsLost() ||
newSnapshotImage == null || newSnapshotImage.contentsLost() )
return;
// draw new UI snapshot
g.drawImage( newSnapshotImage, 0, 0, null );
// draw old UI snapshot
((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
g.drawImage( oldSnapshotImage, 0, 0, null );
}
@Override
public void removeNotify() {
super.removeNotify();
flushSnapshotImages();
}
void flushSnapshotImages() {
// release system resources used by volatile image
oldSnapshotImage.flush();
if( newSnapshotImage != null )
newSnapshotImage.flush();
}
}
} }