Java/Advanced Graphics/Animation
Содержание
Animated Graphics
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/*
* AnimatedGraphics_2008.java
*
* Created on May 2, 2007, 4:02 PM
*
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
* @author Chet
*/
public class AnimatedGraphics_2008 extends JComponent implements ActionListener {
Color startColor = Color.GRAY; // where we start
Color endColor = Color.BLACK; // where we end
Color currentColor = startColor;
int animationDuration = 2000; // each animation will take 2 seconds
long animStartTime; // start time for each animation
/**
* Set up and start the timer
*/
public AnimatedGraphics_2008() {
Timer timer = new Timer(30, this);
// initial delay while window gets set up
timer.setInitialDelay(1000);
animStartTime = 1000 + System.nanoTime() / 1000000;
timer.start();
}
/**
* Erase to the background color and fill an oval with the current
* color (which is being animated elsewhere)
*/
public void paintComponent(Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(currentColor);
g.fillOval(0, 0, getWidth(), getHeight());
}
/**
* Callback from the Swing Timer. Calculate the fraction elapsed of
* our desired animation duration and interpolate between our start and
* end colors accordingly.
*/
public void actionPerformed(ActionEvent ae) {
// calculate elapsed fraction of animation
long currentTime = System.nanoTime() / 1000000;
long totalTime = currentTime - animStartTime;
if (totalTime > animationDuration) {
animStartTime = currentTime;
}
float fraction = (float)totalTime / animationDuration;
fraction = Math.min(1.0f, fraction);
// interpolate between start and end colors with current fraction
int red = (int)(fraction * endColor.getRed() +
(1 - fraction) * startColor.getRed());
int green = (int)(fraction * endColor.getGreen() +
(1 - fraction) * startColor.getGreen());
int blue = (int)(fraction * endColor.getBlue() +
(1 - fraction) * startColor.getBlue());
// set our new color appropriately
currentColor = new Color(red, green, blue);
// force a repaint to display our oval with its new color
repaint();
}
private static void createAndShowGUI() {
JFrame f = new JFrame("Animated Graphics");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(200, 200);
f.add(new AnimatedGraphics_2008());
f.setVisible(true);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
createAndShowGUI();
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
Background Animation
/*
* Copyright (c) 2007, Romain Guy
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.ruponent;
import java.awt.ruposite;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ruponentAdapter;
import java.awt.event.ruponentEvent;
import java.awt.event.ruponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* @author Romain Guy
*/
public class BackgroundAnimation_2008 extends JFrame {
private AvatarChooser chooser;
private CurvesPanel curves;
public BackgroundAnimation_2008() throws HeadlessException {
super("Stack Layout");
buildContentPane();
// buildDebugControls();
startAnimation();
setSize(640, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void startAnimation() {
Timer timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
curves.animate();
curves.repaint();
}
});
timer.start();
}
private void buildDebugControls() {
JPanel pane = new JPanel(new BorderLayout());
pane.setBackground(Color.WHITE);
pane.add(new JSeparator(), BorderLayout.NORTH);
final GraphPanel grapher = new GraphPanel();
JPanel graphPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
graphPane.setOpaque(false);
graphPane.add(grapher);
JPanel buttonsPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonsPane.setOpaque(false);
JSlider spacing = new JSlider(JSlider.HORIZONTAL, 0, 100, 40);
spacing.setBackground(Color.WHITE);
spacing.setPreferredSize(new Dimension(95, spacing.getPreferredSize().height));
spacing.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider) e.getSource();
double spacing = slider.getValue() / 100.0;
chooser.setSpacing(spacing);
grapher.spacing = spacing;
grapher.repaint();
}
});
JSlider sigma = new JSlider(JSlider.HORIZONTAL, 0, 100, 50);
sigma.setBackground(Color.WHITE);
sigma.setPreferredSize(new Dimension(95, sigma.getPreferredSize().height));
sigma.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider) e.getSource();
double sigma = slider.getValue() / 100.0;
chooser.setSigma(sigma);
grapher.sigma = sigma;
grapher.repaint();
}
});
JSlider position = new JSlider(JSlider.HORIZONTAL, -100, 100, 0);
position.setBackground(Color.WHITE);
position.setPreferredSize(new Dimension(95, position.getPreferredSize().height));
position.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider) e.getSource();
double position = slider.getValue() / 100.0;
chooser.setPosition(position);
grapher.position = position;
grapher.repaint();
}
});
JSlider amount = new JSlider(JSlider.HORIZONTAL, 1, 15, 5);
amount.setBackground(Color.WHITE);
amount.setPreferredSize(new Dimension(95, position.getPreferredSize().height));
amount.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider) e.getSource();
int amount = slider.getValue();
chooser.setAmount(amount);
grapher.amount = amount;
grapher.repaint();
}
});
buttonsPane.add(new JLabel("Spacing: "));
buttonsPane.add(spacing);
buttonsPane.add(new JLabel("Sigma: "));
buttonsPane.add(sigma);
buttonsPane.add(new JLabel("Position: "));
buttonsPane.add(position);
buttonsPane.add(new JLabel("Amount: "));
buttonsPane.add(amount);
pane.add(buttonsPane, BorderLayout.NORTH);
pane.add(graphPane, BorderLayout.CENTER);
add(pane, BorderLayout.SOUTH);
}
private void buildContentPane() {
JPanel pane = new JPanel();
pane.setLayout(new StackLayout());
GradientPanel gradient = new GradientPanel();
chooser = new AvatarChooser();
curves = new CurvesPanel();
pane.add(gradient, StackLayout.TOP);
pane.add(chooser, StackLayout.TOP);
pane.add(curves, StackLayout.TOP);
add(pane);
}
private class GraphPanel extends JComponent {
private double spacing = 0.4;
private double position = 0.0;
private double sigma = 0.5;
private int amount = 5;
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 60);
}
@Override
public boolean isOpaque() {
return false;
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.BLACK);
g2.drawLine(0, 50, 100, 50);
g2.drawLine(50, 0, 50, 60);
g2.setColor(Color.BLUE);
double lastY = 50.0;
for (int x = 0; x < 100; x++) {
double y = chooser.ruputeModifier((50.0 - x) / 25.0) * 45.0 + 10;
if (x == 0) {
lastY = y;
}
g2.drawLine(x - 1, 60 - (int) lastY, x, 60 - (int) y);
lastY = y;
}
g2.setColor(Color.RED);
for (int i = 0; i < amount; i++) {
double offset = ((amount / 2) - i) * spacing;
double x = (100.0 - 5.0) / 2.0;
x += 25.0 * (position + offset);
if (x > 100) {
continue;
}
double y = 60.0 - (chooser.ruputeModifier(position + offset) * 45.0 + 10);
g2.fill(new Rectangle2D.Double(x, y - 1, 5.0, 5.0));
}
g2.setColor(Color.GREEN.darker());
g2.drawLine(25, 0, 25, 60);
g2.drawLine(75, 0, 75, 60);
g2.setColor(Color.DARK_GRAY);
g2.drawString("Sigma: " + sigma, 110.0f, 16.0f);
g2.drawString("Spacing: " + spacing, 110.0f, 30.0f);
g2.drawString("Position: " + position, 110.0f, 44.0f);
}
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
} catch (IllegalAccessException ex) {
ex.printStackTrace();
} catch (InstantiationException ex) {
ex.printStackTrace();
} catch (UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
BackgroundAnimation_2008 tester = new BackgroundAnimation_2008();
tester.setVisible(true);
}
});
}
}
/*
* Copyright (c) 2007, Romain Guy All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the
* TimingFramework project nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
class AvatarChooser extends JPanel {
private static final double ANIM_SCROLL_DELAY = 450;
private List<Image> avatars = null;
private boolean loadingDone = false;
private Thread picturesFinder = null;
private Timer scrollerTimer = null;
private Timer faderTimer = null;
private Timer passwordTimer;
private float veilAlphaLevel = 0.0f;
private float alphaLevel = 0.0f;
private float textAlphaLevel = 0.0f;
private int avatarIndex;
private double avatarPosition = 0.0;
private double avatarSpacing = 0.4;
private int avatarAmount = 5;
private double sigma;
private double rho;
private double exp_multiplier;
private double exp_member;
private boolean damaged = true;
private DrawableAvatar[] drawableAvatars;
private String textAvatar;
private FocusGrabber focusGrabber;
private AvatarScroller avatarScroller;
private CursorChanger cursorChanger;
private MouseWheelScroller wheelScroller;
private KeyScroller keyScroller;
public AvatarChooser() {
GridBagLayout layout = new GridBagLayout();
setLayout(layout);
findAvatars();
setSigma(0.5);
addComponentListener(new DamageManager());
initInputListeners();
addInputListeners();
}
public void setAmount(int amount) {
if (amount > avatars.size()) {
throw new IllegalArgumentException("Too many avatars");
}
this.avatarAmount = amount;
repaint();
}
// XXX package access for debugging purpose only
void setPosition(double position) {
this.avatarPosition = position;
this.damaged = true;
repaint();
}
public void setSigma(double sigma) {
this.sigma = sigma;
this.rho = 1.0;
computeEquationParts();
this.rho = computeModifierUnprotected(0.0);
computeEquationParts();
this.damaged = true;
repaint();
}
public void setSpacing(double avatarSpacing) {
if (avatarSpacing < 0.0 || avatarSpacing > 1.0) {
throw new IllegalArgumentException("Spacing must be < 1.0 and > 0.0");
}
this.avatarSpacing = avatarSpacing;
this.damaged = true;
repaint();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(128 * 3, 128 * 2);
}
@Override
public Dimension getMaximumSize() {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@Override
public boolean isOpaque() {
return false;
}
@Override
public boolean isFocusable() {
return true;
}
@Override
protected void paintChildren(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Composite oldComposite = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, veilAlphaLevel));
super.paintChildren(g);
g2.setComposite(oldComposite);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (!loadingDone && faderTimer == null) {
return;
}
Insets insets = getInsets();
int x = insets.left;
int y = insets.top;
int width = getWidth() - insets.left - insets.right;
int height = getHeight() - insets.top - insets.bottom;
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Composite oldComposite = g2.getComposite();
if (damaged) {
drawableAvatars = sortAvatarsByDepth(x, y, width, height);
damaged = false;
}
drawAvatars(g2, drawableAvatars);
if (drawableAvatars.length > 0) {
drawAvatarName(g2);
}
g2.setComposite(oldComposite);
}
private void drawAvatars(Graphics2D g2, DrawableAvatar[] drawableAvatars) {
for (DrawableAvatar avatar : drawableAvatars) {
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) avatar
.getAlpha());
g2.setComposite(composite);
g2.drawImage(avatars.get(avatar.getIndex()), (int) avatar.getX(), (int) avatar.getY(), avatar
.getWidth(), avatar.getHeight(), null);
}
}
private DrawableAvatar[] sortAvatarsByDepth(int x, int y, int width, int height) {
List<DrawableAvatar> drawables = new LinkedList<DrawableAvatar>();
for (int i = 0; i < avatars.size(); i++) {
promoteAvatarToDrawable(drawables, x, y, width, height, i - avatarIndex);
}
DrawableAvatar[] drawableAvatars = new DrawableAvatar[drawables.size()];
drawableAvatars = drawables.toArray(drawableAvatars);
Arrays.sort(drawableAvatars);
return drawableAvatars;
}
private void drawAvatarName(Graphics2D g2) {
Composite composite = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, textAlphaLevel));
double bulletWidth = 150.0;
double bulletHeight = 30.0;
double x = (getWidth() - bulletWidth) / 2.0;
double y = (getHeight() - 164) / 2.0 - bulletHeight * 1.4;
drawAvatarBullet(g2, x, y, bulletWidth, bulletHeight);
drawAvatarText(g2, y, bulletHeight);
g2.setComposite(composite);
}
private void drawAvatarText(Graphics2D g2, double y, double bulletHeight) {
FontRenderContext context = g2.getFontRenderContext();
Font font = new Font("Dialog", Font.PLAIN, 18);
TextLayout layout = new TextLayout(textAvatar, font, context);
Rectangle2D bounds = layout.getBounds();
float text_x = (float) ((getWidth() - bounds.getWidth()) / 2.0);
float text_y = (float) (y + (bulletHeight - layout.getAscent() - layout.getDescent()) / 2.0)
+ layout.getAscent() - layout.getLeading();
g2.setColor(Color.BLACK);
layout.draw(g2, text_x, text_y + 1);
g2.setColor(Color.WHITE);
layout.draw(g2, text_x, text_y);
}
private void drawAvatarBullet(Graphics2D g2, double x, double y, double bulletWidth,
double bulletHeight) {
RoundRectangle2D bullet = new RoundRectangle2D.Double(0.0, 0.0, bulletWidth, bulletHeight,
bulletHeight, bulletHeight);
Ellipse2D curve = new Ellipse2D.Double(-20.0, bulletHeight / 2.0, bulletWidth + 40.0,
bulletHeight);
g2.translate(x, y);
g2.translate(-1, -2);
g2.setColor(new Color(0, 0, 0, 170));
g2.fill(new RoundRectangle2D.Double(0.0, 0.0, bulletWidth + 2, bulletHeight + 4,
bulletHeight + 4, bulletHeight + 4));
g2.translate(1, 2);
Color startColor = new Color(10, 0, 40);
Color endColor = new Color(175, 165, 225);
Paint paint = g2.getPaint();
g2.setPaint(new GradientPaint(0.0f, 0.0f, startColor, 0.0f, (float) bulletHeight, endColor));
g2.fill(bullet);
startColor = new Color(5, 0, 50);
endColor = new Color(105, 100, 155);
g2.setPaint(new GradientPaint(0.0f, 0.0f, startColor, 0.0f, (float) bulletHeight, endColor));
Area area = new Area(bullet);
area.intersect(new Area(curve));
g2.fill(area);
g2.setPaint(paint);
g2.translate(-x, -y);
}
private void promoteAvatarToDrawable(List<DrawableAvatar> drawables, int x, int y, int width,
int height, int offset) {
double spacing = offset * avatarSpacing;
double avatarPosition = this.avatarPosition + spacing;
if (avatarIndex + offset < 0 || avatarIndex + offset >= avatars.size()) {
return;
}
Image avatar = avatars.get(avatarIndex + offset);
int avatarWidth = avatar.getWidth(null);
int avatarHeight = avatar.getHeight(null);
double result = computeModifier(avatarPosition);
int newWidth = (int) (avatarWidth * result);
if (newWidth == 0) {
return;
}
int newHeight = (int) (avatarHeight * result);
if (newHeight == 0) {
return;
}
double avatar_x = x + (width - newWidth) / 2.0;
double avatar_y = y + (height - newHeight / 2.0) / 2.0;
double semiWidth = width / 2.0;
avatar_x += avatarPosition * semiWidth;
if (avatar_x >= width || avatar_x < -newWidth) {
return;
}
drawables.add(new DrawableAvatar(avatarIndex + offset, avatar_x, avatar_y, newWidth, newHeight,
avatarPosition, result));
}
private void startFader() {
faderTimer = new Timer(35, new FaderAction());
faderTimer.start();
}
private void computeEquationParts() {
exp_multiplier = Math.sqrt(2.0 * Math.PI) / sigma / rho;
exp_member = 4.0 * sigma * sigma;
}
// XXX package access for debug purpose only
double computeModifier(double x) {
double result = computeModifierUnprotected(x);
if (result > 1.0) {
result = 1.0;
} else if (result < -1.0) {
result = -1.0;
}
return result;
}
private double computeModifierUnprotected(double x) {
return exp_multiplier * Math.exp((-x * x) / exp_member);
}
private void addInputListeners() {
addMouseListener(focusGrabber);
addMouseListener(avatarScroller);
addMouseMotionListener(cursorChanger);
addMouseWheelListener(wheelScroller);
addKeyListener(keyScroller);
}
private void initInputListeners() {
// input listeners are all stateless
// hence they can be instantiated once
focusGrabber = new FocusGrabber();
avatarScroller = new AvatarScroller();
cursorChanger = new CursorChanger();
wheelScroller = new MouseWheelScroller();
keyScroller = new KeyScroller();
}
private void removeInputListeners() {
removeMouseListener(focusGrabber);
removeMouseListener(avatarScroller);
removeMouseMotionListener(cursorChanger);
removeMouseWheelListener(wheelScroller);
removeKeyListener(keyScroller);
}
private void findAvatars() {
avatars = new ArrayList<Image>();
picturesFinder = new Thread(new PicturesFinderThread());
picturesFinder.start();
}
private void setAvatarIndex(int index) {
avatarIndex = index;
textAvatar = "Photo " + index;
}
private void scrollBy(int increment) {
if (loadingDone) {
setAvatarIndex(avatarIndex + increment);
if (avatarIndex < 0) {
setAvatarIndex(0);
} else if (avatarIndex >= avatars.size()) {
setAvatarIndex(avatars.size() - 1);
}
damaged = true;
repaint();
}
}
private void scrollAndAnimateBy(int increment) {
if (loadingDone && (scrollerTimer == null || !scrollerTimer.isRunning())) {
int index = avatarIndex + increment;
if (index < 0) {
index = 0;
} else if (index >= avatars.size()) {
index = avatars.size() - 1;
}
DrawableAvatar drawable = null;
for (DrawableAvatar avatar : drawableAvatars) {
if (avatar.index == index) {
drawable = avatar;
break;
}
}
if (drawable != null) {
scrollAndAnimate(drawable);
}
}
}
private void scrollAndAnimate(DrawableAvatar avatar) {
if (loadingDone) {
scrollerTimer = new Timer(10, new AutoScroller(avatar));
scrollerTimer.start();
}
}
private BufferedImage createReflectedPicture(BufferedImage avatar) {
int avatarWidth = avatar.getWidth();
int avatarHeight = avatar.getHeight();
BufferedImage alphaMask = createGradientMask(avatarWidth, avatarHeight);
return createReflectedPicture(avatar, alphaMask);
}
private BufferedImage createReflectedPicture(BufferedImage avatar, BufferedImage alphaMask) {
int avatarWidth = avatar.getWidth();
int avatarHeight = avatar.getHeight();
BufferedImage buffer = createReflection(avatar, avatarWidth, avatarHeight);
applyAlphaMask(buffer, alphaMask, avatarWidth, avatarHeight);
return buffer;
}
private void applyAlphaMask(BufferedImage buffer, BufferedImage alphaMask, int avatarWidth,
int avatarHeight) {
Graphics2D g2 = buffer.createGraphics();
g2.setComposite(AlphaComposite.DstOut);
g2.drawImage(alphaMask, null, 0, avatarHeight);
g2.dispose();
}
private BufferedImage createReflection(BufferedImage avatar, int avatarWidth, int avatarHeight) {
BufferedImage buffer = new BufferedImage(avatarWidth, avatarHeight << 1,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = buffer.createGraphics();
g.drawImage(avatar, null, null);
g.translate(0, avatarHeight << 1);
AffineTransform reflectTransform = AffineTransform.getScaleInstance(1.0, -1.0);
g.drawImage(avatar, reflectTransform, null);
g.translate(0, -(avatarHeight << 1));
g.dispose();
return buffer;
}
private BufferedImage createGradientMask(int avatarWidth, int avatarHeight) {
BufferedImage gradient = new BufferedImage(avatarWidth, avatarHeight,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = gradient.createGraphics();
GradientPaint painter = new GradientPaint(0.0f, 0.0f, new Color(1.0f, 1.0f, 1.0f, 0.5f), 0.0f,
avatarHeight / 2.0f, new Color(1.0f, 1.0f, 1.0f, 1.0f));
g.setPaint(painter);
g.fill(new Rectangle2D.Double(0, 0, avatarWidth, avatarHeight));
g.dispose();
return gradient;
}
private DrawableAvatar getHitAvatar(int x, int y) {
for (DrawableAvatar avatar : drawableAvatars) {
Rectangle hit = new Rectangle((int) avatar.getX(), (int) avatar.getY(), avatar.getWidth(),
avatar.getHeight() / 2);
if (hit.contains(x, y)) {
return avatar;
}
}
return null;
}
private class PicturesFinderThread implements Runnable {
public void run() {
UnixGlobFileFilter allPngs = new UnixGlobFileFilter("*.jpg");
try {
FileTreeWalker walker = new FileTreeWalker(new File("."), allPngs);
PictureLoader loader = new PictureLoader();
walker.walk(loader);
List<File> files = loader.getFilesList();
for (int i = 0; i < files.size(); i++) {
BufferedImage image = ImageIO.read(files.get(i));
avatars.add(createReflectedPicture(image));
if (i == (files.size() / 2) + avatarAmount / 2) {
setAvatarIndex(i - avatarAmount / 2);
startFader();
}
}
} catch (IOException e) {
}
loadingDone = true;
}
private class PictureLoader implements FileTreeWalk {
private List<File> filesList;
private PictureLoader() {
filesList = new ArrayList<File>();
}
private List<File> getFilesList() {
Collections.sort(filesList);
return filesList;
}
public void walk(File path) {
filesList.add(path);
}
}
}
private class FaderAction implements ActionListener {
private long start = 0;
private FaderAction() {
alphaLevel = 0.0f;
textAlphaLevel = 0.0f;
}
public void actionPerformed(ActionEvent e) {
if (start == 0) {
start = System.currentTimeMillis();
}
alphaLevel = (System.currentTimeMillis() - start) / 500.0f;
textAlphaLevel = alphaLevel;
if (alphaLevel > 1.0f) {
alphaLevel = 1.0f;
textAlphaLevel = 1.0f;
faderTimer.stop();
}
repaint();
}
}
private class DrawableAvatar implements Comparable {
private int index;
private double x;
private double y;
private int width;
private int height;
private double zOrder;
private double position;
private DrawableAvatar(int index, double x, double y, int width, int height, double position,
double zOrder) {
this.index = index;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.position = position;
this.zOrder = zOrder;
}
public int compareTo(Object o) {
double zOrder2 = ((DrawableAvatar) o).zOrder;
if (zOrder < zOrder2) {
return -1;
} else if (zOrder > zOrder2) {
return 1;
}
return 0;
}
public double getPosition() {
return position;
}
public double getAlpha() {
return zOrder * alphaLevel;
}
public int getHeight() {
return height;
}
public int getIndex() {
return index;
}
public int getWidth() {
return width;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
}
private class MouseWheelScroller implements MouseWheelListener {
public void mouseWheelMoved(MouseWheelEvent e) {
int increment = e.getWheelRotation();
scrollAndAnimateBy(increment);
}
}
private class KeyScroller extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_LEFT:
case KeyEvent.VK_UP:
scrollAndAnimateBy(-1);
break;
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_DOWN:
scrollAndAnimateBy(1);
break;
case KeyEvent.VK_END:
scrollBy(avatars.size() - avatarIndex - 1);
break;
case KeyEvent.VK_HOME:
scrollBy(-avatarIndex - 1);
break;
case KeyEvent.VK_PAGE_UP:
scrollAndAnimateBy(-avatarAmount / 2);
break;
case KeyEvent.VK_PAGE_DOWN:
scrollAndAnimateBy(avatarAmount / 2);
break;
}
}
}
private class FocusGrabber extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
requestFocus();
}
}
private class AvatarScroller extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if ((faderTimer != null && faderTimer.isRunning())
|| (scrollerTimer != null && scrollerTimer.isRunning()) || drawableAvatars == null) {
return;
}
if (e.getButton() == MouseEvent.BUTTON1) {
DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY());
if (avatar != null && avatar.getIndex() != avatarIndex) {
scrollAndAnimate(avatar);
}
}
}
}
private class DamageManager extends ComponentAdapter {
@Override
public void componentResized(ComponentEvent e) {
damaged = true;
}
}
private class AutoScroller implements ActionListener {
private double position;
private int index;
private long start;
private AutoScroller(DrawableAvatar avatar) {
this.index = avatar.getIndex();
this.position = avatar.getPosition();
this.start = System.currentTimeMillis();
}
public void actionPerformed(ActionEvent e) {
long elapsed = System.currentTimeMillis() - start;
if (elapsed < ANIM_SCROLL_DELAY / 2.0) {
textAlphaLevel = (float) (1.0 - 2.0 * (elapsed / ANIM_SCROLL_DELAY));
} else {
textAlphaLevel = (float) (((elapsed / ANIM_SCROLL_DELAY) - 0.5) * 2.0);
if (textAlphaLevel > 1.0f) {
textAlphaLevel = 1.0f;
}
}
if (textAlphaLevel < 0.1f) {
textAlphaLevel = 0.1f;
textAvatar = "LoginName" + index;
}
double newPosition = (elapsed / ANIM_SCROLL_DELAY) * -position;
if (elapsed >= ANIM_SCROLL_DELAY) {
((Timer) e.getSource()).stop();
setAvatarIndex(index);
setPosition(0.0);
return;
}
setPosition(newPosition);
}
}
private class CursorChanger extends MouseMotionAdapter {
@Override
public void mouseMoved(MouseEvent e) {
if ((scrollerTimer != null && scrollerTimer.isRunning()) || drawableAvatars == null) {
return;
}
DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY());
if (avatar != null) {
getParent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
getParent().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
}
}
/*
* Copyright (c) 2007, Romain Guy All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the
* TimingFramework project nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
class CurvesPanel extends JPanel {
protected RenderingHints hints;
protected int counter = 0;
protected Color start = new Color(255, 255, 255, 200);
protected Color end = new Color(255, 255, 255, 0);
public CurvesPanel() {
this(new BorderLayout());
}
public CurvesPanel(LayoutManager manager) {
super(manager);
hints = createRenderingHints();
}
protected RenderingHints createRenderingHints() {
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
return hints;
}
public void animate() {
counter++;
}
@Override
public boolean isOpaque() {
return false;
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
RenderingHints oldHints = g2.getRenderingHints();
g2.setRenderingHints(hints);
float width = getWidth();
float height = getHeight();
g2.translate(0, -30);
drawCurve(g2, 20.0f, -10.0f, 20.0f, -10.0f, width / 2.0f - 40.0f, 10.0f, 0.0f, -5.0f,
width / 2.0f + 40, 1.0f, 0.0f, 5.0f, 50.0f, 5.0f, false);
g2.translate(0, 30);
g2.translate(0, height - 60);
drawCurve(g2, 30.0f, -15.0f, 50.0f, 15.0f, width / 2.0f - 40.0f, 1.0f, 15.0f, -25.0f,
width / 2.0f, 1.0f / 2.0f, 0.0f, 25.0f, 15.0f, 9.0f, false);
g2.translate(0, -height + 60);
drawCurve(g2, height - 35.0f, -5.0f, height - 50.0f, 10.0f, width / 2.0f - 40.0f, 1.0f,
height - 35.0f, -25.0f, width / 2.0f, 1.0f / 2.0f, height - 20.0f, 25.0f, 25.0f, 7.0f, true);
g2.setRenderingHints(oldHints);
}
protected void drawCurve(Graphics2D g2, float y1, float y1_offset, float y2, float y2_offset,
float cx1, float cx1_offset, float cy1, float cy1_offset, float cx2, float cx2_offset,
float cy2, float cy2_offset, float thickness, float speed, boolean invert) {
float width = getWidth();
float offset = (float) Math.sin(counter / (speed * Math.PI));
float start_x = 0.0f;
float start_y = offset * y1_offset + y1;
float end_x = width;
float end_y = offset * y2_offset + y2;
float ctrl1_x = offset * cx1_offset + cx1;
float ctrl1_y = offset * cy1_offset + cy1;
float ctrl2_x = offset * cx2_offset + cx2;
float ctrl2_y = offset * cy2_offset + cy2;
GeneralPath thickCurve = new GeneralPath();
thickCurve.moveTo(start_x, start_y);
thickCurve.curveTo(ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, end_x, end_y);
thickCurve.lineTo(end_x, end_y + thickness);
thickCurve.curveTo(ctrl2_x, ctrl2_y + thickness, ctrl1_x, ctrl1_y + thickness, start_x, start_y
+ thickness);
thickCurve.lineTo(start_x, start_y);
Rectangle bounds = thickCurve.getBounds();
if (!bounds.intersects(g2.getClipBounds())) {
return;
}
GradientPaint painter = new GradientPaint(0, bounds.y, invert ? end : start, 0, bounds.y
+ bounds.height, invert ? start : end);
Paint oldPainter = g2.getPaint();
g2.setPaint(painter);
g2.fill(thickCurve);
g2.setPaint(oldPainter);
}
}
/*
* Copyright (c) 2007, Romain Guy All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the
* TimingFramework project nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
class GradientPanel extends JPanel {
protected BufferedImage gradientImage;
protected Color gradientStart = new Color(204, 249, 124);
protected Color gradientEnd = new Color(174, 222, 94);
public GradientPanel() {
this(new BorderLayout());
}
public GradientPanel(LayoutManager layout) {
super(layout);
addComponentListener(new GradientCacheManager());
}
@Override
protected void paintComponent(Graphics g) {
createImageCache();
if (gradientImage != null) {
g.drawImage(gradientImage, 0, 0, getWidth(), getHeight(), null);
}
}
protected void createImageCache() {
int width = 2;
int height = getHeight();
if (width == 0 || height == 0) {
return;
}
if (gradientImage == null || width != gradientImage.getWidth()
|| height != gradientImage.getHeight()) {
gradientImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = gradientImage.createGraphics();
GradientPaint painter = new GradientPaint(0, 0, gradientEnd, 0, height / 2, gradientStart);
g2.setPaint(painter);
Rectangle2D rect = new Rectangle2D.Double(0, 0, width, height / 2.0);
g2.fill(rect);
painter = new GradientPaint(0, height / 2, gradientStart, 0, height, gradientEnd);
g2.setPaint(painter);
rect = new Rectangle2D.Double(0, (height / 2.0) - 1.0, width, height);
g2.fill(rect);
g2.dispose();
}
}
private void disposeImageCache() {
synchronized (gradientImage) {
gradientImage.flush();
gradientImage = null;
}
}
private class GradientCacheManager implements ComponentListener {
public void componentResized(ComponentEvent e) {
}
public void componentMoved(ComponentEvent e) {
}
public void componentShown(ComponentEvent e) {
}
public void componentHidden(ComponentEvent e) {
disposeImageCache();
}
}
}
/*
* Copyright (c) 2007, Romain Guy All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the
* TimingFramework project nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
* @author Romain Guy <romain.guy@mac.ru>
*/
class StackLayout implements LayoutManager2 {
public static final String BOTTOM = "bottom";
public static final String TOP = "top";
private List<Component> components = new LinkedList<Component>();
public void addLayoutComponent(Component comp, Object constraints) {
synchronized (comp.getTreeLock()) {
if (BOTTOM.equals(constraints)) {
components.add(0, comp);
} else if (TOP.equals(constraints)) {
components.add(comp);
} else {
components.add(comp);
}
}
}
public void addLayoutComponent(String name, Component comp) {
addLayoutComponent(comp, TOP);
}
public void removeLayoutComponent(Component comp) {
synchronized (comp.getTreeLock()) {
components.remove(comp);
}
}
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
public void invalidateLayout(Container target) {
}
public Dimension preferredLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
int width = 0;
int height = 0;
for (Component comp : components) {
Dimension size = comp.getPreferredSize();
width = Math.max(size.width, width);
height = Math.max(size.height, height);
}
Insets insets = parent.getInsets();
width += insets.left + insets.right;
height += insets.top + insets.bottom;
return new Dimension(width, height);
}
}
public Dimension minimumLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
int width = 0;
int height = 0;
for (Component comp : components) {
Dimension size = comp.getMinimumSize();
width = Math.max(size.width, width);
height = Math.max(size.height, height);
}
Insets insets = parent.getInsets();
width += insets.left + insets.right;
height += insets.top + insets.bottom;
return new Dimension(width, height);
}
}
public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
int width = parent.getWidth();
int height = parent.getHeight();
Rectangle bounds = new Rectangle(0, 0, width, height);
int componentsCount = components.size();
for (int i = 0; i < componentsCount; i++) {
Component comp = components.get(i);
comp.setBounds(bounds);
parent.setComponentZOrder(comp, componentsCount - i - 1);
}
}
}
}
/*
* Copyright (c) 2007, Romain Guy All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the
* TimingFramework project nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
class FileTreeWalker {
private File path;
private static final FileFilter directoryFilter = new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory();
}
};
private FileFilter filter;
public FileTreeWalker(File path) throws IOException {
this(path, new FileFilter() {
public boolean accept(File pathname) {
return pathname.isFile();
}
});
}
public FileTreeWalker(File path, FileFilter filter) throws IOException {
if (path == null || !path.exists() || path.isFile()) {
throw new IOException("Path " + path + " is not a valid directory.");
}
this.path = path;
this.filter = filter;
}
public void walk(FileTreeWalk walk) {
walkDirectory(walk, path);
}
private void walkDirectory(FileTreeWalk walk, File dir) {
File[] files = dir.listFiles(filter);
for (File file : files) {
walk.walk(file);
}
File[] dirs = dir.listFiles(directoryFilter);
for (File subDir : dirs) {
walkDirectory(walk, subDir);
}
}
}
/*
* Copyright (c) 2007, Romain Guy All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the
* TimingFramework project nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
interface FileTreeWalk {
public void walk(File path);
}
/*
* Copyright (c) 2007, Romain Guy All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the
* TimingFramework project nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @author Romain Guy
*/
class UnixGlobFileFilter implements FileFilter {
private Pattern pattern;
public UnixGlobFileFilter(String filter) {
pattern = Pattern.rupile(globToRegex(filter));
}
public boolean accept(File file) {
String path = file.getName();
Matcher matcher = pattern.matcher(path);
return matcher.matches();
}
private String globToRegex(String glob) {
char c = "\0";
boolean escape = false;
boolean enclosed = false;
StringBuffer buffer = new StringBuffer(glob.length());
for (int i = 0; i < glob.length(); i++) {
c = glob.charAt(i);
if (escape) {
buffer.append("\\");
buffer.append(c);
escape = false;
continue;
}
switch (c) {
case "*":
buffer.append(".").append("*");
break;
case "?":
buffer.append(".");
break;
case "\\":
escape = true;
break;
case ".":
buffer.append("\\").append(".");
break;
case "{":
buffer.append("(");
enclosed = true;
break;
case "}":
buffer.append(")");
enclosed = false;
break;
case ",":
if (enclosed)
buffer.append("|");
else
buffer.append(",");
break;
default:
buffer.append(c);
}
}
return buffer.toString();
}
}
Clock
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import java.awt.*;
import javax.swing.*;
import no.geosoft.cc.geometry.Geometry;
import no.geosoft.cc.geometry.Matrix4x4;
import no.geosoft.cc.graphics.*;
/**
* G demo program. Demonstrates:
*
* <ul>
* <li> Advaced geometry generation
* <li> Dynamic update
* <li> Threading
* </ul>
*
* @author
== Curve Animation ==
<!-- start source code -->
<source lang="java">
import java.awt.*;
import java.applet.*;
import graph.*;
/*************************************************************************
**
** Applet example0
** Version 1.0 January 1996
**
**************************************************************************
** Copyright (C) 1996 Leigh Brookshaw
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**************************************************************************
**
**
*************************************************************************/
public class example0 extends Applet implements Runnable {
Graph2D graph;
DataSet data;
double max;
double min;
int steps;
Thread thread;
public void init() {
int i;
int j;
graph = new Graph2D();
setLayout( new BorderLayout() );
add("Center", graph);
graph.framecolor = new Color(0,0,0);
graph.borderTop = 0;
graph.borderBottom = 1;
graph.borderLeft = 0;
graph.borderRight = 1;
graph.setGraphBackground(new Color(50,50,200));
max = 15*Math.PI;
min = Math.PI;
steps = 20;
thread = new Thread(this);
thread.start();
}
public void run() {
DataSet data = null;
int count = 1;
int direction = 1;
double range;
Color c = new Color(255,255,0);
while(true) {
graph.detachDataSet(data);
data = null;
if(count >= steps ) direction = -1;
else
if(count <= 0 ) direction = 1;
count += direction;
range = min + (max-min)*count/steps;
// System.out.println("Range="+range);
data = sinx(range);
// data.xmax = max;
// data.xmin = -max;
data.ymax = 1.0;
data.linecolor = c;
graph.repaint(75);
try { thread.sleep(150); }
catch(Exception e) { }
}
}
public DataSet sinx(double range) {
int i, j;
int np = 100;
double data[] = new double[2*np];
double x, y;
for(i=j=0; i<np; i++,j+=2) {
x = (i-np/2)*range/(np/2);
if( x == 0.0 ) y = 1.0;
else y = Math.sin(x)/x;
data[j] = x;
data[j+1] = y;
}
return graph.loadDataSet(data,np);
}
public static void main(String[] a){
javax.swing.JFrame f = new javax.swing.JFrame();
Applet app = new example0();
app.init();
f.getContentPane().add (app);
f.pack();
f.setSize (new Dimension (500, 500));
f.setVisible(true);
}
}
Custom move interaction, Switching interactions, Update world extent geometry and Scroll handling
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import no.geosoft.cc.geometry.Geometry;
import no.geosoft.cc.graphics.*;
/**
* G demo program. Demonstrates:
*
* <ul>
* <li>Custom move interaction
* <li>Switching interactions
* <li>Update world extent geometry
* <li>Scroll handling
* </ul>
*
* @author
== Moving Button ==
<!-- start source code -->
<source lang="java">
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/*
* MovingButton.java
*
* Created on May 2, 2007, 4:17 PM
*
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
* @author Chet
*/
public class MovingButton extends JButton implements ActionListener {
Timer timer; // for later start/stop actions
int animationDuration = 2000; // each animation will take 2 seconds
long animStartTime; // start time for each animation
int translateY = 0; // y location of the button
static final int MAX_Y = 100;
/** Creates a new instance of TranslucentButton */
public MovingButton(String label) {
super(label);
setOpaque(false);
timer = new Timer(30, this);
addActionListener(this);
}
/**
* Displays our component in the location (0, translateY). Note that
* this changes only the rendering location of the button, not the
* physical location of it. Note, also, that rendering into g will
* be clipped to the physical location of the button, so the button will
* disappear as it moves away from that location.
*/
public void paint(Graphics g) {
g.translate(0, translateY);
super.paint(g);
}
/**
* This method handles both button clicks, which start/stop the animation,
* and Swing Timer events.
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getSource().equals(this)) {
// button click
if (!timer.isRunning()) {
animStartTime = System.nanoTime() / 1000000;
this.setText("Stop Animation");
timer.start();
} else {
timer.stop();
this.setText("Start Animation");
// reset translation to 0
translateY = 0;
}
} else {
// Timer event
// calculate the elapsed fraction
long currentTime = System.nanoTime() / 1000000;
long totalTime = currentTime - animStartTime;
if (totalTime > animationDuration) {
animStartTime = currentTime;
}
float fraction = (float)totalTime / animationDuration;
fraction = Math.min(1.0f, fraction);
// This calculation will cause translateY to go from 0 to MAX_Y
// as the fraction goes from 0 to 1
if (fraction < .5f) {
translateY = (int)(MAX_Y * (2 * fraction));
} else {
translateY = (int)(MAX_Y * (2 * (1 - fraction)));
}
// redisplay our component with the new location
repaint();
}
}
private static void createAndShowGUI() {
JFrame f = new JFrame("Moving Button");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
JPanel checkerboard = new Checkerboard();
checkerboard.add(new MovingButton("Start Animation"));
f.add(checkerboard);
f.setVisible(true);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
createAndShowGUI();
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
/**
* Paints a checkerboard background
*/
private static class Checkerboard extends JPanel {
private static final int DIVISIONS = 10;
static final int CHECKER_SIZE = 60;
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
for (int stripeX = 0; stripeX < getWidth(); stripeX += CHECKER_SIZE) {
for (int y = 0, row = 0; y < getHeight(); y += CHECKER_SIZE/2, ++row) {
int x = (row % 2 == 0) ? stripeX : (stripeX + CHECKER_SIZE/2);
g.fillRect(x, y, CHECKER_SIZE/2, CHECKER_SIZE/2);
}
}
}
}
}
Moving Button Container
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/*
* MovingButtonContainer.java
*
* Created on May 2, 2007, 4:39 PM
*
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
* @author Chet
*/
public class MovingButtonContainer extends JComponent implements ActionListener {
Timer timer; // for later start/stop actions
int animationDuration = 2000; // each animation will take 2 seconds
long animStartTime; // start time for each animation
int translateY = 0; // current Y location of button
static final int MAX_Y = 100;
JButton button = null;
private static final int DIVISIONS = 10;
static final int CHECKER_SIZE = 60;
/** Creates a new instance of TranslucentButton */
public MovingButtonContainer() {
setLayout(new java.awt.FlowLayout());
timer = new Timer(30, this);
button = new JButton("Start Animation");
// Need setOpaque(false) to force Swing to paint the button"s parent
button.setOpaque(false);
button.addActionListener(this);
add(button);
}
/**
* This method handles both button clicks, which start/stop the animation,
* and Swing Timer events, which animate translateY.
*/
public void actionPerformed(ActionEvent ae) {
if (ae.getSource().equals(button)) {
// button click
if (!timer.isRunning()) {
animStartTime = System.nanoTime() / 1000000;
button.setText("Stop Animation");
timer.start();
} else {
timer.stop();
button.setText("Start Animation");
// reset translation to 0
translateY = 0;
// Must force repaint to get final animated position erased
repaint();
}
} else {
// Timer event
// calculate elapsed fraction
long currentTime = System.nanoTime() / 1000000;
long totalTime = currentTime - animStartTime;
if (totalTime > animationDuration) {
animStartTime = currentTime;
}
float fraction = (float)totalTime / animationDuration;
fraction = Math.min(1.0f, fraction);
// This calculation will cause translateY to go from 0 to MAX_Y
// as the fraction goes from 0 to 1
if (fraction < .5f) {
translateY = (int)(MAX_Y * (2 * fraction));
} else {
translateY = (int)(MAX_Y * (2 * (1 - fraction)));
}
// redisplay our container to paint the button in the new location
repaint();
}
}
/**
* Paint our container with a checkerboard background. Then set a
* translation factor on the Graphics object and call the superclass,
* which will paint the children (e.g, our button) with this
* translation.
*/
public void paint(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
for (int stripeX = 0; stripeX < getWidth(); stripeX += CHECKER_SIZE) {
for (int y = 0, row = 0; y < getHeight(); y += CHECKER_SIZE/2, ++row) {
int x = (row % 2 == 0) ? stripeX : (stripeX + CHECKER_SIZE/2);
g.fillRect(x, y, CHECKER_SIZE/2, CHECKER_SIZE/2);
}
}
// Translate our graphics according to the animated translateY value
g.translate(0, translateY);
// Defer to superclass to handle painting for component, which includes
// painting the children of this container
super.paint(g);
}
private static void createAndShowGUI() {
JFrame f = new JFrame("Moving Button Container");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
JComponent buttonContainer = new MovingButtonContainer();
f.add(buttonContainer);
f.setVisible(true);
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
createAndShowGUI();
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
Scroll Handling
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import no.geosoft.cc.geometry.Geometry;
import no.geosoft.cc.graphics.*;
/**
* G demo program. Demonstrates:
*
* <ul>
* <li>Custom move interaction
* <li>Switching interactions
* <li>Update world extent geometry
* <li>Scroll handling
* </ul>
*
* @author
== Simple animation technique ==
<!-- start source code -->
<source lang="java">
import java.awt.*;
import javax.swing.*;
import no.geosoft.cc.geometry.Geometry;
import no.geosoft.cc.geometry.Matrix4x4;
import no.geosoft.cc.graphics.*;
/**
* G demo program. Demonstrates:
*
* <ul>
* <li>Simple animation technique
* </ul>
*
* @author
== Smooth Moves ==
<!-- start source code -->
<source lang="java">
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/*
* SmoothMoves.java
*
* Created on May 2, 2007, 4:49 PM
*
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
* @author Chet
*/
public class SmoothMoves extends JComponent implements ActionListener, KeyListener {
/** image holds the graphics we render for each animating object */
BufferedImage image = null;
static int imageW = 100;
static int imageH = 150;
/** Location of fading animation */
int fadeX = 50;
int fadeY = 50;
/** X values that moving animation will move between */
static int moveMinX = 150;
static int moveMaxX = 350;
/** Current x/y location of moving animation */
int moveX = moveMinX;
int moveY = 50;
/** Current opacity of fading animation */
float opacity = 0.0f;
/** Toggles for various demo options (key to toggle in parentheses) */
boolean useImage = false; // (i) image instead of rectangle
boolean useAA = false; // (a) anti-aliased edges (rectangle only)
boolean motionBlur = false; // (b) ghost images behind moving animation
boolean alterColor = false; // (c) light-gray instead of black rectangle
boolean linear = true; // (l) linear vs. non-linear motion
/** Used for motion blur rendering; holds information for ghost trail */
int blurSize = 5;
int prevMoveX[];
int prevMoveY[];
float trailOpacity[];
/** Basic Timer animation info */
final static int CYCLE_TIME = 2000; // One cycle takes 2 seconds
int currentResolution = 50; // current Timer resolution
Timer timer = null; // animation Timer
long cycleStart; // track start time for each cycle
/** Creates a new instance of SmoothAnimation */
public SmoothMoves() {
// createAnimationImage();
cycleStart = System.nanoTime() / 1000000;
startTimer(currentResolution);
}
/**
* Create the image that will be animated. This image may be an actual image
* (duke.gif), or some graphics (a variation on a black filled rectangle) that
* are rendered into an image. The contents of this image are dependent upon
* the runtime toggles that have been set when this method is called.
*/
void createAnimationImage() {
GraphicsConfiguration gc = getGraphicsConfiguration();
image = gc.createCompatibleImage(imageW, imageH, Transparency.TRANSLUCENT);
Graphics2D gImg = image.createGraphics();
if (useImage) {
try {
URL url = getClass().getResource("duke.gif");
Image originalImage = ImageIO.read(url);
gImg.drawImage(originalImage, 0, 0, imageW, imageH, null);
} catch (Exception e) {
}
} else {
// use graphics
Color graphicsColor;
if (alterColor) {
graphicsColor = Color.LIGHT_GRAY;
} else {
graphicsColor = Color.BLACK;
}
gImg.setColor(graphicsColor);
gImg.fillRect(0, 0, imageW, imageH);
if (useAA) {
// Antialiasing hack - just draw a fading-out border around the
// rectangle
gImg.setComposite(AlphaComposite.Src);
int red = graphicsColor.getRed();
int green = graphicsColor.getRed();
int blue = graphicsColor.getRed();
gImg.setColor(new Color(red, green, blue, 50));
gImg.drawRect(0, 0, imageW - 1, imageH - 1);
gImg.setColor(new Color(red, green, blue, 100));
gImg.drawRect(1, 1, imageW - 3, imageH - 3);
gImg.setColor(new Color(red, green, blue, 150));
gImg.drawRect(2, 2, imageW - 5, imageH - 5);
gImg.setColor(new Color(red, green, blue, 200));
gImg.drawRect(3, 3, imageW - 7, imageH - 7);
gImg.setColor(new Color(red, green, blue, 225));
gImg.drawRect(4, 4, imageW - 9, imageH - 9);
}
}
gImg.dispose();
}
public void paintComponent(Graphics g) {
if (image == null) {
createAnimationImage();
}
// Erase the background
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
// Draw the fading image
Graphics2D gFade = (Graphics2D) g.create();
gFade.setComposite(AlphaComposite.SrcOver.derive(opacity));
gFade.drawImage(image, fadeX, fadeY, null);
gFade.dispose();
// Draw the moving image
if (motionBlur) {
// Draw previous locations of the image as a trail of
// ghost images
if (prevMoveX == null) {
// blur location array not yet created; create it now
prevMoveX = new int[blurSize];
prevMoveY = new int[blurSize];
trailOpacity = new float[blurSize];
float incrementalFactor = .2f / (blurSize + 1);
for (int i = 0; i < blurSize; ++i) {
// default values, act as flag to not render these
// until they have real values
prevMoveX[i] = -1;
prevMoveY[i] = -1;
// vary the translucency by the number of the ghost
// image; the further away it is from the current one,
// the more faded it will be
trailOpacity[i] = (.2f - incrementalFactor) - i * incrementalFactor;
}
} else {
Graphics2D gTrail = (Graphics2D) g.create();
for (int i = 0; i < blurSize; ++i) {
if (prevMoveX[i] >= 0) {
// Render each blur image with the appropriate
// amount of translucency
gTrail.setComposite(AlphaComposite.SrcOver.derive(trailOpacity[i]));
gTrail.drawImage(image, prevMoveX[i], prevMoveY[i], null);
}
}
gTrail.dispose();
}
}
g.drawImage(image, moveX, moveY, null);
if (motionBlur) {
// shift the ghost positions to add the current position and
// drop the oldest one
for (int i = blurSize - 1; i > 0; --i) {
prevMoveX[i] = prevMoveX[i - 1];
prevMoveY[i] = prevMoveY[i - 1];
}
prevMoveX[0] = moveX;
prevMoveY[0] = moveY;
}
}
/**
* This method handles the events from the Swing Timer
*/
public void actionPerformed(ActionEvent ae) {
// calculate the fraction elapsed of the animation and call animate()
// to alter the values accordingly
long currentTime = System.nanoTime() / 1000000;
long totalTime = currentTime - cycleStart;
if (totalTime > CYCLE_TIME) {
cycleStart = currentTime;
}
float fraction = (float) totalTime / CYCLE_TIME;
fraction = Math.min(1.0f, fraction);
fraction = 1 - Math.abs(1 - (2 * fraction));
animate(fraction);
}
/**
* Animate the opacity and location factors, according to the current
* fraction.
*/
public void animate(float fraction) {
float animationFactor;
if (linear) {
animationFactor = fraction;
} else {
// Our "nonlinear" motion just uses a sin function to get a
// simple bounce behavior
animationFactor = (float) Math.sin(fraction * (float) Math.PI / 2);
}
// Clamp the value to make sure it does not exceed the bounds
animationFactor = Math.min(animationFactor, 1.0f);
animationFactor = Math.max(animationFactor, 0.0f);
// The opacity, used by the fading animation, will just use the
// animation fraction directly
opacity = animationFactor;
// The move animation will calculate a location based on a linear
// interpolation between its start and end points using the fraction
moveX = moveMinX + (int) (.5f + animationFactor * (float) (moveMaxX - moveMinX));
// redisplay our component with the new animated values
repaint();
}
/**
* Moves the frame rate up or down by changing the Timer resolution
*/
private void changeResolution(boolean faster) {
if (faster) {
currentResolution -= 5;
} else {
currentResolution += 5;
}
currentResolution = Math.max(currentResolution, 0);
currentResolution = Math.min(currentResolution, 500);
startTimer(currentResolution);
}
/**
* Starts the animation
*/
private void startTimer(int resolution) {
if (timer != null) {
timer.stop();
timer.setDelay(resolution);
} else {
timer = new Timer(resolution, this);
}
timer.start();
}
/**
* Toggles various rendering flags
*/
public void keyPressed(KeyEvent ke) {
int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_B) {
// B: Motion blur - displays trail of ghost images
motionBlur = !motionBlur;
} else if (keyCode == KeyEvent.VK_A) {
// A: Antialiasing - Displays soft edges around graphics
useAA = !useAA;
createAnimationImage();
} else if (keyCode == KeyEvent.VK_C) {
// C: Color - Toggles rectangle color between dark and light colors
alterColor = !alterColor;
createAnimationImage();
} else if (keyCode == KeyEvent.VK_I) {
// I: Image - Toggles use of image or filled rectangle to show how
// straight edges affect animation perception
useImage = !useImage;
createAnimationImage();
} else if (keyCode == KeyEvent.VK_UP) {
// Up Arrow: Speed - Speeds up frame rate
changeResolution(true);
} else if (keyCode == KeyEvent.VK_DOWN) {
// Down Arrow: Speed - Slows down frame rate
changeResolution(false);
} else if (keyCode == KeyEvent.VK_L) {
// L: Linearity: Toggles linear/nonlinear motion
linear = !linear;
} else if (keyCode >= KeyEvent.VK_1 && keyCode <= KeyEvent.VK_9) {
// 0-9: Blur size: Toggles size of ghost trail for motion blur
blurSize = keyCode - KeyEvent.VK_0;
prevMoveX = prevMoveY = null;
}
}
// Unused KeyListener implementations
public void keyReleased(KeyEvent ke) {
}
public void keyTyped(KeyEvent ke) {
}
private static void createAndShowGUI() {
JFrame f = new JFrame("Smooth Moves");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(moveMaxX + imageW + 50, 300);
SmoothMoves component = new SmoothMoves();
f.add(component);
f.setVisible(true);
f.addKeyListener(component);
}
public static void main(String[] args) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
createAndShowGUI();
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}