/** * Copyright 2011 Google Inc. * * 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. * */ package org.waveprotocol.wave.client.scroll; import com.google.common.annotations.VisibleForTesting; import com.google.gwt.core.client.Duration; import org.waveprotocol.wave.client.common.util.MathUtil; import org.waveprotocol.wave.client.scheduler.Scheduler.Task; import org.waveprotocol.wave.client.scheduler.SchedulerInstance; import org.waveprotocol.wave.client.scheduler.TimerService; /** * Decorates a scroller, providing a layer of animated scroll movement. * * @author hearnden@google.com (David Hearnden) */ public final class AnimatedScrollPanel<T> implements ScrollPanel<T>, Task { /** Time period for the animation. */ private final static int ANIMATION_DURATION_MS = 300; private final ScrollPanel<? super T> target; private final TimerService scheduler; // Animation state. private Duration counter; private double startLocation; private double endLocation; /** * Creates an animating scroller. * * @param scheduler scheduler for running animations * @param target underlying scroller */ @VisibleForTesting AnimatedScrollPanel(TimerService scheduler, ScrollPanel<? super T> target) { this.scheduler = scheduler; this.target = target; } /** * Creates an animation layer over a scroller. */ public static <T> AnimatedScrollPanel<T> create(ScrollPanel<? super T> target) { return new AnimatedScrollPanel<T>(SchedulerInstance.getHighPriorityTimer(), target); } @Override public void moveTo(double location) { startLocation = getViewport().getStart(); endLocation = location; counter = new Duration(); execute(); scheduler.scheduleDelayed(this, 0); } /** * Sets the underlying target's scroll location to the current animation * location. */ @Override public void execute() { int time = counter.elapsedMillis(); target.moveTo(location(time)); if (time < ANIMATION_DURATION_MS) { // Run again, but delayed so that paint can occur first. scheduler.scheduleDelayed(this, 0); } } /** @return the animation's scroll location at a particular time. */ private double location(int time) { // Clip current time to start/end bounds. double ctime = MathUtil.clip(0, ANIMATION_DURATION_MS, time); // Follow first quadrant of sine. double t = (ctime / ANIMATION_DURATION_MS) * (Math.PI / 2); double x = Math.sin(t); return startLocation + x * (endLocation - startLocation); } @Override public Extent getViewport() { return target.getViewport(); } @Override public Extent getContent() { return target.getContent(); } @Override public Extent extentOf(T measurable) { return target.extentOf(measurable); } }