Skip to content

Commit

Permalink
LafInstallation: Add theme transition animation when installing the l…
Browse files Browse the repository at this point in the history
…af using the LafManager. This can be disabled by setting the "darklaf.animatedLafChange" system property to false.
  • Loading branch information
weisJ committed Mar 6, 2021
1 parent ddf6105 commit cd2e474
Show file tree
Hide file tree
Showing 9 changed files with 660 additions and 49 deletions.
88 changes: 88 additions & 0 deletions core/src/main/java/com/github/weisj/darklaf/LafInstaller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* MIT License
*
* Copyright (c) 2021 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf;

import java.awt.Window;
import java.util.logging.Logger;

import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import com.github.weisj.darklaf.theme.Theme;
import com.github.weisj.darklaf.theme.event.ThemeChangeEvent;
import com.github.weisj.darklaf.theme.event.ThemeChangeListener;
import com.github.weisj.darklaf.theme.event.ThemeEventSupport;
import com.github.weisj.darklaf.util.LogUtil;

final class LafInstaller {

private static final Logger LOGGER = LogUtil.getLogger(LafManager.class);
private static final ThemeEventSupport<ThemeChangeEvent, ThemeChangeListener> eventSupport =
new ThemeEventSupport<>();

void install(final Theme theme) {
try {
LOGGER.fine(() -> "Installing theme " + theme);
LafTransition transition = LafTransition.showSnapshot();
UIManager.setLookAndFeel(new DarkLaf(false));
updateLaf();
SwingUtilities.invokeLater(transition::runTransition);
notifyThemeInstalled(theme);
} catch (final UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
}

void updateLaf() {
for (final Window w : Window.getWindows()) {
updateLafRecursively(w);
}
}

private void updateLafRecursively(final Window window) {
for (final Window childWindow : window.getOwnedWindows()) {
updateLafRecursively(childWindow);
}
SwingUtilities.updateComponentTreeUI(window);
}

void notifyThemeInstalled(final Theme newTheme) {
eventSupport.dispatchEvent(new ThemeChangeEvent(null, newTheme), ThemeChangeListener::themeInstalled);
}

void addThemeChangeListener(final ThemeChangeListener listener) {
eventSupport.addListener(listener);
}

void removeThemeChangeListener(final ThemeChangeListener listener) {
eventSupport.removeListener(listener);
}


void notifyThemeChanged(final Theme oldTheme, final Theme newTheme) {
if (oldTheme != newTheme) {
eventSupport.dispatchEvent(new ThemeChangeEvent(oldTheme, newTheme), ThemeChangeListener::themeChanged);
LOGGER.fine(() -> "Setting theme to " + newTheme);
}
}
}
41 changes: 10 additions & 31 deletions core/src/main/java/com/github/weisj/darklaf/LafManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,12 @@
public final class LafManager {

private static final Logger LOGGER = LogUtil.getLogger(LafManager.class);
private static final LafInstaller installer = new LafInstaller();
private static ThemeProvider themeProvider;
private static Theme theme;
private static final List<Theme> registeredThemes = new ArrayList<>();
private static final Collection<DefaultsAdjustmentTask> uiDefaultsTasks = new ArrayList<>();
private static final Collection<DefaultsInitTask> uiInitTasks = new ArrayList<>();
private static final ThemeEventSupport<ThemeChangeEvent, ThemeChangeListener> eventSupport =
new ThemeEventSupport<>();

static {
setLogLevel(Level.WARNING);
Expand Down Expand Up @@ -179,7 +178,7 @@ public static void removeThemePreferenceChangeListener(final ThemePreferenceList
* @param listener the listener to add.
*/
public static void addThemeChangeListener(final ThemeChangeListener listener) {
eventSupport.addListener(listener);
installer.addThemeChangeListener(listener);
}

/**
Expand All @@ -188,7 +187,7 @@ public static void addThemeChangeListener(final ThemeChangeListener listener) {
* @param listener the listener to add.
*/
public static void removeThemeChangeListener(final ThemeChangeListener listener) {
eventSupport.removeListener(listener);
installer.removeThemeChangeListener(listener);
}

/**
Expand Down Expand Up @@ -373,10 +372,7 @@ public static boolean isInstalled() {
public static void setTheme(final Theme theme) {
Theme old = LafManager.theme;
LafManager.theme = theme;
if (old != theme) {
eventSupport.dispatchEvent(new ThemeChangeEvent(old, theme), ThemeChangeListener::themeChanged);
LOGGER.fine(() -> "Setting theme to " + theme);
}
installer.notifyThemeChanged(old, theme);
if (ThemeSettings.isInitialized()) ThemeSettings.getInstance().refresh();
}

Expand Down Expand Up @@ -436,33 +432,12 @@ public static void install(final Theme theme) {
* {@link ThemeProvider}. This sets the current LaF and applies the given theme.
*/
public static void install() {
try {
getTheme();
LOGGER.fine(() -> "Installing theme " + theme);
UIManager.setLookAndFeel(new DarkLaf());
updateLaf();
notifyThemeInstalled();
} catch (final UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
}

/* default */ static void notifyThemeInstalled() {
eventSupport.dispatchEvent(new ThemeChangeEvent(null, getTheme()), ThemeChangeListener::themeInstalled);
installer.install(getTheme());
}

/** Update the component ui classes for all current windows. */
public static void updateLaf() {
for (final Window w : Window.getWindows()) {
updateLafRecursively(w);
}
}

private static void updateLafRecursively(final Window window) {
for (final Window childWindow : window.getOwnedWindows()) {
updateLafRecursively(childWindow);
}
SwingUtilities.updateComponentTreeUI(window);
installer.updateLaf();
}

/**
Expand Down Expand Up @@ -537,4 +512,8 @@ public static Theme getClosestMatchForTheme(final Theme theme) {
}
return themeForPreferredStyle(null);
}

static void notifyThemeInstalled() {
installer.notifyThemeInstalled(getTheme());
}
}
166 changes: 166 additions & 0 deletions core/src/main/java/com/github/weisj/darklaf/LafTransition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* MIT License
*
* Copyright (c) 2021 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf;



import java.awt.AlphaComposite;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Window;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.RootPaneContainer;

import com.github.weisj.darklaf.graphics.Animator;
import com.github.weisj.darklaf.graphics.DefaultInterpolator;
import com.github.weisj.darklaf.util.ImageUtil;
import com.github.weisj.darklaf.util.PropertyUtil;
import com.github.weisj.darklaf.util.graphics.ImagePainter;
import com.github.weisj.darklaf.util.value.SharedNonNull;

class LafTransition {

private static final String ANIMATED_LAF_CHANGE = DarkLaf.SYSTEM_PROPERTY_PREFIX + "animatedLafChange";

private LafTransition() {}

static LafTransition showSnapshot() {
return PropertyUtil.getSystemFlag(ANIMATED_LAF_CHANGE)
? new AnimatedLafTransition()
: new LafTransition();
}

void runTransition() {
// Do nothing.
}

static final class AnimatedLafTransition extends LafTransition {

private final Animator animator;
private final Map<JLayeredPane, Component> uiSnapshots;
private final SharedNonNull<Float> sharedAlpha;

private AnimatedLafTransition() {
sharedAlpha = new SharedNonNull<>(1f);
animator = new TransitionAnimator();
uiSnapshots = new LinkedHashMap<>();
Window[] windows = Window.getWindows();
for (Window window : windows) {
if (window instanceof RootPaneContainer && window.isShowing()) {
RootPaneContainer rootPaneContainer = (RootPaneContainer) window;
Image img = ImageUtil.scaledImageFromComponent(rootPaneContainer.getRootPane());
JLayeredPane layeredPane = rootPaneContainer.getLayeredPane();
JComponent imageLayer = new ImageLayer(layeredPane, img, sharedAlpha);
imageLayer.setSize(layeredPane.getSize());
layeredPane.add(imageLayer, JLayeredPane.DRAG_LAYER);
uiSnapshots.put(layeredPane, imageLayer);
}
}
doPaint();
}

void runTransition() {
animator.resume();
}

private void disposeSnapshots() {
for (Map.Entry<JLayeredPane, Component> entry : uiSnapshots.entrySet()) {
entry.getKey().remove(entry.getValue());
entry.getKey().revalidate();
entry.getKey().repaint();
}
uiSnapshots.clear();
}

private void doPaint() {
for (Map.Entry<JLayeredPane, Component> entry : uiSnapshots.entrySet()) {
if (entry.getKey().isShowing()) {
entry.getValue().revalidate();
entry.getValue().repaint();
}
}
}

private class TransitionAnimator extends Animator {

private static final int DURATION = 160;
private static final int RESOLUTION = 10;

public TransitionAnimator() {
super(DURATION / RESOLUTION, DURATION, false, DefaultInterpolator.EASE_IN_SINE);
}

@Override
public void resume() {
doPaint();
super.resume();
}

@Override
public void paintNow(final float fraction) {
sharedAlpha.set(1f - fraction);
doPaint();
}

@Override
protected void paintCycleEnd() {
disposeSnapshots();
}
}
}

private static class ImageLayer extends JComponent {

private final JLayeredPane layeredPane;
private final SharedNonNull<Float> sharedAlpha;
private final Image image;

private ImageLayer(final JLayeredPane layeredPane, final Image image, final SharedNonNull<Float> sharedAlpha) {
this.layeredPane = layeredPane;
this.image = image;
this.sharedAlpha = sharedAlpha;
}

@Override
public void updateUI() {}

@Override
public void paint(final Graphics g) {
Graphics gg = g.create();
((Graphics2D) gg).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, sharedAlpha.get()));
ImagePainter.drawImage(gg, image, 0, 0, this);
}

@Override
public Rectangle getBounds() {
return layeredPane.getBounds();
}
}

}
7 changes: 4 additions & 3 deletions core/src/test/java/test/AbstractImageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import com.github.weisj.darklaf.util.ImageUtil;
import com.github.weisj.darklaf.util.Scale;
import com.github.weisj.darklaf.util.graphics.ScaledImage;

abstract class AbstractImageTest {
private static final int SCALING_FACTOR = 3;
Expand Down Expand Up @@ -62,9 +63,9 @@ protected BufferedImage saveScreenShot(final String name, final Component c, fin
File file = new File(name + ".png");
file.getParentFile().mkdirs();
Rectangle rect = new Rectangle(0, 0, c.getWidth(), c.getHeight());
BufferedImage image = ImageUtil.scaledImageFromComponent(c, rect, scalingFactor, scalingFactor, false);
ImageIO.write(image, "png", file);
return image;
ScaledImage image = ImageUtil.scaledImageFromComponent(c, rect, scalingFactor, scalingFactor, false);
ImageIO.write(image.getDelegate(), "png", file);
return image.getDelegate();
} catch (final IOException e) {
e.printStackTrace();
}
Expand Down
Loading

0 comments on commit cd2e474

Please sign in to comment.