TimedElementSupportpublic class TimedElementSupport extends Object The TimedElementSupport class is the main abstraction of the
SMIL timing model implementation.
It is responsible for computing the animation's state (inactive, active,
post-acitve, frozen) and, when the application is active, what the current
simple time is. That simple time is then used by the animation engine to
compute animated values.
SMIL has a notion of time containers. A TimedElementSupport
is the child of a time container. The relationship is that the element's
current time is the container's simple time. In otherwords, sampling a
container for a given time (in its own container), yields a simple time for
the container. That simple time is the timed element's current time, used to
compute the timed element's own simple time. Refer to the SMIL 2
specification for an explanation of what the various time semantics are. |
Fields Summary |
---|
public static final String | LAST_DUR_END_EVENT_TYPELast Simple Duration End event type. | public static final String | SEEK_END_EVENT_TYPESeek End event type. | public static final String | SEEK_BEGIN_EVENT_TYPESeek End event type. | public static final String | END_EVENT_TYPEEnd event type. | public static final String | BEGIN_EVENT_TYPEBegin event type. | public static final String | REPEAT_EVENT_TYPERepeat event type | public static final int | RESTART_ALWAYSUsed when this TimedElement can be restarted under
any circumstance. | public static final int | RESTART_WHEN_NOT_ACTIVEUsed when this TimedElement can only be restarted when
it is not active (i.e., it does not have a current interval. | public static final int | RESTART_NEVERUsed when this TimedElement cannot be restarted under
any circumstance | public static final int | FILL_BEHAVIOR_REMOVEUsed when this TimedElement should not modify the
animated value when it is post active. | public static final int | FILL_BEHAVIOR_FREEZEUsed when this TimedElement should maintain the
last value of its simple interval after it becomes inactive. | protected static final int | STATE_PRE_INITState before the element has been initialized | protected static final int | STATE_NO_INTERVALState where there has never been a current interval
(since the last init). | protected static final int | STATE_WAITING_INTERVAL_0State while the element is waiting to begin
the first interval. | protected static final int | STATE_WAITING_INTERVAL_NState while the element is waiting to begin
an interval other than the first one | protected static final int | STATE_PLAYINGState while the element is playing the current
interval. | protected static final int | STATE_FILLState when the element performs any fill on the previous
interval. | protected final Time | eventTimeThis time value is used to avoid creating Time instances
over and over again in animation loops, when dispatching
events. | int | stateThe element's state. One of the STATE_XXX constants. | boolean | playFillControls whether the element is in this rare state
where it is playing but its last simple duration
is finished. In that state, the element is 'playing',
but acting as if frozen.
There are situations where the min attribute may cause the
active duration to go beyond the time of the last simple
duration instance. In that case, the element is still
in the playing state, but we mark it as 'playFill', which
means that it should behave almost as if in the fill state. | int | curIterThe current iteration (only used while playing | Time | durThis element's simple duration. This corresponds to the 'dur'
attribute. Note that this is only one of the parameters that
defines the actual simple duration. | float | repeatCountThe number of times this element repeats the simple duration.
NaN means unspecified. | Time | repeatDurrepeatDur set a limit on active duration length. | Time | implicitDurationThis element's implicit duration | Time | minmin sets a lower limit on the active duration. | Time | maxmax sets an upper limit on the active duration | int | restartDefines the restart behavior for this element. This should be
one of RESTART_ALWAYS, RESTART_WHEN_NOT_ACTIVE, RESTART_NEVER. | int | fillBehaviorDefines the behavior after this element is no longer active.
If FILL_BEHAVIOR_FILL, it means the element will keep sampling at the
last simple duration time. If FILL_BEHAVIOR_REMOVE (the default),
the effect of the animation is removed when the element is
inactive (i.e., when it no longer has a current interval. | protected TimeContainerSupport | timeContainerA reference to this element's time container. This is initialized
anytime the parent is set. This element's current time is the
container's simple time. | TimeInterval | currentIntervalKeeps a reference to the current TimeInterval | long | lastSampleTimeLast local time (i.e., within the simple duration) this element
was sampled at. | Time | simpleDurThe simple duration for the current interval | TimeInterval | previousIntervalKeeps a reference to the interval preceding the current one. | Vector | beginInstancesList of begin TimeInstance s. | Vector | endInstancesList of end TimeInstance s. | Vector | beginConditionsBegin TimeCondition s. Should _never_ be null. | Vector | endConditionsEnd TimeCondition s. Should _never_ be null. | Vector | beginDependentsList of SyncBaseCondition s depedent on this
element's begin condition. | Vector | endDependentsList of SyncBaseCondition s dependent on this
element's end condition. | ModelNode | animationElementThe associated animation element. | boolean | timingUpdateTime dependency cycles detector. This flag is set to true
when a timing update starts. There should only be one at
a time, and it should always be set back to true after
and timing update is complete. | boolean | seekingThe seeking flag is used when seeking to a particular time
to prevent generation of beginEvent, endEvent and repeat
event. |
Methods Summary |
---|
public void | activate()Called when this element is the target of an hyperlink. The
behavior is that defined in the SMIL 2 specification.
// 1. If the target element is active, seek the document time back to
// the begin time of the current interval for the element.
if (state == STATE_PLAYING) {
Time seekToTime = new Time(currentInterval.begin.value);
seekToTime = toRootContainerSimpleTimeClamp(seekToTime);
if (seekToTime.isResolved()) {
seekTo(seekToTime);
return;
}
}
//
// 2. Else if the target element begin time is resolved (i.e. there is
// at least one interval defined for the element), seek the document
// time (forward or back, as needed) to the begin time of the first
// interval for the target element. Note that the begin time may be
// resolved as a result of an earlier hyperlink, DOM or event
// activation. Once the begin time is resolved (and until the element is
// reset, e.g. when the parent repeats), hyperlink traversal always
// seeks. For a discussion of "reset", see Resetting element state. Note
// also that for an element begin to be resolved, the begin time of all
// ancestor elements must also be resolved.
//
TimeInterval firstInterval = computeFirstInterval();
if (firstInterval != null) {
Time seekToTime = new Time(firstInterval.begin.value);
seekToTime = toRootContainerSimpleTimeClamp(seekToTime);
if (seekToTime.isResolved()) {
seekTo(seekToTime);
return;
}
}
// 3. Else (i.e. there are no defined intervals for the element), the
// target element begin time must be resolved. This may require seeking
// and/or resolving ancestor elements as well. This is done by recursing
// from the target element up to the closest ancestor element that has a
// resolved begin time (again noting that for an element to have a
// resolved begin time, all of its ancestors must have resolved begin
// times). Then, the recursion is "unwound", and for each ancestor in
// turn (beneath the resolved ancestor) as well as the target element,
// the following steps are performed:
//
// 1. If the element begin time is resolved, seek the document time
// (forward or back, as needed) to the begin time of the first
// interval for the target element.
//
// 2. Else (if the begin time is not resolved), just resolve the
// element begin time at the current time on its parent time
// container (given the current document position). Disregard the
// sync-base or event base of the element, and do not
// "back-propagate" any timing logic to resolve the element, but
// rather treat it as though it were defined with begin="indefinite"
// and just resolve begin time to the current parent time. This
// should create an interval and propagate to time dependents.
//
seekTo(Time.UNRESOLVED);
| void | addInstance(boolean isBegin, long offset)Adds a new TimedInstance with the specified offset
to the begin or end instance list (depending on isBegin
Time currentTime = getCurrentTime();
if (!currentTime.isResolved()) {
// If the current time is not resolved, it means the time
// container is not active. Therefore, there is no way
// we can add a new time instance that would be meaningful.
// So we do nothing.
return;
}
Time newTime = new Time(currentTime.value + offset);
TimeInstance newInstance
= new TimeInstance(this, newTime, true, isBegin);
| void | addTimeCondition(TimeCondition newCondition)Called by TimeCondition s when they are constructed.
if (newCondition.isBegin) {
beginConditions.addElement(newCondition);
} else {
endConditions.addElement(newCondition);
}
| void | addTimeInstance(TimeInstance newInstance)Called to add a new TimeInstance to the begin or end
instance list (depending on the new instance's isBegin
setting).
This is also called as a result of invoking beginAt or endAt.
if (timingUpdate) {
// We are in a timing dependency cycle. Break now.
return;
}
timingUpdate = true;
try {
insertTimeInstance(newInstance);
// Do not handle instance time list changes if we are
// initializing the time graph.
if (state == STATE_PRE_INIT) {
return;
}
if (!newInstance.isBegin) {
reEvaluateEndTime();
return;
}
// Impact the current interval if needed
switch (state) {
case STATE_NO_INTERVAL:
checkNewInterval(computeFirstInterval());
break;
case STATE_FILL:
if (restart != RESTART_NEVER) {
checkNewInterval(computeNextInterval());
}
// Else, don't do anything: this element cannot
// restart.
break;
case STATE_WAITING_INTERVAL_0:
{
TimeInterval curInt = computeFirstInterval();
if (curInt != null) {
currentInterval.setBegin(curInt.begin);
currentInterval.setEnd(curInt.end);
computeLastDur(currentInterval);
}
}
break;
case STATE_WAITING_INTERVAL_N:
{
TimeInterval curInt = computeNextInterval();
// We just added a new time instance. It is not possible
// that adding a time instance would make
// computeNextInterval return a null interval here.
// So we do not test for a null condition because it
// can't happen. If it happens,
// it means there is a bug in computeNextInterval or that
// the beginInstance list was messed with, which should not
// happen.
currentInterval.setBegin(curInt.begin);
currentInterval.setEnd(curInt.end);
computeLastDur(currentInterval);
}
break;
case STATE_PLAYING:
if (newInstance.isBegin) {
if (restart == RESTART_ALWAYS) {
if (currentInterval.end
.greaterThan(newInstance.time)
&&
newInstance.time
.greaterThan(currentInterval.begin)) {
// Update the current interval end time with this
// new begin time.
currentInterval.setEnd(newInstance.time);
computeLastDur(currentInterval);
// Next interval will be computed in the next call
// to sample();
}
// Ignore the new instance if it if falls after or
// before the current interval
}
// Ignore the new instance if we cannot restart this
// animation.
}
break;
default:
throw new IllegalStateException();
}
} finally {
timingUpdate = false;
}
| public void | begin()Calling this method causes a new TimeInstance to
be inserted in the begin instance list. The behavior depends
on the restart setting.
beginAt(0);
| public void | beginAt(long offset)Calling this method causes a new TimeInstance to
be inserted in the begin instance list. The behavior depends
on the restart setting.
addInstance(true, offset);
| Time | calculateActiveEnd(Time begin, Time end)Computes the active end of this TimedElement for
the given begin and end time constraints. This accounts for
the other attributes which control or constrain the
active duration.
Time result = end;
Time pad = null;
if (begin == null) {
throw new NullPointerException();
}
if (!begin.isResolved()) {
throw new IllegalArgumentException();
}
//
// In the context of this method, end and begin are the time instances
// passed to this method, and _not_ the begin and end attributes.
//
// If endTime is not null, and none of dur, repeatDur, and repeatCount
// are specified, then the simple duration is indefinite from the simple
// duration table above (see SMIL 2 spec), and the active duration is
// defined by the end value, according to the following cases:
//
// If end is resolved to a value, then pad = end - B,
//
// else, if end is indefinite, then pad = indefinite,
//
// else, if end is unresolved, then pad is unresolved, and needs to be
// recomputed when more information becomes available.
//
if (end != null
&& dur == null
&& repeatDur == null
&& Float.isNaN(repeatCount)) {
if (end.isResolved()) {
pad = new Time(end.value - begin.value);
} else if (end == Time.INDEFINITE) {
pad = Time.INDEFINITE;
} else {
pad = Time.UNRESOLVED;
}
}
// Else, if no end value is specified, or the end value is specified as
// indefinite, then the active duration is determined from the
// Intermediate Active Duration computation given below:
//
// pad = Result from Intermediate Active Duration Computation
else if (end == null || end == Time.INDEFINITE) {
pad =
calculateIntermediateActiveDuration(computeSimpleDuration(end));
}
// Otherwise, an end value not equal to indefinite is specified along
// with at least one of dur, repeatDur, and repeatCount. Then the pad is
// the minimum of the result from the Intermediate Active Duration
// Computation given below and duration between end and the element
// begin:
//
// pad = MIN( Result from Intermediate Active Duration Computation,
// end - B)
else {
pad =
calculateIntermediateActiveDuration(computeSimpleDuration(end));
Time pad2 = Time.UNRESOLVED;
if (end.isResolved()) {
pad2 = new Time(end.value - begin.value);
}
if (pad.greaterThan(pad2)) {
pad = pad2;
}
}
// Finally, the computed active duration ad is obtained by applying min
// and max semantics to the preliminary active duration pad. In the
// following expression, if there is no min value, substitute a value of
// 0, and if there is no max value, substitute a value of "indefinite":
//
// ad = MIN( max, MAX( min, pad ))
// Only do min/max constraint if min < max
boolean doMinMax = true;
if (min != null && max != null && !max.greaterThan(min)) {
doMinMax = false;
}
Time ad = null;
if (pad.isResolved()) {
ad = new Time(pad.value);
} else {
ad = pad;
}
if (doMinMax) {
// MAX( min, pad )
if (min != null && min.greaterThan(ad)) {
if (min.isResolved()) {
ad.value = min.value;
} else {
ad = min;
}
}
// MIN( max, MAX( min, pad ))
if (max != null && !max.greaterThan(ad)) {
if (max == Time.INDEFINITE) {
ad = Time.INDEFINITE;
} else {
if (ad.isResolved()) {
ad.value = max.value;
} else {
ad = new Time(max.value);
}
}
}
}
// We have computed the active duration. Now, offset that
// duration with the begin time to yield the computed end time.
if (ad.isResolved()) {
ad.value += begin.value;
}
// Finally, apply the restart behavior. If restart is always
// we check if there is any end time that is before active
// end time.
if (restart == RESTART_ALWAYS) {
Time restartBegin = getTimeAfterStrict(begin, beginInstances);
if (restartBegin != null && ad.greaterThan(restartBegin)) {
ad.value = restartBegin.value;
}
}
return ad;
| final Time | calculateIntermediateActiveDuration(Time p0)Intermediate Active Duration Computation, as defined in the
SMIL 2 specifications.
if (p0 == null) {
throw new NullPointerException();
}
if (p0.isResolved() && p0.value == 0) {
return new Time(0);
} else if (repeatDur == null && Float.isNaN(repeatCount)) {
return p0;
} else {
Time p1 = Time.INDEFINITE;
Time p2 = repeatDur;
Time iad = Time.UNRESOLVED;
if (!Float.isNaN(repeatCount)) {
if (p0.isResolved()) {
if (repeatCount == Float.MAX_VALUE) {
p1 = Time.INDEFINITE;
} else {
p1 = new Time((long) (p0.value * repeatCount));
}
} else {
p1 = p0; // INDEFINITE or UNRESOLVED
}
}
if (p2 == null) {
p2 = Time.INDEFINITE;
}
iad = Time.INDEFINITE;
if (!p2.greaterThan(iad)) {
iad = p2;
}
if (!p1.greaterThan(iad)) {
iad = p1;
}
return iad;
}
| boolean | checkNewInterval(TimeInterval interval)Checks if the input interval is non null. If it is, that interval
becomes the new currentInterval and the element sets its state
accordingly.
if (interval == null) {
return false;
} else {
currentInterval = interval;
simpleDur = computeSimpleDuration(currentInterval.end);
// Transition to the current state
// Note that we move to the 'playing' stage only in the
// sample method, where event dispatching is done as well.
if (previousInterval == null) {
state = STATE_WAITING_INTERVAL_0;
} else {
state = STATE_WAITING_INTERVAL_N;
}
// Notify dependents of the new interval
dispatchOnNewInterval();
return true;
}
| public void | checkPreInit()Implementation helper. Throws an exception if the element is
not in the STATE_PRE_INIT state.
if (state != STATE_PRE_INIT) {
throw new IllegalStateException();
}
| Time | computeEndTime(Time tempBegin)Computes the end time corresponding to the input begin time.
if (endInstances.size() == 0) {
return calculateActiveEnd(tempBegin, null);
} else {
Time tempEnd = getTimeAfter(tempBegin, endInstances);
if (tempBegin.isSameTime(tempEnd)) {
// IMPL NOTE : Do this to get a strictly superior end time
tempEnd = new Time(tempEnd.value + 1);
tempEnd = getTimeAfter(tempEnd, endInstances);
}
if (tempEnd == null) {
if (endHasEventConditions() || endInstances.size() == 0) {
tempEnd = Time.UNRESOLVED;
} else {
return null;
}
}
return calculateActiveEnd(tempBegin, tempEnd);
}
| TimeInterval | computeFirstInterval()The following computes the element's first interval, as defined
in the SMIL specification.
Time beginAfter = new Time(Long.MIN_VALUE);
Time parentSimpleEnd = getContainerSimpleDuration();
while (true) { // loop till return
Time tempBegin = getTimeAfter(beginAfter, beginInstances);
if (tempBegin == null) {
return null; // No interval
}
if (tempBegin.greaterThan(parentSimpleEnd)) {
return null; // No interval
}
Time tempEnd = null;
if (endConditions.size() == 0) {
tempEnd = calculateActiveEnd(tempBegin, null);
} else {
tempEnd = getTimeAfter(tempBegin, endInstances);
if (tempBegin.isSameTime(tempEnd)) {
tempEnd = getTimeAfterStrict(tempEnd, endInstances);
}
if (tempEnd == null) {
if (endHasEventConditions() || endInstances.size() == 0) {
tempEnd = Time.UNRESOLVED;
} else {
return null; // No interval
}
}
tempEnd = calculateActiveEnd(tempBegin, tempEnd);
}
// We have an end - is it after the parent simple begin?
if (!tempEnd.isResolved() || tempEnd.value > 0) {
return computeLastDur(new TimeInterval(tempBegin, tempEnd));
} else {
beginAfter = tempEnd;
}
}
| TimeInterval | computeLastDur(TimeInterval ti)Computes the time of the last simple duration instance
for the given time interval.
Time simpleDur = computeSimpleDuration(ti.end);
if (simpleDur != null) {
Time iad = calculateIntermediateActiveDuration(simpleDur);
if (iad.isResolved()) {
ti.lastDur = new Time(ti.begin.value + iad.value);
if (ti.lastDur.greaterThan(ti.end)) {
ti.lastDur = ti.end;
}
} else {
ti.lastDur = ti.end;
}
} else {
ti.lastDur = ti.end;
}
return ti;
| TimeInterval | computeNextInterval()The following computes the element's next interval, as defined
in the SMIL specification.
Time curEnd = previousInterval.end;
Time beginAfter = curEnd;
Time parentSimpleEnd = getContainerSimpleDuration();
Time tempBegin = getTimeAfter(beginAfter, beginInstances);
if (tempBegin == null) {
return null;
}
if (tempBegin.greaterThan(parentSimpleEnd)) {
return null;
}
Time tempEnd = null;
if (endConditions.size() == 0) {
tempEnd = calculateActiveEnd(tempBegin, null);
if (endInstances.size() > 0) {
// There is no end-attribute specified, but there are time
// instances. The SMIL Animation specification says that if
// there are times instances from DOM Events or DOM
// beginElementAt calls, they short cut the active duration.
//
// See: http://www.w3.org/TR/2001/REC-smil-animation-20010904/
// section 3.3.4.
//
Time tempEnd2 = getTimeAfter(tempBegin, endInstances);
if (tempEnd2 != null && tempEnd2.isResolved()) {
tempEnd = tempEnd2;
}
}
} else {
// We have a begin value - get an end
tempEnd = getTimeAfter(tempBegin, endInstances);
if (curEnd.isSameTime(tempEnd)) {
tempEnd = getTimeAfterStrict(tempEnd, endInstances);
}
if (tempEnd == null) {
if (endHasEventConditions() || endInstances.size() == 0) {
tempEnd = Time.UNRESOLVED;
} else {
return null;
}
}
tempEnd = calculateActiveEnd(tempBegin, tempEnd);
}
return computeLastDur(new TimeInterval(tempBegin, tempEnd));
| final Time | computeSimpleDuration(Time end)Computes the element's simple duration, as defined in the
SMIL 2 specification except that the 'media' value is
not supported.
For the purpose of computing the simple duration, an unspecified
dur or an UNRESOLVED dur yield the same computed simple duration.
Time implicitDur = getImplicitElementDuration();
// dur | implicit | repeatDur and | simpleDuration
// | duration | repeatCount |
// -------------+-------------+----------------+--------------------
// 1. unspecified | ignored | unspecified, | indefinite
// | | end specified |
// 2. Clock-value | ignored | ignored | dur or Clock-value
// 3. indefinite | ignored | ignored | indefinite
// 4. unspecified | resolved | ignored | implicit duration
// 5. unspecified | unresolved | ignored | unresolved
// -------------+-------------+----------------+--------------------
// First line case
if (dur == null && repeatDur == null
&& Float.isNaN(repeatCount) && end != Time.UNRESOLVED) {
return Time.INDEFINITE;
}
// Second & Third line case
if (dur != null && dur != Time.UNRESOLVED) {
return dur;
}
// Fourth line case. If we got to this point, we know
// dur is UNRESOLVED or null
if (implicitDur != Time.UNRESOLVED) {
return implicitDur;
} else {
// Fifth line
return Time.UNRESOLVED;
}
| void | dispatchBeginEvent(Time currentTime)Dispatches beginEvent. As per the SMIL 2 specification, this dispatches
a beginEvent for the resolved begin time, not the observed begin
time. It also dispatches any repeat event that might be needed.
if (animationElement != null) {
ModelEvent beginEvent = null;
if (seeking) {
beginEvent = animationElement.ownerDocument.initEngineEvent
(SEEK_BEGIN_EVENT_TYPE, animationElement);
} else {
beginEvent = animationElement.ownerDocument.initEngineEvent
(BEGIN_EVENT_TYPE, animationElement);
}
eventTime.value = currentInterval.begin.value;
beginEvent.eventTime = toRootContainerSimpleTimeClamp(eventTime);
animationElement.dispatchEvent(beginEvent);
}
dispatchRepeatEvent(currentTime);
| void | dispatchEndEvent(Time currentTime)Dispatches endEvent. As per the SMIL 2 specification, this dispatches an
endEvent for the resolved end time, not the observed end time.
if ((!seeking || state == STATE_PLAYING)
&& animationElement != null) {
ModelEvent endEvent = animationElement.ownerDocument.initEngineEvent
(END_EVENT_TYPE, animationElement);
if (seeking) {
// We are seeking to a new currentTime and the interval
// ends during the seek interval and the element was
// active at the time of seek. The time for the end
// event should be the time _before_ the seek
eventTime.value = getRootContainer().lastSampleTime.value;
endEvent.eventTime = eventTime;
} else {
eventTime.value = currentInterval.end.value;
endEvent.eventTime = toRootContainerSimpleTimeClamp(eventTime);
}
animationElement.dispatchEvent(endEvent);
}
dispatchRepeatEvent(currentTime);
| void | dispatchLastDurEndEvent()Dispatches lastDurEndEvent.
if (!seeking && simpleDur.isResolved()
&& simpleDur.value > 0) {
int prevIter = curIter;
Time maxTime = currentTime;
if (currentTime.greaterThan(currentInterval.end)) {
maxTime = currentInterval.end;
}
curIter = (int) ((maxTime.value - currentInterval.begin.value)
/
simpleDur.value);
if (curIter < 0) {
curIter = 0;
}
if (maxTime.isSameTime(currentInterval.end)
&&
((maxTime.value - currentInterval.begin.value)
%
simpleDur.value
== 0)) {
curIter--;
}
if (animationElement != null) {
for (int i = prevIter; i < curIter; i++) {
ModelEvent repeatEvent
= animationElement.ownerDocument.initEngineEvent
(REPEAT_EVENT_TYPE, animationElement);
// The event time is the time at which the repeat happened,
// not the time at which it was detected (that would be
// currentTime). So we have to compute the repeat time here
Time repeatTime = eventTime;
repeatTime.value = currentInterval.begin.value
+ (i + 1) * simpleDur.value;
repeatTime = toRootContainerSimpleTimeClamp(repeatTime);
repeatEvent.eventTime = repeatTime;
repeatEvent.repeatCount = i + 1;
animationElement.dispatchEvent(repeatEvent);
}
}
if (prevIter != curIter) {
onStartingRepeat(prevIter, curIter);
}
}
| void | dispatchSeekEndEvent()Dispatches seekeEndEvent.- see
| Time | getTimeAfter(Time after, java.util.Vector instances)
int n = instances.size();
// NOTE: The following _assumes_ that the instances vector
// is sorted in increasing time order.
for (int i = 0; i < n; i++) {
TimeInstance timeInstance = (TimeInstance) instances.elementAt(i);
if (timeInstance.time.greaterThan(after)) {
return timeInstance.time;
}
}
return null;
| Time | getTimeAfterStrict(Time after, java.util.Vector instances)
int n = instances.size();
// NOTE: The following _assumes_ that the instances vector
// is sorted in increasing time order.
for (int i = 0; i < n; i++) {
TimeInstance timeInstance = (TimeInstance) instances.elementAt(i);
if (!after.greaterThan(timeInstance.time)) {
return timeInstance.time;
}
}
return null;
| protected TimeContainerSupport | getTimeContainer()
return timeContainer;
| boolean | hasBeginCondition(EventBaseCondition eventCondition)
int n = beginConditions.size();
for (int i = 0; i < n; i++) {
TimeCondition condition =
(TimeCondition) beginConditions.elementAt(i);
if (condition instanceof EventBaseCondition) {
EventBaseCondition beginCondition =
(EventBaseCondition) condition;
if (beginCondition.eventType.equals(eventCondition.eventType)
&&
beginCondition.eventBase == eventCondition.eventBase) {
return true;
}
}
}
return false;
| protected void | initialize()This method is called by the element's time container when it
resets, i.e., when the container starts it's simple time again.
This has the effect of:
1. clearing all the instance times which have the clearOnReset
flag set to true.
2. compute the first interval.
// Clear instances with clearOnReset == true
reset();
// Compute the first interval
if (!checkNewInterval(computeFirstInterval())) {
state = STATE_NO_INTERVAL;
}
| private void | insertTimeInstance(TimeInstance newInstance)This method inserts the input time instance in the instance list
Vector instances = beginInstances;
if (!newInstance.isBegin) {
instances = endInstances;
}
int n = instances.size();
int i = 0;
Time newTime = newInstance.time;
for (i = 0; i < n; i++) {
TimeInstance timeInstance
= (TimeInstance) instances.elementAt(i);
if (!newTime.greaterThan(timeInstance.time)) {
instances.insertElementAt(newInstance, i);
break;
}
}
if (i == n) {
instances.addElement(newInstance);
}
| boolean | isDescendant(TimeContainerSupport parent)
if (parent == this) {
return true;
} else if (timeContainer != this) {
return timeContainer.isDescendant(parent);
} else {
return false;
}
| boolean | isSeekingBack()Helper method.
return getRootContainer().seekingBack;
| protected void | onStartingRepeat(int prevIter, int curIter)To be overridden by extensions (TimeContainerSupport) to do specific
processing when a new iteration is started.
| void | onTimeInstanceUpdate(TimeInstance timeInstance)Called by TimeInstance s when they are updated.
In response, the TimedElement may recompute its current
interval.
if (timingUpdate) {
// We are in a timing dependency cycle, break now.
return;
}
timingUpdate = true;
try {
// First, reposition the instance in the instance list
if (timeInstance.isBegin) {
beginInstances.removeElement(timeInstance);
} else {
endInstances.removeElement(timeInstance);
}
insertTimeInstance(timeInstance);
if (state == STATE_PRE_INIT) {
return;
}
if (timeInstance.isBegin) {
// We are dealing with a begin instance change.
reEvaluateBeginTime();
} else {
// We are dealing with an end time instance.
reEvaluateEndTime();
}
} finally {
timingUpdate = false;
}
| private void | pruneCurrentInterval()Helper method invoked when the current interval becomes invalid
after re-evaluation of the begin and end instances.
TimeInterval prunedInterval = currentInterval;
currentInterval = null;
if (previousInterval != null) {
state = STATE_FILL;
} else {
state = STATE_NO_INTERVAL;
}
prunedInterval.prune();
| private void | reEvaluateBeginTime()Should be called whenever there is a change in the begin time instance
list.
switch (state) {
case STATE_WAITING_INTERVAL_0:
{
TimeInterval curInt = computeFirstInterval();
if (curInt != null) {
currentInterval.setBegin(curInt.begin);
currentInterval.setEnd(curInt.end);
computeLastDur(currentInterval);
} else {
// There no first interval any more, after
// the begin instance has been changed.
// We move back to the no interval state
pruneCurrentInterval();
}
}
break;
case STATE_WAITING_INTERVAL_N:
{
TimeInterval curInt = computeNextInterval();
if (curInt != null) {
currentInterval.setBegin(curInt.begin);
currentInterval.setEnd(curInt.end);
computeLastDur(currentInterval);
} else {
pruneCurrentInterval();
}
}
default:
return;
}
| void | reEvaluateEndTime()Recomputes the end time. This only does something if the
element is playing or waiting to play, because it may update
the currentInterval's end time.
switch (state) {
case STATE_WAITING_INTERVAL_0:
case STATE_WAITING_INTERVAL_N:
case STATE_PLAYING:
// We are dealing with a new end time and the animation is
// playing. Re-evaluate the end time and update the
// current interval.
//
// Note that we cannot simply do a computeNextInterval as this
// might yield a begin time different from the one on the
// playing interval.
//
Time newEnd = computeEndTime(currentInterval.begin);
// It is unclear what should happen here if newEnd is null.
// This could happen if a list of end times was empty and
// a SyncBaseCondition, for example, inserts a new end instance
// that is _before_ the currentInterval's begin time. In that
// situation, should we just ignore newEnd or should we
// prune the currentInterval (if WAITING_0 or WAITING_N) and
// stop it (if PLAYING)?
//
// Pending further investigation of this corner case, we simply
// ignore a null newEnd
if (newEnd != null) {
currentInterval.setEnd(newEnd);
computeLastDur(currentInterval);
}
break;
default:
break;
}
| void | removeSyncBaseTimesUnder(TimeContainerSupport syncTimeContainer)Removes all IntervalTimeInstance s in the begin and end
instance list if the syncBase is a descendant of syncTimeContainer
int n = beginInstances.size();
for (int i = n - 1; i >= 0; i--) {
TimeInstance timeInstance
= (TimeInstance) beginInstances.elementAt(i);
if (timeInstance instanceof IntervalTimeInstance) {
IntervalTimeInstance iti = (IntervalTimeInstance) timeInstance;
if (iti.syncBase.isDescendant(syncTimeContainer)) {
beginInstances.removeElementAt(i);
iti.dispose();
}
}
}
n = endInstances.size();
for (int i = n - 1; i >= 0; i--) {
TimeInstance timeInstance =
(TimeInstance) endInstances.elementAt(i);
if (timeInstance instanceof IntervalTimeInstance) {
IntervalTimeInstance iti = (IntervalTimeInstance) timeInstance;
if (iti.syncBase.isDescendant(syncTimeContainer)) {
endInstances.removeElementAt(i);
iti.dispose();
}
}
}
| void | removeTimeInstance(TimeInstance timeInstance)Called to remove a TimeInstance when a
TimeInterval is pruned. This causes a re-evaluation
of the current interval's begin or end time.
if (timeInstance.isBegin) {
beginInstances.removeElement(timeInstance);
} else {
endInstances.removeElement(timeInstance);
}
if (timingUpdate) {
return;
}
timingUpdate = true;
try {
if (state != STATE_PRE_INIT) {
if (timeInstance.isBegin) {
reEvaluateBeginTime();
} else {
reEvaluateEndTime();
}
}
} finally {
timingUpdate = false;
}
| void | reset()Resetting the element clears all of its 'clearOnReset' instances from the
begin and end list. This includes event based time instances, time
instances resulting from begin() or end() calls and resolved
IntervalTimeInstance.
currentInterval = null;
simpleDur = Time.UNRESOLVED;
previousInterval = null;
curIter = 0;
state = STATE_PRE_INIT;
lastSampleTime = -1;
int n = beginInstances.size();
for (int i = n - 1; i >= 0; i--) {
TimeInstance timeInstance
= (TimeInstance) beginInstances.elementAt(i);
if (timeInstance.clearOnReset) {
beginInstances.removeElementAt(i);
} else if (timeInstance instanceof IntervalTimeInstance) {
((IntervalTimeInstance) timeInstance).syncTime();
}
}
n = endInstances.size();
for (int i = n - 1; i >= 0; i--) {
TimeInstance timeInstance
= (TimeInstance) endInstances.elementAt(i);
if (timeInstance.clearOnReset) {
endInstances.removeElementAt(i);
} else if (timeInstance instanceof IntervalTimeInstance) {
((IntervalTimeInstance) timeInstance).syncTime();
}
}
| void | sample(Time currentTime)This method is typically called by this element's time container
when it samples.
Note that if this element is not in the waiting or playing
state, this does nothing. This method assumes that successive
calls are made with increasing time values.
// Handle state transitions if there is a current interval
boolean endOfInterval = false;
boolean seekBack = false;
switch (state) {
default:
return;
case STATE_FILL:
if (seeking && isSeekingBack()) {
seekBack = true;
}
break;
case STATE_WAITING_INTERVAL_0:
if (currentTime.greaterThan(currentInterval.begin)) {
// This element has begun between the previous sampling
// and now. Dispatch the beginEvent.
dispatchBeginEvent(currentTime);
if (currentTime.greaterThan(currentInterval.end)) {
// This element has also ended between the
// previous sampling and now. Dispatch the endEvent
dispatchLastDurEndEvent();
dispatchEndEvent(currentTime);
endOfInterval = true;
}
state = STATE_PLAYING;
playFill = false;
}
break;
case STATE_WAITING_INTERVAL_N:
if (currentTime.greaterThan(currentInterval.begin)) {
// This element has begun between the previous sampling
// and now. Dispatch the beginEvent.
dispatchBeginEvent(currentTime);
if (currentTime.greaterThan(currentInterval.end)) {
// This element has also ended between the
// previous sampling and now. Dispatch the endEvent
dispatchLastDurEndEvent();
dispatchEndEvent(currentTime);
endOfInterval = true;
}
state = STATE_PLAYING;
playFill = false;
} else {
if (seeking && isSeekingBack()) {
seekBack = true;
}
}
break;
case STATE_PLAYING:
if (currentTime.greaterThan(currentInterval.lastDur)) {
if (!playFill) {
dispatchLastDurEndEvent();
playFill = true;
}
}
if (currentTime.greaterThan(currentInterval.end)) {
// This element has also ended between the
// previous sampling and now. Dispatch the endEvent
dispatchEndEvent(currentTime);
endOfInterval = true;
} else {
if (!seeking) {
dispatchRepeatEvent(currentTime);
} else if (currentInterval.begin.greaterThan(currentTime)) {
// We are seeking backwards and the current interval
// began during the seek interval. We need to
// dispatch and end event at the time before the
// seek.
dispatchLastDurEndEvent();
dispatchEndEvent(currentTime);
endOfInterval = true;
seekBack = true;
}
}
break;
}
// If we just finished an interval,
// we need to compute the new interval now
if (endOfInterval) {
previousInterval = currentInterval;
currentInterval = null;
// If we cannot restart, we just go to the fill state
if (restart == RESTART_NEVER) {
state = STATE_FILL;
playFill = false;
} else {
TimeInterval nextInterval = null;
if (!seekBack) {
nextInterval = computeNextInterval();
} else {
nextInterval = seekToInterval(currentTime);
}
if (!checkNewInterval(nextInterval)) {
// There is no next interval, we move to the
// fill state.
state = STATE_FILL;
playFill = false;
}
}
// This recursive call is needed in case we sample so far ahead
// that we are skipping several intervals.
// For example if the intervals are:
// [0, 1000[
// [2000, 3000[
// [5000, 6500[
// [8000, 1000[
// and we sample at 0 and then 7000
sample(currentTime);
return;
} else if (seekBack) {
// If we are in the FILL state and we cannot restart, then we stay
// in FILL state if seeking to after the previous interval's
// end. Otherwise, we move to the STATE_NO_INTERVAL state.
if (state == STATE_FILL && restart == RESTART_NEVER) {
if (previousInterval.end.greaterThan(currentTime)) {
dispatchSeekEndEvent();
state = STATE_NO_INTERVAL;
playFill = false;
return;
}
} else {
TimeInterval seekInterval = seekToInterval(currentTime);
// If the seek interval is the same as the current interval, we
// do not need to do further sampling because we were in, or
// have already moved to, the right interval.
if (seekInterval == null
|| currentInterval == null
|| !(seekInterval.begin.isSameTime(currentInterval.begin)
&&
seekInterval.end.isSameTime(currentInterval.end))) {
// We need to check if there is an interval active at the
// time we are seeking to.
if (!checkNewInterval(seekInterval)) {
// There is no currentInterval.
// Switch to the right state
if (previousInterval != null) {
state = STATE_FILL;
playFill = false;
} else {
dispatchSeekEndEvent();
state = STATE_NO_INTERVAL;
playFill = false;
return;
}
} else {
// There is an interval at the time we are
// seeking to. End the current interval (which
// we know is different because of the previous if
// statement) and sample again to have the new current
// interval kick-in.
dispatchSeekEndEvent();
sample(currentTime);
return;
}
}
}
}
//
// Different behaviors depending on the state. At this point, we
// should only be in one of the following states:
// - STATE_FILL
// - STATE_PLAYING
// - STATE_WAITING_INTERVAL_0
// - STATE_WAITING_INTERVAL_N
//
long localTime = 0;
switch (state) {
default:
// This should _never_ happen, given this method's code.
// This is extremely hard to test (would require changing
// state during the execution of this method) and does
// not need to be covered by the test suite.
throw new IllegalStateException();
case STATE_WAITING_INTERVAL_N:
case STATE_FILL:
if (fillBehavior == FILL_BEHAVIOR_FREEZE) {
// If we are in STATE_WAITING_INTERVAL_N or STATE_FILL,
// it means that we had a previous interval. Therefore,
// previousInterval is guaranteed to be not null.
localTime = previousInterval.lastDur.value
- previousInterval.begin.value;
} else {
return;
}
break;
case STATE_WAITING_INTERVAL_0:
return;
case STATE_PLAYING:
// If we are still in the PLAYING state but we are past the
// end of the last simple duration end, make sure we sample at
// that last simple duration time.
if (!playFill) {
localTime = currentTime.value - currentInterval.begin.value;
} else {
localTime = currentInterval.lastDur.value
- currentInterval.begin.value;
}
break;
}
// If we get to this point, it means we were able to
// compute a localTime we need to sample at. We are
// either playing the animation or we are in a frozen
// state. Compute the simple time from the localTime.
if (simpleDur.isResolved()) {
localTime = localTime % simpleDur.value;
if (state != STATE_PLAYING || playFill) {
// If we are not playing, it means we are frozen
// (either STATE_WAITING_N or STATE_FILL).
//
// Make sure we sample on the end of the simple time
// if we froze at the end of the interval.
//
// Note that we do not need to test if the value
// was 0 before %. Because we are _not_ playing, it
// means we are _not_ on the first value of the
// interval, therefore, we never run into the condition
// where localTime would be 0 before % and therefore,
// we do not need to test for that condition
//
if (localTime == 0) {
// Freeze on the last value in the simple duration
// interval only when we freeze.
localTime = simpleDur.value;
}
}
}
// At this point, we have computed our simple time.
// Ready to connect with the animation engine.
sampleAt(localTime);
// Keep the last simpleTime this element was sampled at
lastSampleTime = localTime;
| void | sampleAt(long simpleTime)Samples this element at the given simple time.
Should be overridden for specific behaviors.
| void | seekTo(Time seekToTime)Implementation helper method for seekTo behavior.
if (seekToTime.isResolved()) {
// Now, move up the container graph.
timeContainer.seekTo(seekToTime);
} else {
// 3. Else (i.e. there are no defined intervals for the element),
// the target element begin time must be resolved. This may require
// seeking and/or resolving ancestor elements as well. This is done
// by recursing from the target element up to the closest ancestor
// element that has a resolved begin time (again noting that for an
// element to have a resolved begin time, all of its ancestors must
// have resolved begin times).
if (timeContainer.state != STATE_PLAYING) {
TimeInterval firstContainerInterval
= timeContainer.computeFirstInterval();
if (firstContainerInterval == null) {
timeContainer.seekTo(Time.UNRESOLVED);
} else {
Time parentSeekTo
= new Time(firstContainerInterval.begin.value);
parentSeekTo
= timeContainer
.toRootContainerSimpleTimeClamp(parentSeekTo);
timeContainer.seekTo(parentSeekTo);
}
}
// Then, the recursion is "unwound", and for each ancestor in turn
// (beneath the resolved ancestor) as well as the target element,
// the following steps are performed:
//
// 1. If the element begin time is resolved, seek the document
// time (forward or back, as needed) to the begin time of the
// first interval for the target element.
//
// 2. Else (if the begin time is not resolved), just resolve the
// element begin time at the current time on its parent time
// container (given the current document position). Disregard
// the sync-base or event base of the element, and do not
// "back-propagate" any timing logic to resolve the element, but
// rather treat it as though it were defined with
// begin="indefinite" and just resolve begin time to the current
// parent time. This should create an interval and propagate to
// time dependents.
//
TimeInterval firstInterval = computeFirstInterval();
if (firstInterval == null) {
begin();
firstInterval = computeFirstInterval();
}
Time goToTime = new Time(firstInterval.begin.value);
if (goToTime.value < 0) {
goToTime.value = 0;
}
timeContainer.seekTo(toRootContainerSimpleTimeClamp(goToTime));
}
| TimeInterval | seekToInterval(Time seekToTime)Helper method to find the active interval, or next interval, for
the requested time.
previousInterval = null;
TimeInterval interval = computeFirstInterval();
if (interval == null) {
return null;
}
while (interval != null && seekToTime.greaterThan(interval.end)) {
previousInterval = interval;
interval = computeNextInterval();
}
return interval;
| public void | setDur(Time dur)Sets the duration for this timed element
checkPreInit();
this.dur = dur;
| public void | setFillBehavior(int fillBehavior)Sets the fill state behavior.
checkPreInit();
switch (fillBehavior) {
case FILL_BEHAVIOR_FREEZE:
case FILL_BEHAVIOR_REMOVE:
this.fillBehavior = fillBehavior;
break;
default:
throw new IllegalArgumentException();
}
| public void | setMax(Time max)Sets the maximun active duration for any timed element
interval.
checkPreInit();
if (max == null) {
throw new IllegalArgumentException();
}
this.max = max;
| public void | setMin(Time min)Sets the minimum active duration for any timed element interval.
checkPreInit();
if (min == null) {
throw new IllegalArgumentException();
}
this.min = min;
| public void | setRepeatCount(float repeatCount)Sets the repeat count.
checkPreInit();
if (repeatCount <= 0) {
throw new IllegalArgumentException();
}
this.repeatCount = repeatCount;
| public void | setRepeatDur(Time repeatDur)Sets the repeat duration.
checkPreInit();
this.repeatDur = repeatDur;
| public void | setRestart(int restart)Sets the restart strategy.
checkPreInit();
switch (restart) {
case RESTART_ALWAYS:
case RESTART_NEVER:
case RESTART_WHEN_NOT_ACTIVE:
this.restart = restart;
break;
default:
throw new IllegalArgumentException();
}
| protected void | setTimeContainer(TimeContainerSupport timeContainer)Sets this timed element's time container
if (this.timeContainer != null) {
this.timeContainer.timedElementChildren.removeElement(this);
}
this.timeContainer = timeContainer;
if (timeContainer != null) {
timeContainer.timedElementChildren.addElement(this);
if (timeContainer.state != STATE_PRE_INIT) {
// This is a live addition of an animation to a container.
// We need to initialize this timed element support.
initialize();
}
}
| Time | toContainerSimpleTime(Time simpleTime)Converts the input root container simple time (i.e., a time in the root
container's simple time interval) to a time in this element's time
container simple duration. Note that this method mutates the input
argument. If the associated time container does not
have a current interval, this method returns Time.UNRESOLVED.
if (timeContainer.currentInterval == null) {
return Time.UNRESOLVED;
}
if (!simpleTime.isResolved()) {
return simpleTime;
}
// Convertion to the container's container simple time
simpleTime = timeContainer.toContainerSimpleTime(simpleTime);
// Account for the container's begin value
simpleTime.value -= timeContainer.currentInterval.begin.value;
// Account for repeat behavior
if (timeContainer.simpleDur.isResolved()) {
simpleTime.value -=
timeContainer.curIter * timeContainer.simpleDur.value;
}
return simpleTime;
| Time | toRootContainerSimpleTime(Time simpleTime)Converts the input simple time (i.e., a time in the parent container's
simple duration) to a root container simple time (i.e., a time
in the root time container's simple time interval). Note that this method
mutates the input time value. If the associated time container does not
have a current interval, this method returns Time.UNRESOLVED.
if (timeContainer.currentInterval == null) {
return Time.UNRESOLVED;
}
if (!simpleTime.isResolved()) {
return simpleTime;
}
// Convert to the container's container simple time
// Account for repeat behavior. This yields a time
// in the [0, ActiveTime[ time system
if (timeContainer.simpleDur.isResolved()) {
simpleTime.value +=
timeContainer.curIter * timeContainer.simpleDur.value;
}
// By adding the timeContainer's begin value, we are
// translating the simpleTime to a time in the container's
// container simple duration [0, simpleDur[ interval.
simpleTime.value += timeContainer.currentInterval.begin.value;
// Now, move the conversion one level up.
return timeContainer.toRootContainerSimpleTime(simpleTime);
| Time | toRootContainerSimpleTimeClamp(Time simpleTime)Same definition and behavior as toRootContainerSimpleTime, except that
this method clamps values to the container's simple duration. This
means that a value smaller than zero is converted to 0 and a value
greater than the simple duration is reduced to the simple duration.
if (timeContainer.currentInterval == null) {
return Time.UNRESOLVED;
}
if (!simpleTime.isResolved()) {
return simpleTime;
}
// Clamp to zero.
if (simpleTime.value < 0) {
simpleTime.value = 0;
}
// Convert to the container's _container_ simple time
// Account for repeat behavior. This yields a time
// in the [0, ActiveTime[ time system
if (timeContainer.simpleDur.isResolved()) {
// Clamp to the simple duration.
if (simpleTime.value > timeContainer.simpleDur.value) {
simpleTime.value = timeContainer.simpleDur.value;
}
simpleTime.value +=
timeContainer.curIter * timeContainer.simpleDur.value;
} else {
// Still clamp to the container's active duration.
if (timeContainer.currentInterval.end.isResolved()) {
long maxDur = timeContainer.currentInterval.end.value
- timeContainer.currentInterval.begin.value;
if (simpleTime.value > maxDur) {
simpleTime.value = maxDur;
}
}
}
// By adding the timeContainer's begin value, we are
// translating the simpleTime to a time in the container's
// container simple duration [0, simpleDur[ interval.
simpleTime.value += timeContainer.currentInterval.begin.value;
// Now, move the conversion one level up.
return timeContainer.toRootContainerSimpleTimeClamp(simpleTime);
| public java.lang.String | toString()Debug helper.
String str = "[Animation: " + animationElement + "] ["
+ getStateString() + "]";
switch (state) {
case STATE_WAITING_INTERVAL_N:
case STATE_WAITING_INTERVAL_0:
case STATE_PLAYING:
str += "["
+ currentInterval.begin
+ ", "
+ currentInterval.end
/*
+ ", "
+ currentInterval.lastDur
*/
+ "]";
break;
default:
break;
}
return str;
| long | toWallClockTime(long localTime)Converts the input 'local' time to an absolute wallclock time.
return timeContainer.toWallClockTime(localTime);
|
|
|