public code v1

This commit is contained in:
Francisco Jesús Martínez Mimbrera
2026-05-23 00:32:57 +02:00
commit 759a8968a2
4357 changed files with 163763 additions and 0 deletions
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>flintstones.group</groupId>
<artifactId>flintstones.bundles</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.pushingpixels.trident.swt</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
<name>[bundle] Swt</name>
</project>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.pushingpixels.trident.swt</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1779484362764</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
@@ -0,0 +1,9 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Swt
Bundle-SymbolicName: org.pushingpixels.trident.swt
Bundle-Version: 1.0.0.qualifier
Automatic-Module-Name: org.pushingpixels.trident.swt
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: org.eclipse.swt
Export-Package: org.pushingpixels.trident
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
@@ -0,0 +1,5 @@
package org.pushingpixels.trident;
public interface PropertyGetter<T> {
public T get(Object obj, String fieldName);
}
@@ -0,0 +1,5 @@
package org.pushingpixels.trident;
public interface PropertySetter<T> {
public void set(Object obj, String fieldName, T value);
}
@@ -0,0 +1,647 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
/*
* Added method "setPropertyValues" - Loris Securo for CustomButton
*/
package org.pushingpixels.trident;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.pushingpixels.trident.TimelineEngine.FullObjectID;
import org.pushingpixels.trident.TimelineEngine.TimelineOperationKind;
import org.pushingpixels.trident.TimelinePropertyBuilder.AbstractFieldInfo;
import org.pushingpixels.trident.callback.RunOnUIThread;
import org.pushingpixels.trident.callback.TimelineCallback;
import org.pushingpixels.trident.callback.TimelineCallbackAdapter;
import org.pushingpixels.trident.ease.Linear;
import org.pushingpixels.trident.ease.TimelineEase;
import org.pushingpixels.trident.interpolator.KeyFrames;
@SuppressWarnings({"rawtypes","unchecked"})
public class Timeline implements TimelineScenario.TimelineScenarioActor {
Object mainObject;
Comparable<?> secondaryId;
FullObjectID fullObjectID;
long duration;
long initialDelay;
long cycleDelay;
boolean isLooping;
int repeatCount;
RepeatBehavior repeatBehavior;
UIToolkitHandler uiToolkitHandler;
Chain callback;
String name;
List<AbstractFieldInfo> propertiesToInterpolate;
/**
* Is used to create unique value for the {@link #id} field.
*/
static long counter;
/**
* Unique ID.
*/
protected long id;
/**
* Timeline position.
*/
float durationFraction;
/**
* Timeline position.
*/
float timelinePosition;
long timeUntilPlay;
/**
* Indication whether the looping timeline should stop at reaching the end
* of the cycle. Relevant only when {@link #isLooping} is <code>true</code>.
*/
boolean toCancelAtCycleBreak;
Stack<TimelineState> stateStack;
TimelineEase ease;
private int doneCount;
public enum RepeatBehavior {
LOOP, REVERSE
}
public enum TimelineState {
IDLE(false), READY(false), PLAYING_FORWARD(true), PLAYING_REVERSE(true), SUSPENDED(
false), CANCELLED(false), DONE(false);
private boolean isActive;
private TimelineState(boolean isActive) {
this.isActive = isActive;
}
}
private class Setter extends TimelineCallbackAdapter {
@Override
public void onTimelineStateChanged(TimelineState oldState,
TimelineState newState, float durationFraction,
float timelinePosition) {
if (newState == TimelineState.READY) {
for (AbstractFieldInfo fInfo : propertiesToInterpolate) {
// check whether the object is in the ready state
if ((uiToolkitHandler != null)
&& !uiToolkitHandler.isInReadyState(fInfo.object)) {
continue;
}
fInfo.onStart();
}
}
// Fix for issue 5 - update field values only when
// either old or new state (or both) are active. Otherwise
// it's a transition between inactive states (such as from
// DONE to IDLE) that shouldn't trigger the property changes
if (oldState.isActive || newState.isActive) {
for (AbstractFieldInfo fInfo : propertiesToInterpolate) {
// check whether the object is in the ready state
if ((uiToolkitHandler != null)
&& !uiToolkitHandler.isInReadyState(fInfo.object)) {
continue;
}
fInfo.updateFieldValue(timelinePosition);
}
}
}
@Override
public void onTimelinePulse(float durationFraction,
float timelinePosition) {
for (AbstractFieldInfo fInfo : propertiesToInterpolate) {
// check whether the object is in the ready state
if ((uiToolkitHandler != null)
&& !uiToolkitHandler.isInReadyState(fInfo.object)) {
continue;
}
// System.err.println("Timeline @" + Timeline.this.hashCode()
// + " at position " + timelinePosition);
fInfo.updateFieldValue(timelinePosition);
}
}
}
@RunOnUIThread
private class UISetter extends Setter {
}
class Chain implements TimelineCallback {
private List<TimelineCallback> callbacks;
public Chain(TimelineCallback... callbacks) {
this.callbacks = new ArrayList<TimelineCallback>();
for (TimelineCallback callback : callbacks) {
this.callbacks.add(callback);
}
}
public void addCallback(TimelineCallback callback) {
this.callbacks.add(callback);
}
public void removeCallback(TimelineCallback callback) {
this.callbacks.remove(callback);
}
@Override
public void onTimelineStateChanged(final TimelineState oldState,
final TimelineState newState, final float durationFraction,
final float timelinePosition) {
if ((uiToolkitHandler != null)
&& !uiToolkitHandler.isInReadyState(mainObject)) {
return;
}
for (int i = this.callbacks.size() - 1; i >= 0; i--) {
final TimelineCallback callback = this.callbacks.get(i);
// special handling for chained callbacks not running on UI
// thread
boolean shouldRunOnUIThread = false;
Class<?> clazz = callback.getClass();
while ((clazz != null) && !shouldRunOnUIThread) {
shouldRunOnUIThread = clazz
.isAnnotationPresent(RunOnUIThread.class);
clazz = clazz.getSuperclass();
}
if (shouldRunOnUIThread
&& (Timeline.this.uiToolkitHandler != null)) {
Timeline.this.uiToolkitHandler.runOnUIThread(mainObject,
new Runnable() {
@Override
public void run() {
callback.onTimelineStateChanged(oldState,
newState, durationFraction,
timelinePosition);
}
});
} else {
callback.onTimelineStateChanged(oldState, newState,
durationFraction, timelinePosition);
}
}
}
@Override
public void onTimelinePulse(final float durationFraction,
final float timelinePosition) {
if ((uiToolkitHandler != null)
&& !uiToolkitHandler.isInReadyState(mainObject)) {
return;
}
for (int i = this.callbacks.size() - 1; i >= 0; i--) {
final TimelineCallback callback = this.callbacks.get(i);
// special handling for chained callbacks not running on UI
// thread
boolean shouldRunOnUIThread = false;
Class<?> clazz = callback.getClass();
while ((clazz != null) && !shouldRunOnUIThread) {
shouldRunOnUIThread = clazz
.isAnnotationPresent(RunOnUIThread.class);
clazz = clazz.getSuperclass();
}
if (shouldRunOnUIThread
&& (Timeline.this.uiToolkitHandler != null)) {
Timeline.this.uiToolkitHandler.runOnUIThread(mainObject,
new Runnable() {
@Override
public void run() {
if (Timeline.this.getState() == TimelineState.CANCELLED) {
return;
}
// System.err.println("Timeline @"
// + Timeline.this.hashCode());
callback.onTimelinePulse(durationFraction,
timelinePosition);
}
});
} else {
// System.err.println("Timeline @" +
// Timeline.this.hashCode());
callback.onTimelinePulse(durationFraction, timelinePosition);
}
}
}
}
public Timeline() {
this(null);
}
public Timeline(Object mainTimelineObject) {
this.mainObject = mainTimelineObject;
for (UIToolkitHandler uiToolkitHandler : TridentConfig.getInstance()
.getUIToolkitHandlers()) {
if (uiToolkitHandler.isHandlerFor(mainTimelineObject)) {
this.uiToolkitHandler = uiToolkitHandler;
break;
}
}
// if the main timeline object is handled by a UI toolkit handler,
// the setters registered with the different addProperty
// APIs need to run with the matching threading policy
TimelineCallback setterCallback = (this.uiToolkitHandler != null) ? new UISetter()
: new Setter();
this.callback = new Chain(setterCallback);
this.duration = 500;
this.propertiesToInterpolate = new ArrayList<AbstractFieldInfo>();
this.id = Timeline.getId();
// this.loopsToLive = -1;
this.stateStack = new Stack<TimelineState>();
this.stateStack.push(TimelineState.IDLE);
this.doneCount = 0;
this.ease = new Linear();
}
public final void setSecondaryID(Comparable<?> secondaryId) {
if (this.getState() != TimelineState.IDLE) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline ["
+ this.toString() + "]");
}
this.secondaryId = secondaryId;
}
public final void setDuration(long durationMs) {
if (this.getState() != TimelineState.IDLE) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline ["
+ this.toString() + "]");
}
this.duration = durationMs;
}
public final void setInitialDelay(long initialDelay) {
if (this.getState() != TimelineState.IDLE) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline ["
+ this.toString() + "]");
}
this.initialDelay = initialDelay;
}
public final void setCycleDelay(long cycleDelay) {
if (this.getState() != TimelineState.IDLE) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline ["
+ this.toString() + "]");
}
this.cycleDelay = cycleDelay;
}
public final void addCallback(TimelineCallback callback) {
if (this.getState() != TimelineState.IDLE) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline ["
+ this.toString() + "]");
}
this.callback.addCallback(callback);
}
public final void removeCallback(TimelineCallback callback) {
if (this.getState() != TimelineState.IDLE) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline ["
+ this.toString() + "]");
}
this.callback.removeCallback(callback);
}
public static <T> TimelinePropertyBuilder<T> property(String propertyName) {
return new TimelinePropertyBuilder<T>(propertyName);
}
public final <T> void addPropertyToInterpolate(
TimelinePropertyBuilder<T> propertyBuilder) {
this.propertiesToInterpolate.add(propertyBuilder.getFieldInfo(this));
}
public final <T> void addPropertyToInterpolate(String propName,
KeyFrames<T> keyFrames) {
this.addPropertyToInterpolate(Timeline.<T> property(propName)
.goingThrough(keyFrames));
}
public final <T> void addPropertyToInterpolate(String propName, T from, T to) {
this.addPropertyToInterpolate(Timeline.<T> property(propName)
.from(from).to(to));
}
@Override
public void play() {
this.playSkipping(0);
}
public void playSkipping(final long msToSkip) {
if ((this.initialDelay + this.duration) < msToSkip) {
throw new IllegalArgumentException(
"Required skip longer than initial delay + duration");
}
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.PLAY, new Runnable() {
@Override
public void run() {
Timeline.this.isLooping = false;
TimelineEngine.getInstance().play(Timeline.this, false,
msToSkip);
}
});
}
public void playReverse() {
playReverseSkipping(0);
}
public void playReverseSkipping(final long msToSkip) {
if ((this.initialDelay + this.duration) < msToSkip) {
throw new IllegalArgumentException(
"Required skip longer than initial delay + duration");
}
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.PLAY, new Runnable() {
@Override
public void run() {
Timeline.this.isLooping = false;
TimelineEngine.getInstance().playReverse(Timeline.this,
false, msToSkip);
}
});
}
public void replay() {
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.PLAY, new Runnable() {
@Override
public void run() {
Timeline.this.isLooping = false;
TimelineEngine.getInstance().play(Timeline.this, true,
0);
}
});
}
public void replayReverse() {
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.PLAY, new Runnable() {
@Override
public void run() {
Timeline.this.isLooping = false;
TimelineEngine.getInstance().playReverse(Timeline.this,
true, 0);
}
});
}
public void playLoop(RepeatBehavior repeatBehavior) {
this.playLoop(-1, repeatBehavior);
}
public void playLoopSkipping(RepeatBehavior repeatBehavior,
final long msToSkip) {
this.playLoopSkipping(-1, repeatBehavior, msToSkip);
}
public void playLoop(int loopCount, RepeatBehavior repeatBehavior) {
this.playLoopSkipping(loopCount, repeatBehavior, 0);
}
public void playLoopSkipping(final int loopCount,
final RepeatBehavior repeatBehavior, final long msToSkip) {
if ((this.initialDelay + this.duration) < msToSkip) {
throw new IllegalArgumentException(
"Required skip longer than initial delay + duration");
}
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.PLAY, new Runnable() {
@Override
public void run() {
Timeline.this.isLooping = true;
Timeline.this.repeatCount = loopCount;
Timeline.this.repeatBehavior = repeatBehavior;
TimelineEngine.getInstance().playLoop(Timeline.this,
msToSkip);
}
});
}
/**
* Cancels this timeline. The timeline transitions to the
* {@link TimelineState#CANCELLED} state, preserving its current timeline
* position. After application callbacks and field interpolations are done
* on the {@link TimelineState#CANCELLED} state, the timeline transitions to
* the {@link TimelineState#IDLE} state. Application callbacks and field
* interpolations are done on this state as well.
*
* @see #end()
* @see #abort()
*/
public void cancel() {
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.CANCEL, null);
}
/**
* Ends this timeline. The timeline transitions to the
* {@link TimelineState#DONE} state, with the timeline position set to 0.0
* or 1.0 - based on the direction of the timeline. After application
* callbacks and field interpolations are done on the
* {@link TimelineState#DONE} state, the timeline transitions to the
* {@link TimelineState#IDLE} state. Application callbacks and field
* interpolations are done on this state as well.
*
* @see #cancel()
* @see #abort()
*/
public void end() {
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.END, null);
}
/**
* Aborts this timeline. The timeline transitions to the
* {@link TimelineState#IDLE} state. No application callbacks or field
* interpolations are done.
*
* @see #cancel()
* @see #end()
*/
public void abort() {
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.ABORT, null);
}
public void suspend() {
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.SUSPEND, null);
}
public void resume() {
TimelineEngine.getInstance().runTimelineOperation(this,
TimelineOperationKind.RESUME, null);
}
/**
* Requests that the specified timeline should stop at the end of the cycle.
* This method should be called only on looping timelines.
*/
public void cancelAtCycleBreak() {
if (!this.isLooping) {
throw new IllegalArgumentException(
"Can only be called on looping timelines");
}
this.toCancelAtCycleBreak = true;
}
/**
* Returns a unique ID.
*
* @return Unique ID.
*/
protected static synchronized long getId() {
return counter++;
}
public final float getTimelinePosition() {
return this.timelinePosition;
}
public final float getDurationFraction() {
return this.durationFraction;
}
public final TimelineState getState() {
return this.stateStack.peek();
}
public final void setEase(TimelineEase ease) {
if (this.getState() != TimelineState.IDLE) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline");
}
this.ease = ease;
}
@Override
public boolean isDone() {
return (this.doneCount > 0);
}
@Override
public boolean supportsReplay() {
return true;
}
@Override
public void resetDoneFlag() {
this.doneCount = 0;
}
@Override
public String toString() {
StringBuffer res = new StringBuffer();
if (this.name != null) {
res.append(this.name);
}
if (this.mainObject != null) {
res.append(":" + this.mainObject.getClass().getName());
}
if (this.secondaryId != null) {
res.append(":" + this.secondaryId.toString());
}
res.append(" " + this.getState().name());
res.append(":" + this.timelinePosition);
return res.toString();
}
void replaceState(TimelineState state) {
this.stateStack.pop();
this.pushState(state);
}
void pushState(TimelineState state) {
if (state == TimelineState.DONE) {
this.doneCount++;
}
this.stateStack.add(state);
}
TimelineState popState() {
return this.stateStack.pop();
}
public final long getDuration() {
return this.duration;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getMainObject() {
return this.mainObject;
}
// permit to dynamically change the from and to colors of a property of the transition
public <T> void setPropertyValues(int index, T from, T to) {
propertiesToInterpolate.get(index).setValues(from, to);
}
}
@@ -0,0 +1,959 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.pushingpixels.trident.Timeline.TimelineState;
import org.pushingpixels.trident.TimelineScenario.TimelineScenarioState;
import org.pushingpixels.trident.callback.RunOnUIThread;
/**
* The Trident timeline engine. This is the main entry point to play
* {@link Timeline}s and {@link TimelineScenario}s. Use the
* {@link #getInstance()} method to get the timeline engine.
*
* @author Kirill Grouchnikov
*/
@SuppressWarnings({"rawtypes","unchecked","incomplete-switch"})
class TimelineEngine {
/**
* Debug mode indicator. Set to <code>true</code> to have trace messages on
* console.
*/
public static boolean DEBUG_MODE = false;
/**
* Single instance of <code>this</code> class.
*/
private static TimelineEngine instance;
/**
* All currently running timelines.
*/
private Set<Timeline> runningTimelines;
enum TimelineOperationKind {
PLAY, CANCEL, RESUME, SUSPEND, ABORT, END
}
class TimelineOperation {
public TimelineOperationKind operationKind;
Runnable operationRunnable;
public TimelineOperation(TimelineOperationKind operationKind,
Runnable operationRunnable) {
this.operationKind = operationKind;
this.operationRunnable = operationRunnable;
}
}
private Set<TimelineScenario> runningScenarios;
long lastIterationTimeStamp;
/**
* Identifies a main object and an optional secondary ID.
*
* @author Kirill Grouchnikov
*/
static class FullObjectID {
/**
* Main object for the timeline.
*/
public Object mainObj;
/**
* ID to distinguish between different sub-components of
* {@link #mainObj}. For example, the tabbed pane uses this field to
* make tab-specific animations.
*/
public Comparable subID;
/**
* Creates a new object ID.
*
* @param mainObj
* The main object.
* @param subID
* ID to distinguish between different sub-components of
* <code>mainObj</code>. Can be <code>null</code>.
*/
public FullObjectID(Object mainObj, Comparable subID) {
this.mainObj = mainObj;
this.subID = subID;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = this.mainObj.hashCode();
if (this.subID != null)
result &= (this.subID.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof FullObjectID) {
FullObjectID cid = (FullObjectID) obj;
try {
boolean result = (this.mainObj == cid.mainObj);
if (this.subID == null) {
result = result && (cid.subID == null);
} else {
result = result
&& (this.subID.compareTo(cid.subID) == 0);
}
return result;
} catch (Exception exc) {
return false;
}
}
return false;
}
@Override
public String toString() {
return this.mainObj.getClass().getSimpleName() + ":" + this.subID;
}
}
/**
* The timeline thread.
*/
TridentAnimationThread animatorThread;
private BlockingQueue<Runnable> callbackQueue;
private TimelineCallbackThread callbackThread;
class TridentAnimationThread extends Thread {
public TridentAnimationThread() {
super();
this.setName("Trident pulse source thread");
this.setDaemon(true);
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public final void run() {
TridentConfig.PulseSource pulseSource = TridentConfig.getInstance()
.getPulseSource();
lastIterationTimeStamp = System.currentTimeMillis();
while (true) {
pulseSource.waitUntilNextPulse();
updateTimelines();
// engine.currLoopId++;
}
}
@Override
public void interrupt() {
System.err.println("Interrupted");
super.interrupt();
}
}
private class TimelineCallbackThread extends Thread {
public TimelineCallbackThread() {
super();
this.setName("Trident callback thread");
this.setDaemon(true);
}
@Override
public void run() {
while (true) {
try {
Runnable runnable = callbackQueue.take();
runnable.run();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
/**
* Simple constructor. Defined private for singleton.
*
* @see #getInstance()
*/
private TimelineEngine() {
this.runningTimelines = new HashSet<Timeline>();
this.runningScenarios = new HashSet<TimelineScenario>();
this.callbackQueue = new LinkedBlockingQueue<Runnable>();
this.callbackThread = this.getCallbackThread();
}
/**
* Gets singleton instance.
*
* @return Singleton instance.
*/
public synchronized static TimelineEngine getInstance() {
if (TimelineEngine.instance == null) {
TimelineEngine.instance = new TimelineEngine();
}
return TimelineEngine.instance;
}
/**
* Updates all timelines that are currently registered with
* <code>this</code> tracker.
*/
void updateTimelines() {
synchronized (LOCK) {
if ((this.runningTimelines.size() == 0)
&& (this.runningScenarios.size() == 0)) {
this.lastIterationTimeStamp = System.currentTimeMillis();
return;
}
long passedSinceLastIteration = (System.currentTimeMillis() - this.lastIterationTimeStamp);
if (passedSinceLastIteration < 0) {
// ???
passedSinceLastIteration = 0;
}
if (DEBUG_MODE) {
System.out.println("Elapsed since last iteration: "
+ passedSinceLastIteration + "ms");
}
// System.err.println("Periodic update on "
// + this.runningTimelines.size() + " timelines; "
// + passedSinceLastIteration + " ms passed since last");
// for (Timeline t : runningTimelines) {
// if (t.mainObject != null
// && t.mainObject.getClass().getName().indexOf(
// "ProgressBar") >= 0) {
// continue;
// }
// System.err.println("\tTimeline @"
// + t.hashCode()
// + " ["
// + t.getName()
// + "] on "
// + (t.mainObject == null ? "null" : t.mainObject
// .getClass().getName()));
// }
for (Iterator<Timeline> itTimeline = this.runningTimelines
.iterator(); itTimeline.hasNext();) {
Timeline timeline = itTimeline.next();
if (timeline.getState() == TimelineState.SUSPENDED)
continue;
boolean timelineWasInReadyState = false;
if (timeline.getState() == TimelineState.READY) {
if ((timeline.timeUntilPlay - passedSinceLastIteration) > 0) {
// still needs to wait in the READY state
timeline.timeUntilPlay -= passedSinceLastIteration;
continue;
}
// can go from READY to PLAYING
timelineWasInReadyState = true;
timeline.popState();
this.callbackCallTimelineStateChanged(timeline,
TimelineState.READY);
}
boolean hasEnded = false;
if (DEBUG_MODE) {
System.out.println("Processing " + timeline.id + "["
+ timeline.mainObject.getClass().getSimpleName()
+ "] from " + timeline.durationFraction
+ ". Callback - "
+ (timeline.callback == null ? "no" : "yes"));
}
// Component comp = entry.getKey();
// at this point, the timeline must be playing
switch (timeline.getState()) {
case PLAYING_FORWARD:
if (!timelineWasInReadyState) {
timeline.durationFraction = timeline.durationFraction
+ (float) passedSinceLastIteration
/ (float) timeline.duration;
}
timeline.timelinePosition = timeline.ease
.map(timeline.durationFraction);
if (DEBUG_MODE) {
System.out
.println("Timeline position: "
+ ((long) (timeline.durationFraction * timeline.duration))
+ "/" + timeline.duration + " = "
+ timeline.durationFraction);
}
if (timeline.durationFraction > 1.0f) {
timeline.durationFraction = 1.0f;
timeline.timelinePosition = 1.0f;
if (timeline.isLooping) {
boolean stopLoopingAnimation = timeline.toCancelAtCycleBreak;
int loopsToLive = timeline.repeatCount;
if (loopsToLive > 0) {
loopsToLive--;
stopLoopingAnimation = stopLoopingAnimation
|| (loopsToLive == 0);
timeline.repeatCount = loopsToLive;
}
if (stopLoopingAnimation) {
// end looping animation
hasEnded = true;
itTimeline.remove();
} else {
if (timeline.repeatBehavior == Timeline.RepeatBehavior.REVERSE) {
timeline
.replaceState(TimelineState.PLAYING_REVERSE);
if (timeline.cycleDelay > 0) {
timeline.pushState(TimelineState.READY);
timeline.timeUntilPlay = timeline.cycleDelay;
}
this.callbackCallTimelineStateChanged(
timeline,
TimelineState.PLAYING_FORWARD);
} else {
timeline.durationFraction = 0.0f;
timeline.timelinePosition = 0.0f;
if (timeline.cycleDelay > 0) {
timeline.pushState(TimelineState.READY);
timeline.timeUntilPlay = timeline.cycleDelay;
this.callbackCallTimelineStateChanged(
timeline,
TimelineState.PLAYING_FORWARD);
} else {
// it's still playing forward, but lets
// the app code know
// that the new loop has begun
this.callbackCallTimelineStateChanged(
timeline,
TimelineState.PLAYING_FORWARD);
}
}
}
} else {
hasEnded = true;
itTimeline.remove();
}
}
break;
case PLAYING_REVERSE:
if (!timelineWasInReadyState) {
timeline.durationFraction = timeline.durationFraction
- (float) passedSinceLastIteration
/ (float) timeline.duration;
}
timeline.timelinePosition = timeline.ease
.map(timeline.durationFraction);
// state.timelinePosition = state.timelinePosition
// - stepFactor
// * state.fadeStep.getNextStep(state.timelineKind,
// state.timelinePosition,
// state.isPlayingForward, state.isLooping);
if (DEBUG_MODE) {
System.out
.println("Timeline position: "
+ ((long) (timeline.durationFraction * timeline.duration))
+ "/" + timeline.duration + " = "
+ timeline.durationFraction);
}
if (timeline.durationFraction < 0) {
timeline.durationFraction = 0.0f;
timeline.timelinePosition = 0.0f;
if (timeline.isLooping) {
boolean stopLoopingAnimation = timeline.toCancelAtCycleBreak;
int loopsToLive = timeline.repeatCount;
if (loopsToLive > 0) {
loopsToLive--;
stopLoopingAnimation = stopLoopingAnimation
|| (loopsToLive == 0);
timeline.repeatCount = loopsToLive;
}
if (stopLoopingAnimation) {
// end looping animation
hasEnded = true;
itTimeline.remove();
} else {
timeline
.replaceState(TimelineState.PLAYING_FORWARD);
if (timeline.cycleDelay > 0) {
timeline.pushState(TimelineState.READY);
timeline.timeUntilPlay = timeline.cycleDelay;
}
this.callbackCallTimelineStateChanged(timeline,
TimelineState.PLAYING_REVERSE);
}
} else {
hasEnded = true;
itTimeline.remove();
}
}
break;
default:
throw new IllegalStateException("Timeline cannot be in "
+ timeline.getState() + " state");
}
if (hasEnded) {
if (DEBUG_MODE) {
System.out.println("Ending " + timeline.id + " on "
// + timeline.timelineKind.toString()
+ " in state " + timeline.getState().name()
+ " at position " + timeline.durationFraction);
}
TimelineState oldState = timeline.getState();
timeline.replaceState(TimelineState.DONE);
this.callbackCallTimelineStateChanged(timeline, oldState);
timeline.popState();
if (timeline.getState() != TimelineState.IDLE) {
throw new IllegalStateException(
"Timeline should be IDLE at this point");
}
this.callbackCallTimelineStateChanged(timeline,
TimelineState.DONE);
} else {
if (DEBUG_MODE) {
System.out.println("Calling " + timeline.id + " on "
// + timeline.timelineKind.toString() + " at "
+ timeline.durationFraction);
}
this.callbackCallTimelinePulse(timeline);
}
}
if (this.runningScenarios.size() > 0) {
// System.err.println(Thread.currentThread().getName()
// + " : updating");
for (Iterator<TimelineScenario> it = this.runningScenarios
.iterator(); it.hasNext();) {
TimelineScenario scenario = it.next();
if (scenario.state == TimelineScenarioState.DONE) {
it.remove();
this.callbackCallTimelineScenarioEnded(scenario);
continue;
}
Set<TimelineScenario.TimelineScenarioActor> readyActors = scenario
.getReadyActors();
if (readyActors != null) {
// if (readyActors.size() > 0)
// System.out.println("Scenario : " + scenario.state +
// ":"
// + readyActors.size());
for (TimelineScenario.TimelineScenarioActor readyActor : readyActors) {
readyActor.play();
}
}
}
}
// System.err.println("Periodic update done");
// this.nothingTracked = (this.runningTimelines.size() == 0);
this.lastIterationTimeStamp = System.currentTimeMillis();
}
}
private void callbackCallTimelineStateChanged(final Timeline timeline,
final TimelineState oldState) {
final TimelineState newState = timeline.getState();
final float durationFraction = timeline.durationFraction;
final float timelinePosition = timeline.timelinePosition;
Runnable callbackRunnable = new Runnable() {
@Override
public void run() {
boolean shouldRunOnUIThread = false;
Class<?> clazz = timeline.callback.getClass();
while ((clazz != null) && !shouldRunOnUIThread) {
shouldRunOnUIThread = clazz
.isAnnotationPresent(RunOnUIThread.class);
clazz = clazz.getSuperclass();
}
if (shouldRunOnUIThread && (timeline.uiToolkitHandler != null)) {
timeline.uiToolkitHandler.runOnUIThread(
timeline.mainObject, new Runnable() {
public void run() {
timeline.callback.onTimelineStateChanged(
oldState, newState,
durationFraction, timelinePosition);
}
});
} else {
timeline.callback.onTimelineStateChanged(oldState,
newState, durationFraction, timelinePosition);
}
}
};
this.callbackQueue.add(callbackRunnable);
}
private void callbackCallTimelinePulse(final Timeline timeline) {
final float durationFraction = timeline.durationFraction;
final float timelinePosition = timeline.timelinePosition;
Runnable callbackRunnable = new Runnable() {
@Override
public void run() {
boolean shouldRunOnUIThread = false;
Class<?> clazz = timeline.callback.getClass();
while ((clazz != null) && !shouldRunOnUIThread) {
shouldRunOnUIThread = clazz
.isAnnotationPresent(RunOnUIThread.class);
clazz = clazz.getSuperclass();
}
if (shouldRunOnUIThread && (timeline.uiToolkitHandler != null)) {
timeline.uiToolkitHandler.runOnUIThread(
timeline.mainObject, new Runnable() {
public void run() {
// System.err.println("Timeline @"
// + timeline.hashCode());
timeline.callback.onTimelinePulse(
durationFraction, timelinePosition);
}
});
} else {
// System.err.println("Timeline @" + timeline.hashCode());
timeline.callback.onTimelinePulse(durationFraction,
timelinePosition);
}
}
};
this.callbackQueue.add(callbackRunnable);
}
private void callbackCallTimelineScenarioEnded(
final TimelineScenario timelineScenario) {
Runnable callbackRunnable = new Runnable() {
@Override
public void run() {
timelineScenario.callback.onTimelineScenarioDone();
}
};
this.callbackQueue.offer(callbackRunnable);
}
/**
* Returns an existing running timeline that matches the specified
* parameters.
*
* @param timelineKind
* Timeline kind.
* @param object
* Component.
* @param secondaryId
* Secondary id. Relevant for such components as tabbed panes
* (where animation is performed on different tabs).
* @return An existing running timeline that matches the specified
* parameters.
*/
private Timeline getRunningTimeline(Timeline timeline) {
synchronized (LOCK) {
if (this.runningTimelines.contains(timeline))
return timeline;
return null;
}
}
/**
* Adds the specified timeline.
*
* @param timeline
* Timeline to add.
*/
private void addTimeline(Timeline timeline) {
synchronized (LOCK) {
FullObjectID cid = new FullObjectID(timeline.mainObject,
timeline.secondaryId);
timeline.fullObjectID = cid;
this.runningTimelines.add(timeline);
// this.nothingTracked = false;
if (DEBUG_MODE) {
System.out.println("Added (" + timeline.id + ") on "
+ timeline.fullObjectID + "]. Fade "
// + timeline.timelineKind.toString() + " with state "
+ timeline.getState().name() + ". Callback - "
+ (timeline.callback == null ? "no" : "yes"));
}
}
}
void play(Timeline timeline, boolean reset, long msToSkip) {
synchronized (LOCK) {
getAnimatorThread();
// see if it's already tracked
Timeline existing = this.getRunningTimeline(timeline);
if (existing == null) {
TimelineState oldState = timeline.getState();
timeline.timeUntilPlay = timeline.initialDelay - msToSkip;
if (timeline.timeUntilPlay < 0) {
timeline.durationFraction = (float) -timeline.timeUntilPlay
/ (float) timeline.duration;
timeline.timelinePosition = timeline.ease
.map(timeline.durationFraction);
timeline.timeUntilPlay = 0;
} else {
timeline.durationFraction = 0.0f;
timeline.timelinePosition = 0.0f;
}
timeline.pushState(TimelineState.PLAYING_FORWARD);
timeline.pushState(TimelineState.READY);
this.addTimeline(timeline);
this.callbackCallTimelineStateChanged(timeline, oldState);
} else {
TimelineState oldState = existing.getState();
if (oldState == TimelineState.READY) {
// the timeline remains READY, but after that it will be
// PLAYING_FORWARD
existing.popState();
existing.replaceState(TimelineState.PLAYING_FORWARD);
existing.pushState(TimelineState.READY);
} else {
// change the timeline state
existing.replaceState(TimelineState.PLAYING_FORWARD);
if (oldState != existing.getState()) {
this.callbackCallTimelineStateChanged(timeline,
oldState);
}
}
if (reset) {
existing.durationFraction = 0.0f;
existing.timelinePosition = 0.0f;
this.callbackCallTimelinePulse(existing);
}
}
}
}
void playScenario(TimelineScenario scenario) {
synchronized (LOCK) {
getAnimatorThread();
Set<TimelineScenario.TimelineScenarioActor> readyActors = scenario
.getReadyActors();
// System.err.println(Thread.currentThread().getName() +
// " : adding");
this.runningScenarios.add(scenario);
for (TimelineScenario.TimelineScenarioActor readyActor : readyActors) {
readyActor.play();
}
}
}
void playReverse(Timeline timeline, boolean reset, long msToSkip) {
synchronized (LOCK) {
getAnimatorThread();
if (timeline.isLooping) {
throw new IllegalArgumentException(
"Timeline must not be marked as looping");
}
// see if it's already tracked
Timeline existing = this.getRunningTimeline(timeline);
if (existing == null) {
TimelineState oldState = timeline.getState();
timeline.timeUntilPlay = timeline.initialDelay - msToSkip;
if (timeline.timeUntilPlay < 0) {
timeline.durationFraction = 1.0f
- (float) -timeline.timeUntilPlay
/ (float) timeline.duration;
timeline.timelinePosition = timeline.ease
.map(timeline.durationFraction);
timeline.timeUntilPlay = 0;
} else {
timeline.durationFraction = 1.0f;
timeline.timelinePosition = 1.0f;
}
timeline.pushState(TimelineState.PLAYING_REVERSE);
timeline.pushState(TimelineState.READY);
this.addTimeline(timeline);
this.callbackCallTimelineStateChanged(timeline, oldState);
} else {
TimelineState oldState = existing.getState();
if (oldState == TimelineState.READY) {
// the timeline remains READY, but after that it will be
// PLAYING_REVERSE
existing.popState();
existing.replaceState(TimelineState.PLAYING_REVERSE);
existing.pushState(TimelineState.READY);
} else {
// change the timeline state
existing.replaceState(TimelineState.PLAYING_REVERSE);
if (oldState != existing.getState()) {
this.callbackCallTimelineStateChanged(timeline,
oldState);
}
}
if (reset) {
existing.durationFraction = 1.0f;
existing.timelinePosition = 1.0f;
this.callbackCallTimelinePulse(existing);
}
}
}
}
void playLoop(Timeline timeline, long msToSkip) {
synchronized (LOCK) {
getAnimatorThread();
if (!timeline.isLooping) {
throw new IllegalArgumentException(
"Timeline must be marked as looping");
}
// see if it's already tracked
Timeline existing = this.getRunningTimeline(timeline);
if (existing == null) {
TimelineState oldState = timeline.getState();
timeline.timeUntilPlay = timeline.initialDelay - msToSkip;
if (timeline.timeUntilPlay < 0) {
timeline.durationFraction = (float) -timeline.timeUntilPlay
/ (float) timeline.duration;
timeline.timelinePosition = timeline.ease
.map(timeline.durationFraction);
timeline.timeUntilPlay = 0;
} else {
timeline.durationFraction = 0.0f;
timeline.timelinePosition = 0.0f;
}
timeline.pushState(TimelineState.PLAYING_FORWARD);
timeline.pushState(TimelineState.READY);
timeline.toCancelAtCycleBreak = false;
this.addTimeline(timeline);
this.callbackCallTimelineStateChanged(timeline, oldState);
} else {
existing.toCancelAtCycleBreak = false;
existing.repeatCount = timeline.repeatCount;
}
}
}
/**
* Stops tracking of all timelines. Note that this function <b>does not</b>
* stop the timeline engine thread ({@link #animatorThread}) and the
* timeline callback thread ({@link #callbackThread}).
*/
public void cancelAllTimelines() {
synchronized (LOCK) {
getAnimatorThread();
for (Timeline timeline : this.runningTimelines) {
TimelineState oldState = timeline.getState();
while (timeline.getState() != TimelineState.IDLE)
timeline.popState();
timeline.pushState(TimelineState.CANCELLED);
this.callbackCallTimelineStateChanged(timeline, oldState);
timeline.popState();
this.callbackCallTimelineStateChanged(timeline,
TimelineState.CANCELLED);
}
this.runningTimelines.clear();
this.runningScenarios.clear();
}
}
/**
* Returns an instance of the animator thread.
*
* @return The animator thread.
*/
private TridentAnimationThread getAnimatorThread() {
if (this.animatorThread == null) {
this.animatorThread = new TridentAnimationThread();
this.animatorThread.start();
}
return this.animatorThread;
}
/**
* Returns an instance of the callback thread.
*
* @return The animator thread.
*/
private TimelineCallbackThread getCallbackThread() {
if (this.callbackThread == null) {
this.callbackThread = new TimelineCallbackThread();
this.callbackThread.start();
}
return this.callbackThread;
}
/**
* Cancels the specified timeline instance.
*
* @param timeline
* Timeline to cancel.
*/
private void cancelTimeline(Timeline timeline) {
getAnimatorThread();
if (this.runningTimelines.contains(timeline)) {
this.runningTimelines.remove(timeline);
TimelineState oldState = timeline.getState();
while (timeline.getState() != TimelineState.IDLE)
timeline.popState();
timeline.pushState(TimelineState.CANCELLED);
this.callbackCallTimelineStateChanged(timeline, oldState);
timeline.popState();
this.callbackCallTimelineStateChanged(timeline,
TimelineState.CANCELLED);
}
}
/**
* Ends the specified timeline instance.
*
* @param timeline
* Timeline to end.
*/
private void endTimeline(Timeline timeline) {
getAnimatorThread();
if (this.runningTimelines.contains(timeline)) {
this.runningTimelines.remove(timeline);
TimelineState oldState = timeline.getState();
float endPosition = timeline.timelinePosition;
while (timeline.getState() != TimelineState.IDLE) {
TimelineState state = timeline.popState();
if (state == TimelineState.PLAYING_FORWARD)
endPosition = 1.0f;
if (state == TimelineState.PLAYING_REVERSE)
endPosition = 0.0f;
}
timeline.durationFraction = endPosition;
timeline.timelinePosition = endPosition;
timeline.pushState(TimelineState.DONE);
this.callbackCallTimelineStateChanged(timeline, oldState);
timeline.popState();
this.callbackCallTimelineStateChanged(timeline, TimelineState.DONE);
}
}
/**
* Cancels the specified timeline instance.
*
* @param timeline
* Timeline to cancel.
*/
private void abortTimeline(Timeline timeline) {
getAnimatorThread();
if (this.runningTimelines.contains(timeline)) {
this.runningTimelines.remove(timeline);
while (timeline.getState() != TimelineState.IDLE)
timeline.popState();
}
}
/**
* Suspends the specified timeline instance.
*
* @param timeline
* Timeline to suspend.
*/
private void suspendTimeline(Timeline timeline) {
getAnimatorThread();
if (this.runningTimelines.contains(timeline)) {
TimelineState oldState = timeline.getState();
if ((oldState != TimelineState.PLAYING_FORWARD)
&& (oldState != TimelineState.PLAYING_REVERSE)
&& (oldState != TimelineState.READY)) {
return;
}
timeline.pushState(TimelineState.SUSPENDED);
this.callbackCallTimelineStateChanged(timeline, oldState);
}
}
/**
* Resume the specified timeline instance.
*
* @param timeline
* Timeline to resume.
*/
private void resumeTimeline(Timeline timeline) {
getAnimatorThread();
if (this.runningTimelines.contains(timeline)) {
TimelineState oldState = timeline.getState();
if (oldState != TimelineState.SUSPENDED)
return;
timeline.popState();
this.callbackCallTimelineStateChanged(timeline, oldState);
}
}
void runTimelineOperation(Timeline timeline,
TimelineOperationKind operationKind, Runnable operationRunnable) {
synchronized (LOCK) {
this.getAnimatorThread();
switch (operationKind) {
case CANCEL:
this.cancelTimeline(timeline);
return;
case END:
this.endTimeline(timeline);
return;
case RESUME:
this.resumeTimeline(timeline);
return;
case SUSPEND:
this.suspendTimeline(timeline);
return;
case ABORT:
this.abortTimeline(timeline);
return;
}
operationRunnable.run();
}
}
void runTimelineScenario(TimelineScenario timelineScenario,
Runnable timelineScenarioRunnable) {
synchronized (LOCK) {
this.getAnimatorThread();
timelineScenarioRunnable.run();
}
}
static final Object LOCK = new Object();
}
@@ -0,0 +1,434 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.pushingpixels.trident.interpolator.KeyFrames;
import org.pushingpixels.trident.interpolator.PropertyInterpolator;
@SuppressWarnings({"rawtypes","unchecked"})
public class TimelinePropertyBuilder<T> {
/**
* Defines how to access a property.
*/
public static interface PropertyAccessor<T> extends PropertyGetter<T>,
PropertySetter<T> {
}
/**
* Default property setter.
*/
public static class DefaultPropertySetter<T> implements PropertySetter<T> {
private Method setterMethod;
public DefaultPropertySetter(Object obj, String fieldName) {
setterMethod = getSetter(obj, fieldName);
}
@Override
public void set(Object obj, String fieldName, T value) {
try {
setterMethod.invoke(obj, value);
} catch (Throwable t) {
throw new RuntimeException(
"Unable to set the value of the field '" + fieldName
+ "'", t);
}
}
}
/**
* Default property getter.
*/
public static class DefaultPropertyGetter<T> implements PropertyGetter<T> {
private Method getterMethod;
public DefaultPropertyGetter(Object obj, String fieldName) {
getterMethod = getGetter(obj, fieldName);
}
@Override
public T get(Object obj, String fieldName) {
try {
return (T) getterMethod.invoke(obj);
} catch (Throwable t) {
throw new RuntimeException(
"Unable to get the value of the field '" + fieldName
+ "'", t);
}
}
}
private Object target; // may be null
private final String propertyName; // required
private T from; // optional
private boolean isFromCurrent;
private T to; // must be optional because of KeyFrames
private PropertyInterpolator<T> interpolator; // optional
private PropertyGetter<T> getter; // optional
private PropertySetter<T> setter; // optional
private KeyFrames<T> keyFrames; // optional
TimelinePropertyBuilder(String propertyName) {
this.propertyName = propertyName;
this.isFromCurrent = false;
}
public TimelinePropertyBuilder<T> from(T startValue) {
if (this.from != null) {
throw new IllegalArgumentException("from() can only be called once");
}
if (this.isFromCurrent) {
throw new IllegalArgumentException(
"from() cannot be called after fromCurrent()");
}
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"from() cannot be called after goingThrough()");
}
this.from = startValue;
return this;
}
public TimelinePropertyBuilder<T> fromCurrent() {
if (this.isFromCurrent) {
throw new IllegalArgumentException(
"fromCurrent() can only be called once");
}
if (this.from != null) {
throw new IllegalArgumentException(
"fromCurrent() cannot be called after from()");
}
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"fromCurrent() cannot be called after goingThrough()");
}
this.isFromCurrent = true;
return this;
}
public TimelinePropertyBuilder<T> to(T endValue) {
if (this.to != null) {
throw new IllegalArgumentException("to() can only be called once");
}
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"to() cannot be called after goingThrough()");
}
this.to = endValue;
return this;
}
public TimelinePropertyBuilder<T> on(Object object) {
this.target = object;
return this;
}
public TimelinePropertyBuilder<T> interpolatedWith(
PropertyInterpolator<T> pInterpolator) {
if (this.interpolator != null) {
throw new IllegalArgumentException(
"interpolateWith() can only be called once");
}
this.interpolator = pInterpolator;
return this;
}
public TimelinePropertyBuilder<T> setWith(PropertySetter<T> pSetter) {
if (this.setter != null) {
throw new IllegalArgumentException(
"setWith() can only be called once");
}
this.setter = pSetter;
return this;
}
public TimelinePropertyBuilder<T> getWith(PropertyGetter<T> pGetter) {
if (this.getter != null) {
throw new IllegalArgumentException(
"getWith() can only be called once");
}
this.getter = pGetter;
return this;
}
public TimelinePropertyBuilder<T> accessWith(PropertyAccessor<T> pAccessor) {
if ((this.setter != null) || (this.getter != null)) {
throw new IllegalArgumentException(
"accessWith() can only be called once");
}
this.setter = pAccessor;
this.getter = pAccessor;
return this;
}
public TimelinePropertyBuilder<T> goingThrough(KeyFrames<T> keyFrames) {
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"goingThrough() can only be called once");
}
if (this.isFromCurrent) {
throw new IllegalArgumentException(
"goingThrough() cannot be called after fromCurrent()");
}
if (this.from != null) {
throw new IllegalArgumentException(
"goingThrough() cannot be called after from()");
}
if (this.to != null) {
throw new IllegalArgumentException(
"goingThrough() cannot be called after to()");
}
this.keyFrames = keyFrames;
return this;
}
AbstractFieldInfo getFieldInfo(Timeline timeline) {
if (this.target == null) {
this.target = timeline.mainObject;
}
if (this.keyFrames != null) {
return new KeyFramesFieldInfo(this.target, this.propertyName,
this.keyFrames, this.setter);
}
if (this.isFromCurrent) {
if (this.interpolator == null) {
this.interpolator = TridentConfig.getInstance()
.getPropertyInterpolator(this.to);
if (this.interpolator == null) {
throw new IllegalArgumentException(
"No interpolator found for "
+ this.to.getClass().getName());
}
}
return new GenericFieldInfoTo(this.target, this.propertyName,
this.to, this.interpolator, this.getter, this.setter);
}
if (this.interpolator == null) {
this.interpolator = TridentConfig.getInstance()
.getPropertyInterpolator(this.from, this.to);
// if (this.interpolator == null) {
// throw new IllegalArgumentException("No interpolator found for "
// + this.from.getClass().getName() + ":"
// + this.to.getClass().getName());
// }
}
return new GenericFieldInfo(this.target, this.propertyName, this.from,
this.to, this.interpolator, this.setter);
}
@SuppressWarnings("hiding")
abstract class AbstractFieldInfo<T> {
protected Object object;
protected String fieldName;
protected PropertyGetter getter;
protected PropertySetter setter;
protected T from;
protected T to;
AbstractFieldInfo(Object obj, String fieldName,
PropertyGetter<T> pGetter, PropertySetter<T> pSetter) {
this.object = obj;
this.fieldName = fieldName;
this.getter = pGetter;
this.setter = pSetter;
}
void setValues(T from, T to) {
this.from = from;
this.to = to;
}
abstract void onStart();
abstract void updateFieldValue(float timelinePosition);
}
private static <T> PropertyGetter<T> getPropertyGetter(Object obj,
String fieldName, PropertyGetter<T> pGetter) {
if (pGetter != null) {
return pGetter;
}
return new DefaultPropertyGetter(obj, fieldName);
}
private static <T> PropertySetter<T> getPropertySetter(Object obj,
String fieldName, PropertySetter<T> pSetter) {
if (pSetter != null) {
return pSetter;
}
return new DefaultPropertySetter(obj, fieldName);
}
private class GenericFieldInfoTo extends AbstractFieldInfo<Object> {
private PropertyInterpolator propertyInterpolator;
private Object to;
GenericFieldInfoTo(Object obj, String fieldName, Object to,
PropertyInterpolator propertyInterpolator,
PropertyGetter propertyGetter, PropertySetter propertySetter) {
super(obj, fieldName, getPropertyGetter(obj, fieldName,
propertyGetter), getPropertySetter(obj, fieldName,
propertySetter));
this.propertyInterpolator = propertyInterpolator;
this.to = to;
}
@Override
void onStart() {
this.from = getter.get(object, fieldName);
}
@Override
void updateFieldValue(float timelinePosition) {
try {
Object value = this.propertyInterpolator.interpolate(from, to,
timelinePosition);
this.setter.set(this.object, this.fieldName, value);
} catch (Throwable exc) {
System.err.println("Exception occurred in updating field '"
+ this.fieldName + "' of object "
+ this.object.getClass().getCanonicalName()
+ " at timeline position " + timelinePosition);
exc.printStackTrace();
}
}
}
private class GenericFieldInfo extends AbstractFieldInfo<Object> {
private PropertyInterpolator propertyInterpolator;
GenericFieldInfo(Object obj, String fieldName, Object from, Object to,
PropertyInterpolator propertyInterpolator,
PropertySetter propertySetter) {
super(obj, fieldName, null, getPropertySetter(obj, fieldName,
propertySetter));
this.propertyInterpolator = propertyInterpolator;
this.setValues(from, to);
}
@Override
void onStart() {
}
@Override
void updateFieldValue(float timelinePosition) {
try {
Object value = this.propertyInterpolator.interpolate(from, to,
timelinePosition);
this.setter.set(this.object, this.fieldName, value);
} catch (Throwable exc) {
System.err.println("Exception occurred in updating field '"
+ this.fieldName + "' of object "
+ this.object.getClass().getCanonicalName()
+ " at timeline position " + timelinePosition);
exc.printStackTrace();
}
}
}
private class KeyFramesFieldInfo extends AbstractFieldInfo<Object> {
KeyFrames keyFrames;
KeyFramesFieldInfo(Object obj, String fieldName, KeyFrames keyFrames,
PropertySetter propertySetter) {
super(obj, fieldName, null, getPropertySetter(obj, fieldName,
propertySetter));
this.keyFrames = keyFrames;
}
@Override
void onStart() {
}
@Override
void updateFieldValue(float timelinePosition) {
if (this.setter != null) {
try {
Object value = this.keyFrames.getValue(timelinePosition);
this.setter.set(this.object, this.fieldName, value);
} catch (Throwable exc) {
exc.printStackTrace();
}
}
}
}
private static Method getSetter(Object object, String propertyName) {
String setterMethodName = "set"
+ Character.toUpperCase(propertyName.charAt(0))
+ propertyName.substring(1);
Class oClazz = object.getClass();
while (oClazz != null) {
for (Method m : oClazz.getMethods()) {
if (setterMethodName.equals(m.getName())
&& (m.getParameterTypes().length == 1)
&& (m.getReturnType() == Void.TYPE)
&& (!Modifier.isStatic(m.getModifiers()))) {
return m;
}
}
oClazz = oClazz.getSuperclass();
}
return null;
}
private static Method getGetter(Object object, String propertyName) {
String getterMethodName = "get"
+ Character.toUpperCase(propertyName.charAt(0))
+ propertyName.substring(1);
Class oClazz = object.getClass();
while (oClazz != null) {
for (Method m : oClazz.getMethods()) {
if (getterMethodName.equals(m.getName())
&& (m.getParameterTypes().length == 0)
&& (!Modifier.isStatic(m.getModifiers()))) {
return m;
}
}
oClazz = oClazz.getSuperclass();
}
return null;
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident;
import java.util.concurrent.*;
import org.pushingpixels.trident.TimelineScenario.TimelineScenarioActor;
public abstract class TimelineRunnable implements Runnable,
TimelineScenarioActor {
private static ExecutorService service = new ThreadPoolExecutor(0,
Integer.MAX_VALUE, 10L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
private Future<?> future;
@Override
public void play() {
this.future = service.submit(this);
}
@Override
public boolean isDone() {
if (this.future == null)
return false;
return this.future.isDone();
}
@Override
public boolean supportsReplay() {
return false;
}
@Override
public void resetDoneFlag() {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,357 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident;
import java.util.*;
import org.pushingpixels.trident.callback.TimelineScenarioCallback;
public class TimelineScenario {
private Set<TimelineScenarioActor> waitingActors;
private Set<TimelineScenarioActor> runningActors;
private Set<TimelineScenarioActor> doneActors;
private Map<TimelineScenarioActor, Set<TimelineScenarioActor>> dependencies;
Chain callback;
TimelineScenarioState state;
TimelineScenarioState statePriorToSuspension;
boolean isLooping;
public enum TimelineScenarioState {
DONE, PLAYING, IDLE, SUSPENDED
}
class Chain implements TimelineScenarioCallback {
private List<TimelineScenarioCallback> callbacks;
public Chain(TimelineScenarioCallback... callbacks) {
this.callbacks = new ArrayList<TimelineScenarioCallback>();
for (TimelineScenarioCallback callback : callbacks)
this.callbacks.add(callback);
}
public void addCallback(TimelineScenarioCallback callback) {
this.callbacks.add(callback);
}
@Override
public void onTimelineScenarioDone() {
for (TimelineScenarioCallback callback : this.callbacks)
callback.onTimelineScenarioDone();
}
}
public static interface TimelineScenarioActor {
public boolean isDone();
public boolean supportsReplay();
public void resetDoneFlag();
public void play();
}
public TimelineScenario() {
this.waitingActors = new HashSet<TimelineScenarioActor>();
this.runningActors = new HashSet<TimelineScenarioActor>();
this.doneActors = new HashSet<TimelineScenarioActor>();
this.dependencies = new HashMap<TimelineScenarioActor, Set<TimelineScenarioActor>>();
this.callback = new Chain();
this.state = TimelineScenarioState.IDLE;
}
public void addScenarioActor(TimelineScenarioActor actor) {
if (actor.isDone()) {
throw new IllegalArgumentException("Already finished");
}
this.waitingActors.add(actor);
}
public void addCallback(TimelineScenarioCallback callback) {
if (this.doneActors.size() > 0) {
throw new IllegalArgumentException(
"Cannot change state of non-idle timeline scenario");
}
this.callback.addCallback(callback);
}
private void checkDependencyParam(TimelineScenarioActor actor) {
if (!waitingActors.contains(actor)) {
throw new IllegalArgumentException(
"Must be first added with addScenarioActor() API");
}
}
public void addDependency(TimelineScenarioActor actor,
TimelineScenarioActor... waitFor) {
// check params
this.checkDependencyParam(actor);
for (TimelineScenarioActor wait : waitFor) {
this.checkDependencyParam(wait);
}
if (!this.dependencies.containsKey(actor))
this.dependencies.put(actor, new HashSet<TimelineScenarioActor>());
this.dependencies.get(actor).addAll(Arrays.asList(waitFor));
}
private void checkDoneActors() {
synchronized (TimelineEngine.LOCK) {
for (Iterator<TimelineScenarioActor> itRunning = this.runningActors
.iterator(); itRunning.hasNext();) {
TimelineScenarioActor stillRunning = itRunning.next();
if (stillRunning.isDone()) {
itRunning.remove();
this.doneActors.add(stillRunning);
}
}
}
}
Set<TimelineScenarioActor> getReadyActors() {
synchronized (TimelineEngine.LOCK) {
if (this.state == TimelineScenarioState.SUSPENDED)
return new HashSet<TimelineScenarioActor>();
this.checkDoneActors();
Set<TimelineScenarioActor> result = new HashSet<TimelineScenarioActor>();
for (Iterator<TimelineScenarioActor> itWaiting = this.waitingActors
.iterator(); itWaiting.hasNext();) {
TimelineScenarioActor waitingActor = itWaiting.next();
boolean canRun = true;
Set<TimelineScenarioActor> toWaitFor = this.dependencies
.get(waitingActor);
if (toWaitFor != null) {
for (TimelineScenarioActor actorToWaitFor : toWaitFor) {
if (!doneActors.contains(actorToWaitFor)) {
canRun = false;
break;
}
}
}
if (canRun) {
runningActors.add(waitingActor);
itWaiting.remove();
result.add(waitingActor);
}
}
if (this.waitingActors.isEmpty() && this.runningActors.isEmpty()) {
if (!this.isLooping) {
this.state = TimelineScenarioState.DONE;
} else {
for (TimelineScenarioActor done : this.doneActors)
done.resetDoneFlag();
this.waitingActors.addAll(this.doneActors);
this.doneActors.clear();
}
}
return result;
}
}
public void cancel() {
synchronized (TimelineEngine.LOCK) {
TimelineScenarioState oldState = this.state;
if (oldState != TimelineScenarioState.PLAYING)
return;
this.state = TimelineScenarioState.DONE;
for (TimelineScenarioActor waiting : this.waitingActors) {
if (waiting instanceof Timeline) {
((Timeline) waiting).cancel();
}
}
for (TimelineScenarioActor running : this.runningActors) {
if (running instanceof Timeline) {
((Timeline) running).cancel();
}
}
}
}
public void suspend() {
synchronized (TimelineEngine.LOCK) {
TimelineScenarioState oldState = this.state;
if (oldState != TimelineScenarioState.PLAYING)
return;
this.statePriorToSuspension = oldState;
this.state = TimelineScenarioState.SUSPENDED;
for (TimelineScenarioActor running : this.runningActors) {
if (running instanceof Timeline) {
((Timeline) running).suspend();
}
}
}
}
public void resume() {
synchronized (TimelineEngine.LOCK) {
TimelineScenarioState oldState = this.state;
if (oldState != TimelineScenarioState.SUSPENDED)
return;
this.state = this.statePriorToSuspension;
for (TimelineScenarioActor running : this.runningActors) {
if (running instanceof Timeline) {
((Timeline) running).resume();
}
}
}
}
public void play() {
this.isLooping = false;
this.state = TimelineScenarioState.PLAYING;
TimelineEngine.getInstance().runTimelineScenario(this, new Runnable() {
@Override
public void run() {
TimelineEngine.getInstance()
.playScenario(TimelineScenario.this);
}
});
}
public void playLoop() {
for (TimelineScenarioActor actor : this.waitingActors) {
if (!actor.supportsReplay())
throw new UnsupportedOperationException(
"Can't loop scenario with actor(s) that don't support replay");
}
this.isLooping = true;
this.state = TimelineScenarioState.PLAYING;
TimelineEngine.getInstance().runTimelineScenario(this, new Runnable() {
@Override
public void run() {
TimelineEngine.getInstance()
.playScenario(TimelineScenario.this);
}
});
}
public static class Parallel extends TimelineScenario {
@Override
public void addDependency(TimelineScenarioActor actor,
TimelineScenarioActor... waitFor) {
throw new UnsupportedOperationException(
"Explicit dependencies not supported");
}
}
public static class Sequence extends TimelineScenario {
private TimelineScenarioActor lastActor;
@Override
public void addDependency(TimelineScenarioActor actor,
TimelineScenarioActor... waitFor) {
throw new UnsupportedOperationException(
"Explicit dependencies not supported");
}
@Override
public void addScenarioActor(TimelineScenarioActor actor) {
super.addScenarioActor(actor);
if (this.lastActor != null) {
super.addDependency(actor, this.lastActor);
}
this.lastActor = actor;
}
}
public static class RendezvousSequence extends TimelineScenario {
private Set<TimelineScenarioActor> addedSinceLastRendezvous;
private Set<TimelineScenarioActor> addedPriorToLastRendezvous;
public RendezvousSequence() {
this.addedSinceLastRendezvous = new HashSet<TimelineScenarioActor>();
this.addedPriorToLastRendezvous = new HashSet<TimelineScenarioActor>();
}
@Override
public void addDependency(TimelineScenarioActor actor,
TimelineScenarioActor... waitFor) {
throw new UnsupportedOperationException(
"Explicit dependencies not supported");
}
@Override
public void addScenarioActor(TimelineScenarioActor actor) {
super.addScenarioActor(actor);
this.addedSinceLastRendezvous.add(actor);
}
public void rendezvous() {
// make all actors added since last rendezvous to wait for
// all actors added prior to last rendezvous
if (this.addedPriorToLastRendezvous.size() > 0) {
for (TimelineScenarioActor sinceLast : this.addedSinceLastRendezvous) {
for (TimelineScenarioActor beforeLast : this.addedPriorToLastRendezvous) {
super.addDependency(sinceLast, beforeLast);
}
}
}
this.addedPriorToLastRendezvous.clear();
this.addedPriorToLastRendezvous
.addAll(this.addedSinceLastRendezvous);
this.addedSinceLastRendezvous.clear();
}
@Override
public void play() {
// add last implicit rendezvous
this.rendezvous();
super.play();
}
@Override
public void playLoop() {
// add last implicit rendezvous
this.rendezvous();
super.playLoop();
}
}
public final TimelineScenarioState getState() {
return this.state;
}
}
@@ -0,0 +1,243 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident;
import java.io.*;
import java.net.URL;
import java.util.*;
import org.pushingpixels.trident.TimelineEngine.TridentAnimationThread;
import org.pushingpixels.trident.interpolator.PropertyInterpolator;
import org.pushingpixels.trident.interpolator.PropertyInterpolatorSource;
@SuppressWarnings({"rawtypes","unchecked"})
public class TridentConfig {
private static TridentConfig config;
private Set<UIToolkitHandler> uiToolkitHandlers;
private Set<PropertyInterpolator> propertyInterpolators;
private TridentConfig.PulseSource pulseSource;
public interface PulseSource {
public void waitUntilNextPulse();
}
public static class FixedRatePulseSource implements
TridentConfig.PulseSource {
private int msDelay;
public FixedRatePulseSource(int msDelay) {
this.msDelay = msDelay;
}
@Override
public void waitUntilNextPulse() {
try {
Thread.sleep(this.msDelay);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
private class DefaultPulseSource extends FixedRatePulseSource {
DefaultPulseSource() {
super(40);
}
}
private TridentConfig() {
this.pulseSource = new DefaultPulseSource();
this.uiToolkitHandlers = new HashSet<UIToolkitHandler>();
this.propertyInterpolators = new HashSet<PropertyInterpolator>();
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
try {
Enumeration urls = classLoader
.getResources("META-INF/trident-plugin.properties");
while (urls.hasMoreElements()) {
URL pluginUrl = (URL) urls.nextElement();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(pluginUrl
.openStream()));
while (true) {
String line = reader.readLine();
if (line == null)
break;
String[] parts = line.split("=");
if (parts.length != 2)
continue;
String key = parts[0];
String value = parts[1];
if ("UIToolkitHandler".compareTo(key) == 0) {
try {
Class pluginClass = classLoader
.loadClass(value);
if (pluginClass == null)
continue;
if (UIToolkitHandler.class
.isAssignableFrom(pluginClass)) {
UIToolkitHandler uiToolkitHandler = (UIToolkitHandler) pluginClass
.newInstance();
uiToolkitHandler.isHandlerFor(new Object());
this.uiToolkitHandlers
.add(uiToolkitHandler);
}
} catch (NoClassDefFoundError ncdfe) {
// trying to initialize a plugin with a missing
// class
}
}
if ("PropertyInterpolatorSource".compareTo(key) == 0) {
try {
Class piSourceClass = classLoader
.loadClass(value);
if (piSourceClass == null)
continue;
if (PropertyInterpolatorSource.class
.isAssignableFrom(piSourceClass)) {
PropertyInterpolatorSource piSource = (PropertyInterpolatorSource) piSourceClass
.newInstance();
Set<PropertyInterpolator> interpolators = piSource
.getPropertyInterpolators();
for (PropertyInterpolator pi : interpolators) {
try {
Class basePropertyClass = pi
.getBasePropertyClass();
// is in classpath?
basePropertyClass.getClass();
this.propertyInterpolators.add(pi);
} catch (NoClassDefFoundError ncdfe) {
// trying to initialize a plugin
// with a missing
// class - just skip
}
}
// this.propertyInterpolators.addAll(piSource
// .getPropertyInterpolators());
}
} catch (NoClassDefFoundError ncdfe) {
// trying to initialize a plugin with a missing
// class
}
}
}
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ioe) {
}
}
}
}
} catch (Exception exc) {
exc.printStackTrace();
}
}
public static synchronized TridentConfig getInstance() {
if (config == null)
config = new TridentConfig();
return config;
}
public synchronized Collection<UIToolkitHandler> getUIToolkitHandlers() {
return Collections.unmodifiableSet(this.uiToolkitHandlers);
}
public synchronized Collection<PropertyInterpolator> getPropertyInterpolators() {
return Collections.unmodifiableSet(this.propertyInterpolators);
}
public synchronized PropertyInterpolator getPropertyInterpolator(
Object... values) {
for (PropertyInterpolator interpolator : this.propertyInterpolators) {
try {
Class basePropertyClass = interpolator.getBasePropertyClass();
boolean hasMatch = true;
for (Object value : values) {
if (!basePropertyClass.isAssignableFrom(value.getClass())) {
hasMatch = false;
continue;
}
}
if (hasMatch)
return interpolator;
} catch (NoClassDefFoundError ncdfe) {
continue;
}
}
return null;
}
public synchronized void addPropertyInterpolator(
PropertyInterpolator pInterpolator) {
this.propertyInterpolators.add(pInterpolator);
}
public synchronized void addPropertyInterpolatorSource(
PropertyInterpolatorSource pInterpolatorSource) {
this.propertyInterpolators.addAll(pInterpolatorSource
.getPropertyInterpolators());
}
public synchronized void removePropertyInterpolator(
PropertyInterpolator pInterpolator) {
this.propertyInterpolators.remove(pInterpolator);
}
public synchronized void addUIToolkitHandler(
UIToolkitHandler uiToolkitHandler) {
this.uiToolkitHandlers.add(uiToolkitHandler);
}
public synchronized void removeUIToolkitHandler(
UIToolkitHandler uiToolkitHandler) {
this.uiToolkitHandlers.remove(uiToolkitHandler);
}
public synchronized void setPulseSource(PulseSource pulseSource) {
TridentAnimationThread current = TimelineEngine.getInstance().animatorThread;
if ((current != null) && current.isAlive())
throw new IllegalStateException(
"Cannot replace the pulse source thread once it's running");
this.pulseSource = pulseSource;
}
public synchronized TridentConfig.PulseSource getPulseSource() {
return pulseSource;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident;
public interface UIToolkitHandler {
public boolean isHandlerFor(Object mainTimelineObject);
public boolean isInReadyState(Object mainTimelineObject);
public void runOnUIThread(Object mainTimelineObject, Runnable runnable);
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.callback;
import java.lang.annotation.*;
/**
* Annotation to mark code that should run on UI thread.
*
* @author Kirill Grouchnikov
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunOnUIThread {
}
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.callback;
import org.pushingpixels.trident.Timeline.TimelineState;
/**
* Callback for the fade tracker. Is used when the application (some UI
* delegate) wishes to execute some code on the fade.
*
* @author Kirill Grouchnikov
*/
public interface TimelineCallback {
/**
* Indicates that the timeline state has changed.
*
* @param oldState
* The old timeline state.
* @param newState
* The new timeline state.
* @param durationFraction
* The current timeline duration fraction.Is guaranteed to be in
* 0.0-1.0 range. The rate of change of this value is linear, and
* the value is proportional to
* {@link Timeline#setDuration(long)}.
* @param timelinePosition
* The current timeline position. Is guaranteed to be in 0.0-1.0
* range. The rate of change of this value is not necessarily
* linear and is affected by the
* {@link Timeline#setEase(org.pushingpixels.trident.ease.TimelineEase)}
* .
*/
public void onTimelineStateChanged(TimelineState oldState,
TimelineState newState, float durationFraction,
float timelinePosition);
/**
* Indicates that the timeline pulse has happened.
*
* @param durationFraction
* The current timeline duration fraction.Is guaranteed to be in
* 0.0-1.0 range. The rate of change of this value is linear, and
* the value is proportional to
* {@link Timeline#setDuration(long)}.
* @param timelinePosition
* The current timeline position. Is guaranteed to be in 0.0-1.0
* range. The rate of change of this value is not necessarily
* linear and is affected by the
* {@link Timeline#setEase(org.pushingpixels.trident.ease.TimelineEase)}
* .
*/
public void onTimelinePulse(float durationFraction, float timelinePosition);
}
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.callback;
import org.pushingpixels.trident.Timeline.TimelineState;
/**
* Default implementation of {@link TimelineCallback} that does nothing.
*
* @author Kirill Grouchnikov
*/
public class TimelineCallbackAdapter implements TimelineCallback {
@Override
public void onTimelineStateChanged(TimelineState oldState,
TimelineState newState, float durationFraction,
float timelinePosition) {
}
@Override
public void onTimelinePulse(float durationFraction, float timelinePosition) {
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.callback;
/**
* Callback for tracking the {@link TimelineScenario}s.
*
* @author Kirill Grouchnikov
*/
public interface TimelineScenarioCallback {
/**
* Indicates that the all timelines and swing workers in the timeline
* scenario have finished.
*/
public void onTimelineScenarioDone();
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.callback;
/**
* Empty implementation of {@link TimelineCallback} that does nothing but is
* marked to run on the EDT.
*
* @author Kirill Grouchnikov
*/
@RunOnUIThread
public class UIThreadTimelineCallbackAdapter extends TimelineCallbackAdapter {
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.ease;
public class Linear implements TimelineEase {
@Override
public float map(float durationFraction) {
return durationFraction;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.ease;
public class Sine implements TimelineEase {
@Override
public float map(float durationFraction) {
return (float) Math.sin(durationFraction * Math.PI / 2.0);
}
}
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.ease;
import java.util.ArrayList;
/**
* Spline easer. Is based on the code from <a
* href="https://timingframework.dev.java.net">TimingFramework</a> by Chet Haase
* and Romain Guy.
*
* @author Kirill Grouchnikov
*/
@SuppressWarnings({"unchecked","rawtypes","unused"})
public class Spline implements TimelineEase {
// private float easeAmount;
public Spline(float easeAmount) {
this(easeAmount, 0, 1 - easeAmount, 1);
// this.easeAmount = easeAmount;
}
private static class FloatPoint {
public float x;
public float y;
public FloatPoint(float x, float y) {
this.x = x;
this.y = y;
}
}
// Note: (x0,y0) and (x1,y1) are implicitly (0, 0) and (1,1) respectively
private float x1, y1, x2, y2;
private ArrayList lengths = new ArrayList();
/**
* Creates a new instance of SplineInterpolator with the control points
* defined by (x1, y1) and (x2, y2). The anchor points are implicitly
* defined as (0, 0) and (1, 1).
*
* @throws IllegalArgumentException
* This exception is thrown when values beyond the allowed [0,1]
* range are passed in
*/
public Spline(float x1, float y1, float x2, float y2) {
if (x1 < 0 || x1 > 1.0f || y1 < 0 || y1 > 1.0f || x2 < 0 || x2 > 1.0f
|| y2 < 0 || y2 > 1.0f) {
throw new IllegalArgumentException("Control points must be in "
+ "the range [0, 1]:");
}
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
// Now contruct the array of all lengths to t in [0, 1.0]
float prevX = 0.0f;
float prevY = 0.0f;
float prevLength = 0.0f; // cumulative length
for (float t = 0.01f; t <= 1.0f; t += .01f) {
FloatPoint xy = getXY(t);
float length = prevLength
+ (float) Math.sqrt((xy.x - prevX) * (xy.x - prevX)
+ (xy.y - prevY) * (xy.y - prevY));
LengthItem lengthItem = new LengthItem(length, t);
lengths.add(lengthItem);
prevLength = length;
prevX = xy.x;
prevY = xy.y;
}
// Now calculate the fractions so that we can access the lengths
// array with values in [0,1]. prevLength now holds the total
// length of the spline.
for (int i = 0; i < lengths.size(); ++i) {
LengthItem lengthItem = (LengthItem) lengths.get(i);
lengthItem.setFraction(prevLength);
}
}
/**
* Calculates the XY point for a given t value.
*
* The general spline equation is: x = b0*x0 + b1*x1 + b2*x2 + b3*x3 y =
* b0*y0 + b1*y1 + b2*y2 + b3*y3 where: b0 = (1-t)^3 b1 = 3 * t * (1-t)^2 b2
* = 3 * t^2 * (1-t) b3 = t^3 We know that (x0,y0) == (0,0) and (x1,y1) ==
* (1,1) for our splines, so this simplifies to: x = b1*x1 + b2*x2 + b3 y =
* b1*x1 + b2*x2 + b3
*
* @param t
* parametric value for spline calculation
*/
private FloatPoint getXY(float t) {
FloatPoint xy;
float invT = (1 - t);
float b1 = 3 * t * (invT * invT);
float b2 = 3 * (t * t) * invT;
float b3 = t * t * t;
xy = new FloatPoint((b1 * x1) + (b2 * x2) + b3, (b1 * y1)
+ (b2 * y2) + b3);
return xy;
}
/**
* Utility function: When we are evaluating the spline, we only care about
* the Y values. See {@link getXY getXY} for the details.
*/
private float getY(float t) {
FloatPoint xy;
float invT = (1 - t);
float b1 = 3 * t * (invT * invT);
float b2 = 3 * (t * t) * invT;
float b3 = t * t * t;
return (b1 * y1) + (b2 * y2) + b3;
}
/**
* Given a fraction of time along the spline (which we can interpret as the
* length along a spline), return the interpolated value of the spline. We
* first calculate the t value for the length (by doing a lookup in our
* array of previousloy calculated values and then linearly interpolating
* between the nearest values) and then calculate the Y value for this t.
*
* @param lengthFraction
* Fraction of time in a given time interval.
* @return interpolated fraction between 0 and 1
*/
public float map(float lengthFraction) {
// REMIND: speed this up with binary search
float interpolatedT = 1.0f;
float prevT = 0.0f;
float prevLength = 0.0f;
for (int i = 0; i < lengths.size(); ++i) {
LengthItem lengthItem = (LengthItem) lengths.get(i);
float fraction = lengthItem.getFraction();
float t = lengthItem.getT();
if (lengthFraction <= fraction) {
// answer lies between last item and this one
float proportion = (lengthFraction - prevLength)
/ (fraction - prevLength);
interpolatedT = prevT + proportion * (t - prevT);
return getY(interpolatedT);
}
prevLength = fraction;
prevT = t;
}
return getY(interpolatedT);
}
}
/**
* Struct used to store information about length values. Specifically, each item
* stores the "length" (which can be thought of as the time elapsed along the
* spline path), the "t" value at this length (used to calculate the (x,y) point
* along the spline), and the "fraction" which is equal to the length divided by
* the total absolute length of the spline. After we calculate all LengthItems
* for a give spline, we have a list of entries which can return the t values
* for fractional lengths from 0 to 1.
*/
class LengthItem {
float length;
float t;
float fraction;
LengthItem(float length, float t, float fraction) {
this.length = length;
this.t = t;
this.fraction = fraction;
}
LengthItem(float length, float t) {
this.length = length;
this.t = t;
}
public float getLength() {
return length;
}
public float getT() {
return t;
}
public float getFraction() {
return fraction;
}
void setFraction(float totalLength) {
fraction = length / totalLength;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.ease;
public interface TimelineEase {
public float map(float durationFraction);
}
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.interpolator;
import java.util.*;
@SuppressWarnings("rawtypes")
public class CorePropertyInterpolators implements PropertyInterpolatorSource {
private Set<PropertyInterpolator> interpolators;
public CorePropertyInterpolators() {
this.interpolators = new HashSet<PropertyInterpolator>();
this.interpolators.add(new IntegerPropertyInterpolator());
this.interpolators.add(new FloatPropertyInterpolator());
this.interpolators.add(new DoublePropertyInterpolator());
this.interpolators.add(new LongPropertyInterpolator());
}
@Override
public Set<PropertyInterpolator> getPropertyInterpolators() {
return Collections.unmodifiableSet(this.interpolators);
}
private static class FloatPropertyInterpolator implements
PropertyInterpolator<Float> {
@Override
public Class getBasePropertyClass() {
return Float.class;
}
@Override
public Float interpolate(Float from, Float to, float timelinePosition) {
return from + (to - from) * timelinePosition;
}
}
private static class DoublePropertyInterpolator implements
PropertyInterpolator<Double> {
@Override
public Class getBasePropertyClass() {
return Double.class;
}
@Override
public Double interpolate(Double from, Double to, float timelinePosition) {
return from + (to - from) * timelinePosition;
}
}
private static class IntegerPropertyInterpolator implements
PropertyInterpolator<Integer> {
@Override
public Class getBasePropertyClass() {
return Integer.class;
}
@Override
public Integer interpolate(Integer from, Integer to,
float timelinePosition) {
return (int) (from + (to - from) * timelinePosition);
}
}
private static class LongPropertyInterpolator implements
PropertyInterpolator<Long> {
@Override
public Class getBasePropertyClass() {
return Long.class;
}
@Override
public Long interpolate(Long from, Long to, float timelinePosition) {
return (long) (from + (to - from) * timelinePosition);
}
}
}
@@ -0,0 +1,226 @@
/**
* Copyright (c) 2006, 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.
*/
package org.pushingpixels.trident.interpolator;
import org.pushingpixels.trident.ease.TimelineEase;
/**
*
* KeyFrames holds information about the times at which values are sampled
* (KeyTimes) and the values at those times (KeyValues). It also holds
* information about how to interpolate between these values for times that lie
* between the sampling points.
*
* @author Chet
*/
@SuppressWarnings("rawtypes")
public class KeyFrames<T> {
private KeyValues<T> keyValues;
private KeyTimes keyTimes;
private KeyInterpolators interpolators;
/**
* Simplest variation; determine keyTimes based on even division of 0-1
* range based on number of keyValues. This constructor assumes LINEAR
* interpolation.
*
* @param keyValues
* values that will be assumed at each time in keyTimes
*/
public KeyFrames(KeyValues<T> keyValues) {
init(keyValues, null, (TimelineEase) null);
}
/**
* This variant takes both keyValues (values at each point in time) and
* keyTimes (times at which values are sampled).
*
* @param keyValues
* values that the animation will assume at each of the
* corresponding times in keyTimes
* @param keyTimes
* times at which the animation will assume the corresponding
* values in keyValues
* @throws IllegalArgumentException
* keyTimes and keySizes must have the same number of elements
* since these structures are meant to have corresponding
* entries; an exception is thrown otherwise.
*/
public KeyFrames(KeyValues<T> keyValues, KeyTimes keyTimes) {
init(keyValues, keyTimes, (TimelineEase) null);
}
/**
* Full constructor: caller provides an instance of all key* structures
* which will be used to calculate between all times in the keyTimes list. A
* null interpolator parameter is equivalent to calling
* {@link KeyFrames#KeyFrames(KeyValues, KeyTimes)}.
*
* @param keyValues
* values that the animation will assume at each of the
* corresponding times in keyTimes
* @param keyTimes
* times at which the animation will assume the corresponding
* values in keyValues
* @param interpolators
* collection of Interpolators that control the calculation of
* values in each of the intervals defined by keyFrames. If this
* value is null, a {@link LinearInterpolator} will be used for
* all intervals. If there is only one interpolator, that
* interpolator will be used for all intervals. Otherwise, there
* must be a number of interpolators equal to the number of
* intervals (which is one less than the number of keyTimes).
* @throws IllegalArgumentException
* keyTimes and keyValues must have the same number of elements
* since these structures are meant to have corresponding
* entries; an exception is thrown otherwise.
* @throws IllegalArgumentException
* The number of interpolators must either be zero
* (interpolators == null), one, or one less than the size of
* keyTimes.
*/
public KeyFrames(KeyValues<T> keyValues, KeyTimes keyTimes,
TimelineEase... interpolators) {
init(keyValues, keyTimes, interpolators);
}
/**
* Utility constructor that assumes even division of times according to size
* of keyValues and interpolation according to interpolators parameter.
*
* @param keyValues
* values that the animation will assume at each of the
* corresponding times in keyTimes
* @param interpolators
* collection of Interpolators that control the calculation of
* values in each of the intervals defined by keyFrames. If this
* value is null, a {@link LinearInterpolator} will be used for
* all intervals. If there is only one interpolator, that
* interpolator will be used for all intervals. Otherwise, there
* must be a number of interpolators equal to the number of
* intervals (which is one less than the number of keyTimes).
* @throws IllegalArgumentException
* The number of interpolators must either be zero
* (interpolators == null), one, or one less than the size of
* keyTimes.
*/
public KeyFrames(KeyValues<T> keyValues, TimelineEase... interpolators) {
init(keyValues, null, interpolators);
}
/**
* Utility function called by constructors to perform common initialization
* chores
*/
private void init(KeyValues<T> keyValues, KeyTimes keyTimes,
TimelineEase... interpolators) {
int numFrames = keyValues.getSize();
// If keyTimes null, create our own
if (keyTimes == null) {
float keyTimesArray[] = new float[numFrames];
float timeVal = 0.0f;
keyTimesArray[0] = timeVal;
for (int i = 1; i < (numFrames - 1); ++i) {
timeVal += (1.0f / (numFrames - 1));
keyTimesArray[i] = timeVal;
}
keyTimesArray[numFrames - 1] = 1.0f;
this.keyTimes = new KeyTimes(keyTimesArray);
} else {
this.keyTimes = keyTimes;
}
this.keyValues = keyValues;
if (numFrames != this.keyTimes.getSize()) {
throw new IllegalArgumentException("keyValues and keyTimes"
+ " must be of equal size");
}
if (interpolators != null && (interpolators.length != (numFrames - 1))
&& (interpolators.length != 1)) {
throw new IllegalArgumentException(
"interpolators must be "
+ "either null (implying interpolation for all intervals), "
+ "a single interpolator (which will be used for all "
+ "intervals), or a number of interpolators equal to "
+ "one less than the number of times.");
}
this.interpolators = new KeyInterpolators(numFrames - 1, interpolators);
}
public Class getType() {
return keyValues.getType();
}
KeyValues getKeyValues() {
return keyValues;
}
KeyTimes getKeyTimes() {
return keyTimes;
}
/**
* Returns time interval that contains this time fraction
*/
public int getInterval(float fraction) {
return keyTimes.getInterval(fraction);
}
/**
* Returns a value for the given fraction elapsed of the animation cycle.
* Given the fraction, this method will determine what interval the fraction
* lies within, how much of that interval has elapsed, what the boundary
* values are (from KeyValues), what the interpolated fraction is (from the
* Interpolator for the interval), and what the final interpolated
* intermediate value is (using the appropriate Evaluator). This method will
* call into the Interpolator for the time interval to get the interpolated
* method. To ensure that future operations succeed, the value received from
* the interpolation will be clamped to the interval [0,1].
*/
public Object getValue(float fraction) {
// First, figure out the real fraction to use, given the
// interpolation type and keyTimes
int interval = getInterval(fraction);
float t0 = keyTimes.getTime(interval);
float t1 = keyTimes.getTime(interval + 1);
float t = (fraction - t0) / (t1 - t0);
float interpolatedT = interpolators.interpolate(interval, t);
// clamp to avoid problems with buggy Interpolators
if (interpolatedT < 0f) {
interpolatedT = 0f;
} else if (interpolatedT > 1f) {
interpolatedT = 1f;
}
return keyValues.getValue(interval, (interval + 1), interpolatedT);
}
}
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2006, 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.
*/
package org.pushingpixels.trident.interpolator;
import java.util.ArrayList;
import org.pushingpixels.trident.ease.Linear;
import org.pushingpixels.trident.ease.TimelineEase;
/**
*
* @author Chet
*/
class KeyInterpolators {
private ArrayList<TimelineEase> interpolators = new ArrayList<TimelineEase>();
/**
* Creates a new instance of KeyInterpolators
*/
KeyInterpolators(int numIntervals, TimelineEase... interpolators) {
if (interpolators == null || interpolators[0] == null) {
for (int i = 0; i < numIntervals; ++i) {
this.interpolators.add(new Linear());
}
} else if (interpolators.length < numIntervals) {
for (int i = 0; i < numIntervals; ++i) {
this.interpolators.add(interpolators[0]);
}
} else {
for (int i = 0; i < numIntervals; ++i) {
this.interpolators.add(interpolators[i]);
}
}
}
float interpolate(int interval, float fraction) {
return interpolators.get(interval).map(fraction);
}
}
@@ -0,0 +1,106 @@
/**
* Copyright (c) 2005-2006, 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.
*/
package org.pushingpixels.trident.interpolator;
import java.util.ArrayList;
/**
* Stores a list of times from 0 to 1 (the elapsed fraction of an animation
* cycle) that are used in calculating interpolated
* values for PropertySetter given a matching set of KeyValues and
* Interpolators for those time intervals. In the simplest case, a
* KeyFrame will consist of just two times in KeyTimes: 0 and 1.
*
* @author Chet
*/
@SuppressWarnings("rawtypes")
public class KeyTimes {
private ArrayList<Float> times = new ArrayList<Float>();
/**
* Creates a new instance of KeyTimes. Times should be in increasing
* order and should all be in the range [0,1], with the first value
* being zero and the last being 1
* @throws IllegalArgumentException Time values must be ordered in
* increasing value, the first value must be 0 and the last value
* must be 1
*/
public KeyTimes(float... times) {
if (times[0] != 0) {
throw new IllegalArgumentException("First time value must" +
" be zero");
}
if (times[times.length - 1] != 1.0f) {
throw new IllegalArgumentException("Last time value must" +
" be one");
}
float prevTime = 0;
for (float time : times) {
if (time < prevTime) {
throw new IllegalArgumentException("Time values must be" +
" in increasing order");
}
this.times.add(time);
prevTime = time;
}
}
ArrayList getTimes() {
return times;
}
int getSize() {
return times.size();
}
/**
* Returns time interval that contains this time fraction
*/
int getInterval(float fraction) {
int prevIndex = 0;
for (int i = 1; i < times.size(); ++i) {
float time = times.get(i);
if (time >= fraction) {
// inclusive of start time at next interval. So fraction==1
// will return the final interval (times.size() - 1)
return prevIndex;
}
prevIndex = i;
}
return prevIndex;
}
float getTime(int index) {
return times.get(index);
}
}
@@ -0,0 +1,185 @@
/**
* Copyright (c) 2006, 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.
*/
package org.pushingpixels.trident.interpolator;
import java.util.*;
import org.pushingpixels.trident.TridentConfig;
/**
* Stores a list of values that correspond to the times in a {@link KeyTimes}
* object. These structures are then used to create a {@link KeyFrames} object,
* which is then used to create a {@link PropertySetter} for the purposes of
* modifying an object's property over time.
* <p>
* At each of the times in {@link KeyTimes}, the property will take on the
* corresponding value in the KeyValues object. Between these times, the
* property will take on a value based on the interpolation information stored
* in the KeyFrames object and the {@link Evaluator} for the type of the values
* in KeyValues.
* <p>
* This class has built-in support for various known types, as defined in
* {@link Evaluator}.
* <p>
* For a simple example using KeyValues to create a KeyFrames and PropertySetter
* object, see the class header comments in {@link PropertySetter}.
*
*
* @author Chet
*/
@SuppressWarnings({"rawtypes","unchecked"})
public class KeyValues<T> {
private final List<T> values = new ArrayList<T>();
private final PropertyInterpolator<T> evaluator;
private final Class<?> type;
private T startValue;
/**
* Constructs a KeyValues object from one or more values. The internal
* Evaluator is automatically determined by the type of the parameters.
*
* @param params
* the values to interpolate between. If there is only one
* parameter, this is assumed to be a "to" animation where the
* first value is dynamically determined at runtime when the
* animation is started.
* @throws IllegalArgumentException
* if an {@link Evaluator} cannot be found that can interpolate
* between the value types supplied
*/
public static <T> KeyValues<T> create(T... params) {
return new KeyValues(params);
}
/**
* Constructs a KeyValues object from a Evaluator and one or more values.
*
* @param params
* the values to interpolate between. If there is only one
* parameter, this is assumed to be a "to" animation where the
* first value is dynamically determined at runtime when the
* animation is started.
* @throws IllegalArgumentException
* if params does not have at least one value.
*/
public static <T> KeyValues<T> create(PropertyInterpolator evaluator,
T... params) {
return new KeyValues(evaluator, params);
}
/**
* Private constructor, called by factory method
*/
private KeyValues(T... params) {
this(TridentConfig.getInstance().getPropertyInterpolator(params), params);
}
/**
* Private constructor, called by factory method
*/
private KeyValues(PropertyInterpolator evaluator, T... params) {
if (params == null) {
throw new IllegalArgumentException("params array cannot be null");
} else if (params.length == 0) {
throw new IllegalArgumentException(
"params array must have at least one element");
}
if (params.length == 1) {
// this is a "to" animation; set first element to null
values.add(null);
}
Collections.addAll(values, params);
this.type = params.getClass().getComponentType();
this.evaluator = evaluator;
}
/**
* Returns the number of values stored in this object.
*
* @return the number of values stored in this object
*/
int getSize() {
return values.size();
}
/**
* Returns the data type of the values stored in this object.
*
* @return a Class value representing the type of values stored in this
* object
*/
Class<?> getType() {
return this.type;
}
/**
* Called at start of animation; sets starting value in simple "to"
* animations.
*/
void setStartValue(T startValue) {
if (isToAnimation()) {
this.startValue = startValue;
}
}
/**
* Utility method for determining whether this is a "to" animation (true if
* the first value is null).
*/
boolean isToAnimation() {
return (values.get(0) == null);
}
/**
* Returns value calculated from the value at the lower index, the value at
* the upper index, the fraction elapsed between these endpoints, and the
* evaluator set up by this object at construction time.
*/
T getValue(int i0, int i1, float fraction) {
T value;
T lowerValue = values.get(i0);
if (lowerValue == null) {
// "to" animation
lowerValue = startValue;
}
if (i0 == i1) {
// trivial case
value = lowerValue;
} else {
T v0 = lowerValue;
T v1 = values.get(i1);
value = evaluator.interpolate(v0, v1, fraction);
}
return value;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.interpolator;
@SuppressWarnings({"rawtypes"})
public interface PropertyInterpolator<T> {
public Class getBasePropertyClass();
public T interpolate(T from, T to, float timelinePosition);
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.interpolator;
import java.util.Set;
@SuppressWarnings("rawtypes")
public interface PropertyInterpolatorSource {
public Set<PropertyInterpolator> getPropertyInterpolators();
}
@@ -0,0 +1,140 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.swt;
import java.util.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.Display;
import org.pushingpixels.trident.interpolator.PropertyInterpolator;
import org.pushingpixels.trident.interpolator.PropertyInterpolatorSource;
/**
* Built-in interpolators for SWT classes.
*
* @author Kirill Grouchnikov
*/
@SuppressWarnings("rawtypes")
public class SWTPropertyInterpolators implements PropertyInterpolatorSource {
private Set<PropertyInterpolator> interpolators;
public SWTPropertyInterpolators() {
this.interpolators = new HashSet<PropertyInterpolator>();
this.interpolators.add(new ColorInterpolator());
this.interpolators.add(new PointInterpolator());
this.interpolators.add(new RectangleInterpolator());
}
@Override
public Set<PropertyInterpolator> getPropertyInterpolators() {
return Collections.unmodifiableSet(this.interpolators);
}
static class ColorInterpolator implements PropertyInterpolator<Color> {
@Override
public Class getBasePropertyClass() {
return Color.class;
}
@Override
public Color interpolate(Color from, Color to, float timelinePosition) {
return getInterpolatedColor(from, to, 1.0f - timelinePosition);
}
RGB getInterpolatedRGB(Color color1, Color color2, float color1Likeness) {
if ((color1Likeness < 0.0) || (color1Likeness > 1.0))
throw new IllegalArgumentException(
"Color likeness should be in 0.0-1.0 range [is "
+ color1Likeness + "]");
int lr = color1.getRed();
int lg = color1.getGreen();
int lb = color1.getBlue();
int dr = color2.getRed();
int dg = color2.getGreen();
int db = color2.getBlue();
// using some interpolation values (such as 0.29 from issue 401)
// results in an incorrect final value without Math.round.
int r = (lr == dr) ? lr : (int) Math.round(color1Likeness * lr
+ (1.0 - color1Likeness) * dr);
int g = (lg == dg) ? lg : (int) Math.round(color1Likeness * lg
+ (1.0 - color1Likeness) * dg);
int b = (lb == db) ? lb : (int) Math.round(color1Likeness * lb
+ (1.0 - color1Likeness) * db);
return new RGB(r, g, b);
}
Color getInterpolatedColor(Color color1, Color color2,
float color1Likeness) {
if (color1.equals(color2))
return color1;
if (color1Likeness == 1.0)
return color1;
if (color1Likeness == 0.0)
return color2;
return new Color(Display.getDefault(), getInterpolatedRGB(color1,
color2, color1Likeness));
}
}
static class PointInterpolator implements PropertyInterpolator<Point> {
public Point interpolate(Point from, Point to, float timelinePosition) {
int x = from.x + (int) (timelinePosition * (to.x - from.x));
int y = from.y + (int) (timelinePosition * (to.y - from.y));
return new Point(x, y);
}
@Override
public Class getBasePropertyClass() {
return Point.class;
}
}
static class RectangleInterpolator implements
PropertyInterpolator<Rectangle> {
public Rectangle interpolate(Rectangle from, Rectangle to,
float timelinePosition) {
int x = from.x + (int) (timelinePosition * (to.x - from.x));
int y = from.y + (int) (timelinePosition * (to.y - from.y));
int w = from.width
+ (int) (timelinePosition * (to.width - from.width));
int h = from.height
+ (int) (timelinePosition * (to.height - from.height));
return new Rectangle(x, y, w, h);
}
@Override
public Class getBasePropertyClass() {
return Rectangle.class;
}
}
}
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.swt;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.pushingpixels.trident.Timeline.TimelineState;
import org.pushingpixels.trident.callback.RunOnUIThread;
import org.pushingpixels.trident.callback.TimelineCallback;
@RunOnUIThread
public class SWTRepaintCallback implements TimelineCallback {
private Control control;
private Rectangle rect;
private AtomicBoolean repaintGuard;
public SWTRepaintCallback(Control control) {
this(control, null);
}
public SWTRepaintCallback(Control control, Rectangle rect) {
if (control == null) {
throw new NullPointerException("Control must be non-null");
}
this.control = control;
if (rect != null) {
this.rect = new Rectangle(rect.x, rect.y, rect.width, rect.height);
}
}
public synchronized void setAutoRepaintMode(boolean autoRepaintMode) {
if (autoRepaintMode) {
this.repaintGuard = null;
} else {
this.repaintGuard = new AtomicBoolean(false);
}
}
public synchronized void forceRepaintOnNextPulse() {
if (this.repaintGuard == null) {
throw new IllegalArgumentException(
"This method cannot be called on auto-repaint callback");
}
this.repaintGuard.set(true);
}
public synchronized void setRepaintRectangle(Rectangle rect) {
if (rect == null) {
this.rect = null;
} else {
this.rect = new Rectangle(rect.x, rect.y, rect.width, rect.height);
}
}
@Override
public void onTimelinePulse(float durationFraction, float timelinePosition) {
redrawAsNecessary();
}
@Override
public void onTimelineStateChanged(TimelineState oldState,
TimelineState newState, float durationFraction,
float timelinePosition) {
redrawAsNecessary();
}
private void redrawAsNecessary() {
if (this.control.isDisposed())
return;
if (this.repaintGuard != null) {
if (!this.repaintGuard.compareAndSet(true, false)) {
// no need to repaint
return;
}
}
if (this.rect == null)
this.control.redraw();
else
this.control.redraw(this.rect.x, this.rect.y, this.rect.width,
this.rect.height, true);
}
}
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.swt;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.pushingpixels.trident.Timeline;
public class SWTRepaintTimeline extends Timeline {
private SWTRepaintCallback repaintCallback;
public SWTRepaintTimeline(Control mainTimelineComp) {
this(mainTimelineComp, null);
}
public SWTRepaintTimeline(Control mainTimelineComp, Rectangle toRepaint) {
super(mainTimelineComp);
this.repaintCallback = new SWTRepaintCallback(mainTimelineComp,
toRepaint);
this.addCallback(this.repaintCallback);
}
public void forceRepaintOnNextPulse() {
this.repaintCallback.forceRepaintOnNextPulse();
}
public void setAutoRepaintMode(boolean autoRepaintMode) {
this.repaintCallback.setAutoRepaintMode(autoRepaintMode);
}
public void setRepaintRectangle(Rectangle rect) {
this.repaintCallback.setRepaintRectangle(rect);
}
@Override
public void play() {
throw new UnsupportedOperationException(
"Only infinite looping is supported");
}
@Override
public void playReverse() {
throw new UnsupportedOperationException(
"Only infinite looping is supported");
}
@Override
public void replay() {
throw new UnsupportedOperationException(
"Only infinite looping is supported");
}
@Override
public void replayReverse() {
throw new UnsupportedOperationException(
"Only infinite looping is supported");
}
@Override
public void playLoop(int loopCount, RepeatBehavior repeatBehavior) {
if (loopCount >= 0) {
throw new UnsupportedOperationException(
"Only infinite looping is supported");
}
super.playLoop(loopCount, repeatBehavior);
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Trident Kirill Grouchnikov 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.
*/
package org.pushingpixels.trident.swt;
import org.eclipse.swt.widgets.Widget;
import org.pushingpixels.trident.UIToolkitHandler;
public class SWTToolkitHandler implements UIToolkitHandler {
@Override
public boolean isHandlerFor(Object mainTimelineObject) {
return (mainTimelineObject instanceof Widget);
}
@Override
public boolean isInReadyState(Object mainTimelineObject) {
return !((Widget) mainTimelineObject).isDisposed();
}
@Override
public void runOnUIThread(Object mainTimelineObject, Runnable runnable) {
((Widget) mainTimelineObject).getDisplay().asyncExec(runnable);
}
}