/*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.animations;
import com.sun.lwuit.Button;
import com.sun.lwuit.Component;
import com.sun.lwuit.Container;
import com.sun.lwuit.Dialog;
import com.sun.lwuit.Graphics;
import com.sun.lwuit.Image;
import com.sun.lwuit.Painter;
import com.sun.lwuit.RGBImage;
import com.sun.lwuit.plaf.UIManager;
/**
* Contains common transition animations including the following:
* <ol>
* <li>Slide - the exiting form slides out of the screen while the new form slides in.
* <li>Fade - components fade into/out of the screen
* </ol>
* <p>Instances of this class are created using factory methods.
*
* @author Shai Almog, Chen Fishbein
*/
public final class CommonTransitions extends Transition {
private Motion motion;
private static final int TYPE_EMPTY = 0;
private static final int TYPE_SLIDE = 1;
private static final int TYPE_FADE = 2;
private static final int TYPE_FAST_SLIDE = 3;
/**
* Slide the transition horizontally
* @see #createSlide
*/
public static final int SLIDE_HORIZONTAL = 0;
/**
* Slide the transition vertically
* @see #createSlide
*/
public static final int SLIDE_VERTICAL = 1;
private int slideType;
private int speed;
private int position;
private int transitionType;
private Image buffer;
private Image secondaryBuffer;
private static boolean defaultLinearMotion = false;
private boolean linearMotion = defaultLinearMotion;
private boolean motionSetManually;
/**
* The transition is a special case where we "keep" an allocated buffer
*/
private RGBImage rgbBuffer;
private boolean forward;
private boolean drawDialogMenu;
private boolean firstFinished;
private CommonTransitions(int type) {
transitionType = type;
}
/**
* Creates an empty transition that does nothing. This has the same effect as
* setting a transition to null.
*
* @return empty transition
*/
public static CommonTransitions createEmpty() {
CommonTransitions t = new CommonTransitions(TYPE_EMPTY);
return t;
}
/**
* Creates a slide transition with the given duration and direction, this differs from the
* standard slide animation by focusing on speed rather than on minimizing heap usage.
* This method works by creating two images and sliding them which works much faster for
* all devices however takes up more ram. Notice that this method of painting doesn't
* support various basic LWUIT abilities such as translucent menus/dialogs etc.
*
* @param type type can be either vertically or horizontally, which means
* the movement direction of the transition
* @param forward forward is a boolean value, represent the directions of
* switching forms, for example for a horizontally type, true means
* horizontally movement to right.
* @param duration represent the time the transition should take in millisecond
* @return a transition object
*/
public static CommonTransitions createFastSlide(int type, boolean forward, int duration) {
return createFastSlide(type, forward, duration, false);
}
/**
* Creates a slide transition with the given duration and direction
*
* @param type type can be either vertically or horizontally, which means
* the movement direction of the transition
* @param forward forward is a boolean value, represent the directions of
* switching forms, for example for a horizontally type, true means
* horizontally movement to right.
* @param duration represent the time the transition should take in millisecond
* @return a transition object
*/
public static CommonTransitions createSlide(int type, boolean forward, int duration) {
return createSlide(type, forward, duration, false);
}
/**
* Creates a slide transition with the given duration and direction
*
* @param type type can be either vertically or horizontally, which means
* the movement direction of the transition
* @param forward forward is a boolean value, represent the directions of
* switching forms, for example for a horizontally type, true means
* horizontally movement to right.
* @param duration represent the time the transition should take in millisecond
* @param drawDialogMenu indicates that the menu (softkey area) of the dialog
* should be kept during a slide transition. This is only relevant for
* dialog in/out transitions.
* @return a transition object
*/
public static CommonTransitions createSlide(int type, boolean forward, int duration, boolean drawDialogMenu) {
CommonTransitions t = new CommonTransitions(TYPE_SLIDE);
t.slideType = type;
t.forward = forward;
if ((type==SLIDE_HORIZONTAL) && (UIManager.getInstance().getLookAndFeel().isRTL())) {
t.forward=!t.forward;
}
t.speed = duration;
t.position = 0;
t.drawDialogMenu = drawDialogMenu;
return t;
}
/**
* Creates a slide transition with the given duration and direction, this differs from the
* standard slide animation by focusing on speed rather than on minimizing heap usage
* This method works by creating two images and sliding them which works much faster for
* all devices however takes up more ram. Notice that this method of painting doesn't
* support various basic LWUIT abilities such as translucent menus/dialogs etc.
*
* @param type type can be either vertically or horizontally, which means
* the movement direction of the transition
* @param forward forward is a boolean value, represent the directions of
* switching forms, for example for a horizontally type, true means
* horizontally movement to right.
* @param duration represent the time the transition should take in millisecond
* @param drawDialogMenu indicates that the menu (softkey area) of the dialog
* should be kept during a slide transition. This is only relevant for
* dialog in/out transitions.
* @return a transition object
*/
public static CommonTransitions createFastSlide(int type, boolean forward, int duration, boolean drawDialogMenu) {
CommonTransitions t = new CommonTransitions(TYPE_FAST_SLIDE);
t.slideType = type;
t.forward = forward;
if ((type==SLIDE_HORIZONTAL) && (UIManager.getInstance().getLookAndFeel().isRTL())) {
t.forward=!t.forward;
}
t.speed = duration;
t.position = 0;
t.drawDialogMenu = drawDialogMenu;
return t;
}
/**
* Creates a transition for fading a form in while fading out the original form
*
* @param duration represent the time the transition should take in millisecond
* @return a transition object
*/
public static CommonTransitions createFade(int duration) {
CommonTransitions t = new CommonTransitions(TYPE_FADE);
t.speed = duration;
return t;
}
/**
* @inheritDoc
*/
public void initTransition() {
firstFinished = false;
if(transitionType == TYPE_EMPTY) {
return;
}
Component source = getSource();
Component destination = getDestination();
position = 0;
int w = source.getWidth();
int h = source.getHeight();
// a transition might occur with illegal source or destination values (common with
// improper replace() calls, this may still be valid and shouldn't fail
if(w <= 0 || h <= 0) {
return;
}
if (buffer == null) {
buffer = Image.createImage(w, h);
} else {
// this might happen when screen orientation changes or a MIDlet moves
// to an external screen
if(buffer.getWidth() != w || buffer.getHeight() != h) {
buffer = Image.createImage(w, h);
rgbBuffer = null;
// slide motion might need resetting since screen size is different
motion = null;
}
}
if(transitionType == TYPE_FADE) {
motion = createMotion(0, 256, speed);
motion.start();
Graphics g = buffer.getGraphics();
g.translate(-source.getAbsoluteX(), -source.getAbsoluteY());
if(getSource().getParent() != null){
getSource().getComponentForm().paintComponent(g);
}
//getSource().paintBackgrounds(g);
g.setClip(0, 0, buffer.getWidth()+source.getAbsoluteX(), buffer.getHeight()+source.getAbsoluteY());
paint(g, getDestination(), 0, 0);
if(g.isAlphaSupported()) {
secondaryBuffer = buffer;
buffer = Image.createImage(w, h);
} else {
rgbBuffer = new RGBImage(buffer.getRGBCached(), buffer.getWidth(), buffer.getHeight());
}
paint(g, getSource(), 0, 0);
g.translate(source.getAbsoluteX(), source.getAbsoluteY());
} else {
if (transitionType == TYPE_SLIDE || transitionType == TYPE_FAST_SLIDE) {
int dest;
int startOffset = 0;
if (slideType == SLIDE_HORIZONTAL) {
dest = w;
if(destination instanceof Dialog) {
startOffset = w - ((Dialog)destination).getContentPane().getWidth();
if(forward) {
startOffset -= ((Dialog)destination).getContentPane().getStyle().getMargin(destination.isRTL(), Component.LEFT);
} else {
startOffset -= ((Dialog)destination).getContentPane().getStyle().getMargin(destination.isRTL(), Component.RIGHT);
}
} else {
if(source instanceof Dialog) {
dest = ((Dialog)source).getContentPane().getWidth();
if(forward) {
dest += ((Dialog)source).getContentPane().getStyle().getMargin(source.isRTL(), Component.LEFT);
} else {
dest += ((Dialog)source).getContentPane().getStyle().getMargin(source.isRTL(), Component.RIGHT);
}
}
}
} else {
dest = h;
if(destination instanceof Dialog) {
startOffset = h - ((Dialog)destination).getContentPane().getHeight() -
((Dialog)destination).getTitleComponent().getHeight();
if(forward) {
startOffset -= ((Dialog)destination).getContentPane().getStyle().getMargin(false, Component.BOTTOM);
} else {
startOffset -= ((Dialog)destination).getContentPane().getStyle().getMargin(false, Component.TOP);
startOffset -= ((Dialog)destination).getTitleStyle().getMargin(false, Component.TOP);
if(!drawDialogMenu && ((Dialog)destination).getCommandCount() > 0) {
Container p = ((Dialog)destination).getSoftButton(0).getParent();
if(p != null) {
startOffset -= p.getHeight();
}
}
}
} else {
if(source instanceof Dialog) {
dest = ((Dialog)source).getContentPane().getHeight() +
((Dialog)source).getTitleComponent().getHeight();
if(forward) {
dest += ((Dialog)source).getContentPane().getStyle().getMargin(false, Component.BOTTOM);
} else {
dest += ((Dialog)source).getContentPane().getStyle().getMargin(false, Component.TOP);
dest += ((Dialog)source).getTitleStyle().getMargin(false, Component.TOP);
if(((Dialog)source).getCommandCount() > 0) {
Container p = ((Dialog)source).getSoftButton(0).getParent();
if(p != null) {
dest += p.getHeight();
}
}
}
}
}
}
motion = createMotion(startOffset, dest, speed);
// make sure the destination is painted fully at least once
// we must use a full buffer otherwise the clipping will take effect
Graphics g = buffer.getGraphics();
// If this is a dialog render the tinted frame once since
// tinting is expensive
if(getSource() instanceof Dialog) {
paint(g, getDestination(), 0, 0);
if(transitionType == TYPE_FAST_SLIDE && !(destination instanceof Dialog)) {
Dialog d = (Dialog)source;
secondaryBuffer = Image.createImage(d.getContentPane().getWidth(), d.getContentPane().getHeight() +
d.getTitleComponent().getHeight());
drawDialogCmp(secondaryBuffer.getGraphics(), d);
}
} else {
if(getDestination() instanceof Dialog) {
paint(g, getSource(), 0, 0);
if(transitionType == TYPE_FAST_SLIDE && !(source instanceof Dialog)) {
Dialog d = (Dialog)destination;
secondaryBuffer = Image.createImage(d.getContentPane().getWidth(), d.getContentPane().getHeight() +
d.getTitleComponent().getHeight());
drawDialogCmp(secondaryBuffer.getGraphics(), d);
}
} else {
paint(g, source, -source.getAbsoluteX(), -source.getAbsoluteY());
if(transitionType == TYPE_FAST_SLIDE) {
secondaryBuffer = Image.createImage(destination.getWidth(), destination.getHeight());
paint(secondaryBuffer.getGraphics(), destination, -destination.getAbsoluteX(), -destination.getAbsoluteY());
}
}
}
motion.start();
}
}
}
/**
* This method can be overriden by subclasses to create their own motion object on the fly
*
* @param startOffset the start offset for the menu
* @param dest the destination of the motion
* @param speed the speed of the motion
* @return a motion instance
*/
protected Motion createMotion(int startOffset, int dest, int speed) {
if(motionSetManually) {
return motion;
}
if(linearMotion) {
return Motion.createLinearMotion(startOffset, dest, speed);
}
return Motion.createSplineMotion(startOffset, dest, speed);
}
/**
* @inheritDoc
*/
public boolean animate() {
if(motion == null) {
return false;
}
position = motion.getValue();
// after the motion finished we need to paint one last time otherwise
// there will be a "bump" in sliding
if(firstFinished) {
return false;
}
boolean finished = motion.isFinished();
if(finished) {
if(!firstFinished) {
firstFinished = true;
}
}
return true;
}
/**
* @inheritDoc
*/
public void paint(Graphics g) {
try {
switch (transitionType) {
case TYPE_SLIDE:
// if this is an up or down slide
if (slideType == SLIDE_HORIZONTAL) {
paintSlideAtPosition(g, position, 0);
} else {
paintSlideAtPosition(g, 0, position);
}
return;
case TYPE_FAST_SLIDE:
// if this is an up or down slide
if (slideType == SLIDE_HORIZONTAL) {
paintFastSlideAtPosition(g, position, 0);
} else {
paintFastSlideAtPosition(g, 0, position);
}
return;
case TYPE_FADE:
paintAlpha(g);
return;
}
} catch(Throwable t) {
System.out.println("An exception occurred during transition paint this might be valid in case of a resize in the middle of a transition");
t.printStackTrace();
}
}
private void paintAlpha(Graphics graphics) {
// this will always be invoked on the EDT so there is no race condition risk
if(rgbBuffer != null || secondaryBuffer != null) {
Component src = getSource();
int w = src.getWidth();
int h = src.getHeight();
int position = this.position;
if (position > 255) {
position = 255;
} else {
if (position < 0) {
position = 0;
}
}
if(secondaryBuffer != null) {
Component dest = getDestination();
int x = dest.getAbsoluteX();
int y = dest.getAbsoluteY();
graphics.drawImage(buffer, x, y);
graphics.setAlpha(position);
graphics.drawImage(secondaryBuffer, x, y);
graphics.setAlpha(0xff);
} else {
int alpha = position << 24;
int size = w * h;
int[] bufferArray = rgbBuffer.getRGB();
for (int iter = 0 ; iter < size ; iter++) {
bufferArray[iter] = ((bufferArray[iter] & 0xFFFFFF) | alpha);
}
Component dest = getDestination();
int x = dest.getAbsoluteX();
int y = dest.getAbsoluteY();
graphics.drawImage(buffer, x, y);
graphics.drawImage(rgbBuffer, x, y);
}
}
}
/**
* @inheritDoc
*/
public void cleanup() {
super.cleanup();
buffer = null;
rgbBuffer = null;
secondaryBuffer = null;
}
private void paintSlideAtPosition(Graphics g, int slideX, int slideY) {
Component source = getSource();
// if this is the first form we can't do a slide transition since we have no source form
if (source == null) {
return;
}
Component dest = getDestination();
int w = source.getWidth();
int h = source.getHeight();
if (slideType == SLIDE_HORIZONTAL) {
h = 0;
} else {
w = 0;
}
if(forward) {
w = -w;
h = -h;
} else {
slideX = -slideX;
slideY = -slideY;
}
g.setClip(source.getAbsoluteX()+source.getScrollX(), source.getAbsoluteY()+source.getScrollY(), source.getWidth(), source.getHeight());
// dialog animation is slightly different...
if(source instanceof Dialog) {
g.drawImage(buffer, 0, 0);
paint(g, source, -slideX, -slideY);
return;
}
if(dest instanceof Dialog) {
g.drawImage(buffer, 0, 0);
paint(g, dest, -slideX - w, -slideY - h);
return;
}
if(source.getParent() != null){
source.paintBackgrounds(g);
paint(g, source, slideX , slideY );
}else{
g.drawImage(buffer, slideX, slideY);
}
paint(g, dest, slideX + w, slideY + h);
}
private void paintFastSlideAtPosition(Graphics g, int slideX, int slideY) {
if(secondaryBuffer != null) {
Component source = getSource();
// if this is the first form we can't do a slide transition since we have no source form
if (source == null) {
return;
}
Component dest = getDestination();
int w = buffer.getWidth();
int h = buffer.getHeight();
if (slideType == SLIDE_HORIZONTAL) {
h = 0;
} else {
w = 0;
}
if(forward) {
w = -w;
h = -h;
} else {
slideX = -slideX;
slideY = -slideY;
}
g.setClip(source.getAbsoluteX()+source.getScrollX(), source.getAbsoluteY()+source.getScrollY(), source.getWidth(), source.getHeight());
// dialog animation is slightly different...
if(source instanceof Dialog) {
g.drawImage(buffer, 0, 0);
slideX -= ((Dialog)source).getContentPane().getX();
slideY -= ((Dialog)source).getContentPane().getY();
g.drawImage(secondaryBuffer, -slideX, -slideY);
return;
}
if(dest instanceof Dialog) {
g.drawImage(buffer, 0, 0);
slideY -= ((Dialog)dest).getTitleComponent().getY();
slideX -= ((Dialog)dest).getTitleComponent().getX();
g.drawImage(secondaryBuffer, -slideX - w, -slideY - h);
return;
}
g.drawImage(buffer, slideX, slideY);
g.drawImage(secondaryBuffer, slideX + w, slideY + h);
} else {
paintSlideAtPosition(g, slideX, slideY);
}
}
private void drawDialogCmp(Graphics g, Dialog dlg) {
Painter p = dlg.getStyle().getBgPainter();
dlg.getStyle().setBgPainter(null);
g.translate(-dlg.getTitleComponent().getX(), -dlg.getTitleComponent().getY());
dlg.getTitleComponent().paintComponent(g, false);
g.translate(dlg.getTitleComponent().getX(), dlg.getTitleComponent().getY());
g.setClip(0, 0, dlg.getWidth(), dlg.getHeight());
g.translate(-dlg.getContentPane().getX(), -dlg.getContentPane().getY() + dlg.getTitleComponent().getHeight());
dlg.getContentPane().paintComponent(g, false);
if(drawDialogMenu && dlg.getCommandCount() > 0) {
Component menuBar = dlg.getSoftButton(0).getParent();
if(menuBar != null) {
g.setClip(0, 0, dlg.getWidth(), dlg.getHeight());
menuBar.paintComponent(g, false);
}
}
dlg.getStyle().setBgPainter(p);
}
private void paint(Graphics g, Component cmp, int x, int y) {
int cx = g.getClipX();
int cy = g.getClipY();
int cw = g.getClipWidth();
int ch = g.getClipHeight();
if(cmp instanceof Dialog) {
if(transitionType != TYPE_FADE) {
if(!(getSource() instanceof Dialog && getDestination() instanceof Dialog &&
cmp == getDestination())) {
Painter p = cmp.getStyle().getBgPainter();
cmp.getStyle().setBgPainter(null);
g.translate(x, y);
Dialog dlg = (Dialog)cmp;
g.setClip(0, 0, cmp.getWidth(), cmp.getHeight());
dlg.getTitleComponent().paintComponent(g, false);
g.setClip(0, 0, cmp.getWidth(), cmp.getHeight());
dlg.getContentPane().paintComponent(g, false);
g.translate(-x, -y);
if(drawDialogMenu && dlg.getCommandCount() > 0) {
Component menuBar = dlg.getSoftButton(0).getParent();
if(menuBar != null) {
g.setClip(0, 0, cmp.getWidth(), cmp.getHeight());
menuBar.paintComponent(g, false);
}
}
g.setClip(cx, cy, cw, ch);
cmp.getStyle().setBgPainter(p);
return;
}
}
cmp.paintComponent(g, false);
return;
}
//g.clipRect(cmp.getAbsoluteX(), cmp.getAbsoluteY(), cmp.getWidth(), cmp.getHeight());
g.translate(x, y);
//g.clipRect(cmp.getAbsoluteX(), cmp.getAbsoluteY(), cmp.getWidth(), cmp.getHeight());
cmp.paintComponent(g, false);
g.translate(-x, -y);
g.setClip(cx, cy, cw, ch);
}
/**
* Motion represents the physical movement within a transition, it can
* be replaced by the user to provide a more appropriate physical feel
*
* @return the instanceo of the motion class used by this transition
*/
public Motion getMotion() {
return motion;
}
/**
* Motion represents the physical movement within a transition, it can
* be replaced by the user to provide a more appropriate physical feel
*
* @param motion new instance of the motion class that will be used by the transition
*/
public void setMotion(Motion motion) {
motionSetManually = true;
this.motion = motion;
}
/**
* @inheritDoc
*/
public Transition copy(boolean reverse){
CommonTransitions retVal = null;
switch(transitionType) {
case TYPE_FADE:
retVal = CommonTransitions.createFade(speed);
break;
case TYPE_SLIDE: {
boolean fwd=forward;
// prevent double reversing of forward due to bidi when copying a transition
if ((slideType==SLIDE_HORIZONTAL) && (UIManager.getInstance().getLookAndFeel().isRTL())) {
fwd=!fwd;
}
if(reverse) {
retVal = CommonTransitions.createSlide(slideType, !fwd, speed, drawDialogMenu);
} else {
retVal = CommonTransitions.createSlide(slideType, fwd, speed, drawDialogMenu);
}
break;
}
case TYPE_FAST_SLIDE: {
boolean fwd=forward;
// prevent double reversing of forward due to bidi when copying a transition
if ((slideType==SLIDE_HORIZONTAL) && (UIManager.getInstance().getLookAndFeel().isRTL())) {
fwd=!fwd;
}
if(reverse) {
retVal = CommonTransitions.createFastSlide(slideType, !fwd, speed, drawDialogMenu);
} else {
retVal = CommonTransitions.createFastSlide(slideType, fwd, speed, drawDialogMenu);
}
break;
}
case TYPE_EMPTY:
retVal = CommonTransitions.createEmpty();
break;
}
retVal.linearMotion = linearMotion;
return retVal;
}
/**
* Indicates whether the motion associated with this transition is linear or spline motion
*
* @return the linearMotion
*/
public boolean isLinearMotion() {
return linearMotion;
}
/**
* Indicates whether the motion associated with this transition is linear or spline motion
*
* @param linearMotion the linearMotion to set
*/
public void setLinearMotion(boolean linearMotion) {
this.linearMotion = linearMotion;
}
/**
* Indicates whether the motion associated with these transitions by default is linear or spline motion
*
* @return the defaultLinearMotion
*/
public static boolean isDefaultLinearMotion() {
return defaultLinearMotion;
}
/**
* Indicates whether the motion associated with these transitions by default is linear or spline motion
*
* @param aDefaultLinearMotion the defaultLinearMotion to set
*/
public static void setDefaultLinearMotion(boolean aDefaultLinearMotion) {
defaultLinearMotion = aDefaultLinearMotion;
}
}