Browse Source

LafInstallation: Add theme transition animation when installing the laf using the LafManager. This can be disabled by setting the "darklaf.animatedLafChange" system property to false.

pull/235/head
weisj 4 years ago
parent
commit
cd2e4747af
No known key found for this signature in database
GPG Key ID: 31124CB75461DA2A
  1. 88
      core/src/main/java/com/github/weisj/darklaf/LafInstaller.java
  2. 41
      core/src/main/java/com/github/weisj/darklaf/LafManager.java
  3. 166
      core/src/main/java/com/github/weisj/darklaf/LafTransition.java
  4. 7
      core/src/test/java/test/AbstractImageTest.java
  5. 87
      utils/src/main/java/com/github/weisj/darklaf/util/ImageUtil.java
  6. 168
      utils/src/main/java/com/github/weisj/darklaf/util/graphics/ImagePainter.java
  7. 78
      utils/src/main/java/com/github/weisj/darklaf/util/graphics/ScaledImage.java
  8. 38
      utils/src/main/java/com/github/weisj/darklaf/util/value/Shared.java
  9. 36
      utils/src/main/java/com/github/weisj/darklaf/util/value/SharedNonNull.java

88
core/src/main/java/com/github/weisj/darklaf/LafInstaller.java

@ -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
core/src/main/java/com/github/weisj/darklaf/LafManager.java

@ -51,13 +51,12 @@ import com.github.weisj.darklaf.util.LogUtil;
public final class LafManager { public final class LafManager {
private static final Logger LOGGER = LogUtil.getLogger(LafManager.class); private static final Logger LOGGER = LogUtil.getLogger(LafManager.class);
private static final LafInstaller installer = new LafInstaller();
private static ThemeProvider themeProvider; private static ThemeProvider themeProvider;
private static Theme theme; private static Theme theme;
private static final List<Theme> registeredThemes = new ArrayList<>(); private static final List<Theme> registeredThemes = new ArrayList<>();
private static final Collection<DefaultsAdjustmentTask> uiDefaultsTasks = new ArrayList<>(); private static final Collection<DefaultsAdjustmentTask> uiDefaultsTasks = new ArrayList<>();
private static final Collection<DefaultsInitTask> uiInitTasks = new ArrayList<>(); private static final Collection<DefaultsInitTask> uiInitTasks = new ArrayList<>();
private static final ThemeEventSupport<ThemeChangeEvent, ThemeChangeListener> eventSupport =
new ThemeEventSupport<>();
static { static {
setLogLevel(Level.WARNING); setLogLevel(Level.WARNING);
@ -179,7 +178,7 @@ public final class LafManager {
* @param listener the listener to add. * @param listener the listener to add.
*/ */
public static void addThemeChangeListener(final ThemeChangeListener listener) { public static void addThemeChangeListener(final ThemeChangeListener listener) {
eventSupport.addListener(listener); installer.addThemeChangeListener(listener);
} }
/** /**
@ -188,7 +187,7 @@ public final class LafManager {
* @param listener the listener to add. * @param listener the listener to add.
*/ */
public static void removeThemeChangeListener(final ThemeChangeListener listener) { public static void removeThemeChangeListener(final ThemeChangeListener listener) {
eventSupport.removeListener(listener); installer.removeThemeChangeListener(listener);
} }
/** /**
@ -373,10 +372,7 @@ public final class LafManager {
public static void setTheme(final Theme theme) { public static void setTheme(final Theme theme) {
Theme old = LafManager.theme; Theme old = LafManager.theme;
LafManager.theme = theme; LafManager.theme = theme;
if (old != theme) { installer.notifyThemeChanged(old, theme);
eventSupport.dispatchEvent(new ThemeChangeEvent(old, theme), ThemeChangeListener::themeChanged);
LOGGER.fine(() -> "Setting theme to " + theme);
}
if (ThemeSettings.isInitialized()) ThemeSettings.getInstance().refresh(); if (ThemeSettings.isInitialized()) ThemeSettings.getInstance().refresh();
} }
@ -436,33 +432,12 @@ public final class LafManager {
* {@link ThemeProvider}. This sets the current LaF and applies the given theme. * {@link ThemeProvider}. This sets the current LaF and applies the given theme.
*/ */
public static void install() { public static void install() {
try { installer.install(getTheme());
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);
} }
/** Update the component ui classes for all current windows. */ /** Update the component ui classes for all current windows. */
public static void updateLaf() { public static void updateLaf() {
for (final Window w : Window.getWindows()) { installer.updateLaf();
updateLafRecursively(w);
}
}
private static void updateLafRecursively(final Window window) {
for (final Window childWindow : window.getOwnedWindows()) {
updateLafRecursively(childWindow);
}
SwingUtilities.updateComponentTreeUI(window);
} }
/** /**
@ -537,4 +512,8 @@ public final class LafManager {
} }
return themeForPreferredStyle(null); return themeForPreferredStyle(null);
} }
static void notifyThemeInstalled() {
installer.notifyThemeInstalled(getTheme());
}
} }

166
core/src/main/java/com/github/weisj/darklaf/LafTransition.java

@ -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
core/src/test/java/test/AbstractImageTest.java

@ -31,6 +31,7 @@ import javax.imageio.ImageIO;
import com.github.weisj.darklaf.util.ImageUtil; import com.github.weisj.darklaf.util.ImageUtil;
import com.github.weisj.darklaf.util.Scale; import com.github.weisj.darklaf.util.Scale;
import com.github.weisj.darklaf.util.graphics.ScaledImage;
abstract class AbstractImageTest { abstract class AbstractImageTest {
private static final int SCALING_FACTOR = 3; private static final int SCALING_FACTOR = 3;
@ -62,9 +63,9 @@ abstract class AbstractImageTest {
File file = new File(name + ".png"); File file = new File(name + ".png");
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
Rectangle rect = new Rectangle(0, 0, c.getWidth(), c.getHeight()); Rectangle rect = new Rectangle(0, 0, c.getWidth(), c.getHeight());
BufferedImage image = ImageUtil.scaledImageFromComponent(c, rect, scalingFactor, scalingFactor, false); ScaledImage image = ImageUtil.scaledImageFromComponent(c, rect, scalingFactor, scalingFactor, false);
ImageIO.write(image, "png", file); ImageIO.write(image.getDelegate(), "png", file);
return image; return image.getDelegate();
} catch (final IOException e) { } catch (final IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

87
utils/src/main/java/com/github/weisj/darklaf/util/ImageUtil.java

@ -24,11 +24,25 @@ package com.github.weisj.darklaf.util;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import com.github.weisj.darklaf.util.graphics.GraphicsUtil;
import com.github.weisj.darklaf.util.graphics.ScaledImage;
/** @author Jannis Weis */ /** @author Jannis Weis */
public final class ImageUtil { public final class ImageUtil {
private ImageUtil() {} private ImageUtil() {}
/**
* Create image from component.
*
* @param c the component.
* @return image of the component.
*/
public static ScaledImage imageFromComponent(final Component c) {
return scaledImageFromComponent(c, new Rectangle(0, 0, c.getWidth(), c.getHeight()), 1.0, 1.0, true);
}
/** /**
* Create image from component. * Create image from component.
* *
@ -36,7 +50,7 @@ public final class ImageUtil {
* @param bounds the bounds inside the component to capture. * @param bounds the bounds inside the component to capture.
* @return image containing the captured area. * @return image containing the captured area.
*/ */
public static BufferedImage imageFromComponent(final Component c, final Rectangle bounds) { public static ScaledImage imageFromComponent(final Component c, final Rectangle bounds) {
return scaledImageFromComponent(c, bounds, 1.0, 1.0, true); return scaledImageFromComponent(c, bounds, 1.0, 1.0, true);
} }
@ -47,10 +61,20 @@ public final class ImageUtil {
* @param bounds the bounds inside the component to capture. * @param bounds the bounds inside the component to capture.
* @return image containing the captured area. * @return image containing the captured area.
*/ */
public static BufferedImage imageFromComponent(final Component c, final Rectangle bounds, final boolean print) { public static ScaledImage imageFromComponent(final Component c, final Rectangle bounds, final boolean print) {
return scaledImageFromComponent(c, bounds, 1.0, 1.0, print); return scaledImageFromComponent(c, bounds, 1.0, 1.0, print);
} }
/**
* Create image from component.
*
* @param c the component.
* @return image of the component.
*/
public static ScaledImage scaledImageFromComponent(final Component c) {
return scaledImageFromComponent(c, new Rectangle(0, 0, c.getWidth(), c.getHeight()), false);
}
/** /**
* Create image from component. * Create image from component.
* *
@ -58,9 +82,22 @@ public final class ImageUtil {
* @param bounds the bounds inside the component to capture. * @param bounds the bounds inside the component to capture.
* @return image containing the captured area. * @return image containing the captured area.
*/ */
public static BufferedImage scaledImageFromComponent(final Component c, final Rectangle bounds) { public static ScaledImage scaledImageFromComponent(final Component c, final Rectangle bounds) {
return scaledImageFromComponent(c, bounds, false);
}
/**
* Create image from component.
*
* @param c the component.
* @param bounds the bounds inside the component to capture.
* @param print whether the component should be painted in printing mode or not.
* @return image containing the captured area.
*/
public static ScaledImage scaledImageFromComponent(final Component c, final Rectangle bounds,
final boolean print) {
GraphicsConfiguration gc = c.getGraphicsConfiguration(); GraphicsConfiguration gc = c.getGraphicsConfiguration();
return scaledImageFromComponent(c, bounds, Scale.getScaleX(gc), Scale.getScaleY(gc), true); return scaledImageFromComponent(c, bounds, Scale.getScaleX(gc), Scale.getScaleY(gc), print);
} }
/** /**
@ -72,16 +109,19 @@ public final class ImageUtil {
* @param scaley the y scale * @param scaley the y scale
* @return image containing the captured area. * @return image containing the captured area.
*/ */
public static BufferedImage scaledImageFromComponent(final Component c, final Rectangle bounds, final double scalex, public static ScaledImage scaledImageFromComponent(final Component c, final Rectangle bounds, final double scalex,
final double scaley, final boolean print) { final double scaley, final boolean print) {
BufferedImage image; BufferedImage image;
boolean scale = scalex != 1.0 || scaley != 1.0; boolean scale = scalex != 1.0 || scaley != 1.0;
if (scale) { if (scale) {
image = createCompatibleTransparentImage((int) (scalex * bounds.width), (int) (scaley * bounds.height)); image = createCompatibleTransparentImage(c.getGraphicsConfiguration(),
(int) (scalex * bounds.width), (int) (scaley * bounds.height));
} else { } else {
image = createCompatibleTransparentImage(bounds.width, bounds.height); image = createCompatibleTransparentImage(c.getGraphicsConfiguration(),
bounds.width, bounds.height);
} }
final Graphics2D g2d = (Graphics2D) image.getGraphics(); final Graphics2D g2d = (Graphics2D) image.getGraphics();
GraphicsUtil.setupAntialiasing(g2d);
if (scale) { if (scale) {
g2d.scale(scalex, scaley); g2d.scale(scalex, scaley);
} }
@ -93,22 +133,37 @@ public final class ImageUtil {
} }
g2d.dispose(); g2d.dispose();
return image; return new ScaledImage(image, scalex, scaley);
}
public static BufferedImage createCompatibleImage(final GraphicsConfiguration gc,
final int width, final int height) {
return gc == null ? new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
: gc.createCompatibleImage(width, height, Transparency.OPAQUE);
} }
public static BufferedImage createCompatibleImage(final int width, final int height) { public static BufferedImage createCompatibleImage(final int width, final int height) {
return isHeadless() ? new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) return createCompatibleImage(getGraphicsConfiguration(), width, height);
: getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.OPAQUE); }
public static BufferedImage createCompatibleTransparentImage(final GraphicsConfiguration gc,
final int width, final int height) {
return gc == null ? new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
: gc.createCompatibleImage(width, height, Transparency.BITMASK);
} }
public static BufferedImage createCompatibleTransparentImage(final int width, final int height) { public static BufferedImage createCompatibleTransparentImage(final int width, final int height) {
return isHeadless() ? new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) return createCompatibleTransparentImage(getGraphicsConfiguration(), width, height);
: getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.BITMASK); }
public static BufferedImage createCompatibleTranslucentImage(final GraphicsConfiguration gc,
final int width, final int height) {
return gc == null ? new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
: gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
} }
public static BufferedImage createCompatibleTranslucentImage(final int width, final int height) { public static BufferedImage createCompatibleTranslucentImage(final int width, final int height) {
return isHeadless() ? new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) return createCompatibleTranslucentImage(getGraphicsConfiguration(), width, height);
: getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
} }
private static boolean isHeadless() { private static boolean isHeadless() {
@ -116,6 +171,8 @@ public final class ImageUtil {
} }
private static GraphicsConfiguration getGraphicsConfiguration() { private static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); return isHeadless()
? null
: GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
} }
} }

168
utils/src/main/java/com/github/weisj/darklaf/util/graphics/ImagePainter.java

@ -0,0 +1,168 @@
/*
* 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.util.graphics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
public final class ImagePainter {
/**
* A hidpi-aware wrapper over {@link Graphics#drawImage(Image, int, int, ImageObserver)}.
*
* @see #drawImage(Graphics, Image, Rectangle, Rectangle, ImageObserver)
*/
public static void drawImage(final Graphics g, final Image image, final int x, final int y,
final ImageObserver observer) {
drawImage(g, image, new Rectangle(x, y, -1, -1), null, null, observer);
}
static void drawImage(final Graphics g, final Image image, final int x, final int y, final int width,
final int height, final BufferedImageOp op,
final ImageObserver observer) {
Rectangle srcBounds = width >= 0 && height >= 0 ? new Rectangle(x, y, width, height) : null;
drawImage(g, image, new Rectangle(x, y, width, height), srcBounds, op, observer);
}
/**
* A hidpi-aware wrapper over {@link Graphics#drawImage(Image, int, int, int, int, ImageObserver)}.
*
* @see #drawImage(Graphics, Image, Rectangle, Rectangle, BufferedImageOp, ImageObserver)
*/
public static void drawImage(final Graphics g, final Image image, final Rectangle dstBounds,
final ImageObserver observer) {
drawImage(g, image, dstBounds, null, null, observer);
}
/**
* @see #drawImage(Graphics, Image, Rectangle, Rectangle, BufferedImageOp, ImageObserver)
*/
public static void drawImage(final Graphics g,
final Image image,
final Rectangle dstBounds,
final Rectangle srcBounds,
final ImageObserver observer) {
drawImage(g, image, dstBounds, srcBounds, null, observer);
}
/**
* A hidpi-aware wrapper over
* {@link Graphics#drawImage(Image, int, int, int, int, int, int, int, int, ImageObserver)}.
* <p>
* The {@code dstBounds} and {@code srcBounds} are in the user space (just like the width/height of
* the image). If {@code dstBounds} is null or if its width/height is set to (-1) the image bounds
* or the image width/height is used. If {@code srcBounds} is null or if its width/height is set to
* (-1) the image bounds or the image right/bottom area to the provided x/y is used.
*/
public static void drawImage(Graphics g,
Image image,
final Rectangle dstBounds,
final Rectangle srcBounds,
final BufferedImageOp op,
final ImageObserver observer) {
int userWidth = image.getWidth(observer);
int userHeight = image.getHeight(observer);
int dx = 0;
int dy = 0;
int dw = -1;
int dh = -1;
if (dstBounds != null) {
dx = dstBounds.x;
dy = dstBounds.y;
dw = dstBounds.width;
dh = dstBounds.height;
}
boolean hasDstSize = dw >= 0 && dh >= 0;
Graphics2D invG = null;
double scaleX = 1;
double scaleY = 1;
if (image instanceof ScaledImage) {
ScaledImage scaledImage = (ScaledImage) image;
Image delegate = scaledImage.getDelegate();
if (delegate != null) image = delegate;
scaleX = scaledImage.getScaleX();
scaleY = scaledImage.getScaleY();
userWidth /= scaleX;
userHeight /= scaleY;
AffineTransform tx = ((Graphics2D) g).getTransform();
if (scaleX == tx.getScaleX() && scaleY == tx.getScaleY()) {
// The image has the same original scale as the graphics scale. However, the real image
// scale - userSize/realSize - can suffer from inaccuracy due to the image user size
// rounding to int (userSize = (int)realSize/originalImageScale). This may case quality
// loss if the image is drawn via Graphics.drawImage(image, <srcRect>, <dstRect>)
// due to scaling in Graphics. To avoid that, the image should be drawn directly via
// Graphics.drawImage(image, 0, 0) on the unscaled Graphics.
double gScaleX = tx.getScaleX();
double gScaleY = tx.getScaleY();
tx.scale(1 / gScaleX, 1 / gScaleY);
tx.translate(dx * gScaleX, dy * gScaleY);
dx = dy = 0;
g = invG = (Graphics2D) g.create();
invG.setTransform(tx);
}
}
try {
if (op != null && image instanceof BufferedImage) {
image = op.filter((BufferedImage) image, null);
}
if (invG != null && hasDstSize) {
dw = (int) Math.round(dw * scaleX);
dh = (int) Math.round(dh * scaleY);
}
if (srcBounds != null) {
int sx = (int) Math.round(srcBounds.x * scaleX);
int sy = (int) Math.round(srcBounds.y * scaleY);
int sw = srcBounds.width >= 0
? (int) Math.round(srcBounds.width * scaleX)
: (int) Math.round(userWidth * scaleX) - sx;
int sh = srcBounds.height >= 0
? (int) Math.round(srcBounds.height * scaleY)
: (int) Math.round(userHeight * scaleY) - sy;
if (!hasDstSize) {
dw = (int) Math.round(userWidth * scaleX);
dh = (int) Math.round(userHeight * scaleY);
}
g.drawImage(image,
dx, dy, dx + dw, dy + dh,
sx, sy, sx + sw, sy + sh,
observer);
} else if (hasDstSize) {
g.drawImage(image, dx, dy, dw, dh, observer);
} else if (invG == null) {
g.drawImage(image, dx, dy, userWidth, userHeight, observer);
} else {
g.drawImage(image, dx, dy, observer);
}
} finally {
if (invG != null) invG.dispose();
}
}
}

78
utils/src/main/java/com/github/weisj/darklaf/util/graphics/ScaledImage.java

@ -0,0 +1,78 @@
/*
* 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.util.graphics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
public class ScaledImage extends Image {
private final BufferedImage delegate;
private final double scaleX;
private final double scaleY;
public ScaledImage(final BufferedImage delegate, final double scaleX, final double scaleY) {
this.delegate = delegate;
this.scaleX = scaleX;
this.scaleY = scaleY;
}
public BufferedImage getDelegate() {
return delegate;
}
public double getScaleX() {
return scaleX;
}
public double getScaleY() {
return scaleY;
}
@Override
public int getWidth(final ImageObserver observer) {
return delegate.getWidth(observer);
}
@Override
public int getHeight(final ImageObserver observer) {
return delegate.getHeight(observer);
}
@Override
public ImageProducer getSource() {
return delegate.getSource();
}
@Override
public Graphics getGraphics() {
return delegate.getGraphics();
}
@Override
public Object getProperty(final String name, final ImageObserver observer) {
return delegate.getProperty(name, observer);
}
}

38
utils/src/main/java/com/github/weisj/darklaf/util/value/Shared.java

@ -0,0 +1,38 @@
/*
* 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.util.value;
public class Shared<T> {
private T value;
public Shared(final T initial) {
set(initial);
}
public T get() {
return value;
}
public void set(final T value) {
this.value = value;
}
}

36
utils/src/main/java/com/github/weisj/darklaf/util/value/SharedNonNull.java

@ -0,0 +1,36 @@
/*
* 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.util.value;
import java.util.Objects;
public class SharedNonNull<T> extends Shared<T> {
public SharedNonNull(final T initial) {
super(initial);
}
@Override
public void set(final T value) {
super.set(Objects.requireNonNull(value));
}
}
Loading…
Cancel
Save