Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CodenameOne/src/com/codename1/ui/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
package com.codename1.ui;

import com.codename1.impl.CodenameOneImplementation;
import com.codename1.ui.animations.AnimationTime;
import com.codename1.ui.animations.ComponentAnimation;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.animations.Transition;
Expand Down Expand Up @@ -4497,7 +4498,7 @@ static class MorphAnimation extends ComponentAnimation {
private Component scrollTo;

public MorphAnimation(Container thisContainer, int duration, Motion[][] motions) {
startTime = System.currentTimeMillis();
startTime = AnimationTime.now();
this.duration = duration;
if (Motion.isSlowMotion()) {
this.duration *= 50;
Expand Down Expand Up @@ -4566,7 +4567,7 @@ protected void updateState() {
thisContainer.setSmoothScrolling(s);
}
thisContainer.repaint();
if (System.currentTimeMillis() - startTime >= duration) {
if (AnimationTime.now() - startTime >= duration) {
setEnableLayoutOnPaint(true);
thisContainer.dontRecurseContainer = false;
Form f = thisContainer.getComponentForm();
Expand Down
5 changes: 3 additions & 2 deletions CodenameOne/src/com/codename1/ui/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.codename1.io.FileSystemStorage;
import com.codename1.io.Log;
import com.codename1.io.Util;
import com.codename1.ui.animations.AnimationTime;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.events.ActionSource;
Expand Down Expand Up @@ -1969,10 +1970,10 @@ public boolean isAnimation() {
/// `true` if the animation state changed.
public boolean animate() {
if (imageTime == -1) {
imageTime = System.currentTimeMillis();
imageTime = AnimationTime.now();
}
boolean val = Display.impl.animateImage(image, imageTime);
imageTime = System.currentTimeMillis();
imageTime = AnimationTime.now();
return val;
}

Expand Down
7 changes: 4 additions & 3 deletions CodenameOne/src/com/codename1/ui/Label.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.codename1.ui.TextSelection.Span;
import com.codename1.ui.TextSelection.Spans;
import com.codename1.ui.TextSelection.TextSelectionSupport;
import com.codename1.ui.animations.AnimationTime;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.geom.Dimension;
Expand Down Expand Up @@ -1089,7 +1090,7 @@ public void startTicker(long delay, boolean rightToLeft) {
parent.registerAnimatedInternal(this);
}
}
tickerStartTime = System.currentTimeMillis();
tickerStartTime = AnimationTime.now();
tickerDelay = delay;
tickerRunning = true;
this.rightToLeft = rightToLeft;
Expand Down Expand Up @@ -1169,8 +1170,8 @@ public boolean animate() {
return false;
}
boolean animateTicker = false;
if (tickerRunning && tickerStartTime + tickerDelay < System.currentTimeMillis()) {
tickerStartTime = System.currentTimeMillis();
if (tickerRunning && tickerStartTime + tickerDelay < AnimationTime.now()) {
tickerStartTime = AnimationTime.now();
if (rightToLeft) {
shiftText -= Display.getInstance().convertToPixels(shiftMillimeters);
if (shiftText + getStringWidth(getStyle().getFont()) < 0) {
Expand Down
85 changes: 85 additions & 0 deletions CodenameOne/src/com/codename1/ui/animations/AnimationTime.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui.animations;

/// Pluggable time source for animations across the framework. Defaults to
/// `System.currentTimeMillis()` but can be overridden with an explicit value to
/// support deterministic playback (e.g. UI tests), to advance animations at a
/// custom pace (slow-motion, fast-forward), or to step through animation frames
/// manually.
///
/// All methods are static and the class holds only primitive state to keep the
/// hot path cheap - `now()` is invoked from every animation tick.
///
/// @author Shai Almog
public final class AnimationTime {
private static long overrideTime;
private static boolean overridden;

private AnimationTime() {
}

/// Returns the current animation time in milliseconds. Returns
/// `System.currentTimeMillis()` unless an override has been set via
/// [setTime(long)][#setTime(long)].
///
/// #### Returns
///
/// the current animation time in milliseconds
public static long now() {
if (overridden) {
return overrideTime;
}
return System.currentTimeMillis();
}

/// Overrides the value returned by [now()][#now()]. Once set, every animation
/// reading the clock will see the same `time` value until either this method
/// is called again or [reset()][#reset()] is invoked. Advancing animations
/// while overridden requires repeatedly calling this method with increasing
/// values.
///
/// #### Parameters
///
/// - `time`: the time in milliseconds that [now()][#now()] should return
public static void setTime(long time) {
overrideTime = time;
overridden = true;
}

/// Clears any override and restores [now()][#now()] to delegate to
/// `System.currentTimeMillis()`.
public static void reset() {
overridden = false;
}

/// Returns true when an override time is currently active.
///
/// #### Returns
///
/// true when [now()][#now()] is returning an overridden value
public static boolean isOverridden() {
return overridden;
}
}
17 changes: 9 additions & 8 deletions CodenameOne/src/com/codename1/ui/animations/Motion.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
/// another. This class can be subclassed to implement any motion equation for
/// appropriate physics effects.
///
/// This class relies on the System.currentTimeMillis() method to provide
/// transitions between coordinates. The motion can be subclassed to provide every
/// This class relies on [AnimationTime.now()][AnimationTime#now()] to provide
/// transitions between coordinates, allowing the underlying clock to be
/// overridden for deterministic playback or custom animation pacing. The motion can be subclassed to provide every
/// type of motion feel from parabolic motion to spline and linear motion. The default
/// implementation provides a simple algorithm giving the feel of acceleration and
/// deceleration.
Expand Down Expand Up @@ -357,7 +358,7 @@ public static Motion createDecelerationMotionFrom(Motion motion, int maxDestinat
motion.destinationValue < motion.sourceValue
? Math.min(motion.destinationValue, maxDestinationValue)
: Math.max(motion.destinationValue, maxDestinationValue),
(int) Math.min(maxDuration, motion.duration - (System.currentTimeMillis() - motion.startTime))
(int) Math.min(maxDuration, motion.duration - (AnimationTime.now() - motion.startTime))
);
}

Expand Down Expand Up @@ -396,25 +397,25 @@ public static Motion createExponentialDecayMotion(int sourceValue, int maxValue,
/// Sends the motion to the end time instantly which is useful for flushing an animation
public void finish() {
if (!isFinished()) {
startTime = System.currentTimeMillis() - duration;
startTime = AnimationTime.now() - duration;
currentMotionTime = -1;
previousCurrentMotionTime = -1;
}
}

/// Sets the start time to the current time
public void start() {
startTime = System.currentTimeMillis();
startTime = AnimationTime.now();
}

/// Returns the current time within the motion relative to start time
///
/// #### Returns
///
/// long value representing System.currentTimeMillis() - startTime
/// long value representing AnimationTime.now() - startTime
public long getCurrentMotionTime() {
if (currentMotionTime < 0) {
return System.currentTimeMillis() - startTime;
return AnimationTime.now() - startTime;
}
return currentMotionTime;
}
Expand Down Expand Up @@ -444,7 +445,7 @@ public boolean isDecayMotion() {
///
/// #### Returns
///
/// true if System.currentTimeMillis() > duration + startTime or the last returned value is the destination value
/// true if AnimationTime.now() > duration + startTime or the last returned value is the destination value
public boolean isFinished() {
return getCurrentMotionTime() > duration || destinationValue == lastReturnedValue || (EXPONENTIAL_DECAY == motionType && previousLastReturnedValue[0] == lastReturnedValue);
}
Expand Down
6 changes: 3 additions & 3 deletions CodenameOne/src/com/codename1/ui/animations/Timeline.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public void setTime(int time) {
if (!pause) {
if (time >= 0 && time <= duration) {
this.time = time;
currentTime = System.currentTimeMillis();
currentTime = AnimationTime.now();
}
}
}
Expand All @@ -161,11 +161,11 @@ public boolean isAnimation() {
public boolean animate() {
if (!pause) {
if (currentTime < 0) {
currentTime = System.currentTimeMillis();
currentTime = AnimationTime.now();
setTime(0);
return true;
} else {
long newCurrentTime = System.currentTimeMillis();
long newCurrentTime = AnimationTime.now();
if (newCurrentTime - currentTime >= animationDelay) {
int newTime = (int) (time + (newCurrentTime - currentTime));
currentTime = newCurrentTime;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.codename1.ui.animations;

import com.codename1.junit.UITestBase;
import com.codename1.ui.geom.Dimension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class AnimationTimeTest extends UITestBase {

@AfterEach
void resetClock() {
AnimationTime.reset();
}

@Test
void nowDefaultsToSystemClock() {
long before = System.currentTimeMillis();
long now = AnimationTime.now();
long after = System.currentTimeMillis();
assertFalse(AnimationTime.isOverridden());
assertTrue(now >= before && now <= after);
}

@Test
void setTimeOverridesNow() {
AnimationTime.setTime(123_456L);
assertTrue(AnimationTime.isOverridden());
assertEquals(123_456L, AnimationTime.now());
assertEquals(123_456L, AnimationTime.now());
}

@Test
void setTimeAcceptsZero() {
AnimationTime.setTime(0L);
assertTrue(AnimationTime.isOverridden());
assertEquals(0L, AnimationTime.now());
}

@Test
void setTimeAcceptsNegative() {
AnimationTime.setTime(-100L);
assertTrue(AnimationTime.isOverridden());
assertEquals(-100L, AnimationTime.now());
}

@Test
void resetRestoresSystemClock() {
AnimationTime.setTime(42L);
assertEquals(42L, AnimationTime.now());

AnimationTime.reset();
assertFalse(AnimationTime.isOverridden());

long before = System.currentTimeMillis();
long now = AnimationTime.now();
long after = System.currentTimeMillis();
assertTrue(now >= before && now <= after);
}

@Test
void motionHonorsOverriddenClock() {
AnimationTime.setTime(1000L);
Motion m = Motion.createLinearMotion(0, 100, 1000);
m.start();
assertEquals(0L, m.getCurrentMotionTime());

AnimationTime.setTime(1500L);
assertEquals(500L, m.getCurrentMotionTime());
assertEquals(50, m.getValue());

// advance past duration so isFinished() trips on the time check
AnimationTime.setTime(2001L);
assertTrue(m.isFinished());
assertEquals(100, m.getValue());
}

@Test
void motionFinishUsesOverriddenClock() {
AnimationTime.setTime(5000L);
Motion m = Motion.createLinearMotion(0, 200, 1000);
m.start();
AnimationTime.setTime(5100L);
assertFalse(m.isFinished());

m.finish();
// finish() rewinds startTime to (now - duration); reading the value latches
// lastReturnedValue to destinationValue, which makes isFinished() true.
assertEquals(200, m.getValue());
assertTrue(m.isFinished());
}

@Test
void timelineAnimateHonorsOverriddenClock() {
AnimationTime.setTime(10_000L);
Timeline timeline = Timeline.createTimeline(1000, new AnimationObject[0], new Dimension(1, 1));
timeline.setAnimationDelay(0);

// first animate() seeds the clock and sets time to 0
assertTrue(timeline.animate());
assertEquals(0, timeline.getTime());

// advance the clock and re-animate; timeline should advance by the same delta
AnimationTime.setTime(10_250L);
assertTrue(timeline.animate());
assertEquals(250, timeline.getTime());

AnimationTime.setTime(10_750L);
assertTrue(timeline.animate());
assertEquals(750, timeline.getTime());
}
}
Loading