
com.threerings.media.TimerView Maven / Gradle / Ivy
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/nenya
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import javax.swing.JComponent;
import com.samskivert.util.ResultListener;
/**
* A generic timer class that can be rendered on screen.
*
* NOTE: This base class doesn't actually render anything, but it's still
* useful for triggering user supplied callback functions. Derived classes
* are more than welcome to setup their own rendering, of course.
*/
public class TimerView
implements FrameParticipant, HierarchyListener
{
/**
* Constructs a timer view that fires at the default rate.
*/
public TimerView (FrameManager fmgr, JComponent host, Rectangle bounds)
{
// Cache the input arguments
_fmgr = fmgr;
_host = host;
_bounds = new Rectangle(bounds);
// Watch for changes in the timer's state so it can effectively
// register or unregister with the frame manager as necessary
_host.addHierarchyListener(this);
checkFrameParticipation();
}
/**
* Sets whether this timer should be rendered.
*/
public void setEnabled (boolean enabled)
{
if (_enabled != enabled)
{
_enabled = enabled;
invalidate();
}
}
/**
* Get the amount of time it takes to render digital changes in the
* completion state.
*/
public long getTransitionTime ()
{
return _transitionTime;
}
/**
* Set the amount of time it takes to render digital changes in the
* completion state (probably due to a timer reset).
*/
public void setTransitionTime (long time)
{
_transitionTime = Math.max(0, time);
}
/**
* Setup a warning to trigger after the timer is "warnPercent"
* or more completed. If "warner" is non-null, it will be
* called at that time.
*/
public void setWarning (float warnPercent, ResultListener warner)
{
// This warning hasn't triggered yet
_warned = false;
// Here are the details
_warnPercent = warnPercent;
_warner = warner;
}
/**
* Remove any warning this timer might have had.
*/
public void removeWarning ()
{
// Don't trigger any warnings
_warned = false;
_warnPercent = -1;
_warner = null;
}
/** Test if the timer is running right now. */
public boolean running ()
{
return _running;
}
/**
* Start the timer running from the specified percentage complete,
* to expire at the specified time.
*
* @param startPercent a value in [0f, 1f) indicating how much
* hourglass time has already elapsed when the timer starts.
* @param duration The time interval over which the timer would
* run if it started at 0%.
* @param finisher a listener that will be notified when the timer
* finishes, or null if nothing should be notified.
*/
public void start (float startPercent, long duration, ResultListener finisher)
{
// Sanity check input arguments
if (startPercent < 0.0f || startPercent >= 1.0f) {
throw new IllegalArgumentException(
"Invalid starting percent " + startPercent);
}
if (duration < 0) {
throw new IllegalArgumentException("Invalid duration " + duration);
}
// Stop any current processing
stop();
// Record the timer's full duration and effective start time
_duration = duration;
// Change the completion percent and make sure the starting
// time gets updated on the next tick
changeComplete(startPercent);
_start = Long.MIN_VALUE;
// Thank you sir; would you kindly take a chair in the waiting room?
_finisher = finisher;
// The warning and completion handlers haven't been triggered yet
_warned = false;
_completed = false;
// Start things running
_running = true;
}
/**
* Reset the timer.
*/
public void reset ()
{
// Stop processing permanently
stop();
// Reset the completion percent
changeComplete(0f);
}
/**
* Stop the timer.
*/
public void stop ()
{
// Stop processing
pause();
// Prevent it from being unpaused
_lastUpdate = Long.MIN_VALUE;
}
/**
* Pause the timer from processing.
*/
public void pause ()
{
// Stop processing
_running = false;
}
/**
* Unpause the timer.
*/
public void unpause ()
{
// Don't unpause the timer if it wasn't paused to begin with
if (_lastUpdate == Long.MIN_VALUE || _start == Long.MIN_VALUE) {
return;
}
// Adjust the starting time when the timer next ticks
_processUnpause = true;
// Start things running again
_running = true;
}
/**
* Generate an unexpected change in the timer's completion and
* set up the necessary details to render interpolation from the old
* to new states
*/
public void changeComplete (float complete)
{
// Store the new state
_complete = complete;
// Determine the percentage difference between the "actual"
// completion state and the completion amount to render during
// the smooth interpolation
_renderOffset = _renderComplete - _complete;
// When the timer next ticks, find out when this interpolation
// should start
_renderOffsetTime = Long.MIN_VALUE;
}
/**
* Updates the timer for the current inputted time.
*/
protected void update (long now)
{
}
/**
* Test if the warning was triggered and needs to be processed.
*/
protected boolean triggeredWarning ()
{
// Trigger a warning if it exists, it hasn't occured yet,
// and the timer has passed the warning threshold.
return ((_warnPercent >= 0f) &&
!_warned && (_warnPercent <= _complete));
}
/**
* Test if the completion was triggered and needs to be processed.
*/
protected boolean triggeredCompleted ()
{
return (!_completed && (1.0 <= _complete));
}
/**
* Handle the trigger of the warning.
*/
protected void handleWarning ()
{
// Remember that this warning was handled
_warned = true;
// Execute the warning listener if one was supplied
if (_warner != null) {
_warner.requestCompleted(this);
}
}
/**
* Handle the trigger of the timer's completion.
*/
protected void handleCompleted ()
{
// Remember that the completion was handled
_completed = true;
// Stop the timer
stop();
// Handle the trigger function if one was supplied
if (_finisher != null) {
_finisher.requestCompleted(this);
}
}
/**
* Invalidates this view's bounds via the host component.
*/
protected void invalidate ()
{
// Schedule the timer's location on screen to get repainted
_invalidated = true;
_host.repaint(_bounds.x, _bounds.y, _bounds.width, _bounds.height);
}
/**
* Renders the timer to the given graphics context if enabled.
*/
public void render (Graphics2D gfx)
{
// Paint the timer if its enabled
if (_enabled) {
paint(gfx, _renderComplete);
}
_invalidated = false;
}
/**
* Paint the timer into the given graphics context at the inputted
* percent complete (0f means just started, 1f means just finished).
*/
public void paint (Graphics2D gfx, float complete)
{
// Inheriting classes will want to implement their own
// version of this function. Remember to call this one though!
// Remember the completion level the last time the timer was painted
_paintComplete = complete;
}
// documentation inherited
public void tick (long now)
{
if (!_enabled) {
return;
}
// Initialize the starting time if necessary
if (_start == Long.MIN_VALUE) {
_start = now - Math.round(_duration * _complete);
}
// Initialize the timestamp of the rendering error if necessary
if (_renderOffsetTime == Long.MIN_VALUE) {
_renderOffsetTime = now;
}
// If the timer was just unpaused, handle the updates that
// need a timestamp
if (_processUnpause) {
_processUnpause = false;
_start += now - _lastUpdate;
}
// Update the completion and triggers when the timer is running
if (running())
{
// Figure out how what percent the timer has completed
_lastUpdate = now;
_complete = ((float) (_lastUpdate - _start)) / _duration;
// Handle warning trigger if necessary
if (triggeredWarning()) {
handleWarning();
}
// Handle completion trigger if necessary
if (triggeredCompleted()) {
handleCompleted();
}
}
// Add error to the render completion percent if the interpolation
// hasn't finished yet
_renderComplete = _complete;
if (_renderOffsetTime + _transitionTime > now) {
_renderComplete += _renderOffset *
(1f - (now - _renderOffsetTime) / (float) _transitionTime);
_renderComplete = Math.max(0f, Math.min(1f, _renderComplete));
}
// Possibly orce a repaint if the render level changed (highly
// probable but not guaranteed)
if (_renderComplete != _paintComplete)
{
// Always force a repaint when changing to or from a boundary
if (_renderComplete <= 0f || _paintComplete <= 0f ||
_renderComplete >= 1f || _paintComplete >= 1f)
{
invalidate();
}
// Also force a repaint if the completion state sufficiently
// changed
else if (Math.abs(_renderComplete - _paintComplete) >
_changeThreshold)
{
invalidate();
}
}
}
// documentation inherited
public boolean needsPaint ()
{
// Always paint if the timer was invalidated
return _invalidated;
}
// documentation inherited
public Component getComponent ()
{
return null;
}
// documentation inherited
public void hierarchyChanged (HierarchyEvent e)
{
checkFrameParticipation();
}
/** Check that the frame knows about the timer. */
public void checkFrameParticipation ()
{
// Determine whether or not the timer should participate in
// media ticks
boolean participate = _host.isShowing();
// Start participating if necessary
if (participate && !_participating)
{
_fmgr.registerFrameParticipant(this);
_participating = true;
}
// Stop participating if necessary
else if (!participate && _participating)
{
_fmgr.removeFrameParticipant(this);
_participating = false;
}
}
/** The frame manager that manages our animated view. */
protected FrameManager _fmgr;
/** The media panel containing the view. */
protected JComponent _host;
/** The screen coordinates of the timer's bounding box. */
protected Rectangle _bounds;
/** Whether to render the timer. */
protected boolean _enabled = true;
/** Whether the timer is running. */
protected boolean _running = false;
/** Whether the timer is participating in media tick updates. */
protected boolean _participating = false;
/** The last time the timer updated while running. */
protected long _lastUpdate = Long.MIN_VALUE;
/** The total amount of time in the timer when it was 0% done. */
protected long _duration;
/** The timestamp when the timer effectively started. */
protected long _start;
/** The percent of the duration time completed. */
protected float _complete = 0f;
/** The difference between the old rendered completion percent and the
* new completion percent the last time the completion percent had
* an unexpected change. In other words, this is the greatest amount
* that _renderComplete will differ from _complete during an
* state interpolation.
*/
protected float _renderOffset = 0f;
/** The timestamp of _renderOffset. */
protected long _renderOffsetTime = Long.MIN_VALUE;
/** The amount of time it takes to fully interpolation a render
* transition from completion 0.0 to 1.0. */
protected long _transitionTime = 0;
/** The completion percent at which to render the timer. */
protected float _renderComplete = 0f;
/** The completion percent last time the timer was repainted. */
protected float _paintComplete = -1;
/**
* The timer will not invalidate itself until the completion level
* to render changes by at least this much from the previous time it
* was invalidated.
*/
protected float _changeThreshold = 0.0f;
/** True if the timer has been invalidated since its last repaint. */
protected boolean _invalidated;
/** Trigger the warning when at least this much time has elapsed,
* or -1 for no warning. */
protected float _warnPercent = -1;
/** True if the warning has already been processed. */
protected boolean _warned;
/** True if the completion has already been processed. */
protected boolean _completed;
/** True if the code should process everything required for an unpause
* on the next tick(). */
protected boolean _processUnpause = false;
/** A listener to be notified when the timer finishes. */
protected ResultListener _finisher;
/** A listener to be notified when the warning time occurs. */
protected ResultListener _warner;
/** The default update date. */
protected static final long DEFAULT_RATE = 100L;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy