Browse Source

Replace generic bezier interpolator with predefined optimized interpolation functions.

Convert DefaultInterpolator to enum.
Add demo for Animator/different interpolators.
pull/214/head
weisj 4 years ago
parent
commit
6b748624af
  1. 12
      core/src/main/java/com/github/weisj/darklaf/graphics/Animator.java
  2. 103
      core/src/main/java/com/github/weisj/darklaf/graphics/DefaultInterpolator.java
  3. 9
      core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarListener.java
  4. 129
      core/src/test/java/ui/AnimationDemo.java

12
core/src/main/java/com/github/weisj/darklaf/graphics/Animator.java

@ -40,7 +40,7 @@ public abstract class Animator {
private boolean forward;
private final Interpolator interpolator;
private Interpolator interpolator;
private ScheduledFuture<?> ticker;
private int startFrame;
@ -83,7 +83,7 @@ public abstract class Animator {
this.forward = forward;
}
public void resetTime() {
private void resetTime() {
startTime = -1;
}
@ -214,4 +214,12 @@ public abstract class Animator {
public int getTotalFrames() {
return totalFrames;
}
public Interpolator getInterpolator() {
return interpolator;
}
public void setInterpolator(final Interpolator interpolator) {
this.interpolator = interpolator;
}
}

103
core/src/main/java/com/github/weisj/darklaf/graphics/DefaultInterpolator.java

@ -21,93 +21,24 @@
*/
package com.github.weisj.darklaf.graphics;
public class DefaultInterpolator {
public static final Interpolator LINEAR = f -> f;
public static final Interpolator LINEAR_REVERSE = f -> 1 - f;
public static final Interpolator EASE = new CubicBezierEasingInterpolator(0.25f, 0.1f, 0.25f, 1f);
public static final Interpolator EASE_IN = new CubicBezierEasingInterpolator(0.42f, 0f, 1f, 1f);
public static final Interpolator EASE_IN_OUT = new CubicBezierEasingInterpolator(0.42f, 0f, 0.58f, 1f);
public static final Interpolator EASE_OUT = new CubicBezierEasingInterpolator(0f, 0f, 0.58f, 1f);
private DefaultInterpolator() {
throw new IllegalStateException("Utility class");
public enum DefaultInterpolator implements Interpolator {
LINEAR(x -> x), EASE_IN_SINE(x -> (float) (1 - Math.cos((x * Math.PI) / 2))),
EASE_OUT_SINE(x -> (float) Math.sin((x * Math.PI) / 2)), EASE_IN_QUAD(x -> x * x), EASE_OUT_QUAD(x -> {
float tmp = 1 - x;
return 1 - tmp * tmp;
}), EASE_IN_CUBIC(x -> x * x * x), EASE_OUT_CUBIC(x -> {
float tmp = 1 - x;
return 1 - tmp * tmp * tmp;
});
private final Interpolator delegate;
DefaultInterpolator(final Interpolator delegate) {
this.delegate = delegate;
}
private static class CubicBezierEasingInterpolator implements Interpolator {
private final float x1;
private final float y1;
private final float x2;
private final float y2;
private CubicBezierEasingInterpolator(final float x1, final float y1, final float x2, final float y2) {
this.x1 = checkControlPoint(x1);
this.y1 = checkControlPoint(y1);
this.x2 = checkControlPoint(x2);
this.y2 = checkControlPoint(y2);
}
private float checkControlPoint(final float p) {
if (p < 0 || p > 1) {
throw new IllegalArgumentException("control points must be in range [0, 1]");
}
return p;
}
@Override
public float interpolate(final float fraction) {
if (fraction <= 0) return 0;
if (fraction >= 1) return 1;
// use binary search
float low = 0;
float high = 1;
while (true) {
float mid = (low + high) / 2;
float estimate = cubicBezier(mid, x1, x2);
if (Math.abs(fraction - estimate) < 0.0005f) return cubicBezier(mid, y1, y2);
if (estimate < fraction)
low = mid;
else
high = mid;
}
}
/**
* Computes the x or y point on a cubic bezier curve for a given t value.
* <p>
* https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B%C3%A9zier_curves
* <p>
* The general cubic bezier formula is:
*
* <pre>
* x = b0*x0 + b1*x1 + b2*x2 + b3*x3
* y = b0*y0 + b1*y1 + b2*y2 + b3*y3
* </pr>
* <p>
* where:
* <pre>
* b0 = (1-t)^3
* b1 = 3 * t * (1-t)^2
* b2 = 3 * t^2 * (1-t)
* b3 = t^3
* </pr>
*
* x0,y0 is always 0,0 and x3,y3 is 1,1, so we can simplify to:
*
* <pre>
* x = b1*x1 + b2*x2 + b3
* y = b1*x1 + b2*x2 + b3
* </pre>
*/
private static float cubicBezier(final float t, final float xy1, final float xy2) {
float invT = (1 - t);
float b1 = 3 * t * (invT * invT);
float b2 = 3 * (t * t) * invT;
float b3 = t * t * t;
return (b1 * xy1) + (b2 * xy2) + b3;
}
@Override
public float interpolate(final float fraction) {
return delegate.interpolate(fraction);
}
}

9
core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarListener.java

@ -289,10 +289,10 @@ public class DarkScrollBarListener extends MouseAdapter implements AdjustmentLis
private final float maxValue;
private final boolean fadeIn;
public SBAnimator(final int totaleFrames, final int cycleDuration, final int delayFrames,
public SBAnimator(final int totalFrames, final int cycleDuration, final int delayFrames,
final Component component, final float minValue, final float maxValue, final boolean fadeIn) {
super(totaleFrames, cycleDuration, delayFrames, false, true,
fadeIn ? DefaultInterpolator.LINEAR : DefaultInterpolator.LINEAR_REVERSE);
super(totalFrames, cycleDuration, delayFrames, false, true,
fadeIn ? DefaultInterpolator.EASE_OUT_CUBIC : DefaultInterpolator.EASE_IN_CUBIC);
this.component = component;
this.minValue = minValue;
this.maxValue = maxValue;
@ -303,7 +303,8 @@ public class DarkScrollBarListener extends MouseAdapter implements AdjustmentLis
@Override
public void paintNow(final float fraction) {
updateValue(minValue + maxValue * fraction);
float fr = fadeIn ? fraction : (1 - fraction);
updateValue(minValue + maxValue * fr);
repaint();
}

129
core/src/test/java/ui/AnimationDemo.java

@ -0,0 +1,129 @@
/*
* MIT License
*
* Copyright (c) 2020 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 ui;
import java.awt.*;
import java.util.Arrays;
import java.util.List;
import javax.swing.*;
import com.github.weisj.darklaf.graphics.Animator;
import com.github.weisj.darklaf.graphics.DefaultInterpolator;
import com.github.weisj.darklaf.graphics.Interpolator;
public class AnimationDemo implements ComponentDemo {
public static void main(final String[] args) {
ComponentDemo.showDemo(new AnimationDemo());
}
@Override
public JComponent createComponent() {
List<Interpolator> interpolators = Arrays.asList(DefaultInterpolator.values());
JList<Interpolator> listComp = new JList<>();
DefaultListModel<Interpolator> listModel = new DefaultListModel<>();
interpolators.forEach(listModel::addElement);
listComp.setModel(listModel);
listComp.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
AnimatedElement animatedElement = new AnimatedElement();
listComp.setSelectedValue(animatedElement.animator.getInterpolator(), true);
JPanel top = new JPanel(new GridBagLayout());
JPanel topHolder = new JPanel(new BorderLayout());
topHolder.add(top, BorderLayout.CENTER);
top.add(animatedElement, null);
Box buttonHolder = Box.createHorizontalBox();
buttonHolder.add(Box.createHorizontalGlue());
JButton runButton = new JButton("Run");
runButton.addActionListener(e -> {
runButton.setEnabled(false);
listComp.setEnabled(false);
animatedElement.animator.setInterpolator(listComp.getSelectedValue());
animatedElement.animator.suspend();
animatedElement.animator.resume();
});
animatedElement.endCallback = () -> {
runButton.setEnabled(true);
listComp.setEnabled(true);
};
buttonHolder.add(runButton);
buttonHolder.add(Box.createHorizontalGlue());
topHolder.add(buttonHolder, BorderLayout.SOUTH);
JPanel content = new JPanel(new BorderLayout());
content.add(topHolder, BorderLayout.CENTER);
content.add(listComp, BorderLayout.SOUTH);
return content;
}
@Override
public String getTitle() {
return "Animation Demo";
}
private static class AnimatedElement extends JPanel {
private float state;
private Runnable endCallback;
Animator animator = new Animator(1000, 1000, 0) {
@Override
public void paintNow(final float fraction) {
state = fraction;
paintImmediately(getVisibleRect());
}
@Override
protected void paintCycleEnd() {
super.paintCycleEnd();
if (endCallback != null) endCallback.run();
}
@Override
public void suspend() {
super.suspend();
paintImmediately(getVisibleRect());
}
};
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
g.setColor(getForeground());
int x = (int) ((getWidth() - 5) * state);
g.fillRect(x, 0, 5, getHeight());
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 50);
}
}
}
Loading…
Cancel
Save