Merge remote-tracking branch 'goog/stage-aosp-master' into HEAD
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..135327b
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, java/api/src)
+
+LOCAL_MODULE := universal-tween-engine
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bcbe519
--- /dev/null
+++ b/README.md
@@ -0,0 +1,119 @@
+![](http://www.aurelienribon.com/blog/wp-content/uploads/2012/05/tween-engine-big-logo.jpg)
+
+## Check out the demo!
+
+  * [Android application](https://play.google.com/store/apps/details?id=aurelienribon.tweenengine.demo)
+  * [Desktop application](http://code.google.com/p/java-universal-tween-engine/downloads/detail?name=tween-engine-demo-6.3.0.zip)
+  * [WebGL html5 page](http://www.aurelienribon.com/universal-tween-engine/gwt/demo.html) (requires a WebGL enabled browser)
+
+## Introduction
+
+The Universal Tween Engine enables the interpolation of every attribute from any object in any Java project (being Swing, SWT, OpenGL or even Console-based). Implement the TweenAccessor interface, register it to the engine, and animate anything you want!
+
+In one line, send your objects to another position (here x=20 and y=30), with a smooth elastic transition, during 1 second).
+
+```java
+// Arguments are (1) the target, (2) the type of interpolation, 
+// and (3) the duration in seconds. Additional methods specify  
+// the target values, and the easing function. 
+
+Tween.to(mySprite, Type.POSITION_XY, 1.0f).target(20, 30).ease(Elastic.INOUT);
+
+// Possibilities are:
+
+Tween.to(...); // interpolates from the current values to the targets
+Tween.from(...); // interpolates from the given values to the current ones
+Tween.set(...); // apply the target values without animation (useful with a delay)
+Tween.call(...); // calls a method (useful with a delay)
+
+// Current options are:
+
+myTween.delay(0.5f);
+myTween.repeat(2, 0.5f);
+myTween.repeatYoyo(2, 0.5f);
+myTween.pause();
+myTween.resume();
+myTween.setCallback(callback);
+myTween.setCallbackTriggers(flags);
+myTween.setUserData(obj);
+
+// You can of course chain everything:
+
+Tween.to(...).delay(1.0f).repeat(2, 0.5f).start(myManager);
+
+// Moreover, slow-motion, fast-motion and reverse play is easy,
+// you just need to change the speed of the update:
+
+myManager.update(delta * speed);
+```
+
+Create some powerful animation sequences!
+
+```java
+Timeline.createSequence()
+    // First, set all objects to their initial positions
+    .push(Tween.set(...))
+    .push(Tween.set(...))
+    .push(Tween.set(...))
+
+    // Wait 1s
+    .pushPause(1.0f)
+
+    // Move the objects around, one after the other
+    .push(Tween.to(...))
+    .push(Tween.to(...))
+    .push(Tween.to(...))
+
+    // Then, move the objects around at the same time
+    .beginParallel()
+        .push(Tween.to(...))
+        .push(Tween.to(...))
+        .push(Tween.to(...))
+    .end()
+
+    // And repeat the whole sequence 2 times
+    // with a 0.5s pause between each iteration
+    .repeatYoyo(2, 0.5f)
+
+    // Let's go!
+    .start(myManager);
+```
+
+You can also quickly create timers:
+
+```java
+Tween.call(myCallback).delay(3000).start(myManager);
+```
+
+Main features are:
+
+  * Supports every interpolation function defined by [Robert Penner](http://www.robertpenner.com/easing/).
+  * Can be used with any object. You just have to implement the TweenAccessor interface when you want interpolation capacities.
+  * Every attribute can be interpolated. The only requirement is that what you want to interpolate can be represented as a float number.
+  * One line is sufficient to create and start a simple interpolation.
+  * Delays can be specified, to trigger the interpolation only after some time.
+  * Many callbacks can be specified (when tweens complete, start, end, etc.).
+  * Tweens and Timelines are pooled by default. If enabled, there won't be any object allocation during runtime! You can safely use it in Android game development without fearing the garbage collector.
+  * Tweens can be sequenced when used in Timelines.
+  * Tweens can act on more than one value at a time, so a single tween can change the whole position (X and Y) of a sprite for instance !
+  * Tweens and Timelines can be repeated, with a yoyo style option.
+  * Simple timers can be built with Tween.call().
+  * **Source code extensively documented!**
+
+## Get started and documentation index
+
+Detailed documentation with code snippets and examples is available for the following topics:
+  * Get started --- A step-by-step example to get you started, with code
+
+  * The TweenAccessor interface --- Know how to implement it
+  * Tweens and options --- See what are the possibilities
+  * Timelines and options --- Learn how to build powerful sequences
+  * Animating Android apps --- See how to use the engine with Android UIs
+
+## Where can I ask for help?
+
+There is a dedicated forum for you:
+http://www.aurelienribon.com/forum/viewforum.php?f=5
+
+Also, the following link will guide you to a discussion thread that started it all:  
+http://www.badlogicgames.com/forum/viewtopic.php?f=17&t=494
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine.gwt.xml b/java/api/src/aurelienribon/tweenengine.gwt.xml
new file mode 100644
index 0000000..3b4e623
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine.gwt.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<module rename-to="aurelienribon.tweenengine">
+	<source path="tweenengine" />
+</module>
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/BaseTween.java b/java/api/src/aurelienribon/tweenengine/BaseTween.java
new file mode 100644
index 0000000..d8cf0e6
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/BaseTween.java
@@ -0,0 +1,543 @@
+package aurelienribon.tweenengine;
+
+/**
+ * BaseTween is the base class of Tween and Timeline. It defines the
+ * iteration engine used to play animations for any number of times, and in
+ * any direction, at any speed.
+ * <p/>
+ *
+ * It is responsible for calling the different callbacks at the right moments,
+ * and for making sure that every callbacks are triggered, even if the update
+ * engine gets a big delta time at once.
+ *
+ * @see Tween
+ * @see Timeline
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class BaseTween<T> {
+	// General
+	private int step;
+	private int repeatCnt;
+	private boolean isIterationStep;
+	private boolean isYoyo;
+
+	// Timings
+	protected float delay;
+	protected float duration;
+	private float repeatDelay;
+	private float currentTime;
+	private float deltaTime;
+	private boolean isStarted; // true when the object is started
+	private boolean isInitialized; // true after the delay
+	private boolean isFinished; // true when all repetitions are done
+	private boolean isKilled; // true if kill() was called
+	private boolean isPaused; // true if pause() was called
+
+	// Misc
+	private TweenCallback callback;
+	private int callbackTriggers;
+	private Object userData;
+
+	// Package access
+	boolean isAutoRemoveEnabled;
+	boolean isAutoStartEnabled;
+
+	// -------------------------------------------------------------------------
+
+	protected void reset() {
+		step = -2;
+		repeatCnt = 0;
+		isIterationStep = isYoyo = false;
+
+		delay = duration = repeatDelay = currentTime = deltaTime = 0;
+		isStarted = isInitialized = isFinished = isKilled = isPaused = false;
+
+		callback = null;
+		callbackTriggers = TweenCallback.COMPLETE;
+		userData = null;
+
+		isAutoRemoveEnabled = isAutoStartEnabled = true;
+	}
+
+	// -------------------------------------------------------------------------
+	// Public API
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Builds and validates the object. Only needed if you want to finalize a
+	 * tween or timeline without starting it, since a call to ".start()" also
+	 * calls this method.
+	 *
+	 * @return The current object, for chaining instructions.
+	 */
+	public T build() {
+		return (T) this;
+	}
+
+	/**
+	 * Starts or restarts the object unmanaged. You will need to take care of
+	 * its life-cycle. If you want the tween to be managed for you, use a
+	 * {@link TweenManager}.
+	 *
+	 * @return The current object, for chaining instructions.
+	 */
+	public T start() {
+		build();
+		currentTime = 0;
+		isStarted = true;
+		return (T) this;
+	}
+
+	/**
+	 * Convenience method to add an object to a manager. Its life-cycle will be
+	 * handled for you. Relax and enjoy the animation.
+	 *
+	 * @return The current object, for chaining instructions.
+	 */
+	public T start(TweenManager manager) {
+		manager.add(this);
+		return (T) this;
+	}
+
+	/**
+	 * Adds a delay to the tween or timeline.
+	 *
+	 * @param delay A duration.
+	 * @return The current object, for chaining instructions.
+	 */
+	public T delay(float delay) {
+		this.delay += delay;
+		return (T) this;
+	}
+
+	/**
+	 * Kills the tween or timeline. If you are using a TweenManager, this object
+	 * will be removed automatically.
+	 */
+	public void kill() {
+		isKilled = true;
+	}
+
+	/**
+	 * Stops and resets the tween or timeline, and sends it to its pool, for
++	 * later reuse. Note that if you use a {@link TweenManager}, this method
++	 * is automatically called once the animation is finished.
+	 */
+	public void free() {
+	}
+
+	/**
+	 * Pauses the tween or timeline. Further update calls won't have any effect.
+	 */
+	public void pause() {
+		isPaused = true;
+	}
+
+	/**
+	 * Resumes the tween or timeline. Has no effect is it was no already paused.
+	 */
+	public void resume() {
+		isPaused = false;
+	}
+
+	/**
+	 * Repeats the tween or timeline for a given number of times.
+	 * @param count The number of repetitions. For infinite repetition,
+	 * use Tween.INFINITY, or a negative number.
+	 *
+	 * @param delay A delay between each iteration.
+	 * @return The current tween or timeline, for chaining instructions.
+	 */
+	public T repeat(int count, float delay) {
+		if (isStarted) throw new RuntimeException("You can't change the repetitions of a tween or timeline once it is started");
+		repeatCnt = count;
+		repeatDelay = delay >= 0 ? delay : 0;
+		isYoyo = false;
+		return (T) this;
+	}
+
+	/**
+	 * Repeats the tween or timeline for a given number of times.
+	 * Every two iterations, it will be played backwards.
+	 *
+	 * @param count The number of repetitions. For infinite repetition,
+	 * use Tween.INFINITY, or '-1'.
+	 * @param delay A delay before each repetition.
+	 * @return The current tween or timeline, for chaining instructions.
+	 */
+	public T repeatYoyo(int count, float delay) {
+		if (isStarted) throw new RuntimeException("You can't change the repetitions of a tween or timeline once it is started");
+		repeatCnt = count;
+		repeatDelay = delay >= 0 ? delay : 0;
+		isYoyo = true;
+		return (T) this;
+	}
+
+	/**
+	 * Sets the callback. By default, it will be fired at the completion of the
+	 * tween or timeline (event COMPLETE). If you want to change this behavior
+	 * and add more triggers, use the {@link setCallbackTriggers()} method.
+	 *
+	 * @see TweenCallback
+	 */
+	public T setCallback(TweenCallback callback) {
+		this.callback = callback;
+		return (T) this;
+	}
+
+	/**
+	 * Changes the triggers of the callback. The available triggers, listed as
+	 * members of the {@link TweenCallback} interface, are:
+	 * <p/>
+	 *
+	 * <b>BEGIN</b>: right after the delay (if any)<br/>
+	 * <b>START</b>: at each iteration beginning<br/>
+	 * <b>END</b>: at each iteration ending, before the repeat delay<br/>
+	 * <b>COMPLETE</b>: at last END event<br/>
+	 * <b>BACK_BEGIN</b>: at the beginning of the first backward iteration<br/>
+	 * <b>BACK_START</b>: at each backward iteration beginning, after the repeat delay<br/>
+	 * <b>BACK_END</b>: at each backward iteration ending<br/>
+	 * <b>BACK_COMPLETE</b>: at last BACK_END event
+	 * <p/>
+	 *
+	 * <pre> {@code
+	 * forward :      BEGIN                                   COMPLETE
+	 * forward :      START    END      START    END      START    END
+	 * |--------------[XXXXXXXXXX]------[XXXXXXXXXX]------[XXXXXXXXXX]
+	 * backward:      bEND  bSTART      bEND  bSTART      bEND  bSTART
+	 * backward:      bCOMPLETE                                 bBEGIN
+	 * }</pre>
+	 *
+	 * @param flags one or more triggers, separated by the '|' operator.
+	 * @see TweenCallback
+	 */
+	public T setCallbackTriggers(int flags) {
+		this.callbackTriggers = flags;
+		return (T) this;
+	}
+
+	/**
+	 * Attaches an object to this tween or timeline. It can be useful in order
+	 * to retrieve some data from a TweenCallback.
+	 *
+	 * @param data Any kind of object.
+	 * @return The current tween or timeline, for chaining instructions.
+	 */
+	public T setUserData(Object data) {
+		userData = data;
+		return (T) this;
+	}
+
+	// -------------------------------------------------------------------------
+	// Getters
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Gets the delay of the tween or timeline. Nothing will happen before
+	 * this delay.
+	 */
+	public float getDelay() {
+		return delay;
+	}
+
+	/**
+	 * Gets the duration of a single iteration.
+	 */
+	public float getDuration() {
+		return duration;
+	}
+
+	/**
+	 * Gets the number of iterations that will be played.
+	 */
+	public int getRepeatCount() {
+		return repeatCnt;
+	}
+
+	/**
+	 * Gets the delay occuring between two iterations.
+	 */
+	public float getRepeatDelay() {
+		return repeatDelay;
+	}
+
+	/**
+	 * Returns the complete duration, including initial delay and repetitions.
+	 * The formula is as follows:
+	 * <pre>
+	 * fullDuration = delay + duration + (repeatDelay + duration) * repeatCnt
+	 * </pre>
+	 */
+	public float getFullDuration() {
+		if (repeatCnt < 0) return -1;
+		return delay + duration + (repeatDelay + duration) * repeatCnt;
+	}
+
+	/**
+	 * Gets the attached data, or null if none.
+	 */
+	public Object getUserData() {
+		return userData;
+	}
+
+	/**
+	 * Gets the id of the current step. Values are as follows:<br/>
+	 * <ul>
+	 * <li>even numbers mean that an iteration is playing,<br/>
+	 * <li>odd numbers mean that we are between two iterations,<br/>
+	 * <li>-2 means that the initial delay has not ended,<br/>
+	 * <li>-1 means that we are before the first iteration,<br/>
+	 * <li>repeatCount*2 + 1 means that we are after the last iteration
+	 */
+	public int getStep() {
+		return step;
+	}
+
+	/**
+	 * Gets the local time.
+	 */
+	public float getCurrentTime() {
+		return currentTime;
+	}
+
+	/**
+	 * Returns true if the tween or timeline has been started.
+	 */
+	public boolean isStarted() {
+		return isStarted;
+	}
+
+	/**
+	 * Returns true if the tween or timeline has been initialized. Starting
+	 * values for tweens are stored at initialization time. This initialization
+	 * takes place right after the initial delay, if any.
+	 */
+	public boolean isInitialized() {
+		return isInitialized;
+	}
+
+	/**
+	 * Returns true if the tween is finished (i.e. if the tween has reached
+	 * its end or has been killed). If you don't use a TweenManager, you may
+	 * want to call {@link free()} to reuse the object later.
+	 */
+	public boolean isFinished() {
+		return isFinished || isKilled;
+	}
+
+	/**
+	 * Returns true if the iterations are played as yoyo. Yoyo means that
+	 * every two iterations, the animation will be played backwards.
+	 */
+	public boolean isYoyo() {
+		return isYoyo;
+	}
+
+	/**
+	 * Returns true if the tween or timeline is currently paused.
+	 */
+	public boolean isPaused() {
+		return isPaused;
+	}
+
+	// -------------------------------------------------------------------------
+	// Abstract API
+	// -------------------------------------------------------------------------
+
+	protected abstract void forceStartValues();
+	protected abstract void forceEndValues();
+
+	protected abstract boolean containsTarget(Object target);
+	protected abstract boolean containsTarget(Object target, int tweenType);
+
+	// -------------------------------------------------------------------------
+	// Protected API
+	// -------------------------------------------------------------------------
+
+	protected void initializeOverride() {
+	}
+
+	protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
+	}
+
+	protected void forceToStart() {
+		currentTime = -delay;
+		step = -1;
+		isIterationStep = false;
+		if (isReverse(0)) forceEndValues();
+		else forceStartValues();
+	}
+
+	protected void forceToEnd(float time) {
+		currentTime = time - getFullDuration();
+		step = repeatCnt*2 + 1;
+		isIterationStep = false;
+		if (isReverse(repeatCnt*2)) forceStartValues();
+		else forceEndValues();
+	}
+
+	protected void callCallback(int type) {
+		if (callback != null && (callbackTriggers & type) > 0) callback.onEvent(type, this);
+	}
+
+	protected boolean isReverse(int step) {
+		return isYoyo && Math.abs(step%4) == 2;
+	}
+
+	protected boolean isValid(int step) {
+		return (step >= 0 && step <= repeatCnt*2) || repeatCnt < 0;
+	}
+
+	protected void killTarget(Object target) {
+		if (containsTarget(target)) kill();
+	}
+
+	protected void killTarget(Object target, int tweenType) {
+		if (containsTarget(target, tweenType)) kill();
+	}
+
+	// -------------------------------------------------------------------------
+	// Update engine
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Updates the tween or timeline state. <b>You may want to use a
+	 * TweenManager to update objects for you.</b>
+	 *
+	 * Slow motion, fast motion and backward play can be easily achieved by
+	 * tweaking this delta time. Multiply it by -1 to play the animation
+	 * backward, or by 0.5 to play it twice slower than its normal speed.
+	 *
+	 * @param delta A delta time between now and the last call.
+	 */
+	public void update(float delta) {
+		if (!isStarted || isPaused || isKilled) return;
+
+		deltaTime = delta;
+
+		if (!isInitialized) {
+			initialize();
+		}
+
+		if (isInitialized) {
+			testRelaunch();
+			updateStep();
+			testCompletion();
+		}
+
+		currentTime += deltaTime;
+		deltaTime = 0;
+	}
+
+	private void initialize() {
+		if (currentTime+deltaTime >= delay) {
+			initializeOverride();
+			isInitialized = true;
+			isIterationStep = true;
+			step = 0;
+			deltaTime -= delay-currentTime;
+			currentTime = 0;
+			callCallback(TweenCallback.BEGIN);
+			callCallback(TweenCallback.START);
+		}
+	}
+
+	private void testRelaunch() {
+		if (!isIterationStep && repeatCnt >= 0 && step < 0 && currentTime+deltaTime >= 0) {
+			assert step == -1;
+			isIterationStep = true;
+			step = 0;
+			float delta = 0-currentTime;
+			deltaTime -= delta;
+			currentTime = 0;
+			callCallback(TweenCallback.BEGIN);
+			callCallback(TweenCallback.START);
+			updateOverride(step, step-1, isIterationStep, delta);
+
+		} else if (!isIterationStep && repeatCnt >= 0 && step > repeatCnt*2 && currentTime+deltaTime < 0) {
+			assert step == repeatCnt*2 + 1;
+			isIterationStep = true;
+			step = repeatCnt*2;
+			float delta = 0-currentTime;
+			deltaTime -= delta;
+			currentTime = duration;
+			callCallback(TweenCallback.BACK_BEGIN);
+			callCallback(TweenCallback.BACK_START);
+			updateOverride(step, step+1, isIterationStep, delta);
+		}
+	}
+
+	private void updateStep() {
+		while (isValid(step)) {
+			if (!isIterationStep && currentTime+deltaTime <= 0) {
+				isIterationStep = true;
+				step -= 1;
+
+				float delta = 0-currentTime;
+				deltaTime -= delta;
+				currentTime = duration;
+
+				if (isReverse(step)) forceStartValues(); else forceEndValues();
+				callCallback(TweenCallback.BACK_START);
+				updateOverride(step, step+1, isIterationStep, delta);
+
+			} else if (!isIterationStep && currentTime+deltaTime >= repeatDelay) {
+				isIterationStep = true;
+				step += 1;
+
+				float delta = repeatDelay-currentTime;
+				deltaTime -= delta;
+				currentTime = 0;
+
+				if (isReverse(step)) forceEndValues(); else forceStartValues();
+				callCallback(TweenCallback.START);
+				updateOverride(step, step-1, isIterationStep, delta);
+
+			} else if (isIterationStep && currentTime+deltaTime < 0) {
+				isIterationStep = false;
+				step -= 1;
+
+				float delta = 0-currentTime;
+				deltaTime -= delta;
+				currentTime = 0;
+
+				updateOverride(step, step+1, isIterationStep, delta);
+				callCallback(TweenCallback.BACK_END);
+
+				if (step < 0 && repeatCnt >= 0) callCallback(TweenCallback.BACK_COMPLETE);
+				else currentTime = repeatDelay;
+
+			} else if (isIterationStep && currentTime+deltaTime > duration) {
+				isIterationStep = false;
+				step += 1;
+
+				float delta = duration-currentTime;
+				deltaTime -= delta;
+				currentTime = duration;
+
+				updateOverride(step, step-1, isIterationStep, delta);
+				callCallback(TweenCallback.END);
+
+				if (step > repeatCnt*2 && repeatCnt >= 0) callCallback(TweenCallback.COMPLETE);
+				currentTime = 0;
+
+			} else if (isIterationStep) {
+				float delta = deltaTime;
+				deltaTime -= delta;
+				currentTime += delta;
+				updateOverride(step, step, isIterationStep, delta);
+				break;
+
+			} else {
+				float delta = deltaTime;
+				deltaTime -= delta;
+				currentTime += delta;
+				break;
+			}
+		}
+	}
+
+	private void testCompletion() {
+		isFinished = repeatCnt >= 0 && (step > repeatCnt*2 || step < 0);
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/Pool.java b/java/api/src/aurelienribon/tweenengine/Pool.java
new file mode 100644
index 0000000..0b7b2fc
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/Pool.java
@@ -0,0 +1,55 @@
+package aurelienribon.tweenengine;
+
+import java.util.ArrayList;
+
+/**
+ * A light pool of objects that can be resused to avoid allocation.
+ * Based on Nathan Sweet pool implementation
+ */
+abstract class Pool<T> {
+	private final ArrayList<T> objects;
+	private final Callback<T> callback;
+
+	protected abstract T create();
+
+	public Pool(int initCapacity, Callback<T> callback) {
+		this.objects = new ArrayList<T>(initCapacity);
+		this.callback = callback;
+	}
+
+	public T get() {
+		T obj = null;
+		try {
+			obj = objects.isEmpty() ? create() : objects.remove(0);
+		} catch (Exception e) {}
+		if (obj == null) obj = create();
+		if (callback != null) callback.onUnPool(obj);
+		return obj;
+	}
+
+	public void free(T obj) {
+		if (obj == null) return;
+
+		if (!objects.contains(obj)) {
+			if (callback != null) callback.onPool(obj);
+			objects.add(obj);
+		}
+	}
+
+	public void clear() {
+		objects.clear();
+	}
+
+	public int size() {
+		return objects.size();
+	}
+
+	public void ensureCapacity(int minCapacity) {
+		objects.ensureCapacity(minCapacity);
+	}
+
+	public interface Callback<T> {
+		public void onPool(T obj);
+		public void onUnPool(T obj);
+	}
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/Timeline.java b/java/api/src/aurelienribon/tweenengine/Timeline.java
new file mode 100644
index 0000000..5e1f765
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/Timeline.java
@@ -0,0 +1,363 @@
+package aurelienribon.tweenengine;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A Timeline can be used to create complex animations made of sequences and
+ * parallel sets of Tweens.
+ * <p/>
+ *
+ * The following example will create an animation sequence composed of 5 parts:
+ * <p/>
+ *
+ * 1. First, opacity and scale are set to 0 (with Tween.set() calls).<br/>
+ * 2. Then, opacity and scale are animated in parallel.<br/>
+ * 3. Then, the animation is paused for 1s.<br/>
+ * 4. Then, position is animated to x=100.<br/>
+ * 5. Then, rotation is animated to 360°.
+ * <p/>
+ *
+ * This animation will be repeated 5 times, with a 500ms delay between each
+ * iteration:
+ * <br/><br/>
+ *
+ * <pre> {@code
+ * Timeline.createSequence()
+ *     .push(Tween.set(myObject, OPACITY).target(0))
+ *     .push(Tween.set(myObject, SCALE).target(0, 0))
+ *     .beginParallel()
+ *          .push(Tween.to(myObject, OPACITY, 0.5f).target(1).ease(Quad.INOUT))
+ *          .push(Tween.to(myObject, SCALE, 0.5f).target(1, 1).ease(Quad.INOUT))
+ *     .end()
+ *     .pushPause(1.0f)
+ *     .push(Tween.to(myObject, POSITION_X, 0.5f).target(100).ease(Quad.INOUT))
+ *     .push(Tween.to(myObject, ROTATION, 0.5f).target(360).ease(Quad.INOUT))
+ *     .repeat(5, 0.5f)
+ *     .start(myManager);
+ * }</pre>
+ *
+ * @see Tween
+ * @see TweenManager
+ * @see TweenCallback
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public final class Timeline extends BaseTween<Timeline> {
+	// -------------------------------------------------------------------------
+	// Static -- pool
+	// -------------------------------------------------------------------------
+
+	private static final Pool.Callback<Timeline> poolCallback = new Pool.Callback<Timeline>() {
+		@Override public void onPool(Timeline obj) {obj.reset();}
+		@Override public void onUnPool(Timeline obj) {obj.reset();}
+	};
+
+	static final Pool<Timeline> pool = new Pool<Timeline>(10, poolCallback) {
+		@Override protected Timeline create() {return new Timeline();}
+	};
+
+	/**
+	 * Used for debug purpose. Gets the current number of empty timelines that
+	 * are waiting in the Timeline pool.
+	 */
+	public static int getPoolSize() {
+		return pool.size();
+	}
+
+	/**
+	 * Increases the minimum capacity of the pool. Capacity defaults to 10.
+	 */
+	public static void ensurePoolCapacity(int minCapacity) {
+		pool.ensureCapacity(minCapacity);
+	}
+
+	// -------------------------------------------------------------------------
+	// Static -- factories
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Creates a new timeline with a 'sequence' behavior. Its children will
+	 * be delayed so that they are triggered one after the other.
+	 */
+	public static Timeline createSequence() {
+		Timeline tl = pool.get();
+		tl.setup(Modes.SEQUENCE);
+		return tl;
+	}
+
+	/**
+	 * Creates a new timeline with a 'parallel' behavior. Its children will be
+	 * triggered all at once.
+	 */
+	public static Timeline createParallel() {
+		Timeline tl = pool.get();
+		tl.setup(Modes.PARALLEL);
+		return tl;
+	}
+
+	// -------------------------------------------------------------------------
+	// Attributes
+	// -------------------------------------------------------------------------
+
+	private enum Modes {SEQUENCE, PARALLEL}
+
+	private final List<BaseTween<?>> children = new ArrayList<BaseTween<?>>(10);
+	private Timeline current;
+	private Timeline parent;
+	private Modes mode;
+	private boolean isBuilt;
+
+	// -------------------------------------------------------------------------
+	// Setup
+	// -------------------------------------------------------------------------
+
+	private Timeline() {
+		reset();
+	}
+
+	@Override
+	protected void reset() {
+		super.reset();
+
+		children.clear();
+		current = parent = null;
+
+		isBuilt = false;
+	}
+
+	private void setup(Modes mode) {
+		this.mode = mode;
+		this.current = this;
+	}
+
+	// -------------------------------------------------------------------------
+	// Public API
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Adds a Tween to the current timeline.
+	 *
+	 * @return The current timeline, for chaining instructions.
+	 */
+	public Timeline push(Tween tween) {
+		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
+		current.children.add(tween);
+		return this;
+	}
+
+	/**
+	 * Nests a Timeline in the current one.
+	 *
+	 * @return The current timeline, for chaining instructions.
+	 */
+	public Timeline push(Timeline timeline) {
+		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
+		if (timeline.current != timeline) throw new RuntimeException("You forgot to call a few 'end()' statements in your pushed timeline");
+		timeline.parent = current;
+		current.children.add(timeline);
+		return this;
+	}
+
+	/**
+	 * Adds a pause to the timeline. The pause may be negative if you want to
+	 * overlap the preceding and following children.
+	 *
+	 * @param time A positive or negative duration.
+	 * @return The current timeline, for chaining instructions.
+	 */
+	public Timeline pushPause(float time) {
+		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
+		current.children.add(Tween.mark().delay(time));
+		return this;
+	}
+
+	/**
+	 * Starts a nested timeline with a 'sequence' behavior. Don't forget to
+	 * call {@link end()} to close this nested timeline.
+	 *
+	 * @return The current timeline, for chaining instructions.
+	 */
+	public Timeline beginSequence() {
+		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
+		Timeline tl = pool.get();
+		tl.parent = current;
+		tl.mode = Modes.SEQUENCE;
+		current.children.add(tl);
+		current = tl;
+		return this;
+	}
+
+	/**
+	 * Starts a nested timeline with a 'parallel' behavior. Don't forget to
+	 * call {@link end()} to close this nested timeline.
+	 *
+	 * @return The current timeline, for chaining instructions.
+	 */
+	public Timeline beginParallel() {
+		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
+		Timeline tl = pool.get();
+		tl.parent = current;
+		tl.mode = Modes.PARALLEL;
+		current.children.add(tl);
+		current = tl;
+		return this;
+	}
+
+	/**
+	 * Closes the last nested timeline.
+	 *
+	 * @return The current timeline, for chaining instructions.
+	 */
+	public Timeline end() {
+		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
+		if (current == this) throw new RuntimeException("Nothing to end...");
+		current = current.parent;
+		return this;
+	}
+
+	/**
+	 * Gets a list of the timeline children. If the timeline is started, the
+	 * list will be immutable.
+	 */
+	public List<BaseTween<?>> getChildren() {
+		if (isBuilt) return Collections.unmodifiableList(current.children);
+		else return current.children;
+	}
+
+	// -------------------------------------------------------------------------
+	// Overrides
+	// -------------------------------------------------------------------------
+
+	@Override
+	public Timeline build() {
+		if (isBuilt) return this;
+
+		duration = 0;
+
+		for (int i=0; i<children.size(); i++) {
+			BaseTween<?> obj = children.get(i);
+
+			if (obj.getRepeatCount() < 0) throw new RuntimeException("You can't push an object with infinite repetitions in a timeline");
+			obj.build();
+
+			switch (mode) {
+				case SEQUENCE:
+					float tDelay = duration;
+					duration += obj.getFullDuration();
+					obj.delay += tDelay;
+					break;
+
+				case PARALLEL:
+					duration = Math.max(duration, obj.getFullDuration());
+					break;
+			}
+		}
+
+		isBuilt = true;
+		return this;
+	}
+
+	@Override
+	public Timeline start() {
+		super.start();
+
+		for (int i=0; i<children.size(); i++) {
+			BaseTween<?> obj = children.get(i);
+			obj.start();
+		}
+
+		return this;
+	}
+
+	@Override
+	public void free() {
+		for (int i=children.size()-1; i>=0; i--) {
+			BaseTween<?> obj = children.remove(i);
+			obj.free();
+		}
+
+		pool.free(this);
+	}
+
+	@Override
+	protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
+		if (!isIterationStep && step > lastStep) {
+			assert delta >= 0;
+			float dt = isReverse(lastStep) ? -delta-1 : delta+1;
+			for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
+			return;
+		}
+
+		if (!isIterationStep && step < lastStep) {
+			assert delta <= 0;
+			float dt = isReverse(lastStep) ? -delta-1 : delta+1;
+			for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
+			return;
+		}
+
+		assert isIterationStep;
+
+		if (step > lastStep) {
+			if (isReverse(step)) {
+				forceEndValues();
+				for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
+			} else {
+				forceStartValues();
+				for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
+			}
+
+		} else if (step < lastStep) {
+			if (isReverse(step)) {
+				forceStartValues();
+				for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
+			} else {
+				forceEndValues();
+				for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
+			}
+
+		} else {
+			float dt = isReverse(step) ? -delta : delta;
+			if (delta >= 0) for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
+			else for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
+		}
+	}
+
+	// -------------------------------------------------------------------------
+	// BaseTween impl.
+	// -------------------------------------------------------------------------
+
+	@Override
+	protected void forceStartValues() {
+		for (int i=children.size()-1; i>=0; i--) {
+			BaseTween<?> obj = children.get(i);
+			obj.forceToStart();
+		}
+	}
+
+	@Override
+	protected void forceEndValues() {
+		for (int i=0, n=children.size(); i<n; i++) {
+			BaseTween<?> obj = children.get(i);
+			obj.forceToEnd(duration);
+		}
+	}
+
+	@Override
+	protected boolean containsTarget(Object target) {
+		for (int i=0, n=children.size(); i<n; i++) {
+			BaseTween<?> obj = children.get(i);
+			if (obj.containsTarget(target)) return true;
+		}
+		return false;
+	}
+
+	@Override
+	protected boolean containsTarget(Object target, int tweenType) {
+		for (int i=0, n=children.size(); i<n; i++) {
+			BaseTween<?> obj = children.get(i);
+			if (obj.containsTarget(target, tweenType)) return true;
+		}
+		return false;
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/Tween.java b/java/api/src/aurelienribon/tweenengine/Tween.java
new file mode 100644
index 0000000..911182e
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/Tween.java
@@ -0,0 +1,923 @@
+package aurelienribon.tweenengine;
+
+import aurelienribon.tweenengine.equations.Quad;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Core class of the Tween Engine. A Tween is basically an interpolation
+ * between two values of an object attribute. However, the main interest of a
+ * Tween is that you can apply an easing formula on this interpolation, in
+ * order to smooth the transitions or to achieve cool effects like springs or
+ * bounces.
+ * <p/>
+ *
+ * The Universal Tween Engine is called "universal" because it is able to apply
+ * interpolations on every attribute from every possible object. Therefore,
+ * every object in your application can be animated with cool effects: it does
+ * not matter if your application is a game, a desktop interface or even a
+ * console program! If it makes sense to animate something, then it can be
+ * animated through this engine.
+ * <p/>
+ *
+ * This class contains many static factory methods to create and instantiate
+ * new interpolations easily. The common way to create a Tween is by using one
+ * of these factories:
+ * <p/>
+ *
+ * - Tween.to(...)<br/>
+ * - Tween.from(...)<br/>
+ * - Tween.set(...)<br/>
+ * - Tween.call(...)
+ * <p/>
+ *
+ * <h2>Example - firing a Tween</h2>
+ *
+ * The following example will move the target horizontal position from its
+ * current value to x=200 and y=300, during 500ms, but only after a delay of
+ * 1000ms. The animation will also be repeated 2 times (the starting position
+ * is registered at the end of the delay, so the animation will automatically
+ * restart from this registered position).
+ * <p/>
+ *
+ * <pre> {@code
+ * Tween.to(myObject, POSITION_XY, 0.5f)
+ *      .target(200, 300)
+ *      .ease(Quad.INOUT)
+ *      .delay(1.0f)
+ *      .repeat(2, 0.2f)
+ *      .start(myManager);
+ * }</pre>
+ *
+ * Tween life-cycles can be automatically managed for you, thanks to the
+ * {@link TweenManager} class. If you choose to manage your tween when you start
+ * it, then you don't need to care about it anymore. <b>Tweens are
+ * <i>fire-and-forget</i>: don't think about them anymore once you started
+ * them (if they are managed of course).</b>
+ * <p/>
+ *
+ * You need to periodicaly update the tween engine, in order to compute the new
+ * values. If your tweens are managed, only update the manager; else you need
+ * to call {@link #update()} on your tweens periodically.
+ * <p/>
+ *
+ * <h2>Example - setting up the engine</h2>
+ *
+ * The engine cannot directly change your objects attributes, since it doesn't
+ * know them. Therefore, you need to tell him how to get and set the different
+ * attributes of your objects: <b>you need to implement the {@link
+ * TweenAccessor} interface for each object class you will animate</b>. Once
+ * done, don't forget to register these implementations, using the static method
+ * {@link registerAccessor()}, when you start your application.
+ *
+ * @see TweenAccessor
+ * @see TweenManager
+ * @see TweenEquation
+ * @see Timeline
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public final class Tween extends BaseTween<Tween> {
+	// -------------------------------------------------------------------------
+	// Static -- misc
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Used as parameter in {@link #repeat(int, float)} and
+	 * {@link #repeatYoyo(int, float)} methods.
+	 */
+	public static final int INFINITY = -1;
+
+	private static int combinedAttrsLimit = 3;
+	private static int waypointsLimit = 0;
+
+	/**
+	 * Changes the limit for combined attributes. Defaults to 3 to reduce
+	 * memory footprint.
+	 */
+	public static void setCombinedAttributesLimit(int limit) {
+		Tween.combinedAttrsLimit = limit;
+	}
+
+	/**
+	 * Changes the limit of allowed waypoints for each tween. Defaults to 0 to
+	 * reduce memory footprint.
+	 */
+	public static void setWaypointsLimit(int limit) {
+		Tween.waypointsLimit = limit;
+	}
+
+	/**
+	 * Gets the version number of the library.
+	 */
+	public static String getVersion() {
+		return "6.3.3";
+	}
+
+	// -------------------------------------------------------------------------
+	// Static -- pool
+	// -------------------------------------------------------------------------
+
+	private static final Pool.Callback<Tween> poolCallback = new Pool.Callback<Tween>() {
+		@Override public void onPool(Tween obj) {obj.reset();}
+		@Override public void onUnPool(Tween obj) {obj.reset();}
+	};
+
+	private static final Pool<Tween> pool = new Pool<Tween>(20, poolCallback) {
+		@Override protected Tween create() {return new Tween();}
+	};
+
+	/**
+	 * Used for debug purpose. Gets the current number of objects that are
+	 * waiting in the Tween pool.
+	 */
+	public static int getPoolSize() {
+		return pool.size();
+	}
+
+	/**
+	 * Increases the minimum capacity of the pool. Capacity defaults to 20.
+	 */
+	public static void ensurePoolCapacity(int minCapacity) {
+		pool.ensureCapacity(minCapacity);
+	}
+
+	// -------------------------------------------------------------------------
+	// Static -- tween accessors
+	// -------------------------------------------------------------------------
+
+	private static final Map<Class<?>, TweenAccessor<?>> registeredAccessors = new HashMap<Class<?>, TweenAccessor<?>>();
+
+	/**
+	 * Registers an accessor with the class of an object. This accessor will be
+	 * used by tweens applied to every objects implementing the registered
+	 * class, or inheriting from it.
+	 *
+	 * @param someClass An object class.
+	 * @param defaultAccessor The accessor that will be used to tween any
+	 * object of class "someClass".
+	 */
+	public static void registerAccessor(Class<?> someClass, TweenAccessor<?> defaultAccessor) {
+		registeredAccessors.put(someClass, defaultAccessor);
+	}
+
+	/**
+	 * Gets the registered TweenAccessor associated with the given object class.
+	 *
+	 * @param someClass An object class.
+	 */
+	public static TweenAccessor<?> getRegisteredAccessor(Class<?> someClass) {
+		return registeredAccessors.get(someClass);
+	}
+
+	// -------------------------------------------------------------------------
+	// Static -- factories
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Factory creating a new standard interpolation. This is the most common
+	 * type of interpolation. The starting values are retrieved automatically
+	 * after the delay (if any).
+	 * <br/><br/>
+	 *
+	 * <b>You need to set the target values of the interpolation by using one
+	 * of the target() methods</b>. The interpolation will run from the
+	 * starting values to these target values.
+	 * <br/><br/>
+	 *
+	 * The common use of Tweens is "fire-and-forget": you do not need to care
+	 * for tweens once you added them to a TweenManager, they will be updated
+	 * automatically, and cleaned once finished. Common call:
+	 * <br/><br/>
+	 *
+	 * <pre> {@code
+	 * Tween.to(myObject, POSITION, 1.0f)
+	 *      .target(50, 70)
+	 *      .ease(Quad.INOUT)
+	 *      .start(myManager);
+	 * }</pre>
+	 *
+	 * Several options such as delay, repetitions and callbacks can be added to
+	 * the tween.
+	 *
+	 * @param target The target object of the interpolation.
+	 * @param tweenType The desired type of interpolation.
+	 * @param duration The duration of the interpolation, in milliseconds.
+	 * @return The generated Tween.
+	 */
+	public static Tween to(Object target, int tweenType, float duration) {
+		Tween tween = pool.get();
+		tween.setup(target, tweenType, duration);
+		tween.ease(Quad.INOUT);
+		tween.path(TweenPaths.catmullRom);
+		return tween;
+	}
+
+	/**
+	 * Factory creating a new reversed interpolation. The ending values are
+	 * retrieved automatically after the delay (if any).
+	 * <br/><br/>
+	 *
+	 * <b>You need to set the starting values of the interpolation by using one
+	 * of the target() methods</b>. The interpolation will run from the
+	 * starting values to these target values.
+	 * <br/><br/>
+	 *
+	 * The common use of Tweens is "fire-and-forget": you do not need to care
+	 * for tweens once you added them to a TweenManager, they will be updated
+	 * automatically, and cleaned once finished. Common call:
+	 * <br/><br/>
+	 *
+	 * <pre> {@code
+	 * Tween.from(myObject, POSITION, 1.0f)
+	 *      .target(0, 0)
+	 *      .ease(Quad.INOUT)
+	 *      .start(myManager);
+	 * }</pre>
+	 *
+	 * Several options such as delay, repetitions and callbacks can be added to
+	 * the tween.
+	 *
+	 * @param target The target object of the interpolation.
+	 * @param tweenType The desired type of interpolation.
+	 * @param duration The duration of the interpolation, in milliseconds.
+	 * @return The generated Tween.
+	 */
+	public static Tween from(Object target, int tweenType, float duration) {
+		Tween tween = pool.get();
+		tween.setup(target, tweenType, duration);
+		tween.ease(Quad.INOUT);
+		tween.path(TweenPaths.catmullRom);
+		tween.isFrom = true;
+		return tween;
+	}
+
+	/**
+	 * Factory creating a new instantaneous interpolation (thus this is not
+	 * really an interpolation).
+	 * <br/><br/>
+	 *
+	 * <b>You need to set the target values of the interpolation by using one
+	 * of the target() methods</b>. The interpolation will set the target
+	 * attribute to these values after the delay (if any).
+	 * <br/><br/>
+	 *
+	 * The common use of Tweens is "fire-and-forget": you do not need to care
+	 * for tweens once you added them to a TweenManager, they will be updated
+	 * automatically, and cleaned once finished. Common call:
+	 * <br/><br/>
+	 *
+	 * <pre> {@code
+	 * Tween.set(myObject, POSITION)
+	 *      .target(50, 70)
+	 *      .delay(1.0f)
+	 *      .start(myManager);
+	 * }</pre>
+	 *
+	 * Several options such as delay, repetitions and callbacks can be added to
+	 * the tween.
+	 *
+	 * @param target The target object of the interpolation.
+	 * @param tweenType The desired type of interpolation.
+	 * @return The generated Tween.
+	 */
+	public static Tween set(Object target, int tweenType) {
+		Tween tween = pool.get();
+		tween.setup(target, tweenType, 0);
+		tween.ease(Quad.INOUT);
+		return tween;
+	}
+
+	/**
+	 * Factory creating a new timer. The given callback will be triggered on
+	 * each iteration start, after the delay.
+	 * <br/><br/>
+	 *
+	 * The common use of Tweens is "fire-and-forget": you do not need to care
+	 * for tweens once you added them to a TweenManager, they will be updated
+	 * automatically, and cleaned once finished. Common call:
+	 * <br/><br/>
+	 *
+	 * <pre> {@code
+	 * Tween.call(myCallback)
+	 *      .delay(1.0f)
+	 *      .repeat(10, 1000)
+	 *      .start(myManager);
+	 * }</pre>
+	 *
+	 * @param callback The callback that will be triggered on each iteration
+	 * start.
+	 * @return The generated Tween.
+	 * @see TweenCallback
+	 */
+	public static Tween call(TweenCallback callback) {
+		Tween tween = pool.get();
+		tween.setup(null, -1, 0);
+		tween.setCallback(callback);
+		tween.setCallbackTriggers(TweenCallback.START);
+		return tween;
+	}
+
+	/**
+	 * Convenience method to create an empty tween. Such object is only useful
+	 * when placed inside animation sequences (see {@link Timeline}), in which
+	 * it may act as a beacon, so you can set a callback on it in order to
+	 * trigger some action at the right moment.
+	 *
+	 * @return The generated Tween.
+	 * @see Timeline
+	 */
+	public static Tween mark() {
+		Tween tween = pool.get();
+		tween.setup(null, -1, 0);
+		return tween;
+	}
+
+	// -------------------------------------------------------------------------
+	// Attributes
+	// -------------------------------------------------------------------------
+
+	// Main
+	private Object target;
+	private Class<?> targetClass;
+	private TweenAccessor<Object> accessor;
+	private int type;
+	private TweenEquation equation;
+	private TweenPath path;
+
+	// General
+	private boolean isFrom;
+	private boolean isRelative;
+	private int combinedAttrsCnt;
+	private int waypointsCnt;
+
+	// Values
+	private final float[] startValues = new float[combinedAttrsLimit];
+	private final float[] targetValues = new float[combinedAttrsLimit];
+	private final float[] waypoints = new float[waypointsLimit * combinedAttrsLimit];
+
+	// Buffers
+	private float[] accessorBuffer = new float[combinedAttrsLimit];
+	private float[] pathBuffer = new float[(2+waypointsLimit)*combinedAttrsLimit];
+
+	// -------------------------------------------------------------------------
+	// Setup
+	// -------------------------------------------------------------------------
+
+	private Tween() {
+		reset();
+	}
+
+	@Override
+	protected void reset() {
+		super.reset();
+
+		target = null;
+		targetClass = null;
+		accessor = null;
+		type = -1;
+		equation = null;
+		path = null;
+
+		isFrom = isRelative = false;
+		combinedAttrsCnt = waypointsCnt = 0;
+
+		if (accessorBuffer.length != combinedAttrsLimit) {
+			accessorBuffer = new float[combinedAttrsLimit];
+		}
+
+		if (pathBuffer.length != (2+waypointsLimit)*combinedAttrsLimit) {
+			pathBuffer = new float[(2+waypointsLimit)*combinedAttrsLimit];
+		}
+	}
+
+	private void setup(Object target, int tweenType, float duration) {
+		if (duration < 0) throw new RuntimeException("Duration can't be negative");
+
+		this.target = target;
+		this.targetClass = target != null ? findTargetClass() : null;
+		this.type = tweenType;
+		this.duration = duration;
+	}
+
+	private Class<?> findTargetClass() {
+		if (registeredAccessors.containsKey(target.getClass())) return target.getClass();
+		if (target instanceof TweenAccessor) return target.getClass();
+
+		Class<?> parentClass = target.getClass().getSuperclass();
+		while (parentClass != null && !registeredAccessors.containsKey(parentClass))
+			parentClass = parentClass.getSuperclass();
+
+		return parentClass;
+	}
+
+	// -------------------------------------------------------------------------
+	// Public API
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Sets the easing equation of the tween. Existing equations are located in
+	 * <i>aurelienribon.tweenengine.equations</i> package, but you can of course
+	 * implement your owns, see {@link TweenEquation}. You can also use the
+	 * {@link TweenEquations} static instances to quickly access all the
+	 * equations. Default equation is Quad.INOUT.
+	 * <p/>
+	 *
+	 * <b>Proposed equations are:</b><br/>
+	 * - Linear.INOUT,<br/>
+	 * - Quad.IN | OUT | INOUT,<br/>
+	 * - Cubic.IN | OUT | INOUT,<br/>
+	 * - Quart.IN | OUT | INOUT,<br/>
+	 * - Quint.IN | OUT | INOUT,<br/>
+	 * - Circ.IN | OUT | INOUT,<br/>
+	 * - Sine.IN | OUT | INOUT,<br/>
+	 * - Expo.IN | OUT | INOUT,<br/>
+	 * - Back.IN | OUT | INOUT,<br/>
+	 * - Bounce.IN | OUT | INOUT,<br/>
+	 * - Elastic.IN | OUT | INOUT
+	 *
+	 * @return The current tween, for chaining instructions.
+	 * @see TweenEquation
+	 * @see TweenEquations
+	 */
+	public Tween ease(TweenEquation easeEquation) {
+		this.equation = easeEquation;
+		return this;
+	}
+
+	/**
+	 * Forces the tween to use the TweenAccessor registered with the given
+	 * target class. Useful if you want to use a specific accessor associated
+	 * to an interface, for instance.
+	 *
+	 * @param targetClass A class registered with an accessor.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween cast(Class<?> targetClass) {
+		if (isStarted()) throw new RuntimeException("You can't cast the target of a tween once it is started");
+		this.targetClass = targetClass;
+		return this;
+	}
+
+	/**
+	 * Sets the target value of the interpolation. The interpolation will run
+	 * from the <b>value at start time (after the delay, if any)</b> to this
+	 * target value.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start value: value at start time, after delay<br/>
+	 * - end value: param
+	 *
+	 * @param targetValue The target value of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween target(float targetValue) {
+		targetValues[0] = targetValue;
+		return this;
+	}
+
+	/**
+	 * Sets the target values of the interpolation. The interpolation will run
+	 * from the <b>values at start time (after the delay, if any)</b> to these
+	 * target values.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start values: values at start time, after delay<br/>
+	 * - end values: params
+	 *
+	 * @param targetValue1 The 1st target value of the interpolation.
+	 * @param targetValue2 The 2nd target value of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween target(float targetValue1, float targetValue2) {
+		targetValues[0] = targetValue1;
+		targetValues[1] = targetValue2;
+		return this;
+	}
+
+	/**
+	 * Sets the target values of the interpolation. The interpolation will run
+	 * from the <b>values at start time (after the delay, if any)</b> to these
+	 * target values.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start values: values at start time, after delay<br/>
+	 * - end values: params
+	 *
+	 * @param targetValue1 The 1st target value of the interpolation.
+	 * @param targetValue2 The 2nd target value of the interpolation.
+	 * @param targetValue3 The 3rd target value of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween target(float targetValue1, float targetValue2, float targetValue3) {
+		targetValues[0] = targetValue1;
+		targetValues[1] = targetValue2;
+		targetValues[2] = targetValue3;
+		return this;
+	}
+
+	/**
+	 * Sets the target values of the interpolation. The interpolation will run
+	 * from the <b>values at start time (after the delay, if any)</b> to these
+	 * target values.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start values: values at start time, after delay<br/>
+	 * - end values: params
+	 *
+	 * @param targetValues The target values of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween target(float... targetValues) {
+		if (targetValues.length > combinedAttrsLimit) throwCombinedAttrsLimitReached();
+		System.arraycopy(targetValues, 0, this.targetValues, 0, targetValues.length);
+		return this;
+	}
+
+	/**
+	 * Sets the target value of the interpolation, relatively to the <b>value
+	 * at start time (after the delay, if any)</b>.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start value: value at start time, after delay<br/>
+	 * - end value: param + value at start time, after delay
+	 *
+	 * @param targetValue The relative target value of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween targetRelative(float targetValue) {
+		isRelative = true;
+		targetValues[0] = isInitialized() ? targetValue + startValues[0] : targetValue;
+		return this;
+	}
+
+	/**
+	 * Sets the target values of the interpolation, relatively to the <b>values
+	 * at start time (after the delay, if any)</b>.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start values: values at start time, after delay<br/>
+	 * - end values: params + values at start time, after delay
+	 *
+	 * @param targetValue1 The 1st relative target value of the interpolation.
+	 * @param targetValue2 The 2nd relative target value of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween targetRelative(float targetValue1, float targetValue2) {
+		isRelative = true;
+		targetValues[0] = isInitialized() ? targetValue1 + startValues[0] : targetValue1;
+		targetValues[1] = isInitialized() ? targetValue2 + startValues[1] : targetValue2;
+		return this;
+	}
+
+	/**
+	 * Sets the target values of the interpolation, relatively to the <b>values
+	 * at start time (after the delay, if any)</b>.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start values: values at start time, after delay<br/>
+	 * - end values: params + values at start time, after delay
+	 *
+	 * @param targetValue1 The 1st relative target value of the interpolation.
+	 * @param targetValue2 The 2nd relative target value of the interpolation.
+	 * @param targetValue3 The 3rd relative target value of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween targetRelative(float targetValue1, float targetValue2, float targetValue3) {
+		isRelative = true;
+		targetValues[0] = isInitialized() ? targetValue1 + startValues[0] : targetValue1;
+		targetValues[1] = isInitialized() ? targetValue2 + startValues[1] : targetValue2;
+		targetValues[2] = isInitialized() ? targetValue3 + startValues[2] : targetValue3;
+		return this;
+	}
+
+	/**
+	 * Sets the target values of the interpolation, relatively to the <b>values
+	 * at start time (after the delay, if any)</b>.
+	 * <p/>
+	 *
+	 * To sum-up:<br/>
+	 * - start values: values at start time, after delay<br/>
+	 * - end values: params + values at start time, after delay
+	 *
+	 * @param targetValues The relative target values of the interpolation.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween targetRelative(float... targetValues) {
+		if (targetValues.length > combinedAttrsLimit) throwCombinedAttrsLimitReached();
+		for (int i=0; i<targetValues.length; i++) {
+			this.targetValues[i] = isInitialized() ? targetValues[i] + startValues[i] : targetValues[i];
+		}
+
+		isRelative = true;
+		return this;
+	}
+
+	/**
+	 * Adds a waypoint to the path. The default path runs from the start values
+	 * to the end values linearly. If you add waypoints, the default path will
+	 * use a smooth catmull-rom spline to navigate between the waypoints, but
+	 * you can change this behavior by using the {@link #path(TweenPath)}
+	 * method.
+	 *
+	 * @param targetValue The target of this waypoint.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween waypoint(float targetValue) {
+		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
+		waypoints[waypointsCnt] = targetValue;
+		waypointsCnt += 1;
+		return this;
+	}
+
+	/**
+	 * Adds a waypoint to the path. The default path runs from the start values
+	 * to the end values linearly. If you add waypoints, the default path will
+	 * use a smooth catmull-rom spline to navigate between the waypoints, but
+	 * you can change this behavior by using the {@link #path(TweenPath)}
+	 * method.
+	 * <p/>
+	 * Note that if you want waypoints relative to the start values, use one of
+	 * the .targetRelative() methods to define your target.
+	 *
+	 * @param targetValue1 The 1st target of this waypoint.
+	 * @param targetValue2 The 2nd target of this waypoint.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween waypoint(float targetValue1, float targetValue2) {
+		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
+		waypoints[waypointsCnt*2] = targetValue1;
+		waypoints[waypointsCnt*2+1] = targetValue2;
+		waypointsCnt += 1;
+		return this;
+	}
+
+	/**
+	 * Adds a waypoint to the path. The default path runs from the start values
+	 * to the end values linearly. If you add waypoints, the default path will
+	 * use a smooth catmull-rom spline to navigate between the waypoints, but
+	 * you can change this behavior by using the {@link #path(TweenPath)}
+	 * method.
+	 * <p/>
+	 * Note that if you want waypoints relative to the start values, use one of
+	 * the .targetRelative() methods to define your target.
+	 *
+	 * @param targetValue1 The 1st target of this waypoint.
+	 * @param targetValue2 The 2nd target of this waypoint.
+	 * @param targetValue3 The 3rd target of this waypoint.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween waypoint(float targetValue1, float targetValue2, float targetValue3) {
+		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
+		waypoints[waypointsCnt*3] = targetValue1;
+		waypoints[waypointsCnt*3+1] = targetValue2;
+		waypoints[waypointsCnt*3+2] = targetValue3;
+		waypointsCnt += 1;
+		return this;
+	}
+
+	/**
+	 * Adds a waypoint to the path. The default path runs from the start values
+	 * to the end values linearly. If you add waypoints, the default path will
+	 * use a smooth catmull-rom spline to navigate between the waypoints, but
+	 * you can change this behavior by using the {@link #path(TweenPath)}
+	 * method.
+	 * <p/>
+	 * Note that if you want waypoints relative to the start values, use one of
+	 * the .targetRelative() methods to define your target.
+	 *
+	 * @param targetValues The targets of this waypoint.
+	 * @return The current tween, for chaining instructions.
+	 */
+	public Tween waypoint(float... targetValues) {
+		if (waypointsCnt == waypointsLimit) throwWaypointsLimitReached();
+		System.arraycopy(targetValues, 0, waypoints, waypointsCnt*targetValues.length, targetValues.length);
+		waypointsCnt += 1;
+		return this;
+	}
+
+	/**
+	 * Sets the algorithm that will be used to navigate through the waypoints,
+	 * from the start values to the end values. Default is a catmull-rom spline,
+	 * but you can find other paths in the {@link TweenPaths} class.
+	 *
+	 * @param path A TweenPath implementation.
+	 * @return The current tween, for chaining instructions.
+	 * @see TweenPath
+	 * @see TweenPaths
+	 */
+	public Tween path(TweenPath path) {
+		this.path = path;
+		return this;
+	}
+
+	// -------------------------------------------------------------------------
+	// Getters
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Gets the target object.
+	 */
+	public Object getTarget() {
+		return target;
+	}
+
+	/**
+	 * Gets the type of the tween.
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * Gets the easing equation.
+	 */
+	public TweenEquation getEasing() {
+		return equation;
+	}
+
+	/**
+	 * Gets the target values. The returned buffer is as long as the maximum
+	 * allowed combined values. Therefore, you're surely not interested in all
+	 * its content. Use {@link #getCombinedTweenCount()} to get the number of
+	 * interesting slots.
+	 */
+	public float[] getTargetValues() {
+		return targetValues;
+	}
+
+	/**
+	 * Gets the number of combined animations.
+	 */
+	public int getCombinedAttributesCount() {
+		return combinedAttrsCnt;
+	}
+
+	/**
+	 * Gets the TweenAccessor used with the target.
+	 */
+	public TweenAccessor<?> getAccessor() {
+		return accessor;
+	}
+
+	/**
+	 * Gets the class that was used to find the associated TweenAccessor.
+	 */
+	public Class<?> getTargetClass() {
+		return targetClass;
+	}
+
+	// -------------------------------------------------------------------------
+	// Overrides
+	// -------------------------------------------------------------------------
+
+	@Override
+	public Tween build() {
+		if (target == null) return this;
+
+		accessor = (TweenAccessor<Object>) registeredAccessors.get(targetClass);
+		if (accessor == null && target instanceof TweenAccessor) accessor = (TweenAccessor<Object>) target;
+		if (accessor != null) combinedAttrsCnt = accessor.getValues(target, type, accessorBuffer);
+		else throw new RuntimeException("No TweenAccessor was found for the target");
+
+		if (combinedAttrsCnt > combinedAttrsLimit) throwCombinedAttrsLimitReached();
+		return this;
+	}
+
+	@Override
+	public void free() {
+		pool.free(this);
+	}
+
+	@Override
+	protected void initializeOverride() {
+		if (target == null) return;
+
+		accessor.getValues(target, type, startValues);
+
+		for (int i=0; i<combinedAttrsCnt; i++) {
+			targetValues[i] += isRelative ? startValues[i] : 0;
+
+			for (int ii=0; ii<waypointsCnt; ii++) {
+				waypoints[ii*combinedAttrsCnt+i] += isRelative ? startValues[i] : 0;
+			}
+
+			if (isFrom) {
+				float tmp = startValues[i];
+				startValues[i] = targetValues[i];
+				targetValues[i] = tmp;
+			}
+		}
+	}
+
+	@Override
+	protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
+		if (target == null || equation == null) return;
+
+		// Case iteration end has been reached
+
+		if (!isIterationStep && step > lastStep) {
+			accessor.setValues(target, type, isReverse(lastStep) ? startValues : targetValues);
+			return;
+		}
+
+		if (!isIterationStep && step < lastStep) {
+			accessor.setValues(target, type, isReverse(lastStep) ? targetValues : startValues);
+			return;
+		}
+
+		// Validation
+
+		assert isIterationStep;
+		assert getCurrentTime() >= 0;
+		assert getCurrentTime() <= duration;
+
+		// Case duration equals zero
+
+		if (duration < 0.00000000001f && delta > -0.00000000001f) {
+			accessor.setValues(target, type, isReverse(step) ? targetValues : startValues);
+			return;
+		}
+
+		if (duration < 0.00000000001f && delta < 0.00000000001f) {
+			accessor.setValues(target, type, isReverse(step) ? startValues : targetValues);
+			return;
+		}
+
+		// Normal behavior
+
+		float time = isReverse(step) ? duration - getCurrentTime() : getCurrentTime();
+		float t = equation.compute(time/duration);
+
+		if (waypointsCnt == 0 || path == null) {
+			for (int i=0; i<combinedAttrsCnt; i++) {
+				accessorBuffer[i] = startValues[i] + t * (targetValues[i] - startValues[i]);
+			}
+
+		} else {
+			for (int i=0; i<combinedAttrsCnt; i++) {
+				pathBuffer[0] = startValues[i];
+				pathBuffer[1+waypointsCnt] = targetValues[i];
+				for (int ii=0; ii<waypointsCnt; ii++) {
+					pathBuffer[ii+1] = waypoints[ii*combinedAttrsCnt+i];
+				}
+
+				accessorBuffer[i] = path.compute(t, pathBuffer, waypointsCnt+2);
+			}
+		}
+
+		accessor.setValues(target, type, accessorBuffer);
+	}
+
+	// -------------------------------------------------------------------------
+	// BaseTween impl.
+	// -------------------------------------------------------------------------
+
+	@Override
+	protected void forceStartValues() {
+		if (target == null) return;
+		accessor.setValues(target, type, startValues);
+	}
+
+	@Override
+	protected void forceEndValues() {
+		if (target == null) return;
+		accessor.setValues(target, type, targetValues);
+	}
+
+	@Override
+	protected boolean containsTarget(Object target) {
+		return this.target == target;
+	}
+
+	@Override
+	protected boolean containsTarget(Object target, int tweenType) {
+		return this.target == target && this.type == tweenType;
+	}
+
+	// -------------------------------------------------------------------------
+	// Helpers
+	// -------------------------------------------------------------------------
+
+	private void throwCombinedAttrsLimitReached() {
+		String msg = "You cannot combine more than " + combinedAttrsLimit + " "
+			+ "attributes in a tween. You can raise this limit with "
+			+ "Tween.setCombinedAttributesLimit(), which should be called once "
+			+ "in application initialization code.";
+		throw new RuntimeException(msg);
+	}
+
+	private void throwWaypointsLimitReached() {
+		String msg = "You cannot add more than " + waypointsLimit + " "
+			+ "waypoints to a tween. You can raise this limit with "
+			+ "Tween.setWaypointsLimit(), which should be called once in "
+			+ "application initialization code.";
+		throw new RuntimeException(msg);
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenAccessor.java b/java/api/src/aurelienribon/tweenengine/TweenAccessor.java
new file mode 100644
index 0000000..780fb3c
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenAccessor.java
@@ -0,0 +1,82 @@
+package aurelienribon.tweenengine;
+
+/**
+ * The TweenAccessor interface lets you interpolate any attribute from any
+ * object. Just implement it as you want and register it to the engine by
+ * calling {@link Tween#registerAccessor}.
+ * <p/>
+ *
+ * <h2>Example</h2>
+ *
+ * The following code snippet presents an example of implementation for tweening
+ * a Particle class. This Particle class is supposed to only define a position
+ * with an "x" and an "y" fields, and their associated getters and setters.
+ * <p/>
+ *
+ * <pre> {@code
+ * public class ParticleAccessor implements TweenAccessor<Particle> {
+ *     public static final int X = 1;
+ *     public static final int Y = 2;
+ *     public static final int XY = 3;
+ *
+ *     public int getValues(Particle target, int tweenType, float[] returnValues) {
+ *         switch (tweenType) {
+ *             case X: returnValues[0] = target.getX(); return 1;
+ *             case Y: returnValues[0] = target.getY(); return 1;
+ *             case XY:
+ *                 returnValues[0] = target.getX();
+ *                 returnValues[1] = target.getY();
+ *                 return 2;
+ *             default: assert false; return 0;
+ *         }
+ *     }
+ *
+ *     public void setValues(Particle target, int tweenType, float[] newValues) {
+ *         switch (tweenType) {
+ *             case X: target.setX(newValues[0]); break;
+ *             case Y: target.setY(newValues[1]); break;
+ *             case XY:
+ *                 target.setX(newValues[0]);
+ *                 target.setY(newValues[1]);
+ *                 break;
+ *             default: assert false; break;
+ *         }
+ *     }
+ * }
+ * }</pre>
+ *
+ * Once done, you only need to register this TweenAccessor once to be able to
+ * use it for every Particle objects in your application:
+ * <p/>
+ *
+ * <pre> {@code
+ * Tween.registerAccessor(Particle.class, new ParticleAccessor());
+ * }</pre>
+ *
+ * And that's all, the Tween Engine can no work with all your particles!
+ *
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public interface TweenAccessor<T> {
+	/**
+	 * Gets one or many values from the target object associated to the
+	 * given tween type. It is used by the Tween Engine to determine starting
+	 * values.
+	 *
+	 * @param target The target object of the tween.
+	 * @param tweenType An integer representing the tween type.
+	 * @param returnValues An array which should be modified by this method.
+	 * @return The count of modified slots from the returnValues array.
+	 */
+	public int getValues(T target, int tweenType, float[] returnValues);
+
+	/**
+	 * This method is called by the Tween Engine each time a running tween
+	 * associated with the current target object has been updated.
+	 *
+	 * @param target The target object of the tween.
+	 * @param tweenType An integer representing the tween type.
+	 * @param newValues The new values determined by the Tween Engine.
+	 */
+	public void setValues(T target, int tweenType, float[] newValues);
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenCallback.java b/java/api/src/aurelienribon/tweenengine/TweenCallback.java
new file mode 100644
index 0000000..5a733cb
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenCallback.java
@@ -0,0 +1,45 @@
+package aurelienribon.tweenengine;
+
+/**
+ * TweenCallbacks are used to trigger actions at some specific times. They are
+ * used in both Tweens and Timelines. The moment when the callback is
+ * triggered depends on its registered triggers:
+ * <p/>
+ *
+ * <b>BEGIN</b>: right after the delay (if any)<br/>
+ * <b>START</b>: at each iteration beginning<br/>
+ * <b>END</b>: at each iteration ending, before the repeat delay<br/>
+ * <b>COMPLETE</b>: at last END event<br/>
+ * <b>BACK_BEGIN</b>: at the beginning of the first backward iteration<br/>
+ * <b>BACK_START</b>: at each backward iteration beginning, after the repeat delay<br/>
+ * <b>BACK_END</b>: at each backward iteration ending<br/>
+ * <b>BACK_COMPLETE</b>: at last BACK_END event
+ * <p/>
+ *
+ * <pre> {@code
+ * forward :      BEGIN                                   COMPLETE
+ * forward :      START    END      START    END      START    END
+ * |--------------[XXXXXXXXXX]------[XXXXXXXXXX]------[XXXXXXXXXX]
+ * backward:      bEND  bSTART      bEND  bSTART      bEND  bSTART
+ * backward:      bCOMPLETE                                 bBEGIN
+ * }</pre>
+ *
+ * @see Tween
+ * @see Timeline
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public interface TweenCallback {
+	public static final int BEGIN = 0x01;
+	public static final int START = 0x02;
+	public static final int END = 0x04;
+	public static final int COMPLETE = 0x08;
+	public static final int BACK_BEGIN = 0x10;
+	public static final int BACK_START = 0x20;
+	public static final int BACK_END = 0x40;
+	public static final int BACK_COMPLETE = 0x80;
+	public static final int ANY_FORWARD = 0x0F;
+	public static final int ANY_BACKWARD = 0xF0;
+	public static final int ANY = 0xFF;
+
+	public void onEvent(int type, BaseTween<?> source);
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenEquation.java b/java/api/src/aurelienribon/tweenengine/TweenEquation.java
new file mode 100644
index 0000000..d550703
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenEquation.java
@@ -0,0 +1,28 @@
+package aurelienribon.tweenengine;
+
+/**
+ * Base class for every easing equation. You can create your own equations
+ * and directly use them in the Tween engine by inheriting from this class.
+ *
+ * @see Tween
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class TweenEquation {
+
+	/**
+	 * Computes the next value of the interpolation.
+	 *
+	 * @param t The current time, between 0 and 1.
+	 * @return The current value.
+	 */
+    public abstract float compute(float t);
+
+	/**
+	 * Returns true if the given string is the name of this equation (the name
+	 * is returned in the toString() method, don't forget to override it).
+	 * This method is usually used to save/load a tween to/from a text file.
+	 */
+	public boolean isValueOf(String str) {
+		return str.equals(toString());
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenEquations.java b/java/api/src/aurelienribon/tweenengine/TweenEquations.java
new file mode 100644
index 0000000..115fc32
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenEquations.java
@@ -0,0 +1,52 @@
+package aurelienribon.tweenengine;
+
+import aurelienribon.tweenengine.equations.Back;
+import aurelienribon.tweenengine.equations.Bounce;
+import aurelienribon.tweenengine.equations.Circ;
+import aurelienribon.tweenengine.equations.Cubic;
+import aurelienribon.tweenengine.equations.Elastic;
+import aurelienribon.tweenengine.equations.Expo;
+import aurelienribon.tweenengine.equations.Linear;
+import aurelienribon.tweenengine.equations.Quad;
+import aurelienribon.tweenengine.equations.Quart;
+import aurelienribon.tweenengine.equations.Quint;
+import aurelienribon.tweenengine.equations.Sine;
+
+/**
+ * Collection of built-in easing equations
+ *
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public interface TweenEquations {
+	public static final Linear easeNone = Linear.INOUT;
+	public static final Quad easeInQuad = Quad.IN;
+	public static final Quad easeOutQuad = Quad.OUT;
+	public static final Quad easeInOutQuad = Quad.INOUT;
+	public static final Cubic easeInCubic = Cubic.IN;
+	public static final Cubic easeOutCubic = Cubic.OUT;
+	public static final Cubic easeInOutCubic = Cubic.INOUT;
+	public static final Quart easeInQuart = Quart.IN;
+	public static final Quart easeOutQuart = Quart.OUT;
+	public static final Quart easeInOutQuart = Quart.INOUT;
+	public static final Quint easeInQuint = Quint.IN;
+	public static final Quint easeOutQuint = Quint.OUT;
+	public static final Quint easeInOutQuint = Quint.INOUT;
+	public static final Circ easeInCirc = Circ.IN;
+	public static final Circ easeOutCirc = Circ.OUT;
+	public static final Circ easeInOutCirc = Circ.INOUT;
+	public static final Sine easeInSine = Sine.IN;
+	public static final Sine easeOutSine = Sine.OUT;
+	public static final Sine easeInOutSine = Sine.INOUT;
+	public static final Expo easeInExpo = Expo.IN;
+	public static final Expo easeOutExpo = Expo.OUT;
+	public static final Expo easeInOutExpo = Expo.INOUT;
+	public static final Back easeInBack = Back.IN;
+	public static final Back easeOutBack = Back.OUT;
+	public static final Back easeInOutBack = Back.INOUT;
+	public static final Bounce easeInBounce = Bounce.IN;
+	public static final Bounce easeOutBounce = Bounce.OUT;
+	public static final Bounce easeInOutBounce = Bounce.INOUT;
+	public static final Elastic easeInElastic = Elastic.IN;
+	public static final Elastic easeOutElastic = Elastic.OUT;
+	public static final Elastic easeInOutElastic = Elastic.INOUT;
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenManager.java b/java/api/src/aurelienribon/tweenengine/TweenManager.java
new file mode 100644
index 0000000..bc6ec7b
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenManager.java
@@ -0,0 +1,237 @@
+package aurelienribon.tweenengine;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A TweenManager updates all your tweens and timelines at once.
+ * Its main interest is that it handles the tween/timeline life-cycles for you,
+ * as well as the pooling constraints (if object pooling is enabled).
+ * <p/>
+ *
+ * Just give it a bunch of tweens or timelines and call update() periodically,
+ * you don't need to care for anything else! Relax and enjoy your animations.
+ *
+ * @see Tween
+ * @see Timeline
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public class TweenManager {
+	// -------------------------------------------------------------------------
+	// Static API
+	// -------------------------------------------------------------------------
+
+	/**
+	 * Disables or enables the "auto remove" mode of any tween manager for a
+	 * particular tween or timeline. This mode is activated by default. The
+	 * interest of desactivating it is to prevent some tweens or timelines from
+	 * being automatically removed from a manager once they are finished.
+	 * Therefore, if you update a manager backwards, the tweens or timelines
+	 * will be played again, even if they were finished.
+	 */
+	public static void setAutoRemove(BaseTween<?> object, boolean value) {
+		object.isAutoRemoveEnabled = value;
+	}
+
+	/**
+	 * Disables or enables the "auto start" mode of any tween manager for a
+	 * particular tween or timeline. This mode is activated by default. If it
+	 * is not enabled, add a tween or timeline to any manager won't start it
+	 * automatically, and you'll need to call .start() manually on your object.
+	 */
+	public static void setAutoStart(BaseTween<?> object, boolean value) {
+		object.isAutoStartEnabled = value;
+	}
+
+	// -------------------------------------------------------------------------
+	// Public API
+	// -------------------------------------------------------------------------
+
+	private final ArrayList<BaseTween<?>> objects = new ArrayList<BaseTween<?>>(20);
+	private boolean isPaused = false;
+
+	/**
+	 * Adds a tween or timeline to the manager and starts or restarts it.
+	 *
+	 * @return The manager, for instruction chaining.
+	 */
+	public TweenManager add(BaseTween<?> object) {
+		if (!objects.contains(object)) objects.add(object);
+		if (object.isAutoStartEnabled) object.start();
+		return this;
+	}
+
+	/**
+	 * Returns true if the manager contains any valid interpolation associated
+	 * to the given target object.
+	 */
+	public boolean containsTarget(Object target) {
+		for (int i=0, n=objects.size(); i<n; i++) {
+			BaseTween<?> obj = objects.get(i);
+			if (obj.containsTarget(target)) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Returns true if the manager contains any valid interpolation associated
+	 * to the given target object and to the given tween type.
+	 */
+	public boolean containsTarget(Object target, int tweenType) {
+		for (int i=0, n=objects.size(); i<n; i++) {
+			BaseTween<?> obj = objects.get(i);
+			if (obj.containsTarget(target, tweenType)) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Kills every managed tweens and timelines.
+	 */
+	public void killAll() {
+		for (int i=0, n=objects.size(); i<n; i++) {
+			BaseTween<?> obj = objects.get(i);
+			obj.kill();
+		}
+	}
+
+	/**
+	 * Kills every tweens associated to the given target. Will also kill every
+	 * timelines containing a tween associated to the given target.
+	 */
+	public void killTarget(Object target) {
+		for (int i=0, n=objects.size(); i<n; i++) {
+			BaseTween<?> obj = objects.get(i);
+			obj.killTarget(target);
+		}
+	}
+
+	/**
+	 * Kills every tweens associated to the given target and tween type. Will
+	 * also kill every timelines containing a tween associated to the given
+	 * target and tween type.
+	 */
+	public void killTarget(Object target, int tweenType) {
+		for (int i=0, n=objects.size(); i<n; i++) {
+			BaseTween<?> obj = objects.get(i);
+			obj.killTarget(target, tweenType);
+		}
+	}
+
+	/**
+	 * Increases the minimum capacity of the manager. Defaults to 20.
+	 */
+	public void ensureCapacity(int minCapacity) {
+		objects.ensureCapacity(minCapacity);
+	}
+
+	/**
+	 * Pauses the manager. Further update calls won't have any effect.
+	 */
+	public void pause() {
+		isPaused = true;
+	}
+
+	/**
+	 * Resumes the manager, if paused.
+	 */
+	public void resume() {
+		isPaused = false;
+	}
+
+	/**
+	 * Updates every tweens with a delta time ang handles the tween life-cycles
+	 * automatically. If a tween is finished, it will be removed from the
+	 * manager. The delta time represents the elapsed time between now and the
+	 * last update call. Each tween or timeline manages its local time, and adds
+	 * this delta to its local time to update itself.
+	 * <p/>
+	 *
+	 * Slow motion, fast motion and backward play can be easily achieved by
+	 * tweaking this delta time. Multiply it by -1 to play the animation
+	 * backward, or by 0.5 to play it twice slower than its normal speed.
+	 */
+	public void update(float delta) {
+		for (int i=objects.size()-1; i>=0; i--) {
+			BaseTween<?> obj = objects.get(i);
+			if (obj.isFinished() && obj.isAutoRemoveEnabled) {
+				objects.remove(i);
+				obj.free();
+			}
+		}
+
+		if (!isPaused) {
+			if (delta >= 0) {
+				for (int i=0, n=objects.size(); i<n; i++) objects.get(i).update(delta);
+			} else {
+				for (int i=objects.size()-1; i>=0; i--) objects.get(i).update(delta);
+			}
+		}
+	}
+
+	/**
+	 * Gets the number of managed objects. An object may be a tween or a
+	 * timeline. Note that a timeline only counts for 1 object, since it
+	 * manages its children itself.
+	 * <p/>
+	 * To get the count of running tweens, see {@link #getRunningTweensCount()}.
+	 */
+	public int size() {
+		return objects.size();
+	}
+
+	/**
+	 * Gets the number of running tweens. This number includes the tweens
+	 * located inside timelines (and nested timelines).
+	 * <p/>
+	 * <b>Provided for debug purpose only.</b>
+	 */
+	public int getRunningTweensCount() {
+		return getTweensCount(objects);
+	}
+
+	/**
+	 * Gets the number of running timelines. This number includes the timelines
+	 * nested inside other timelines.
+	 * <p/>
+	 * <b>Provided for debug purpose only.</b>
+	 */
+	public int getRunningTimelinesCount() {
+		return getTimelinesCount(objects);
+	}
+
+	/**
+	 * Gets an immutable list of every managed object.
+	 * <p/>
+	 * <b>Provided for debug purpose only.</b>
+	 */
+	public List<BaseTween<?>> getObjects() {
+		return Collections.unmodifiableList(objects);
+	}
+
+	// -------------------------------------------------------------------------
+	// Helpers
+	// -------------------------------------------------------------------------
+
+	private static int getTweensCount(List<BaseTween<?>> objs) {
+		int cnt = 0;
+		for (int i=0, n=objs.size(); i<n; i++) {
+			BaseTween<?> obj = objs.get(i);
+			if (obj instanceof Tween) cnt += 1;
+			else cnt += getTweensCount(((Timeline)obj).getChildren());
+		}
+		return cnt;
+	}
+
+	private static int getTimelinesCount(List<BaseTween<?>> objs) {
+		int cnt = 0;
+		for (int i=0, n=objs.size(); i<n; i++) {
+			BaseTween<?> obj = objs.get(i);
+			if (obj instanceof Timeline) {
+				cnt += 1 + getTimelinesCount(((Timeline)obj).getChildren());
+			}
+		}
+		return cnt;
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenPath.java b/java/api/src/aurelienribon/tweenengine/TweenPath.java
new file mode 100644
index 0000000..724741b
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenPath.java
@@ -0,0 +1,22 @@
+package aurelienribon.tweenengine;
+
+/**
+ * Base class for every paths. You can create your own paths and directly use
+ * them in the Tween engine by inheriting from this class.
+ *
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public interface TweenPath {
+
+	/**
+	 * Computes the next value of the interpolation, based on its waypoints and
+	 * the current progress.
+	 *
+	 * @param t The progress of the interpolation, between 0 and 1. May be out
+	 * of these bounds if the easing equation involves some kind of rebounds.
+	 * @param points The waypoints of the tween, from start to target values.
+	 * @param pointsCnt The number of valid points in the array.
+	 * @return The next value of the interpolation.
+	 */
+	public float compute(float t, float[] points, int pointsCnt);
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenPaths.java b/java/api/src/aurelienribon/tweenengine/TweenPaths.java
new file mode 100644
index 0000000..dbea075
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenPaths.java
@@ -0,0 +1,14 @@
+package aurelienribon.tweenengine;
+
+import aurelienribon.tweenengine.paths.CatmullRom;
+import aurelienribon.tweenengine.paths.Linear;
+
+/**
+ * Collection of built-in paths.
+ *
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public interface TweenPaths {
+	public static final Linear linear = new Linear();
+	public static final CatmullRom catmullRom = new CatmullRom();
+}
diff --git a/java/api/src/aurelienribon/tweenengine/TweenUtils.java b/java/api/src/aurelienribon/tweenengine/TweenUtils.java
new file mode 100644
index 0000000..6c1b12c
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/TweenUtils.java
@@ -0,0 +1,53 @@
+package aurelienribon.tweenengine;
+
+import aurelienribon.tweenengine.equations.Back;
+import aurelienribon.tweenengine.equations.Bounce;
+import aurelienribon.tweenengine.equations.Circ;
+import aurelienribon.tweenengine.equations.Cubic;
+import aurelienribon.tweenengine.equations.Elastic;
+import aurelienribon.tweenengine.equations.Expo;
+import aurelienribon.tweenengine.equations.Linear;
+import aurelienribon.tweenengine.equations.Quad;
+import aurelienribon.tweenengine.equations.Quart;
+import aurelienribon.tweenengine.equations.Quint;
+import aurelienribon.tweenengine.equations.Sine;
+
+/**
+ * Collection of miscellaneous utilities.
+ *
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public class TweenUtils {
+	private static TweenEquation[] easings;
+
+	/**
+	 * Takes an easing name and gives you the corresponding TweenEquation.
+	 * You probably won't need this, but tools will love that.
+	 *
+	 * @param easingName The name of an easing, like "Quad.INOUT".
+	 * @return The parsed equation, or null if there is no match.
+	 */
+	public static TweenEquation parseEasing(String easingName) {
+		if (easings == null) {
+			easings = new TweenEquation[] {Linear.INOUT,
+				Quad.IN, Quad.OUT, Quad.INOUT,
+				Cubic.IN, Cubic.OUT, Cubic.INOUT,
+				Quart.IN, Quart.OUT, Quart.INOUT,
+				Quint.IN, Quint.OUT, Quint.INOUT,
+				Circ.IN, Circ.OUT, Circ.INOUT,
+				Sine.IN, Sine.OUT, Sine.INOUT,
+				Expo.IN, Expo.OUT, Expo.INOUT,
+				Back.IN, Back.OUT, Back.INOUT,
+				Bounce.IN, Bounce.OUT, Bounce.INOUT,
+				Elastic.IN, Elastic.OUT, Elastic.INOUT
+			};
+		}
+
+		for (int i=0; i<easings.length; i++) {
+			if (easingName.equals(easings[i].toString()))
+				return easings[i];
+		}
+
+		return null;
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Back.java b/java/api/src/aurelienribon/tweenengine/equations/Back.java
new file mode 100644
index 0000000..46385ed
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Back.java
@@ -0,0 +1,59 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Back extends TweenEquation {
+	public static final Back IN = new Back() {
+		@Override
+		public final float compute(float t) {
+			float s = param_s;
+			return t*t*((s+1)*t - s);
+		}
+
+		@Override
+		public String toString() {
+			return "Back.IN";
+		}
+	};
+
+	public static final Back OUT = new Back() {
+		@Override
+		public final float compute(float t) {
+			float s = param_s;
+			return (t-=1)*t*((s+1)*t + s) + 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Back.OUT";
+		}
+	};
+
+	public static final Back INOUT = new Back() {
+		@Override
+		public final float compute(float t) {
+			float s = param_s;
+			if ((t*=2) < 1) return 0.5f*(t*t*(((s*=(1.525f))+1)*t - s));
+			return 0.5f*((t-=2)*t*(((s*=(1.525f))+1)*t + s) + 2);
+		}
+
+		@Override
+		public String toString() {
+			return "Back.INOUT";
+		}
+	};
+
+	// -------------------------------------------------------------------------
+
+	protected float param_s = 1.70158f;
+
+	public Back s(float s) {
+		param_s = s;
+		return this;
+	}
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Bounce.java b/java/api/src/aurelienribon/tweenengine/equations/Bounce.java
new file mode 100644
index 0000000..3cfc4e3
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Bounce.java
@@ -0,0 +1,55 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Bounce extends TweenEquation {
+	public static final Bounce IN = new Bounce() {
+		@Override
+		public final float compute(float t) {
+			return 1 - OUT.compute(1-t);
+		}
+
+		@Override
+		public String toString() {
+			return "Bounce.IN";
+		}
+	};
+
+	public static final Bounce OUT = new Bounce() {
+		@Override
+		public final float compute(float t) {
+			if (t < (1/2.75)) {
+				return 7.5625f*t*t;
+			} else if (t < (2/2.75)) {
+				return 7.5625f*(t-=(1.5f/2.75f))*t + .75f;
+			} else if (t < (2.5/2.75)) {
+				return 7.5625f*(t-=(2.25f/2.75f))*t + .9375f;
+			} else {
+				return 7.5625f*(t-=(2.625f/2.75f))*t + .984375f;
+			}
+		}
+
+		@Override
+		public String toString() {
+			return "Bounce.OUT";
+		}
+	};
+
+	public static final Bounce INOUT = new Bounce() {
+		@Override
+		public final float compute(float t) {
+			if (t < 0.5f) return IN.compute(t*2) * .5f;
+			else return OUT.compute(t*2-1) * .5f + 0.5f;
+		}
+
+		@Override
+		public String toString() {
+			return "Bounce.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Circ.java b/java/api/src/aurelienribon/tweenengine/equations/Circ.java
new file mode 100644
index 0000000..63a0145
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Circ.java
@@ -0,0 +1,47 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Circ extends TweenEquation {
+	public static final Circ IN = new Circ() {
+		@Override
+		public final float compute(float t) {
+			return (float) -Math.sqrt(1 - t*t) - 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Circ.IN";
+		}
+	};
+
+	public static final Circ OUT = new Circ() {
+		@Override
+		public final float compute(float t) {
+			return (float) Math.sqrt(1 - (t-=1)*t);
+		}
+
+		@Override
+		public String toString() {
+			return "Circ.OUT";
+		}
+	};
+
+	public static final Circ INOUT = new Circ() {
+		@Override
+		public final float compute(float t) {
+			if ((t*=2) < 1) return -0.5f * ((float)Math.sqrt(1 - t*t) - 1);
+			return 0.5f * ((float)Math.sqrt(1 - (t-=2)*t) + 1);
+		}
+
+		@Override
+		public String toString() {
+			return "Circ.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Cubic.java b/java/api/src/aurelienribon/tweenengine/equations/Cubic.java
new file mode 100644
index 0000000..9d86988
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Cubic.java
@@ -0,0 +1,47 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Cubic extends TweenEquation {
+	public static final Cubic IN = new Cubic() {
+		@Override
+		public final float compute(float t) {
+			return t*t*t;
+		}
+
+		@Override
+		public String toString() {
+			return "Cubic.IN";
+		}
+	};
+
+	public static final Cubic OUT = new Cubic() {
+		@Override
+		public final float compute(float t) {
+			return (t-=1)*t*t + 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Cubic.OUT";
+		}
+	};
+
+	public static final Cubic INOUT = new Cubic() {
+		@Override
+		public final float compute(float t) {
+			if ((t*=2) < 1) return 0.5f*t*t*t;
+			return 0.5f * ((t-=2)*t*t + 2);
+		}
+
+		@Override
+		public String toString() {
+			return "Cubic.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Elastic.java b/java/api/src/aurelienribon/tweenengine/equations/Elastic.java
new file mode 100644
index 0000000..925993d
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Elastic.java
@@ -0,0 +1,86 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Elastic extends TweenEquation {
+	private static final float PI = 3.14159265f;
+
+	public static final Elastic IN = new Elastic() {
+		@Override
+		public final float compute(float t) {
+			float a = param_a;
+			float p = param_p;
+			if (t==0) return 0;  if (t==1) return 1; if (!setP) p=.3f;
+			float s;
+			if (!setA || a < 1) { a=1; s=p/4; }
+			else s = p/(2*PI) * (float)Math.asin(1/a);
+			return -(a*(float)Math.pow(2,10*(t-=1)) * (float)Math.sin( (t-s)*(2*PI)/p ));
+		}
+
+		@Override
+		public String toString() {
+			return "Elastic.IN";
+		}
+	};
+
+	public static final Elastic OUT = new Elastic() {
+		@Override
+		public final float compute(float t) {
+			float a = param_a;
+			float p = param_p;
+			if (t==0) return 0;  if (t==1) return 1; if (!setP) p=.3f;
+			float s;
+			if (!setA || a < 1) { a=1; s=p/4; }
+			else s = p/(2*PI) * (float)Math.asin(1/a);
+			return a*(float)Math.pow(2,-10*t) * (float)Math.sin( (t-s)*(2*PI)/p ) + 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Elastic.OUT";
+		}
+	};
+
+	public static final Elastic INOUT = new Elastic() {
+		@Override
+		public final float compute(float t) {
+			float a = param_a;
+			float p = param_p;
+			if (t==0) return 0;  if ((t*=2)==2) return 1; if (!setP) p=.3f*1.5f;
+			float s;
+			if (!setA || a < 1) { a=1; s=p/4; }
+			else s = p/(2*PI) * (float)Math.asin(1/a);
+			if (t < 1) return -.5f*(a*(float)Math.pow(2,10*(t-=1)) * (float)Math.sin( (t-s)*(2*PI)/p ));
+			return a*(float)Math.pow(2,-10*(t-=1)) * (float)Math.sin( (t-s)*(2*PI)/p )*.5f + 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Elastic.INOUT";
+		}
+	};
+
+	// -------------------------------------------------------------------------
+
+	protected float param_a;
+	protected float param_p;
+	protected boolean setA = false;
+	protected boolean setP = false;
+
+	public Elastic a(float a) {
+		param_a = a;
+		this.setA = true;
+		return this;
+	}
+
+	public Elastic p(float p) {
+		param_p = p;
+		this.setP = true;
+		return this;
+	}
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Expo.java b/java/api/src/aurelienribon/tweenengine/equations/Expo.java
new file mode 100644
index 0000000..38a6a00
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Expo.java
@@ -0,0 +1,49 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Expo extends TweenEquation {
+	public static final Expo IN = new Expo() {
+		@Override
+		public final float compute(float t) {
+			return (t==0) ? 0 : (float) Math.pow(2, 10 * (t - 1));
+		}
+
+		@Override
+		public String toString() {
+			return "Expo.IN";
+		}
+	};
+
+	public static final Expo OUT = new Expo() {
+		@Override
+		public final float compute(float t) {
+			return (t==1) ? 1 : -(float) Math.pow(2, -10 * t) + 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Expo.OUT";
+		}
+	};
+
+	public static final Expo INOUT = new Expo() {
+		@Override
+		public final float compute(float t) {
+			if (t==0) return 0;
+			if (t==1) return 1;
+			if ((t*=2) < 1) return 0.5f * (float) Math.pow(2, 10 * (t - 1));
+			return 0.5f * (-(float)Math.pow(2, -10 * --t) + 2);
+		}
+
+		@Override
+		public String toString() {
+			return "Expo.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Linear.java b/java/api/src/aurelienribon/tweenengine/equations/Linear.java
new file mode 100644
index 0000000..1f95103
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Linear.java
@@ -0,0 +1,22 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Linear extends TweenEquation {
+	public static final Linear INOUT = new Linear() {
+		@Override
+		public float compute(float t) {
+			return t;
+		}
+
+		@Override
+		public String toString() {
+			return "Linear.INOUT";
+		}
+	};
+}
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Quad.java b/java/api/src/aurelienribon/tweenengine/equations/Quad.java
new file mode 100644
index 0000000..d015112
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Quad.java
@@ -0,0 +1,47 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Quad extends TweenEquation {
+	public static final Quad IN = new Quad() {
+		@Override
+		public final float compute(float t) {
+			return t*t;
+		}
+
+		@Override
+		public String toString() {
+			return "Quad.IN";
+		}
+	};
+
+	public static final Quad OUT = new Quad() {
+		@Override
+		public final float compute(float t) {
+			return -t*(t-2);
+		}
+
+		@Override
+		public String toString() {
+			return "Quad.OUT";
+		}
+	};
+
+	public static final Quad INOUT = new Quad() {
+		@Override
+		public final float compute(float t) {
+			if ((t*=2) < 1) return 0.5f*t*t;
+			return -0.5f * ((--t)*(t-2) - 1);
+		}
+
+		@Override
+		public String toString() {
+			return "Quad.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Quart.java b/java/api/src/aurelienribon/tweenengine/equations/Quart.java
new file mode 100644
index 0000000..826eebb
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Quart.java
@@ -0,0 +1,47 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Quart extends TweenEquation {
+	public static final Quart IN = new Quart() {
+		@Override
+		public final float compute(float t) {
+			return t*t*t*t;
+		}
+
+		@Override
+		public String toString() {
+			return "Quart.IN";
+		}
+	};
+
+	public static final Quart OUT = new Quart() {
+		@Override
+		public final float compute(float t) {
+			return -((t-=1)*t*t*t - 1);
+		}
+
+		@Override
+		public String toString() {
+			return "Quart.OUT";
+		}
+	};
+
+	public static final Quart INOUT = new Quart() {
+		@Override
+		public final float compute(float t) {
+			if ((t*=2) < 1) return 0.5f*t*t*t*t;
+			return -0.5f * ((t-=2)*t*t*t - 2);
+		}
+
+		@Override
+		public String toString() {
+			return "Quart.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Quint.java b/java/api/src/aurelienribon/tweenengine/equations/Quint.java
new file mode 100644
index 0000000..80e2412
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Quint.java
@@ -0,0 +1,47 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Quint extends TweenEquation {
+	public static final Quint IN = new Quint() {
+		@Override
+		public final float compute(float t) {
+			return t*t*t*t*t;
+		}
+
+		@Override
+		public String toString() {
+			return "Quint.IN";
+		}
+	};
+
+	public static final Quint OUT = new Quint() {
+		@Override
+		public final float compute(float t) {
+			return (t-=1)*t*t*t*t + 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Quint.OUT";
+		}
+	};
+
+	public static final Quint INOUT = new Quint() {
+		@Override
+		public final float compute(float t) {
+			if ((t*=2) < 1) return 0.5f*t*t*t*t*t;
+			return 0.5f*((t-=2)*t*t*t*t + 2);
+		}
+
+		@Override
+		public String toString() {
+			return "Quint.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/equations/Sine.java b/java/api/src/aurelienribon/tweenengine/equations/Sine.java
new file mode 100644
index 0000000..a4aec5c
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/equations/Sine.java
@@ -0,0 +1,48 @@
+package aurelienribon.tweenengine.equations;
+
+import aurelienribon.tweenengine.TweenEquation;
+
+/**
+ * Easing equation based on Robert Penner's work:
+ * http://robertpenner.com/easing/
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public abstract class Sine extends TweenEquation {
+	private static final float PI = 3.14159265f;
+
+	public static final Sine IN = new Sine() {
+		@Override
+		public final float compute(float t) {
+			return (float) -Math.cos(t * (PI/2)) + 1;
+		}
+
+		@Override
+		public String toString() {
+			return "Sine.IN";
+		}
+	};
+
+	public static final Sine OUT = new Sine() {
+		@Override
+		public final float compute(float t) {
+			return (float) Math.sin(t * (PI/2));
+		}
+
+		@Override
+		public String toString() {
+			return "Sine.OUT";
+		}
+	};
+
+	public static final Sine INOUT = new Sine() {
+		@Override
+		public final float compute(float t) {
+			return -0.5f * ((float) Math.cos(PI*t) - 1);
+		}
+
+		@Override
+		public String toString() {
+			return "Sine.INOUT";
+		}
+	};
+}
\ No newline at end of file
diff --git a/java/api/src/aurelienribon/tweenengine/paths/CatmullRom.java b/java/api/src/aurelienribon/tweenengine/paths/CatmullRom.java
new file mode 100644
index 0000000..5dedae4
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/paths/CatmullRom.java
@@ -0,0 +1,39 @@
+package aurelienribon.tweenengine.paths;
+
+import aurelienribon.tweenengine.TweenPath;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public class CatmullRom implements TweenPath {
+	@Override
+	public float compute(float t, float[] points, int pointsCnt) {
+		int segment = (int) Math.floor((pointsCnt-1) * t);
+		segment = Math.max(segment, 0);
+		segment = Math.min(segment, pointsCnt-2);
+
+		t = t * (pointsCnt-1) - segment;
+
+		if (segment == 0) {
+			return catmullRomSpline(points[0], points[0], points[1], points[2], t);
+		}
+
+		if (segment == pointsCnt-2) {
+			return catmullRomSpline(points[pointsCnt-3], points[pointsCnt-2], points[pointsCnt-1], points[pointsCnt-1], t);
+		}
+
+		return catmullRomSpline(points[segment-1], points[segment], points[segment+1], points[segment+2], t);
+	}
+
+	private float catmullRomSpline(float a, float b, float c, float d, float t) {
+		float t1 = (c - a) * 0.5f;
+		float t2 = (d - b) * 0.5f;
+
+		float h1 = +2 * t * t * t - 3 * t * t + 1;
+		float h2 = -2 * t * t * t + 3 * t * t;
+		float h3 = t * t * t - 2 * t * t + t;
+		float h4 = t * t * t - t * t;
+
+		return b * h1 + c * h2 + t1 * h3 + t2 * h4;
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/paths/Linear.java b/java/api/src/aurelienribon/tweenengine/paths/Linear.java
new file mode 100644
index 0000000..a21b7b5
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/paths/Linear.java
@@ -0,0 +1,19 @@
+package aurelienribon.tweenengine.paths;
+
+import aurelienribon.tweenengine.TweenPath;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public class Linear implements TweenPath {
+	@Override
+	public float compute(float t, float[] points, int pointsCnt) {
+		int segment = (int) Math.floor((pointsCnt-1) * t);
+		segment = Math.max(segment, 0);
+		segment = Math.min(segment, pointsCnt-2);
+
+		t = t * (pointsCnt-1) - segment;
+
+		return points[segment] + t * (points[segment+1] - points[segment]);
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/primitives/MutableFloat.java b/java/api/src/aurelienribon/tweenengine/primitives/MutableFloat.java
new file mode 100644
index 0000000..a2683b9
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/primitives/MutableFloat.java
@@ -0,0 +1,34 @@
+package aurelienribon.tweenengine.primitives;
+
+import aurelienribon.tweenengine.TweenAccessor;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public class MutableFloat extends Number implements TweenAccessor<MutableFloat> {
+	private float value;
+
+	public MutableFloat(float value) {
+		this.value = value;
+	}
+
+	public void setValue(float value) {
+		this.value = value;
+	}
+
+	@Override public int intValue() {return (int) value;}
+	@Override public long longValue() {return (long) value;}
+	@Override public float floatValue() {return (float) value;}
+	@Override public double doubleValue() {return (double) value;}
+
+	@Override
+	public int getValues(MutableFloat target, int tweenType, float[] returnValues) {
+		returnValues[0] = target.value;
+		return 1;
+	}
+
+	@Override
+	public void setValues(MutableFloat target, int tweenType, float[] newValues) {
+		target.value = newValues[0];
+	}
+}
diff --git a/java/api/src/aurelienribon/tweenengine/primitives/MutableInteger.java b/java/api/src/aurelienribon/tweenengine/primitives/MutableInteger.java
new file mode 100644
index 0000000..a7ab932
--- /dev/null
+++ b/java/api/src/aurelienribon/tweenengine/primitives/MutableInteger.java
@@ -0,0 +1,34 @@
+package aurelienribon.tweenengine.primitives;
+
+import aurelienribon.tweenengine.TweenAccessor;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com/
+ */
+public class MutableInteger extends Number implements TweenAccessor<MutableInteger> {
+	private int value;
+
+	public MutableInteger(int value) {
+		this.value = value;
+	}
+
+	public void setValue(int value) {
+		this.value = value;
+	}
+
+	@Override public int intValue() {return (int) value;}
+	@Override public long longValue() {return (long) value;}
+	@Override public float floatValue() {return (float) value;}
+	@Override public double doubleValue() {return (double) value;}
+
+	@Override
+	public int getValues(MutableInteger target, int tweenType, float[] returnValues) {
+		returnValues[0] = target.value;
+		return 1;
+	}
+
+	@Override
+	public void setValues(MutableInteger target, int tweenType, float[] newValues) {
+		target.value = (int) newValues[0];
+	}
+}
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/Sprite.java b/java/applets/src/aurelienribon/tweenengine/applets/Sprite.java
new file mode 100644
index 0000000..a86735b
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/Sprite.java
@@ -0,0 +1,74 @@
+package aurelienribon.tweenengine.applets;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com
+ */
+public class Sprite {
+	private BufferedImage image;
+	private float x = 0;
+	private float y = 0;
+	private float scaleX = 1;
+	private float scaleY = 1;
+	private boolean isCentered = true;
+	private boolean isVisible = true;
+
+	public Sprite(String gfxName) {
+		try {
+			image = ImageIO.read(Sprite.class.getResource("/aurelienribon/tweenengine/applets/gfx/" + gfxName));
+		} catch (IOException ex) {
+		}
+	}
+
+	public void draw(Graphics2D gg) {
+		if (!isVisible) return;
+		gg = (Graphics2D) gg.create();
+		gg.translate(x, y);
+		gg.scale(scaleX, scaleY);
+		gg.drawImage(image, null, isCentered ? -image.getWidth()/2 : 0, isCentered ? -image.getHeight()/2 : 0);
+		gg.dispose();
+	}
+
+	public void setPosition(float x, float y) {
+		this.x = x;
+		this.y = y;
+	}
+
+	public void setScale(float scaleX, float scaleY) {
+		this.scaleX = scaleX;
+		this.scaleY = scaleY;
+	}
+
+	public Sprite setCentered(boolean isCentered) {
+		this.isCentered = isCentered;
+		return this;
+	}
+
+	public void setVisible(boolean isVisible) {
+		this.isVisible = isVisible;
+	}
+
+	public float getX() {
+		return x;
+	}
+
+	public float getY() {
+		return y;
+	}
+
+	public float getScaleX() {
+		return scaleX;
+	}
+
+	public float getScaleY() {
+		return scaleY;
+	}
+
+	public boolean isVisible() {
+		return isVisible;
+	}
+}
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/SpriteAccessor.java b/java/applets/src/aurelienribon/tweenengine/applets/SpriteAccessor.java
new file mode 100644
index 0000000..243468c
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/SpriteAccessor.java
@@ -0,0 +1,43 @@
+package aurelienribon.tweenengine.applets;
+
+import aurelienribon.tweenengine.TweenAccessor;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com
+ */
+public class SpriteAccessor implements TweenAccessor<Sprite> {
+	public static final int POSITION_XY = 1;
+	public static final int SCALE_XY = 2;
+	public static final int VISIBILITY = 3;
+
+	@Override
+	public int getValues(Sprite target, int tweenType, float[] returnValues) {
+		switch (tweenType) {
+			case POSITION_XY:
+				returnValues[0] = target.getX();
+				returnValues[1] = target.getY();
+				return 2;
+
+			case SCALE_XY:
+				returnValues[0] = target.getScaleX();
+				returnValues[1] = target.getScaleY();
+				return 2;
+
+			case VISIBILITY:
+				returnValues[0] = target.isVisible() ? 1 : 0;
+				return 1;
+
+			default: assert false; return -1;
+		}
+	}
+
+	@Override
+	public void setValues(Sprite target, int tweenType, float[] newValues) {
+		switch (tweenType) {
+			case POSITION_XY: target.setPosition(newValues[0], newValues[1]); break;
+			case SCALE_XY: target.setScale(newValues[0], newValues[1]); break;
+			case VISIBILITY: target.setVisible(newValues[0] > 0); break;
+			default: assert false;
+		}
+	}
+}
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/Theme.java b/java/applets/src/aurelienribon/tweenengine/applets/Theme.java
new file mode 100644
index 0000000..90e9877
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/Theme.java
@@ -0,0 +1,75 @@
+package aurelienribon.tweenengine.applets;
+
+import aurelienribon.utils.swing.GroupBorder;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Font;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JSlider;
+import javax.swing.border.Border;
+
+public class Theme {
+    public static final Color MAIN_BACKGROUND = new Color(0x444444);
+    public static final Color MAIN_FOREGROUND = new Color(0xF0F0F0);
+    public static final Color MAIN_ALT_BACKGROUND = new Color(0x707070);
+    public static final Color MAIN_ALT_FOREGROUND = new Color(0xF0F0F0);
+
+    public static final Color HEADER_BACKGROUND = new Color(0x707070);
+    public static final Color HEADER_FOREGROUND = new Color(0xF0F0F0);
+
+    public static final Color TEXTAREA_BACKGROUND = new Color(0x333333);
+    public static final Color TEXTAREA_FOREGROUND = new Color(0xF0F0F0);
+    public static final Color TEXTAREA_SELECTED_BACKGROUND = new Color(0x808080);
+    public static final Color TEXTAREA_SELECTED_FOREGROUND = new Color(0xF0F0F0);
+
+    public static final Color CONSOLE_BACKGROUND = new Color(0xA5A5A5);
+    public static final Color CONSOLE_FOREGROUND = new Color(0x000000);
+
+    public static final Color SEPARATOR = new Color(0xB5B5B5);
+
+	public static void apply(Component cmp) {
+		if (cmp instanceof JComponent) {
+			JComponent c = (JComponent) cmp;
+			Border border = c.getBorder();
+			if (border != null && border instanceof GroupBorder) {
+				Font font = c.getFont();
+				c.setFont(new Font(font.getFamily(), Font.BOLD, font.getSize()));
+				c.setBackground(MAIN_ALT_BACKGROUND);
+				c.setForeground(MAIN_ALT_FOREGROUND);
+				c.setOpaque(false);
+			}
+		}
+
+		if (cmp instanceof JLabel) {
+			JLabel c = (JLabel) cmp;
+			c.setForeground(MAIN_FOREGROUND);
+		}
+
+		if (cmp instanceof JCheckBox) {
+			JCheckBox c = (JCheckBox) cmp;
+			c.setForeground(MAIN_FOREGROUND);
+			c.setOpaque(false);
+		}
+
+		if (cmp instanceof Container) {
+			Container c = (Container) cmp;
+			for (Component child : c.getComponents())
+				apply(child);
+		}
+
+		if (cmp instanceof JButton) {
+			JButton c = (JButton) cmp;
+			c.setOpaque(false);
+		}
+
+		if (cmp instanceof JSlider) {
+			JSlider c = (JSlider) cmp;
+			c.setOpaque(false);
+			c.setForeground(MAIN_FOREGROUND);
+		}
+	}
+}
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/TimelineApplet.form b/java/applets/src/aurelienribon/tweenengine/applets/TimelineApplet.form
new file mode 100644
index 0000000..272227f
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/TimelineApplet.form
@@ -0,0 +1,422 @@
+<?xml version="1.1" encoding="UTF-8" ?>
+
+<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JAppletFormInfo">
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+  </AuxValues>
+
+  <Layout>
+    <DimensionLayout dim="0">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" alignment="0" attributes="0">
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="jPanel1" alignment="1" max="32767" attributes="0"/>
+                  <Component id="jPanel4" alignment="0" max="32767" attributes="1"/>
+                  <Group type="102" alignment="1" attributes="0">
+                      <Component id="canvasWrapper" pref="448" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="jPanel3" min="-2" max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+    <DimensionLayout dim="1">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" alignment="1" attributes="0">
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="canvasWrapper" pref="258" max="32767" attributes="0"/>
+                  <Component id="jPanel3" alignment="0" max="32767" attributes="0"/>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
+              <Component id="jPanel4" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+              <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+  </Layout>
+  <SubComponents>
+    <Container class="javax.swing.JPanel" name="jPanel1">
+      <Properties>
+        <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+            <PropertyBean type="aurelienribon.utils.swing.GroupBorder"/>
+        </Property>
+      </Properties>
+
+      <Layout>
+        <DimensionLayout dim="0">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
+                      <Component id="jScrollPane1" alignment="0" pref="598" max="32767" attributes="0"/>
+                      <Group type="102" alignment="0" attributes="0">
+                          <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace pref="273" max="32767" attributes="0"/>
+                          <Component id="jLabel9" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+        <DimensionLayout dim="1">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="3" attributes="0">
+                      <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="jLabel9" alignment="3" min="-2" max="-2" attributes="0"/>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="jScrollPane1" pref="214" max="32767" attributes="0"/>
+                  <EmptySpace max="-2" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+      </Layout>
+      <SubComponents>
+        <Container class="javax.swing.JScrollPane" name="jScrollPane1">
+          <Properties>
+            <Property name="verticalScrollBarPolicy" type="int" value="22"/>
+          </Properties>
+          <AuxValues>
+            <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+          </AuxValues>
+
+          <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+          <SubComponents>
+            <Component class="javax.swing.JTextArea" name="resultArea">
+              <Properties>
+                <Property name="columns" type="int" value="20"/>
+              </Properties>
+            </Component>
+          </SubComponents>
+        </Container>
+        <Component class="javax.swing.JLabel" name="jLabel1">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Java code:"/>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JLabel" name="jLabel9">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="&lt;html&gt;&#xa;Universal Tween Engine v6.0.0 - &lt;font color=&quot;#77C8FF&quot;&gt;www.aurelienribon.com&lt;/font&gt;"/>
+          </Properties>
+        </Component>
+      </SubComponents>
+    </Container>
+    <Container class="javax.swing.JPanel" name="canvasWrapper">
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+    </Container>
+    <Container class="javax.swing.JPanel" name="jPanel3">
+      <Properties>
+        <Property name="opaque" type="boolean" value="false"/>
+      </Properties>
+
+      <Layout>
+        <DimensionLayout dim="0">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Component id="jLabel2" alignment="0" pref="164" max="32767" attributes="0"/>
+              <Component id="jPanel5" alignment="0" max="32767" attributes="0"/>
+              <Component id="jPanel2" alignment="0" max="32767" attributes="1"/>
+          </Group>
+        </DimensionLayout>
+        <DimensionLayout dim="1">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" attributes="0">
+                  <Component id="jLabel2" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace max="32767" attributes="0"/>
+                  <Component id="jPanel2" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="jPanel5" min="-2" max="-2" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+      </Layout>
+      <SubComponents>
+        <Component class="javax.swing.JLabel" name="jLabel2">
+          <Properties>
+            <Property name="horizontalAlignment" type="int" value="0"/>
+            <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+              <Image iconType="3" name="/aurelienribon/tweenengine/applets/gfx/logo-timeline.png"/>
+            </Property>
+          </Properties>
+        </Component>
+        <Container class="javax.swing.JPanel" name="jPanel2">
+          <Properties>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+              <PropertyBean type="aurelienribon.utils.swing.GroupBorder">
+                <Property name="title" type="java.lang.String" value="Timeline options"/>
+              </PropertyBean>
+            </Property>
+          </Properties>
+
+          <Layout>
+            <DimensionLayout dim="0">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="1" attributes="0">
+                      <EmptySpace max="32767" attributes="0"/>
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Component id="yoyoChk" alignment="1" min="-2" max="-2" attributes="0"/>
+                          <Group type="102" alignment="1" attributes="0">
+                              <Component id="jLabel4" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                              <Component id="rptSpinner" min="-2" pref="66" max="-2" attributes="0"/>
+                          </Group>
+                          <Group type="102" alignment="1" attributes="0">
+                              <Component id="jLabel6" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                              <Component id="rptDelaySpinner" min="-2" pref="66" max="-2" attributes="0"/>
+                          </Group>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+            <DimensionLayout dim="1">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="rptSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel4" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="rptDelaySpinner" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel6" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace type="separate" max="-2" attributes="0"/>
+                      <Component id="yoyoChk" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="32767" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+          </Layout>
+          <SubComponents>
+            <Component class="javax.swing.JLabel" name="jLabel4">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Repetitions:"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JSpinner" name="rptSpinner">
+              <Properties>
+                <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
+                  <SpinnerModel initial="2" minimum="0" numberType="java.lang.Integer" stepSize="1" type="number"/>
+                </Property>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JCheckBox" name="yoyoChk">
+              <Properties>
+                <Property name="selected" type="boolean" value="true"/>
+                <Property name="text" type="java.lang.String" value="Yoyo repetitions"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JLabel" name="jLabel6">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Repeat delay:"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JSpinner" name="rptDelaySpinner">
+              <Properties>
+                <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
+                  <SpinnerModel initial="500" minimum="0" numberType="java.lang.Integer" stepSize="100" type="number"/>
+                </Property>
+              </Properties>
+            </Component>
+          </SubComponents>
+        </Container>
+        <Container class="javax.swing.JPanel" name="jPanel5">
+          <Properties>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+              <PropertyBean type="aurelienribon.utils.swing.GroupBorder">
+                <Property name="title" type="java.lang.String" value="Animation speed"/>
+              </PropertyBean>
+            </Property>
+          </Properties>
+
+          <Layout>
+            <DimensionLayout dim="0">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="speedSlider" pref="144" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+            <DimensionLayout dim="1">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="speedSlider" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="32767" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+          </Layout>
+          <SubComponents>
+            <Component class="javax.swing.JSlider" name="speedSlider">
+              <Properties>
+                <Property name="majorTickSpacing" type="int" value="100"/>
+                <Property name="maximum" type="int" value="300"/>
+                <Property name="minimum" type="int" value="-300"/>
+                <Property name="paintLabels" type="boolean" value="true"/>
+                <Property name="paintTicks" type="boolean" value="true"/>
+                <Property name="value" type="int" value="100"/>
+              </Properties>
+            </Component>
+          </SubComponents>
+        </Container>
+      </SubComponents>
+    </Container>
+    <Container class="javax.swing.JPanel" name="jPanel4">
+      <Properties>
+        <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+            <PropertyBean type="aurelienribon.utils.swing.GroupBorder"/>
+        </Property>
+      </Properties>
+
+      <Layout>
+        <DimensionLayout dim="0">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="1" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
+                      <Component id="restartBtn" linkSize="1" min="-2" max="-2" attributes="0"/>
+                      <Component id="reverseBtn" linkSize="1" alignment="0" min="-2" max="-2" attributes="0"/>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" max="-2" attributes="0">
+                      <Group type="102" alignment="0" attributes="1">
+                          <Component id="resumeBtn" linkSize="1" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace max="32767" attributes="0"/>
+                          <Component id="jLabel3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <Group type="102" alignment="0" attributes="0">
+                          <Component id="pauseBtn" linkSize="1" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace type="separate" max="-2" attributes="0"/>
+                          <Component id="jLabel5" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
+                      <Component id="totalTimeSlider" pref="395" max="32767" attributes="0"/>
+                      <Component id="iterationTimeSlider" alignment="0" pref="395" max="32767" attributes="0"/>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+        <DimensionLayout dim="1">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
+                      <Group type="103" alignment="0" groupAlignment="3" attributes="0">
+                          <Component id="restartBtn" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="pauseBtn" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel5" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <Component id="iterationTimeSlider" alignment="0" min="-2" max="-2" attributes="0"/>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="resumeBtn" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="reverseBtn" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel3" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <Component id="totalTimeSlider" min="-2" max="-2" attributes="0"/>
+                  </Group>
+                  <EmptySpace max="32767" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+      </Layout>
+      <SubComponents>
+        <Component class="javax.swing.JButton" name="restartBtn">
+          <Properties>
+            <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
+              <Font name="Tahoma" size="11" style="1"/>
+            </Property>
+            <Property name="text" type="java.lang.String" value="Restart"/>
+            <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
+              <Insets value="[2, 3, 2, 3]"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="restartBtnActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="pauseBtn">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Pause"/>
+            <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
+              <Insets value="[2, 3, 2, 3]"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pauseBtnActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="resumeBtn">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Resume"/>
+            <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
+              <Insets value="[2, 3, 2, 3]"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="resumeBtnActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="reverseBtn">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Reverse"/>
+            <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
+              <Insets value="[2, 3, 2, 3]"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="reverseBtnActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JLabel" name="jLabel3">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Total time:"/>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JLabel" name="jLabel5">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Iteration time:"/>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JSlider" name="iterationTimeSlider">
+          <Properties>
+            <Property name="enabled" type="boolean" value="false"/>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JSlider" name="totalTimeSlider">
+          <Properties>
+            <Property name="enabled" type="boolean" value="false"/>
+          </Properties>
+        </Component>
+      </SubComponents>
+    </Container>
+  </SubComponents>
+</Form>
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/TimelineApplet.java b/java/applets/src/aurelienribon/tweenengine/applets/TimelineApplet.java
new file mode 100644
index 0000000..3d00956
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/TimelineApplet.java
@@ -0,0 +1,623 @@
+package aurelienribon.tweenengine.applets;
+
+import aurelienribon.tweenengine.BaseTween;
+import aurelienribon.tweenengine.Timeline;
+import aurelienribon.tweenengine.Tween;
+import aurelienribon.tweenengine.TweenCallback;
+import aurelienribon.tweenengine.TweenCallback.EventType;
+import aurelienribon.tweenengine.TweenManager;
+import aurelienribon.tweenengine.equations.Back;
+import aurelienribon.tweenengine.equations.Bounce;
+import aurelienribon.tweenengine.equations.Quart;
+import aurelienribon.utils.swing.DrawingCanvas;
+import java.awt.BorderLayout;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.TexturePaint;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.Hashtable;
+import javax.imageio.ImageIO;
+import javax.swing.JLabel;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com
+ */
+public class TimelineApplet extends javax.swing.JApplet {
+	/*public static void main(String[] args) {
+		TimelineApplet applet = new TimelineApplet();
+		applet.init();
+		applet.start();
+
+		javax.swing.JFrame wnd = new javax.swing.JFrame();
+		wnd.add(applet);
+		wnd.setSize(600, 700);
+		wnd.setVisible(true);
+	}*/
+
+	// -------------------------------------------------------------------------
+	// Applet
+	// -------------------------------------------------------------------------
+
+	private MyCanvas canvas;
+	private boolean isPaused = false;
+
+	@Override
+	public void init() {
+		try {
+			java.awt.EventQueue.invokeAndWait(new Runnable() {
+				@Override public void run() {load();}
+			});
+		} catch (Exception ex) {
+		}
+	}
+
+	@Override
+	public void destroy() {
+		DrawingCanvas canvas = (DrawingCanvas) canvasWrapper.getComponent(0);
+		canvas.stop();
+	}
+
+	private void load() {
+		try {
+			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+		} catch (ClassNotFoundException ex) {
+		} catch (InstantiationException ex) {
+		} catch (IllegalAccessException ex) {
+		} catch (UnsupportedLookAndFeelException ex) {
+		}
+
+		initComponents();
+
+		getContentPane().setBackground(Theme.MAIN_BACKGROUND);
+		Theme.apply(getContentPane());
+
+		OptionsListener listener = new OptionsListener();
+		rptSpinner.addChangeListener(listener);
+		rptDelaySpinner.addChangeListener(listener);
+		yoyoChk.addActionListener(listener);
+
+		generateCode();
+
+		Hashtable<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
+		labels.put(-300, new JLabel("-3"));
+		labels.put(-200, new JLabel("-2"));
+		labels.put(-100, new JLabel("-1"));
+		labels.put(0, new JLabel("0"));
+		labels.put(100, new JLabel("1"));
+		labels.put(200, new JLabel("2"));
+		labels.put(300, new JLabel("3"));
+		for (JLabel lbl : labels.values()) lbl.setForeground(Theme.MAIN_FOREGROUND);
+		speedSlider.setLabelTable(labels);
+
+		canvas = (MyCanvas) new MyCanvas().start();
+		canvasWrapper.add(canvas, BorderLayout.CENTER);
+		canvas.setCallback(new DrawingCanvas.Callback() {
+			@Override public void onUpdate(int elapsedMillis) {
+				if (canvas.getTimeline() == null || isPaused) return;
+				int delta = (int) (elapsedMillis * (speedSlider.getValue() / 100f));
+
+				if (canvas.getTimeline().getState()%4 == 2 && canvas.getTimeline().isYoyo())
+					iterationTimeSlider.setValue(iterationTimeSlider.getValue() - delta);
+				else if (canvas.getTimeline().getState()%2 == 0)
+					iterationTimeSlider.setValue(iterationTimeSlider.getValue() + delta);
+				totalTimeSlider.setValue(totalTimeSlider.getValue() + delta);
+			}
+		});
+
+		canvas.createTimeline();
+		initTimeline();
+	}
+
+	private void initTimeline() {
+		iterationTimeSlider.setMaximum(canvas.getTimeline().getDuration());
+		totalTimeSlider.setMaximum(canvas.getTimeline().getFullDuration());
+
+		canvas.getTimeline().addCallback(EventType.BEGIN, new TweenCallback() {
+			@Override public void onEvent(EventType eventType, BaseTween source) {
+				totalTimeSlider.setValue(0);
+			}
+		});
+
+		canvas.getTimeline().addCallback(EventType.START, new TweenCallback() {
+			@Override public void onEvent(EventType eventType, BaseTween source) {
+				if (canvas.getTimeline().getState()%4 == 2 && canvas.getTimeline().isYoyo())
+					iterationTimeSlider.setValue(canvas.getTimeline().getFullDuration());
+				else if (canvas.getTimeline().getState()%2 == 0)
+					iterationTimeSlider.setValue(0);
+			}
+		});
+	}
+
+	private void generateCode() {
+		int rptCnt = (Integer) rptSpinner.getValue();
+		int rptDelay = (Integer) rptDelaySpinner.getValue();
+		boolean isYoyo = yoyoChk.isSelected();
+
+		String code = "Timeline.createSequence()" +
+				"\n    .push(Tween.to(imgTweenSprite, POSITION_XY, 500).target(60, 90).ease(Quart.OUT))" +
+				"\n    .push(Tween.to(imgEngineSprite, POSITION_XY, 500).target(200, 90).ease(Quart.OUT))" +
+				"\n    .push(Tween.to(imgUniversalSprite, POSITION_XY, 1000).target(60, 55).ease(Bounce.OUT))" +
+				"\n    .pushPause(500)" +
+				"\n    .beginParallel()" +
+				"\n        .push(Tween.set(imgLogoSprite, VISIBILITY).target(1))" +
+				"\n        .push(Tween.to(imgLogoSprite, SCALE_XY, 800).target(1, 1).ease(Back.OUT))" +
+				"\n        .push(Tween.to(blankStripSprite, SCALE_XY, 500).target(1, 1).ease(Back.OUT))" +
+				"\n    .end()";
+
+		if (rptCnt > 0) code += "\n    .repeat" + (isYoyo ? "Yoyo" : "") + "(" + rptCnt + ", " + rptDelay + ")";
+
+		code += "\n    .start(myManager);";
+		
+		resultArea.setText(code);
+	}
+
+	private void restart() {
+		speedSlider.setValue(100);
+		canvas.createTimeline();
+		initTimeline();
+	}
+
+	private class OptionsListener implements ChangeListener, ActionListener {
+		@Override public void stateChanged(ChangeEvent e) {onEvent();}
+		@Override public void actionPerformed(ActionEvent e) {onEvent();}
+		private void onEvent() {
+			generateCode();
+			restart();
+		}
+	}
+
+	// -------------------------------------------------------------------------
+	// Canvas
+	// -------------------------------------------------------------------------
+
+	private class MyCanvas extends DrawingCanvas {
+		private final TweenManager tweenManager = new TweenManager();
+		private final Sprite imgUniversalSprite;
+		private final Sprite imgTweenSprite;
+		private final Sprite imgEngineSprite;
+		private final Sprite imgLogoSprite;
+		private final Sprite blankStripSprite;
+		private TexturePaint bgPaint;
+		private Timeline timeline;
+
+		public MyCanvas() {
+			Tween.enablePooling(false);
+			Tween.registerAccessor(Sprite.class, new SpriteAccessor());
+			
+			imgUniversalSprite = new Sprite("img-universal.png").setCentered(false);
+			imgTweenSprite = new Sprite("img-tween.png").setCentered(false);
+			imgEngineSprite = new Sprite("img-engine.png").setCentered(false);
+			imgLogoSprite = new Sprite("img-logo.png");
+			blankStripSprite = new Sprite("blankStrip.png");
+
+			try {
+				BufferedImage bgImage = ImageIO.read(TimelineApplet.class.getResource("/aurelienribon/tweenengine/applets/gfx/transparent-dark.png"));
+				bgPaint = new TexturePaint(bgImage, new Rectangle(0, 0, bgImage.getWidth(), bgImage.getHeight()));
+			} catch (IOException ex) {
+			}
+		}
+
+		@Override
+		protected void update(int elapsedMillis) {
+			if (isPaused) return;
+			int delta = (int) (elapsedMillis * (speedSlider.getValue() / 100f));
+			tweenManager.update(delta);
+			repaint();
+		}
+
+		@Override
+		protected void paintComponent(Graphics g) {
+			Graphics2D gg = (Graphics2D) g;
+
+			if (bgPaint != null) {
+				gg.setPaint(bgPaint);
+				gg.fillRect(0, 0, getWidth(), getHeight());
+				gg.setPaint(null);
+			}
+
+			blankStripSprite.draw(gg);
+			imgUniversalSprite.draw(gg);
+			imgTweenSprite.draw(gg);
+			imgEngineSprite.draw(gg);
+			imgLogoSprite.draw(gg);
+		}
+
+		public void createTimeline() {
+			tweenManager.killAll();
+			
+			imgUniversalSprite.setPosition(60, 105 - 200);
+			imgTweenSprite.setPosition(60 - 300, 140);
+			imgEngineSprite.setPosition(200 + 300, 140);
+
+			imgLogoSprite.setPosition(310, 120);
+			imgLogoSprite.setScale(7, 7);
+			imgLogoSprite.setVisible(false);
+
+			blankStripSprite.setPosition(250, 140);
+			blankStripSprite.setScale(1, 0);
+
+			timeline = Timeline.createSequence()
+				.push(Tween.to(imgTweenSprite, SpriteAccessor.POSITION_XY, 500).target(60, 140).ease(Quart.OUT))
+				.push(Tween.to(imgEngineSprite, SpriteAccessor.POSITION_XY, 500).target(200, 140).ease(Quart.OUT))
+				.push(Tween.to(imgUniversalSprite, SpriteAccessor.POSITION_XY, 1000).target(60, 105).ease(Bounce.OUT))
+				.pushPause(500)
+				.beginParallel()
+					.push(Tween.set(imgLogoSprite, SpriteAccessor.VISIBILITY).target(1))
+					.push(Tween.to(imgLogoSprite, SpriteAccessor.SCALE_XY, 800).target(1, 1).ease(Back.OUT))
+					.push(Tween.to(blankStripSprite, SpriteAccessor.SCALE_XY, 500).target(1, 1).ease(Back.OUT))
+				.end();
+
+			int rptCnt = (Integer) rptSpinner.getValue();
+			int rpDelay = (Integer) rptDelaySpinner.getValue();
+			boolean yoyo = yoyoChk.isSelected();
+
+			if (rptCnt > 0 && yoyo) timeline.repeatYoyo(rptCnt, rpDelay);
+			else if (rptCnt > 0) timeline.repeat(rptCnt, rpDelay);
+
+			timeline.addCallback(EventType.COMPLETE, new TweenCallback() {
+				@Override public void onEvent(EventType eventType, BaseTween source) {
+					timeline = null;
+				}
+			});
+
+			timeline.addCallback(EventType.BACK_COMPLETE, new TweenCallback() {
+				@Override public void onEvent(EventType eventType, BaseTween source) {
+					timeline = null;
+				}
+			});
+
+			timeline.start(tweenManager);
+		}
+
+		public Timeline getTimeline() {
+			return timeline;
+		}
+	}
+
+	// -------------------------------------------------------------------------
+	// Generated stuff
+	// -------------------------------------------------------------------------
+
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        jPanel1 = new javax.swing.JPanel();
+        jScrollPane1 = new javax.swing.JScrollPane();
+        resultArea = new javax.swing.JTextArea();
+        jLabel1 = new javax.swing.JLabel();
+        jLabel9 = new javax.swing.JLabel();
+        canvasWrapper = new javax.swing.JPanel();
+        jPanel3 = new javax.swing.JPanel();
+        jLabel2 = new javax.swing.JLabel();
+        jPanel2 = new javax.swing.JPanel();
+        jLabel4 = new javax.swing.JLabel();
+        rptSpinner = new javax.swing.JSpinner();
+        yoyoChk = new javax.swing.JCheckBox();
+        jLabel6 = new javax.swing.JLabel();
+        rptDelaySpinner = new javax.swing.JSpinner();
+        jPanel5 = new javax.swing.JPanel();
+        speedSlider = new javax.swing.JSlider();
+        jPanel4 = new javax.swing.JPanel();
+        restartBtn = new javax.swing.JButton();
+        pauseBtn = new javax.swing.JButton();
+        resumeBtn = new javax.swing.JButton();
+        reverseBtn = new javax.swing.JButton();
+        jLabel3 = new javax.swing.JLabel();
+        jLabel5 = new javax.swing.JLabel();
+        iterationTimeSlider = new javax.swing.JSlider();
+        totalTimeSlider = new javax.swing.JSlider();
+
+        jPanel1.setBorder(new aurelienribon.utils.swing.GroupBorder());
+
+        jScrollPane1.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
+
+        resultArea.setColumns(20);
+        jScrollPane1.setViewportView(resultArea);
+
+        jLabel1.setText("Java code:");
+
+        jLabel9.setText("<html>\nUniversal Tween Engine v6.0.0 - <font color=\"#77C8FF\">www.aurelienribon.com</font>");
+
+        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
+        jPanel1.setLayout(jPanel1Layout);
+        jPanel1Layout.setHorizontalGroup(
+            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel1Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 598, Short.MAX_VALUE)
+                    .addGroup(jPanel1Layout.createSequentialGroup()
+                        .addComponent(jLabel1)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 273, Short.MAX_VALUE)
+                        .addComponent(jLabel9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addContainerGap())
+        );
+        jPanel1Layout.setVerticalGroup(
+            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel1Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(jLabel1)
+                    .addComponent(jLabel9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 214, Short.MAX_VALUE)
+                .addContainerGap())
+        );
+
+        canvasWrapper.setLayout(new java.awt.BorderLayout());
+
+        jPanel3.setOpaque(false);
+
+        jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+        jLabel2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/aurelienribon/tweenengine/applets/gfx/logo-timeline.png"))); // NOI18N
+
+        aurelienribon.utils.swing.GroupBorder groupBorder1 = new aurelienribon.utils.swing.GroupBorder();
+        groupBorder1.setTitle("Timeline options");
+        jPanel2.setBorder(groupBorder1);
+
+        jLabel4.setText("Repetitions:");
+
+        rptSpinner.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(2), Integer.valueOf(0), null, Integer.valueOf(1)));
+
+        yoyoChk.setSelected(true);
+        yoyoChk.setText("Yoyo repetitions");
+
+        jLabel6.setText("Repeat delay:");
+
+        rptDelaySpinner.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(500), Integer.valueOf(0), null, Integer.valueOf(100)));
+
+        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
+        jPanel2.setLayout(jPanel2Layout);
+        jPanel2Layout.setHorizontalGroup(
+            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(yoyoChk, javax.swing.GroupLayout.Alignment.TRAILING)
+                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
+                        .addComponent(jLabel4)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addComponent(rptSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE))
+                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
+                        .addComponent(jLabel6)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addComponent(rptDelaySpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addContainerGap())
+        );
+        jPanel2Layout.setVerticalGroup(
+            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel2Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(rptSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(jLabel4))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(rptDelaySpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(jLabel6))
+                .addGap(18, 18, 18)
+                .addComponent(yoyoChk)
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+        );
+
+        aurelienribon.utils.swing.GroupBorder groupBorder2 = new aurelienribon.utils.swing.GroupBorder();
+        groupBorder2.setTitle("Animation speed");
+        jPanel5.setBorder(groupBorder2);
+
+        speedSlider.setMajorTickSpacing(100);
+        speedSlider.setMaximum(300);
+        speedSlider.setMinimum(-300);
+        speedSlider.setPaintLabels(true);
+        speedSlider.setPaintTicks(true);
+        speedSlider.setValue(100);
+
+        javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5);
+        jPanel5.setLayout(jPanel5Layout);
+        jPanel5Layout.setHorizontalGroup(
+            jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel5Layout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(speedSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 144, Short.MAX_VALUE)
+                .addContainerGap())
+        );
+        jPanel5Layout.setVerticalGroup(
+            jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel5Layout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(speedSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+        );
+
+        javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
+        jPanel3.setLayout(jPanel3Layout);
+        jPanel3Layout.setHorizontalGroup(
+            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, 164, Short.MAX_VALUE)
+            .addComponent(jPanel5, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+            .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+        );
+        jPanel3Layout.setVerticalGroup(
+            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel3Layout.createSequentialGroup()
+                .addComponent(jLabel2)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jPanel5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+        );
+
+        jPanel4.setBorder(new aurelienribon.utils.swing.GroupBorder());
+
+        restartBtn.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
+        restartBtn.setText("Restart");
+        restartBtn.setMargin(new java.awt.Insets(2, 3, 2, 3));
+        restartBtn.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                restartBtnActionPerformed(evt);
+            }
+        });
+
+        pauseBtn.setText("Pause");
+        pauseBtn.setMargin(new java.awt.Insets(2, 3, 2, 3));
+        pauseBtn.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                pauseBtnActionPerformed(evt);
+            }
+        });
+
+        resumeBtn.setText("Resume");
+        resumeBtn.setMargin(new java.awt.Insets(2, 3, 2, 3));
+        resumeBtn.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                resumeBtnActionPerformed(evt);
+            }
+        });
+
+        reverseBtn.setText("Reverse");
+        reverseBtn.setMargin(new java.awt.Insets(2, 3, 2, 3));
+        reverseBtn.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                reverseBtnActionPerformed(evt);
+            }
+        });
+
+        jLabel3.setText("Total time:");
+
+        jLabel5.setText("Iteration time:");
+
+        iterationTimeSlider.setEnabled(false);
+
+        totalTimeSlider.setEnabled(false);
+
+        javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
+        jPanel4.setLayout(jPanel4Layout);
+        jPanel4Layout.setHorizontalGroup(
+            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(restartBtn)
+                    .addComponent(reverseBtn))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+                    .addGroup(jPanel4Layout.createSequentialGroup()
+                        .addComponent(resumeBtn)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                        .addComponent(jLabel3))
+                    .addGroup(jPanel4Layout.createSequentialGroup()
+                        .addComponent(pauseBtn)
+                        .addGap(18, 18, 18)
+                        .addComponent(jLabel5)))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(totalTimeSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 395, Short.MAX_VALUE)
+                    .addComponent(iterationTimeSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 395, Short.MAX_VALUE))
+                .addContainerGap())
+        );
+
+        jPanel4Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pauseBtn, restartBtn, resumeBtn, reverseBtn});
+
+        jPanel4Layout.setVerticalGroup(
+            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel4Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                        .addComponent(restartBtn)
+                        .addComponent(pauseBtn)
+                        .addComponent(jLabel5))
+                    .addComponent(iterationTimeSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                        .addComponent(resumeBtn)
+                        .addComponent(reverseBtn)
+                        .addComponent(jLabel3))
+                    .addComponent(totalTimeSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+        );
+
+        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+        getContentPane().setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+                        .addComponent(canvasWrapper, javax.swing.GroupLayout.DEFAULT_SIZE, 448, Short.MAX_VALUE)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addContainerGap())
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(canvasWrapper, javax.swing.GroupLayout.DEFAULT_SIZE, 258, Short.MAX_VALUE)
+                    .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addContainerGap())
+        );
+    }// </editor-fold>//GEN-END:initComponents
+
+	private void restartBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_restartBtnActionPerformed
+		restart();
+	}//GEN-LAST:event_restartBtnActionPerformed
+
+	private void pauseBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseBtnActionPerformed
+		isPaused = true;
+	}//GEN-LAST:event_pauseBtnActionPerformed
+
+	private void resumeBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resumeBtnActionPerformed
+		isPaused = false;
+	}//GEN-LAST:event_resumeBtnActionPerformed
+
+	private void reverseBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_reverseBtnActionPerformed
+		speedSlider.setValue(-speedSlider.getValue());
+	}//GEN-LAST:event_reverseBtnActionPerformed
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JPanel canvasWrapper;
+    private javax.swing.JSlider iterationTimeSlider;
+    private javax.swing.JLabel jLabel1;
+    private javax.swing.JLabel jLabel2;
+    private javax.swing.JLabel jLabel3;
+    private javax.swing.JLabel jLabel4;
+    private javax.swing.JLabel jLabel5;
+    private javax.swing.JLabel jLabel6;
+    private javax.swing.JLabel jLabel9;
+    private javax.swing.JPanel jPanel1;
+    private javax.swing.JPanel jPanel2;
+    private javax.swing.JPanel jPanel3;
+    private javax.swing.JPanel jPanel4;
+    private javax.swing.JPanel jPanel5;
+    private javax.swing.JScrollPane jScrollPane1;
+    private javax.swing.JButton pauseBtn;
+    private javax.swing.JButton restartBtn;
+    private javax.swing.JTextArea resultArea;
+    private javax.swing.JButton resumeBtn;
+    private javax.swing.JButton reverseBtn;
+    private javax.swing.JSpinner rptDelaySpinner;
+    private javax.swing.JSpinner rptSpinner;
+    private javax.swing.JSlider speedSlider;
+    private javax.swing.JSlider totalTimeSlider;
+    private javax.swing.JCheckBox yoyoChk;
+    // End of variables declaration//GEN-END:variables
+
+}
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/TweenApplet.form b/java/applets/src/aurelienribon/tweenengine/applets/TweenApplet.form
new file mode 100644
index 0000000..8dec46e
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/TweenApplet.form
@@ -0,0 +1,392 @@
+<?xml version="1.1" encoding="UTF-8" ?>
+
+<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JAppletFormInfo">
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+  </AuxValues>
+
+  <Layout>
+    <DimensionLayout dim="0">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" alignment="0" attributes="0">
+              <EmptySpace min="-2" max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="jPanel1" alignment="1" max="32767" attributes="0"/>
+                  <Group type="102" alignment="1" attributes="0">
+                      <Component id="canvasWrapper" pref="412" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="jPanel3" min="-2" max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+              <EmptySpace min="-2" max="-2" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+    <DimensionLayout dim="1">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" alignment="1" attributes="0">
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="canvasWrapper" pref="365" max="32767" attributes="0"/>
+                  <Component id="jPanel3" alignment="0" max="32767" attributes="0"/>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
+              <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+  </Layout>
+  <SubComponents>
+    <Container class="javax.swing.JPanel" name="jPanel1">
+      <Properties>
+        <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+            <PropertyBean type="aurelienribon.utils.swing.GroupBorder"/>
+        </Property>
+      </Properties>
+
+      <Layout>
+        <DimensionLayout dim="0">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
+                      <Component id="jScrollPane1" alignment="0" pref="562" max="32767" attributes="0"/>
+                      <Group type="102" alignment="0" attributes="0">
+                          <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace pref="237" max="32767" attributes="0"/>
+                          <Component id="jLabel9" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+        <DimensionLayout dim="1">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="3" attributes="0">
+                      <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="jLabel9" alignment="3" min="-2" max="-2" attributes="0"/>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="jScrollPane1" pref="101" max="32767" attributes="0"/>
+                  <EmptySpace max="-2" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+      </Layout>
+      <SubComponents>
+        <Container class="javax.swing.JScrollPane" name="jScrollPane1">
+          <Properties>
+            <Property name="verticalScrollBarPolicy" type="int" value="22"/>
+          </Properties>
+          <AuxValues>
+            <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+          </AuxValues>
+
+          <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+          <SubComponents>
+            <Component class="javax.swing.JTextArea" name="resultArea">
+              <Properties>
+                <Property name="columns" type="int" value="20"/>
+                <Property name="rows" type="int" value="5"/>
+              </Properties>
+            </Component>
+          </SubComponents>
+        </Container>
+        <Component class="javax.swing.JLabel" name="jLabel1">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Java code:"/>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JLabel" name="jLabel9">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="&lt;html&gt;&#xa;Universal Tween Engine v6.0.0 - &lt;font color=&quot;#77C8FF&quot;&gt;www.aurelienribon.com&lt;/font&gt;"/>
+          </Properties>
+        </Component>
+      </SubComponents>
+    </Container>
+    <Container class="javax.swing.JPanel" name="canvasWrapper">
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+    </Container>
+    <Container class="javax.swing.JPanel" name="jPanel3">
+      <Properties>
+        <Property name="opaque" type="boolean" value="false"/>
+      </Properties>
+
+      <Layout>
+        <DimensionLayout dim="0">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Component id="jLabel2" alignment="0" pref="164" max="32767" attributes="0"/>
+              <Component id="jPanel4" alignment="0" max="32767" attributes="1"/>
+              <Component id="jPanel2" alignment="0" max="32767" attributes="1"/>
+          </Group>
+        </DimensionLayout>
+        <DimensionLayout dim="1">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" attributes="0">
+                  <Component id="jLabel2" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace pref="26" max="32767" attributes="0"/>
+                  <Component id="jPanel2" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="jPanel4" min="-2" max="-2" attributes="0"/>
+              </Group>
+          </Group>
+        </DimensionLayout>
+      </Layout>
+      <SubComponents>
+        <Component class="javax.swing.JLabel" name="jLabel2">
+          <Properties>
+            <Property name="horizontalAlignment" type="int" value="0"/>
+            <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+              <Image iconType="3" name="/aurelienribon/tweenengine/applets/gfx/logo-tween.png"/>
+            </Property>
+          </Properties>
+        </Component>
+        <Container class="javax.swing.JPanel" name="jPanel2">
+          <Properties>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+              <PropertyBean type="aurelienribon.utils.swing.GroupBorder">
+                <Property name="title" type="java.lang.String" value="Options"/>
+              </PropertyBean>
+            </Property>
+          </Properties>
+
+          <Layout>
+            <DimensionLayout dim="0">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Component id="yoyoChk" alignment="1" min="-2" max="-2" attributes="0"/>
+                          <Group type="102" alignment="0" attributes="0">
+                              <Component id="jLabel7" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Component id="easingCbox" pref="105" max="32767" attributes="0"/>
+                          </Group>
+                          <Group type="102" alignment="1" attributes="0">
+                              <EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
+                              <Group type="103" groupAlignment="1" attributes="0">
+                                  <Component id="jLabel5" alignment="1" min="-2" max="-2" attributes="0"/>
+                                  <Component id="jLabel3" alignment="1" min="-2" max="-2" attributes="0"/>
+                              </Group>
+                              <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                              <Group type="103" groupAlignment="0" attributes="0">
+                                  <Component id="durationSpinner" alignment="1" min="-2" pref="66" max="-2" attributes="0"/>
+                                  <Component id="delaySpinner" alignment="1" min="-2" pref="66" max="-2" attributes="0"/>
+                              </Group>
+                          </Group>
+                          <Group type="102" alignment="1" attributes="0">
+                              <Component id="jLabel4" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                              <Component id="rptSpinner" min="-2" pref="66" max="-2" attributes="0"/>
+                          </Group>
+                          <Group type="102" alignment="1" attributes="0">
+                              <Component id="jLabel6" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                              <Component id="rptDelaySpinner" min="-2" pref="66" max="-2" attributes="0"/>
+                          </Group>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+            <DimensionLayout dim="1">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="jLabel7" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="easingCbox" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace type="separate" max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="delaySpinner" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel3" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="durationSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel5" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="rptSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel4" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="rptDelaySpinner" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel6" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace type="separate" max="-2" attributes="0"/>
+                      <Component id="yoyoChk" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="32767" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+          </Layout>
+          <SubComponents>
+            <Component class="javax.swing.JLabel" name="jLabel3">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Delay:"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JSpinner" name="delaySpinner">
+              <Properties>
+                <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
+                  <SpinnerModel initial="0" minimum="0" numberType="java.lang.Integer" stepSize="100" type="number"/>
+                </Property>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JLabel" name="jLabel4">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Repetitions:"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JSpinner" name="rptSpinner">
+              <Properties>
+                <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
+                  <SpinnerModel initial="0" minimum="0" numberType="java.lang.Integer" stepSize="1" type="number"/>
+                </Property>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JCheckBox" name="yoyoChk">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Yoyo repetitions"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JLabel" name="jLabel5">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Duration:"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JSpinner" name="durationSpinner">
+              <Properties>
+                <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
+                  <SpinnerModel initial="500" minimum="0" numberType="java.lang.Integer" stepSize="100" type="number"/>
+                </Property>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JLabel" name="jLabel7">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Easing:"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JComboBox" name="easingCbox">
+              <Properties>
+                <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
+                  <StringArray count="41">
+                    <StringItem index="0" value="Linear.INOUT"/>
+                    <StringItem index="1" value="----------"/>
+                    <StringItem index="2" value="Quad.IN"/>
+                    <StringItem index="3" value="Quad.OUT"/>
+                    <StringItem index="4" value="Quad.INOUT"/>
+                    <StringItem index="5" value="----------"/>
+                    <StringItem index="6" value="Cubic.IN"/>
+                    <StringItem index="7" value="Cubic.OUT"/>
+                    <StringItem index="8" value="Cubic.INOUT"/>
+                    <StringItem index="9" value="----------"/>
+                    <StringItem index="10" value="Quart.IN"/>
+                    <StringItem index="11" value="Quart.OUT"/>
+                    <StringItem index="12" value="Quart.INOUT"/>
+                    <StringItem index="13" value="----------"/>
+                    <StringItem index="14" value="Quint.IN"/>
+                    <StringItem index="15" value="Quint.OUT"/>
+                    <StringItem index="16" value="Quint.INOUT"/>
+                    <StringItem index="17" value="----------"/>
+                    <StringItem index="18" value="Circ.IN"/>
+                    <StringItem index="19" value="Circ.OUT"/>
+                    <StringItem index="20" value="Circ.INOUT"/>
+                    <StringItem index="21" value="----------"/>
+                    <StringItem index="22" value="Sine.IN"/>
+                    <StringItem index="23" value="Sine.OUT"/>
+                    <StringItem index="24" value="Sine.INOUT"/>
+                    <StringItem index="25" value="----------"/>
+                    <StringItem index="26" value="Expo.IN"/>
+                    <StringItem index="27" value="Expo.OUT"/>
+                    <StringItem index="28" value="Expo.INOUT"/>
+                    <StringItem index="29" value="----------"/>
+                    <StringItem index="30" value="Back.IN"/>
+                    <StringItem index="31" value="Back.OUT"/>
+                    <StringItem index="32" value="Back.INOUT"/>
+                    <StringItem index="33" value="----------"/>
+                    <StringItem index="34" value="Bounce.IN"/>
+                    <StringItem index="35" value="Bounce.OUT"/>
+                    <StringItem index="36" value="Bounce.INOUT"/>
+                    <StringItem index="37" value="----------"/>
+                    <StringItem index="38" value="Elastic.IN"/>
+                    <StringItem index="39" value="Elastic.OUT"/>
+                    <StringItem index="40" value="Elastic.INOUT"/>
+                  </StringArray>
+                </Property>
+                <Property name="selectedIndex" type="int" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+                  <Connection code="31" type="code"/>
+                </Property>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JLabel" name="jLabel6">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Repeat delay:"/>
+              </Properties>
+            </Component>
+            <Component class="javax.swing.JSpinner" name="rptDelaySpinner">
+              <Properties>
+                <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
+                  <SpinnerModel initial="0" minimum="0" numberType="java.lang.Integer" stepSize="100" type="number"/>
+                </Property>
+              </Properties>
+            </Component>
+          </SubComponents>
+        </Container>
+        <Container class="javax.swing.JPanel" name="jPanel4">
+          <Properties>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+                <PropertyBean type="aurelienribon.utils.swing.GroupBorder"/>
+            </Property>
+          </Properties>
+
+          <Layout>
+            <DimensionLayout dim="0">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="jLabel8" pref="144" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+            <DimensionLayout dim="1">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="jLabel8" pref="42" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+            </DimensionLayout>
+          </Layout>
+          <SubComponents>
+            <Component class="javax.swing.JLabel" name="jLabel8">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="&lt;html&gt;&#xa;Click anywhere on the canvas to fire your custom tween."/>
+                <Property name="verticalAlignment" type="int" value="1"/>
+              </Properties>
+            </Component>
+          </SubComponents>
+        </Container>
+      </SubComponents>
+    </Container>
+  </SubComponents>
+</Form>
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/TweenApplet.java b/java/applets/src/aurelienribon/tweenengine/applets/TweenApplet.java
new file mode 100644
index 0000000..a1a2e2e
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/TweenApplet.java
@@ -0,0 +1,435 @@
+package aurelienribon.tweenengine.applets;
+
+import aurelienribon.tweenengine.Tween;
+import aurelienribon.tweenengine.TweenEquation;
+import aurelienribon.tweenengine.TweenManager;
+import aurelienribon.utils.swing.DrawingCanvas;
+import java.awt.BorderLayout;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.TexturePaint;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com
+ */
+public class TweenApplet extends javax.swing.JApplet {
+	/*public static void main(String[] args) {
+		TweenApplet applet = new TweenApplet();
+		applet.init();
+		applet.start();
+
+		javax.swing.JFrame wnd = new javax.swing.JFrame();
+		wnd.add(applet);
+		wnd.setSize(600, 550);
+		wnd.setVisible(true);
+	}*/
+
+	// -------------------------------------------------------------------------
+	// Applet
+	// -------------------------------------------------------------------------
+
+	@Override
+	public void init() {
+		try {
+			java.awt.EventQueue.invokeAndWait(new Runnable() {
+				@Override public void run() {load();}
+			});
+		} catch (Exception ex) {
+		}
+	}
+
+	@Override
+	public void destroy() {
+		DrawingCanvas canvas = (DrawingCanvas) canvasWrapper.getComponent(0);
+		canvas.stop();
+	}
+
+	private void load() {
+		try {
+			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+		} catch (ClassNotFoundException ex) {
+		} catch (InstantiationException ex) {
+		} catch (IllegalAccessException ex) {
+		} catch (UnsupportedLookAndFeelException ex) {
+		}
+
+		initComponents();
+
+		getContentPane().setBackground(Theme.MAIN_BACKGROUND);
+		Theme.apply(getContentPane());
+
+		OptionsListener listener = new OptionsListener();
+		easingCbox.addActionListener(listener);
+		delaySpinner.addChangeListener(listener);
+		durationSpinner.addChangeListener(listener);
+		rptSpinner.addChangeListener(listener);
+		rptDelaySpinner.addChangeListener(listener);
+		yoyoChk.addActionListener(listener);
+
+		generateCode();
+
+		canvasWrapper.add(new MyCanvas().start(), BorderLayout.CENTER);
+	}
+
+	private void generateCode() {
+		String easing = (String) easingCbox.getSelectedItem();
+		int delay = (Integer) delaySpinner.getValue();
+		int duration = (Integer) durationSpinner.getValue();
+		int rptCnt = (Integer) rptSpinner.getValue();
+		int rptDelay = (Integer) rptDelaySpinner.getValue();
+		boolean isYoyo = yoyoChk.isSelected();
+
+		String code = "Tween.to(mySprite, POSITION_XY, " + duration + ")";
+		code += "\n     .target()";
+
+		if (!easing.equals("Linear") && !easing.equals("----------")) code += "\n     .ease(" + easing + ")";
+		if (delay > 0) code += "\n     .delay(" + delay + ")";
+		if (rptCnt > 0) code += "\n     .repeat" + (isYoyo ? "Yoyo" : "") + "(" + rptCnt + ", " + rptDelay + ")";
+
+		code += "\n     .start(myManager);";
+		
+		resultArea.setText(code);
+	}
+
+	private class OptionsListener implements ChangeListener, ActionListener {
+		@Override public void stateChanged(ChangeEvent e) {onEvent();}
+		@Override public void actionPerformed(ActionEvent e) {onEvent();}
+		private void onEvent() {
+			generateCode();
+		}
+	}
+
+	// -------------------------------------------------------------------------
+	// Canvas
+	// -------------------------------------------------------------------------
+
+	private class MyCanvas extends DrawingCanvas {
+		private final TweenManager tweenManager = new TweenManager();
+		private final Sprite vialSprite;
+		private TexturePaint bgPaint;
+
+		public MyCanvas() {
+			Tween.enablePooling(false);
+			Tween.registerAccessor(Sprite.class, new SpriteAccessor());
+			addMouseListener(mouseAdapter);
+
+			vialSprite = new Sprite("vial.png");
+			vialSprite.setPosition(100, 100);
+
+			try {
+				BufferedImage bgImage = ImageIO.read(TweenApplet.class.getResource("/aurelienribon/tweenengine/applets/gfx/transparent-dark.png"));
+				bgPaint = new TexturePaint(bgImage, new Rectangle(0, 0, bgImage.getWidth(), bgImage.getHeight()));
+			} catch (IOException ex) {
+			}
+		}
+
+		@Override
+		protected void update(int elapsedMillis) {
+			tweenManager.update(elapsedMillis);
+			repaint();
+		}
+
+		@Override
+		protected void paintComponent(Graphics g) {
+			Graphics2D gg = (Graphics2D) g;
+
+			if (bgPaint != null) {
+				gg.setPaint(bgPaint);
+				gg.fillRect(0, 0, getWidth(), getHeight());
+				gg.setPaint(null);
+			}
+
+			vialSprite.draw(gg);
+		}
+
+		private final MouseAdapter mouseAdapter = new MouseAdapter() {
+			@Override public void mousePressed(MouseEvent e) {
+				TweenEquation easing = TweenEquation.parse((String) easingCbox.getSelectedItem());
+				int delay = (Integer) delaySpinner.getValue();
+				int duration = (Integer) durationSpinner.getValue();
+				int rptCnt = (Integer) rptSpinner.getValue();
+				int rptDelay = (Integer) rptDelaySpinner.getValue();
+				boolean isYoyo = yoyoChk.isSelected();
+
+				tweenManager.killAll();
+
+				Tween tween = Tween.to(vialSprite, SpriteAccessor.POSITION_XY, duration)
+					.target(e.getX(), e.getY())
+					.delay(delay);
+
+				if (easing != null) tween.ease(easing);
+				if (isYoyo) tween.repeatYoyo(rptCnt, rptDelay);
+				else tween.repeat(rptCnt, rptDelay);
+
+				tween.start(tweenManager);
+			}
+		};
+	}
+
+	// -------------------------------------------------------------------------
+	// Generated stuff
+	// -------------------------------------------------------------------------
+
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        jPanel1 = new javax.swing.JPanel();
+        jScrollPane1 = new javax.swing.JScrollPane();
+        resultArea = new javax.swing.JTextArea();
+        jLabel1 = new javax.swing.JLabel();
+        jLabel9 = new javax.swing.JLabel();
+        canvasWrapper = new javax.swing.JPanel();
+        jPanel3 = new javax.swing.JPanel();
+        jLabel2 = new javax.swing.JLabel();
+        jPanel2 = new javax.swing.JPanel();
+        jLabel3 = new javax.swing.JLabel();
+        delaySpinner = new javax.swing.JSpinner();
+        jLabel4 = new javax.swing.JLabel();
+        rptSpinner = new javax.swing.JSpinner();
+        yoyoChk = new javax.swing.JCheckBox();
+        jLabel5 = new javax.swing.JLabel();
+        durationSpinner = new javax.swing.JSpinner();
+        jLabel7 = new javax.swing.JLabel();
+        easingCbox = new javax.swing.JComboBox();
+        jLabel6 = new javax.swing.JLabel();
+        rptDelaySpinner = new javax.swing.JSpinner();
+        jPanel4 = new javax.swing.JPanel();
+        jLabel8 = new javax.swing.JLabel();
+
+        jPanel1.setBorder(new aurelienribon.utils.swing.GroupBorder());
+
+        jScrollPane1.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
+
+        resultArea.setColumns(20);
+        resultArea.setRows(5);
+        jScrollPane1.setViewportView(resultArea);
+
+        jLabel1.setText("Java code:");
+
+        jLabel9.setText("<html>\nUniversal Tween Engine v6.0.0 - <font color=\"#77C8FF\">www.aurelienribon.com</font>");
+
+        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
+        jPanel1.setLayout(jPanel1Layout);
+        jPanel1Layout.setHorizontalGroup(
+            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel1Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 562, Short.MAX_VALUE)
+                    .addGroup(jPanel1Layout.createSequentialGroup()
+                        .addComponent(jLabel1)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 237, Short.MAX_VALUE)
+                        .addComponent(jLabel9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addContainerGap())
+        );
+        jPanel1Layout.setVerticalGroup(
+            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel1Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(jLabel1)
+                    .addComponent(jLabel9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 101, Short.MAX_VALUE)
+                .addContainerGap())
+        );
+
+        canvasWrapper.setLayout(new java.awt.BorderLayout());
+
+        jPanel3.setOpaque(false);
+
+        jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+        jLabel2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/aurelienribon/tweenengine/applets/gfx/logo-tween.png"))); // NOI18N
+
+        aurelienribon.utils.swing.GroupBorder groupBorder1 = new aurelienribon.utils.swing.GroupBorder();
+        groupBorder1.setTitle("Options");
+        jPanel2.setBorder(groupBorder1);
+
+        jLabel3.setText("Delay:");
+
+        delaySpinner.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(0), Integer.valueOf(0), null, Integer.valueOf(100)));
+
+        jLabel4.setText("Repetitions:");
+
+        rptSpinner.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(0), Integer.valueOf(0), null, Integer.valueOf(1)));
+
+        yoyoChk.setText("Yoyo repetitions");
+
+        jLabel5.setText("Duration:");
+
+        durationSpinner.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(500), Integer.valueOf(0), null, Integer.valueOf(100)));
+
+        jLabel7.setText("Easing:");
+
+        easingCbox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Linear.INOUT", "----------", "Quad.IN", "Quad.OUT", "Quad.INOUT", "----------", "Cubic.IN", "Cubic.OUT", "Cubic.INOUT", "----------", "Quart.IN", "Quart.OUT", "Quart.INOUT", "----------", "Quint.IN", "Quint.OUT", "Quint.INOUT", "----------", "Circ.IN", "Circ.OUT", "Circ.INOUT", "----------", "Sine.IN", "Sine.OUT", "Sine.INOUT", "----------", "Expo.IN", "Expo.OUT", "Expo.INOUT", "----------", "Back.IN", "Back.OUT", "Back.INOUT", "----------", "Bounce.IN", "Bounce.OUT", "Bounce.INOUT", "----------", "Elastic.IN", "Elastic.OUT", "Elastic.INOUT" }));
+        easingCbox.setSelectedIndex(31);
+
+        jLabel6.setText("Repeat delay:");
+
+        rptDelaySpinner.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(0), Integer.valueOf(0), null, Integer.valueOf(100)));
+
+        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
+        jPanel2.setLayout(jPanel2Layout);
+        jPanel2Layout.setHorizontalGroup(
+            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel2Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(yoyoChk, javax.swing.GroupLayout.Alignment.TRAILING)
+                    .addGroup(jPanel2Layout.createSequentialGroup()
+                        .addComponent(jLabel7)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(easingCbox, 0, 105, Short.MAX_VALUE))
+                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
+                        .addGap(13, 13, 13)
+                        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+                            .addComponent(jLabel5)
+                            .addComponent(jLabel3))
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(durationSpinner, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE)
+                            .addComponent(delaySpinner, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
+                        .addComponent(jLabel4)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addComponent(rptSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE))
+                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
+                        .addComponent(jLabel6)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addComponent(rptDelaySpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addContainerGap())
+        );
+        jPanel2Layout.setVerticalGroup(
+            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel2Layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(jLabel7)
+                    .addComponent(easingCbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addGap(18, 18, 18)
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(delaySpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(jLabel3))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(durationSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(jLabel5))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(rptSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(jLabel4))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(rptDelaySpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(jLabel6))
+                .addGap(18, 18, 18)
+                .addComponent(yoyoChk)
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+        );
+
+        jPanel4.setBorder(new aurelienribon.utils.swing.GroupBorder());
+
+        jLabel8.setText("<html>\nClick anywhere on the canvas to fire your custom tween.");
+        jLabel8.setVerticalAlignment(javax.swing.SwingConstants.TOP);
+
+        javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
+        jPanel4.setLayout(jPanel4Layout);
+        jPanel4Layout.setHorizontalGroup(
+            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel4Layout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(jLabel8, javax.swing.GroupLayout.DEFAULT_SIZE, 144, Short.MAX_VALUE)
+                .addContainerGap())
+        );
+        jPanel4Layout.setVerticalGroup(
+            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel4Layout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(jLabel8, javax.swing.GroupLayout.DEFAULT_SIZE, 42, Short.MAX_VALUE)
+                .addContainerGap())
+        );
+
+        javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
+        jPanel3.setLayout(jPanel3Layout);
+        jPanel3Layout.setHorizontalGroup(
+            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, 164, Short.MAX_VALUE)
+            .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+            .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+        );
+        jPanel3Layout.setVerticalGroup(
+            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel3Layout.createSequentialGroup()
+                .addComponent(jLabel2)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 26, Short.MAX_VALUE)
+                .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+        );
+
+        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+        getContentPane().setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+                        .addComponent(canvasWrapper, javax.swing.GroupLayout.DEFAULT_SIZE, 412, Short.MAX_VALUE)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addContainerGap())
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(canvasWrapper, javax.swing.GroupLayout.DEFAULT_SIZE, 365, Short.MAX_VALUE)
+                    .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addContainerGap())
+        );
+    }// </editor-fold>//GEN-END:initComponents
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JPanel canvasWrapper;
+    private javax.swing.JSpinner delaySpinner;
+    private javax.swing.JSpinner durationSpinner;
+    private javax.swing.JComboBox easingCbox;
+    private javax.swing.JLabel jLabel1;
+    private javax.swing.JLabel jLabel2;
+    private javax.swing.JLabel jLabel3;
+    private javax.swing.JLabel jLabel4;
+    private javax.swing.JLabel jLabel5;
+    private javax.swing.JLabel jLabel6;
+    private javax.swing.JLabel jLabel7;
+    private javax.swing.JLabel jLabel8;
+    private javax.swing.JLabel jLabel9;
+    private javax.swing.JPanel jPanel1;
+    private javax.swing.JPanel jPanel2;
+    private javax.swing.JPanel jPanel3;
+    private javax.swing.JPanel jPanel4;
+    private javax.swing.JScrollPane jScrollPane1;
+    private javax.swing.JTextArea resultArea;
+    private javax.swing.JSpinner rptDelaySpinner;
+    private javax.swing.JSpinner rptSpinner;
+    private javax.swing.JCheckBox yoyoChk;
+    // End of variables declaration//GEN-END:variables
+
+}
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/blankStrip.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/blankStrip.png
new file mode 100644
index 0000000..aac8e74
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/blankStrip.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-engine.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-engine.png
new file mode 100644
index 0000000..966d1fd
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-engine.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-logo.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-logo.png
new file mode 100644
index 0000000..65116f5
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-logo.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-tween.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-tween.png
new file mode 100644
index 0000000..5380f6f
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-tween.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-universal.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-universal.png
new file mode 100644
index 0000000..173d429
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/img-universal.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/logo-timeline.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/logo-timeline.png
new file mode 100644
index 0000000..f4bb828
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/logo-timeline.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/logo-tween.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/logo-tween.png
new file mode 100644
index 0000000..4ce2e41
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/logo-tween.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/transparent-dark.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/transparent-dark.png
new file mode 100644
index 0000000..e693df3
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/transparent-dark.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/transparent-light.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/transparent-light.png
new file mode 100644
index 0000000..447cbee
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/transparent-light.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/tweenengine/applets/gfx/vial.png b/java/applets/src/aurelienribon/tweenengine/applets/gfx/vial.png
new file mode 100644
index 0000000..2ab0064
--- /dev/null
+++ b/java/applets/src/aurelienribon/tweenengine/applets/gfx/vial.png
Binary files differ
diff --git a/java/applets/src/aurelienribon/utils/swing/DrawingCanvas.java b/java/applets/src/aurelienribon/utils/swing/DrawingCanvas.java
new file mode 100644
index 0000000..7baa279
--- /dev/null
+++ b/java/applets/src/aurelienribon/utils/swing/DrawingCanvas.java
@@ -0,0 +1,51 @@
+package aurelienribon.utils.swing;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JPanel;
+import javax.swing.Timer;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com
+ */
+public abstract class DrawingCanvas extends JPanel {
+	private final Timer timer;
+	private long lastMillis;
+	private Callback callback;
+
+	public DrawingCanvas() {
+		timer = new Timer(1000/60, loop);
+		timer.setRepeats(true);
+	}
+
+	protected abstract void update(int elapsedMillis);
+
+	public DrawingCanvas start() {
+		lastMillis = System.currentTimeMillis();
+		timer.start();
+		return this;
+	}
+
+	public void stop() {
+		timer.stop();
+	}
+
+	public void setCallback(Callback callback) {
+		this.callback = callback;
+	}
+
+	private final ActionListener loop = new ActionListener() {
+		@Override public void actionPerformed(ActionEvent e) {
+			final long millis = System.currentTimeMillis();
+			final long delta = millis - lastMillis;
+			lastMillis = millis;
+
+			update((int) delta);
+			if (callback != null) callback.onUpdate((int) delta);
+		}
+	};
+
+	public interface Callback {
+		public void onUpdate(int elapsedMillis);
+	}
+}
diff --git a/java/applets/src/aurelienribon/utils/swing/GroupBorder.java b/java/applets/src/aurelienribon/utils/swing/GroupBorder.java
new file mode 100644
index 0000000..a4db3a9
--- /dev/null
+++ b/java/applets/src/aurelienribon/utils/swing/GroupBorder.java
@@ -0,0 +1,54 @@
+package aurelienribon.utils.swing;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import javax.swing.border.Border;
+
+public class GroupBorder implements Border {
+	private final int titleHeight = 20;
+	private final int borderPadding = 0;
+	private String title = "";
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	@Override
+	public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+		Graphics2D gg = (Graphics2D) g.create();
+		
+		int titleW = gg.getFontMetrics().stringWidth(title) + 20;
+		int titleDescent = gg.getFontMetrics().getDescent();
+		
+		gg.setColor(c.getBackground());
+
+		if (!title.equals("")) {
+			int[] xs = {0, titleW, titleW + titleHeight, 0};
+			int[] ys = {0, 0, titleHeight, titleHeight};
+			gg.fillPolygon(xs, ys, 4);
+			gg.fillRect(0, titleHeight, width, height);
+			gg.setColor(c.getForeground());
+			gg.drawString(title, 10, titleHeight - titleDescent);
+		} else {
+			gg.fillRect(0, 0, width, height);
+		}
+		
+		gg.dispose();
+	}
+
+	@Override
+	public Insets getBorderInsets(Component c) {
+		return new Insets(!title.equals("") ? borderPadding + titleHeight : borderPadding, borderPadding, borderPadding, borderPadding);
+	}
+
+	@Override
+	public boolean isBorderOpaque() {
+		return false;
+	}
+}
diff --git a/java/applets/src/aurelienribon/utils/swing/ImagePanel.java b/java/applets/src/aurelienribon/utils/swing/ImagePanel.java
new file mode 100644
index 0000000..8e9be0f
--- /dev/null
+++ b/java/applets/src/aurelienribon/utils/swing/ImagePanel.java
@@ -0,0 +1,129 @@
+package aurelienribon.utils.swing;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.TexturePaint;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.imageio.ImageIO;
+import javax.swing.JPanel;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com
+ */
+public class ImagePanel extends JPanel {
+	private BufferedImage background;
+	private BufferedImage image;
+	private boolean useRegion;
+	private int x, y, width, height;
+
+	public void setBackground(URL bgURL) {
+		try {
+			background = ImageIO.read(bgURL);
+		} catch (IOException ex) {
+		}
+	}
+
+	public void clearImage() {
+		image = null;
+		repaint();
+	}
+
+	public void setImage(BufferedImage img) {
+		image = img;
+		repaint();
+	}
+
+	public void setImage(File img) {
+		setImage(img, 0, 0, 0, 0);
+		useRegion = false;
+	}
+
+	public void setImage(URL imgUrl) {
+		setImage(imgUrl, 0, 0, 0, 0);
+		useRegion = false;
+	}
+
+	public void setImage(File img, int x, int y, int width, int height) {
+		this.x = x;
+		this.y = y;
+		this.width = width;
+		this.height = height;
+		useRegion = true;
+
+		try {
+			image = img != null ? ImageIO.read(img) : null;
+			repaint();
+		} catch (IOException ex) {
+			Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex);
+		}
+	}
+
+	public void setImage(URL imgUrl, int x, int y, int width, int height) {
+		this.x = x;
+		this.y = y;
+		this.width = width;
+		this.height = height;
+		useRegion = true;
+
+		try {
+			image = imgUrl != null ? ImageIO.read(imgUrl) : null;
+			repaint();
+		} catch (IOException ex) {
+			Logger.getLogger(ImagePanel.class.getName()).log(Level.SEVERE, null, ex);
+		}
+	}
+
+	@Override
+	protected void paintComponent(Graphics g) {
+		Graphics2D gg = (Graphics2D)g;
+		gg.setColor(Color.LIGHT_GRAY);
+		gg.fillRect(0, 0, getWidth(), getHeight());
+
+		if (background != null) {
+			TexturePaint paint = new TexturePaint(background, new Rectangle(0, 0, background.getWidth(), background.getHeight()));
+			gg.setPaint(paint);
+			gg.fillRect(0, 0, getWidth(), getHeight());
+			gg.setPaint(null);
+		}
+
+		if (image != null && !useRegion) {
+			float panelRatio = (float)getWidth() / (float)getHeight();
+			float imgRatio = (float)image.getWidth() / (float)image.getHeight();
+
+			if (imgRatio > panelRatio) {
+				float tw = (float)getWidth();
+				float th = (float)getWidth() / imgRatio;
+				gg.drawImage(image, 0, (int)(getHeight()/2 - th/2), (int) tw, (int) th, null);
+			} else {
+				float tw = (float)getHeight() * imgRatio;
+				float th = (float)getHeight();
+				gg.drawImage(image, (int)((float)getWidth()/2 - tw/2), 0, (int) tw, (int) th, null);
+			}
+
+		} else if (image != null && useRegion) {
+			float panelRatio = (float)getWidth() / (float)getHeight();
+			float imgRatio = (float)width / (float)height;
+
+			if (imgRatio > panelRatio) {
+				int tw = getWidth();
+				int th = (int) (getWidth() / imgRatio);
+				int tx = 0;
+				int ty = getHeight()/2 - th/2;
+				gg.drawImage(image, tx, ty, tx + tw, ty + th, x, y, x + width, y + width, null);
+			} else {
+				int tw = (int) (getHeight() * imgRatio);
+				int th = getHeight();
+				int tx = getWidth()/2 - tw/2;
+				int ty = 0;
+				gg.drawImage(image, tx, ty, tx + tw, ty + th, x, y, x + width, y + width, null);
+			}
+		}
+	}
+}
diff --git a/java/applets/src/aurelienribon/utils/swing/SwingHelper.java b/java/applets/src/aurelienribon/utils/swing/SwingHelper.java
new file mode 100644
index 0000000..60c2758
--- /dev/null
+++ b/java/applets/src/aurelienribon/utils/swing/SwingHelper.java
@@ -0,0 +1,81 @@
+package aurelienribon.utils.swing;
+
+import java.awt.Component;
+import java.awt.Desktop;
+import java.awt.Desktop.Action;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Window;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.event.WindowListener;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+/**
+ * @author Aurelien Ribon | http://www.aurelienribon.com
+ */
+public class SwingHelper {
+	/**
+	 * Adds a listener to the window parent of the given component. Can be
+	 * before the component is really added to its hierachy.
+	 * @param source The source component
+	 * @param listener The listener to add to the window
+	 */
+	public static void addWindowListener(final Component source, final WindowListener listener) {
+		if (source instanceof Window) {
+			((Window)source).addWindowListener(listener);
+		} else {
+			source.addHierarchyListener(new HierarchyListener() {
+				@Override public void hierarchyChanged(HierarchyEvent e) {
+					if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) == HierarchyEvent.SHOWING_CHANGED) {
+						SwingUtilities.getWindowAncestor(source).addWindowListener(listener);
+					}
+				}
+			});
+		}
+	}
+
+	/**
+	 * Centers a component according to the window location.
+	 * @param wnd The parent window
+	 * @param cmp A component, usually a dialog
+	 */
+	public static void centerInWindow(Window wnd, Component cmp) {
+		Dimension size = wnd.getSize();
+		Point loc = wnd.getLocationOnScreen();
+		Dimension cmpSize = cmp.getSize();
+		loc.x += (size.width  - cmpSize.width)/2;
+		loc.y += (size.height - cmpSize.height)/2;
+		cmp.setBounds(loc.x, loc.y, cmpSize.width, cmpSize.height);
+	}
+
+	/**
+	 * Opens the given website in the default browser, or show a message saying
+	 * that no default browser could be accessed.
+	 * @param parent The parent of the error message, if raised
+	 * @param uri The website uri
+	 */
+	public static void browse(Component parent, String uri) {
+		boolean cannotBrowse = false;
+		if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Action.BROWSE)) {
+			try {
+				Desktop.getDesktop().browse(new URI(uri));
+			} catch (URISyntaxException ex) {
+			} catch (IOException ex) {
+				cannotBrowse = true;
+			}
+		} else {
+			cannotBrowse = true;
+		}
+
+		if (cannotBrowse) {
+			JOptionPane.showMessageDialog(parent,
+				"It seems that I can't open a website using your"
+				+ "default browser, sorry.");
+		}
+	}
+}
diff --git a/java/applets/test-timeline.html b/java/applets/test-timeline.html
new file mode 100644
index 0000000..d70feca
--- /dev/null
+++ b/java/applets/test-timeline.html
@@ -0,0 +1,9 @@
+<html>	
+	<body>
+		<applet archive="tween-engine-applets.jar" 
+		        code="aurelienribon.tweenengine.applets.TimelineApplet"
+				codebase="dist"
+				width="600"
+				height="670"/>
+	</body>
+</html>
\ No newline at end of file
diff --git a/java/applets/test-tween.html b/java/applets/test-tween.html
new file mode 100644
index 0000000..47aa731
--- /dev/null
+++ b/java/applets/test-tween.html
@@ -0,0 +1,9 @@
+<html>	
+	<body>
+		<applet archive="tween-engine-applets.jar" 
+		        code="aurelienribon.tweenengine.applets.TweenApplet"
+				codebase="dist"
+				width="600"
+				height="550"/>
+	</body>
+</html>
\ No newline at end of file
diff --git a/java/build.xml b/java/build.xml
new file mode 100644
index 0000000..ea8cca5
--- /dev/null
+++ b/java/build.xml
@@ -0,0 +1,51 @@
+<project name="tween-engine-java" default="all" basedir=".">
+
+	<!-- ****************************************************************** -->
+	<!-- Definitions -->
+	<!-- ****************************************************************** -->
+	
+	<!-- version -->
+	<property name="version" value="6.3.3" />
+
+	<!-- projects properties -->
+	<property name="api.dir" value="api/" />
+	<property name="api.name" value="tween-engine-api" />
+
+	<!-- ****************************************************************** -->
+	<!-- Build tasks -->
+	<!-- ****************************************************************** -->
+	
+	<!-- clean -->	
+	<target name="clean">
+		<delete dir="build/" />
+	</target>
+
+	<!-- init -->
+	<target name="init" depends="clean">
+		<mkdir dir="build/${api.dir}/bin/" />
+	</target>
+
+	<!-- compile -->
+	<target name="compile" depends="init">
+		<javac target="1.6" source="1.6" debug="on" srcdir="${api.dir}/src/" destdir="build/${api.dir}/bin/" />
+	</target>
+	
+	<!-- package -->
+	<target name="package" depends="compile">
+		<!-- api -->
+		<jar destfile="build/${api.dir}/${api.name}.jar" basedir="build/${api.dir}/bin/" />
+		<jar destfile="build/${api.dir}/${api.name}-sources.jar" basedir="${api.dir}/src/" />
+	</target>
+
+	<!-- zip -->
+	<target name="zip" depends="package">
+		<zip destfile="${api.name}-${version}.zip">
+			<fileset dir="build/${api.dir}/" includes="*.jar" />
+		</zip>
+	</target>
+	
+	<!-- all -->
+	<target name="all" depends="zip">
+		<antcall target="clean"/>
+	</target>
+</project>