summaryrefslogtreecommitdiffstats
path: root/cc/input
diff options
context:
space:
mode:
authorjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 07:41:21 +0000
committerjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 07:41:21 +0000
commit3052b10f18b49c24c05cec21be0d901189a9b487 (patch)
tree951c8347b53f393768ac63621149b94dabf08fa8 /cc/input
parent8ba4bbe268033b0534b00a4261f092e1bbe31190 (diff)
downloadchromium_src-3052b10f18b49c24c05cec21be0d901189a9b487.zip
chromium_src-3052b10f18b49c24c05cec21be0d901189a9b487.tar.gz
chromium_src-3052b10f18b49c24c05cec21be0d901189a9b487.tar.bz2
Part 5 of cc/ directory shuffles: input
Continuation of https://src.chromium.org/viewvc/chrome?view=rev&revision=188681 BUG=190824 TBR=piman@chromium.org, enne@chromium.org Review URL: https://codereview.chromium.org/12722006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188691 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc/input')
-rw-r--r--cc/input/input_handler.h98
-rw-r--r--cc/input/page_scale_animation.cc225
-rw-r--r--cc/input/page_scale_animation.h104
-rw-r--r--cc/input/top_controls_manager.cc197
-rw-r--r--cc/input/top_controls_manager.h96
-rw-r--r--cc/input/top_controls_manager_client.h24
-rw-r--r--cc/input/top_controls_manager_unittest.cc293
7 files changed, 1037 insertions, 0 deletions
diff --git a/cc/input/input_handler.h b/cc/input/input_handler.h
new file mode 100644
index 0000000..04973d5
--- /dev/null
+++ b/cc/input/input_handler.h
@@ -0,0 +1,98 @@
+// Copyright 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_INPUT_INPUT_HANDLER_H_
+#define CC_INPUT_INPUT_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/time.h"
+#include "cc/base/cc_export.h"
+
+namespace gfx {
+class Point;
+class PointF;
+class Vector2d;
+class Vector2dF;
+}
+
+namespace cc {
+
+// The InputHandler is a way for the embedders to interact with
+// the impl thread side of the compositor implementation.
+//
+// There is one InputHandler for every LayerTreeHost. It is
+// created on the main thread and used only on the impl thread.
+//
+// The InputHandler is constructed with a InputHandlerClient, which is the
+// interface by which the handler can manipulate the LayerTree.
+class CC_EXPORT InputHandlerClient {
+ public:
+ enum ScrollStatus { ScrollOnMainThread, ScrollStarted, ScrollIgnored };
+ enum ScrollInputType { Gesture, Wheel, NonBubblingGesture };
+
+ // Selects a layer to be scrolled at a given point in viewport (logical
+ // pixel) coordinates. Returns ScrollStarted if the layer at the coordinates
+ // can be scrolled, ScrollOnMainThread if the scroll event should instead be
+ // delegated to the main thread, or ScrollIgnored if there is nothing to be
+ // scrolled at the given coordinates.
+ virtual ScrollStatus ScrollBegin(gfx::Point viewport_point,
+ ScrollInputType type) = 0;
+
+ // Scroll the selected layer starting at the given position. If the scroll
+ // type given to scrollBegin was a gesture, then the scroll point and delta
+ // should be in viewport (logical pixel) coordinates. Otherwise they are in
+ // scrolling layer's (logical pixel) space. If there is no room to move the
+ // layer in the requested direction, its first ancestor layer that can be
+ // scrolled will be moved instead. If no layer can be moved in the requested
+ // direction at all, then false is returned. If any layer is moved, then
+ // true is returned.
+ // Should only be called if ScrollBegin() returned ScrollStarted.
+ virtual bool ScrollBy(gfx::Point viewport_point,
+ gfx::Vector2dF scroll_delta) = 0;
+
+ // Stop scrolling the selected layer. Should only be called if ScrollBegin()
+ // returned ScrollStarted.
+ virtual void ScrollEnd() = 0;
+
+ virtual void PinchGestureBegin() = 0;
+ virtual void PinchGestureUpdate(float magnify_delta, gfx::Point anchor) = 0;
+ virtual void PinchGestureEnd() = 0;
+
+ virtual void StartPageScaleAnimation(gfx::Vector2d target_offset,
+ bool anchor_point,
+ float page_scale,
+ base::TimeTicks start_time,
+ base::TimeDelta duration) = 0;
+
+ // Request another callback to InputHandler::Animate().
+ virtual void ScheduleAnimation() = 0;
+
+ virtual bool HaveTouchEventHandlersAt(gfx::Point viewport_point) = 0;
+
+ protected:
+ InputHandlerClient() {}
+ virtual ~InputHandlerClient() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InputHandlerClient);
+};
+
+class CC_EXPORT InputHandler {
+ public:
+ virtual ~InputHandler() {}
+
+ virtual void BindToClient(InputHandlerClient* client) = 0;
+ virtual void Animate(base::TimeTicks time) = 0;
+ virtual void MainThreadHasStoppedFlinging() = 0;
+
+ protected:
+ InputHandler() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InputHandler);
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_INPUT_HANDLER_H_
diff --git a/cc/input/page_scale_animation.cc b/cc/input/page_scale_animation.cc
new file mode 100644
index 0000000..f4f4061
--- /dev/null
+++ b/cc/input/page_scale_animation.cc
@@ -0,0 +1,225 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/input/page_scale_animation.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+namespace {
+
+// This takes a viewport-relative vector and returns a vector whose values are
+// between 0 and 1, representing the percentage position within the viewport.
+gfx::Vector2dF NormalizeFromViewport(gfx::Vector2dF denormalized,
+ gfx::SizeF viewport_size) {
+ return gfx::ScaleVector2d(denormalized,
+ 1.f / viewport_size.width(),
+ 1.f / viewport_size.height());
+}
+
+gfx::Vector2dF DenormalizeToViewport(gfx::Vector2dF normalized,
+ gfx::SizeF viewport_size) {
+ return gfx::ScaleVector2d(normalized,
+ viewport_size.width(),
+ viewport_size.height());
+}
+
+gfx::Vector2dF InterpolateBetween(gfx::Vector2dF start,
+ gfx::Vector2dF end,
+ float interp) {
+ return start + gfx::ScaleVector2d(end - start, interp);
+}
+
+}
+
+namespace cc {
+
+scoped_ptr<PageScaleAnimation> PageScaleAnimation::Create(
+ gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time) {
+ return make_scoped_ptr(new PageScaleAnimation(start_scroll_offset,
+ start_page_scale_factor,
+ viewport_size,
+ root_layer_size,
+ start_time));
+}
+
+PageScaleAnimation::PageScaleAnimation(gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time)
+ : start_page_scale_factor_(start_page_scale_factor),
+ target_page_scale_factor_(0.f),
+ start_scroll_offset_(start_scroll_offset),
+ start_anchor_(),
+ target_anchor_(),
+ viewport_size_(viewport_size),
+ root_layer_size_(root_layer_size),
+ start_time_(start_time),
+ duration_(0.0) {}
+
+PageScaleAnimation::~PageScaleAnimation() {}
+
+void PageScaleAnimation::ZoomTo(gfx::Vector2dF target_scroll_offset,
+ float target_page_scale_factor,
+ double duration) {
+ target_page_scale_factor_ = target_page_scale_factor;
+ target_scroll_offset_ = target_scroll_offset;
+ ClampTargetScrollOffset();
+ duration_ = duration;
+
+ if (start_page_scale_factor_ == target_page_scale_factor) {
+ start_anchor_ = start_scroll_offset_;
+ target_anchor_ = target_scroll_offset;
+ return;
+ }
+
+ // For uniform-looking zooming, infer an anchor from the start and target
+ // viewport rects.
+ InferTargetAnchorFromScrollOffsets();
+ start_anchor_ = target_anchor_;
+}
+
+void PageScaleAnimation::ZoomWithAnchor(gfx::Vector2dF anchor,
+ float target_page_scale_factor,
+ double duration) {
+ start_anchor_ = anchor;
+ target_page_scale_factor_ = target_page_scale_factor;
+ duration_ = duration;
+
+ // We start zooming out from the anchor tapped by the user. But if
+ // the target scale is impossible to attain without hitting the root layer
+ // edges, then infer an anchor that doesn't collide with the edges.
+ // We will interpolate between the two anchors during the animation.
+ InferTargetScrollOffsetFromStartAnchor();
+ ClampTargetScrollOffset();
+
+ if (start_page_scale_factor_ == target_page_scale_factor_) {
+ target_anchor_ = start_anchor_;
+ return;
+ }
+ InferTargetAnchorFromScrollOffsets();
+}
+
+void PageScaleAnimation::InferTargetScrollOffsetFromStartAnchor() {
+ gfx::Vector2dF normalized = NormalizeFromViewport(
+ start_anchor_ - start_scroll_offset_, StartViewportSize());
+ target_scroll_offset_ =
+ start_anchor_ - DenormalizeToViewport(normalized, TargetViewportSize());
+}
+
+void PageScaleAnimation::InferTargetAnchorFromScrollOffsets() {
+ // The anchor is the point which is at the same normalized relative position
+ // within both start viewport rect and target viewport rect. For example, a
+ // zoom-in double-tap to a perfectly centered rect will have normalized
+ // anchor (0.5, 0.5), while one to a rect touching the bottom-right of the
+ // screen will have normalized anchor (1.0, 1.0). In other words, it obeys
+ // the equations:
+ // anchor = start_size * normalized + start_offset
+ // anchor = target_size * normalized + target_offset
+ // where both anchor and normalized begin as unknowns. Solving
+ // for the normalized, we get the following:
+ float width_scale =
+ 1.f / (TargetViewportSize().width() - StartViewportSize().width());
+ float height_scale =
+ 1.f / (TargetViewportSize().height() - StartViewportSize().height());
+ gfx::Vector2dF normalized = gfx::ScaleVector2d(
+ start_scroll_offset_ - target_scroll_offset_, width_scale, height_scale);
+ target_anchor_ =
+ target_scroll_offset_ + DenormalizeToViewport(normalized,
+ TargetViewportSize());
+}
+
+void PageScaleAnimation::ClampTargetScrollOffset() {
+ gfx::Vector2dF max_scroll_offset =
+ gfx::RectF(root_layer_size_).bottom_right() -
+ gfx::RectF(TargetViewportSize()).bottom_right();
+ target_scroll_offset_.ClampToMin(gfx::Vector2dF());
+ target_scroll_offset_.ClampToMax(max_scroll_offset);
+}
+
+gfx::SizeF PageScaleAnimation::StartViewportSize() const {
+ return gfx::ScaleSize(viewport_size_, 1.f / start_page_scale_factor_);
+}
+
+gfx::SizeF PageScaleAnimation::TargetViewportSize() const {
+ return gfx::ScaleSize(viewport_size_, 1.f / target_page_scale_factor_);
+}
+
+gfx::SizeF PageScaleAnimation::ViewportSizeAt(float interp) const {
+ return gfx::ScaleSize(viewport_size_, 1.f / PageScaleFactorAt(interp));
+}
+
+gfx::Vector2dF PageScaleAnimation::ScrollOffsetAtTime(double time) const {
+ return ScrollOffsetAt(InterpAtTime(time));
+}
+
+float PageScaleAnimation::PageScaleFactorAtTime(double time) const {
+ return PageScaleFactorAt(InterpAtTime(time));
+}
+
+bool PageScaleAnimation::IsAnimationCompleteAtTime(double time) const {
+ return time >= end_time();
+}
+
+float PageScaleAnimation::InterpAtTime(double time) const {
+ DCHECK_GE(time, start_time_);
+ if (IsAnimationCompleteAtTime(time))
+ return 1.f;
+
+ return (time - start_time_) / duration_;
+}
+
+gfx::Vector2dF PageScaleAnimation::ScrollOffsetAt(float interp) const {
+ if (interp <= 0.f)
+ return start_scroll_offset_;
+ if (interp >= 1.f)
+ return target_scroll_offset_;
+
+ return AnchorAt(interp) - ViewportRelativeAnchorAt(interp);
+}
+
+gfx::Vector2dF PageScaleAnimation::AnchorAt(float interp) const {
+ // Interpolate from start to target anchor in absolute space.
+ return InterpolateBetween(start_anchor_, target_anchor_, interp);
+}
+
+gfx::Vector2dF PageScaleAnimation::ViewportRelativeAnchorAt(
+ float interp) const {
+ // Interpolate from start to target anchor in normalized space.
+ gfx::Vector2dF start_normalized =
+ NormalizeFromViewport(start_anchor_ - start_scroll_offset_,
+ StartViewportSize());
+ gfx::Vector2dF target_normalized =
+ NormalizeFromViewport(target_anchor_ - target_scroll_offset_,
+ TargetViewportSize());
+ gfx::Vector2dF interp_normalized =
+ InterpolateBetween(start_normalized, target_normalized, interp);
+
+ return DenormalizeToViewport(interp_normalized, ViewportSizeAt(interp));
+}
+
+float PageScaleAnimation::PageScaleFactorAt(float interp) const {
+ if (interp <= 0.f)
+ return start_page_scale_factor_;
+ if (interp >= 1.f)
+ return target_page_scale_factor_;
+
+ // Linearly interpolate the magnitude in log scale.
+ float diff = target_page_scale_factor_ / start_page_scale_factor_;
+ float log_diff = log(diff);
+ log_diff *= interp;
+ diff = exp(log_diff);
+ return start_page_scale_factor_ * diff;
+}
+
+} // namespace cc
diff --git a/cc/input/page_scale_animation.h b/cc/input/page_scale_animation.h
new file mode 100644
index 0000000..09fbd15
--- /dev/null
+++ b/cc/input/page_scale_animation.h
@@ -0,0 +1,104 @@
+// Copyright 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_INPUT_PAGE_SCALE_ANIMATION_H_
+#define CC_INPUT_PAGE_SCALE_ANIMATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+
+// A small helper class that does the math for zoom animations, primarily for
+// double-tap zoom. Initialize it with starting and ending scroll/page scale
+// positions and an animation length time, then call ...AtTime() at every frame
+// to obtain the current interpolated position.
+//
+// All sizes and vectors in this class's public methods are in the root scroll
+// layer's coordinate space.
+class PageScaleAnimation {
+ public:
+ // Construct with the state at the beginning of the animation.
+ static scoped_ptr<PageScaleAnimation> Create(
+ gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time);
+
+ ~PageScaleAnimation();
+
+ // The following methods initialize the animation. Call one of them
+ // immediately after construction to set the final scroll and page scale.
+
+ // Zoom while explicitly specifying the top-left scroll position.
+ void ZoomTo(gfx::Vector2dF target_scroll_offset,
+ float target_page_scale_factor,
+ double duration);
+
+ // Zoom based on a specified anchor. The animator will attempt to keep it
+ // at the same position on the physical display throughout the animation,
+ // unless the edges of the root layer are hit. The anchor is specified
+ // as an offset from the content layer.
+ void ZoomWithAnchor(gfx::Vector2dF anchor,
+ float target_page_scale_factor,
+ double duration);
+
+ // Call these functions while the animation is in progress to output the
+ // current state.
+ gfx::Vector2dF ScrollOffsetAtTime(double time) const;
+ float PageScaleFactorAtTime(double time) const;
+ bool IsAnimationCompleteAtTime(double time) const;
+
+ // The following methods return state which is invariant throughout the
+ // course of the animation.
+ double start_time() const { return start_time_; }
+ double duration() const { return duration_; }
+ double end_time() const { return start_time_ + duration_; }
+ gfx::Vector2dF target_scroll_offset() const { return target_scroll_offset_; }
+ float target_page_scale_factor() const { return target_page_scale_factor_; }
+
+ protected:
+ PageScaleAnimation(gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time);
+
+ private:
+ void ClampTargetScrollOffset();
+ void InferTargetScrollOffsetFromStartAnchor();
+ void InferTargetAnchorFromScrollOffsets();
+
+ gfx::SizeF StartViewportSize() const;
+ gfx::SizeF TargetViewportSize() const;
+ float InterpAtTime(double time) const;
+ gfx::SizeF ViewportSizeAt(float interp) const;
+ gfx::Vector2dF ScrollOffsetAt(float interp) const;
+ gfx::Vector2dF AnchorAt(float interp) const;
+ gfx::Vector2dF ViewportRelativeAnchorAt(float interp) const;
+ float PageScaleFactorAt(float interp) const;
+
+ float start_page_scale_factor_;
+ float target_page_scale_factor_;
+ gfx::Vector2dF start_scroll_offset_;
+ gfx::Vector2dF target_scroll_offset_;
+
+ gfx::Vector2dF start_anchor_;
+ gfx::Vector2dF target_anchor_;
+
+ gfx::SizeF viewport_size_;
+ gfx::SizeF root_layer_size_;
+
+ double start_time_;
+ double duration_;
+
+ DISALLOW_COPY_AND_ASSIGN(PageScaleAnimation);
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_PAGE_SCALE_ANIMATION_H_
diff --git a/cc/input/top_controls_manager.cc b/cc/input/top_controls_manager.cc
new file mode 100644
index 0000000..24dc359
--- /dev/null
+++ b/cc/input/top_controls_manager.cc
@@ -0,0 +1,197 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/input/top_controls_manager.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/time.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/input/top_controls_manager_client.h"
+#include "cc/layer_tree_impl.h"
+#include "cc/timing_function.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+namespace {
+// These constants were chosen empirically for their visually pleasant behavior.
+// Contact tedchoc@chromium.org for questions about changing these values.
+const int64 kShowHideMaxDurationMs = 175;
+}
+
+// static
+scoped_ptr<TopControlsManager> TopControlsManager::Create(
+ TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold) {
+ return make_scoped_ptr(new TopControlsManager(client,
+ top_controls_height,
+ top_controls_show_threshold,
+ top_controls_hide_threshold));
+}
+
+TopControlsManager::TopControlsManager(TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold)
+ : client_(client),
+ animation_direction_(NO_ANIMATION),
+ enable_hiding_(true),
+ controls_top_offset_(0.f),
+ top_controls_height_(top_controls_height),
+ current_scroll_delta_(0.f),
+ controls_scroll_begin_offset_(0.f),
+ top_controls_show_height_(
+ top_controls_height * top_controls_hide_threshold),
+ top_controls_hide_height_(
+ top_controls_height * (1.f - top_controls_show_threshold)) {
+ CHECK(client_);
+}
+
+TopControlsManager::~TopControlsManager() {
+}
+
+void TopControlsManager::EnableHidingTopControls(bool enable) {
+ enable_hiding_ = enable;
+
+ if (!enable) {
+ ResetAnimations();
+ if (controls_top_offset_ != 0) {
+ SetupAnimation(SHOWING_CONTROLS);
+ client_->setNeedsRedraw();
+ }
+ }
+}
+
+void TopControlsManager::ScrollBegin() {
+ ResetAnimations();
+ current_scroll_delta_ = 0.f;
+ controls_scroll_begin_offset_ = controls_top_offset_;
+}
+
+gfx::Vector2dF TopControlsManager::ScrollBy(
+ const gfx::Vector2dF pending_delta) {
+ if (!enable_hiding_ && pending_delta.y() > 0)
+ return pending_delta;
+
+ current_scroll_delta_ += pending_delta.y();
+
+ float old_offset = controls_top_offset_;
+ SetControlsTopOffset(controls_scroll_begin_offset_ - current_scroll_delta_);
+
+ // If the controls are fully visible, treat the current position as the
+ // new baseline even if the gesture didn't end.
+ if (controls_top_offset_ == 0.f) {
+ current_scroll_delta_ = 0.f;
+ controls_scroll_begin_offset_ = 0.f;
+ }
+
+ ResetAnimations();
+
+ gfx::Vector2dF applied_delta(0.f, old_offset - controls_top_offset_);
+ return pending_delta - applied_delta;
+}
+
+void TopControlsManager::ScrollEnd() {
+ StartAnimationIfNecessary();
+}
+
+void TopControlsManager::SetControlsTopOffset(float controls_top_offset) {
+ controls_top_offset = std::max(controls_top_offset, -top_controls_height_);
+ controls_top_offset = std::min(controls_top_offset, 0.f);
+
+ if (controls_top_offset_ == controls_top_offset)
+ return;
+
+ controls_top_offset_ = controls_top_offset;
+
+ client_->setNeedsRedraw();
+ client_->setActiveTreeNeedsUpdateDrawProperties();
+}
+
+gfx::Vector2dF TopControlsManager::Animate(base::TimeTicks monotonic_time) {
+ if (!top_controls_animation_ || !client_->haveRootScrollLayer())
+ return gfx::Vector2dF();
+
+ double time = (monotonic_time - base::TimeTicks()).InMillisecondsF();
+
+ float old_offset = controls_top_offset_;
+ SetControlsTopOffset(top_controls_animation_->GetValue(time));
+
+ if (IsAnimationCompleteAtTime(monotonic_time))
+ ResetAnimations();
+
+ gfx::Vector2dF scroll_delta(0.f, controls_top_offset_ - old_offset);
+ return scroll_delta;
+}
+
+void TopControlsManager::ResetAnimations() {
+ if (top_controls_animation_)
+ top_controls_animation_.reset();
+
+ animation_direction_ = NO_ANIMATION;
+}
+
+void TopControlsManager::SetupAnimation(AnimationDirection direction) {
+ top_controls_animation_ = KeyframedFloatAnimationCurve::Create();
+ double start_time =
+ (base::TimeTicks::Now() - base::TimeTicks()).InMillisecondsF();
+ top_controls_animation_->AddKeyframe(
+ FloatKeyframe::Create(start_time, controls_top_offset_,
+ scoped_ptr<TimingFunction>()));
+ float max_ending_offset =
+ (direction == SHOWING_CONTROLS ? 1 : -1) * top_controls_height_;
+ top_controls_animation_->AddKeyframe(
+ FloatKeyframe::Create(start_time + kShowHideMaxDurationMs,
+ controls_top_offset_ + max_ending_offset,
+ EaseTimingFunction::create()));
+ animation_direction_ = direction;
+}
+
+void TopControlsManager::StartAnimationIfNecessary() {
+ if (controls_top_offset_ != 0
+ && controls_top_offset_ != -top_controls_height_) {
+ AnimationDirection show_controls = NO_ANIMATION;
+
+ if (controls_top_offset_ >= -top_controls_show_height_) {
+ // If we're showing so much that the hide threshold won't trigger, show.
+ show_controls = SHOWING_CONTROLS;
+ } else if (controls_top_offset_ <= -top_controls_hide_height_) {
+ // If we're showing so little that the show threshold won't trigger, hide.
+ show_controls = HIDING_CONTROLS;
+ } else {
+ // If we could be either showing or hiding, we determine which one to
+ // do based on whether or not the total scroll delta was moving up or
+ // down.
+ show_controls = current_scroll_delta_ <= 0.f ?
+ SHOWING_CONTROLS : HIDING_CONTROLS;
+ }
+
+ if (show_controls != NO_ANIMATION &&
+ (!top_controls_animation_ || animation_direction_ != show_controls)) {
+ SetupAnimation(show_controls);
+ client_->setNeedsRedraw();
+ }
+ }
+}
+
+bool TopControlsManager::IsAnimationCompleteAtTime(base::TimeTicks time) {
+ if (!top_controls_animation_)
+ return true;
+
+ double time_ms = (time - base::TimeTicks()).InMillisecondsF();
+ float new_offset = top_controls_animation_->GetValue(time_ms);
+
+ if ((animation_direction_ == SHOWING_CONTROLS && new_offset >= 0) ||
+ (animation_direction_ == HIDING_CONTROLS
+ && new_offset <= -top_controls_height_)) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace cc
diff --git a/cc/input/top_controls_manager.h b/cc/input/top_controls_manager.h
new file mode 100644
index 0000000..5a60a9a
--- /dev/null
+++ b/cc/input/top_controls_manager.h
@@ -0,0 +1,96 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_INPUT_TOP_CONTROLS_MANAGER_H_
+#define CC_INPUT_TOP_CONTROLS_MANAGER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/layer_impl.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace base {
+class TimeTicks;
+}
+
+namespace cc {
+
+class KeyframedFloatAnimationCurve;
+class LayerTreeImpl;
+class TopControlsManagerClient;
+
+// Manages the position of the top controls.
+class CC_EXPORT TopControlsManager
+ : public base::SupportsWeakPtr<TopControlsManager> {
+ public:
+ enum AnimationDirection {
+ NO_ANIMATION,
+ SHOWING_CONTROLS,
+ HIDING_CONTROLS
+ };
+
+ static scoped_ptr<TopControlsManager> Create(
+ TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold);
+ virtual ~TopControlsManager();
+
+ float controls_top_offset() { return controls_top_offset_; }
+ float content_top_offset() {
+ return controls_top_offset_ + top_controls_height_;
+ }
+ KeyframedFloatAnimationCurve* animation() {
+ return top_controls_animation_.get();
+ }
+ AnimationDirection animation_direction() { return animation_direction_; }
+
+ void EnableHidingTopControls(bool enable);
+
+ void ScrollBegin();
+ gfx::Vector2dF ScrollBy(const gfx::Vector2dF pending_delta);
+ void ScrollEnd();
+
+ gfx::Vector2dF Animate(base::TimeTicks monotonic_time);
+
+ protected:
+ TopControlsManager(TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold);
+
+ private:
+ void SetControlsTopOffset(float);
+ void ResetAnimations();
+ void SetupAnimation(AnimationDirection direction);
+ void StartAnimationIfNecessary();
+ bool IsAnimationCompleteAtTime(base::TimeTicks time);
+
+ TopControlsManagerClient* client_; // The client manages the lifecycle of
+ // this.
+
+ scoped_ptr<KeyframedFloatAnimationCurve> top_controls_animation_;
+ AnimationDirection animation_direction_;
+ bool enable_hiding_;
+ float controls_top_offset_;
+ float top_controls_height_;
+
+ float current_scroll_delta_;
+ float controls_scroll_begin_offset_;
+
+ // The height of the visible top control such that it must be shown when
+ // the user stops the scroll.
+ float top_controls_show_height_;
+
+ // The height of the visible top control such that it must be hidden when
+ // the user stops the scroll.
+ float top_controls_hide_height_;
+
+ DISALLOW_COPY_AND_ASSIGN(TopControlsManager);
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_TOP_CONTROLS_MANAGER_H_
diff --git a/cc/input/top_controls_manager_client.h b/cc/input/top_controls_manager_client.h
new file mode 100644
index 0000000..b90de7c8
--- /dev/null
+++ b/cc/input/top_controls_manager_client.h
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_INPUT_TOP_CONTROLS_MANAGER_CLIENT_H_
+#define CC_INPUT_TOP_CONTROLS_MANAGER_CLIENT_H_
+
+namespace cc {
+
+class LayerTreeImpl;
+
+class CC_EXPORT TopControlsManagerClient {
+ public:
+ virtual void setNeedsRedraw() = 0;
+ virtual void setActiveTreeNeedsUpdateDrawProperties() = 0;
+ virtual bool haveRootScrollLayer() const = 0;
+
+ protected:
+ virtual ~TopControlsManagerClient() {}
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_TOP_CONTROLS_MANAGER_CLIENT_H_
diff --git a/cc/input/top_controls_manager_unittest.cc b/cc/input/top_controls_manager_unittest.cc
new file mode 100644
index 0000000..0e426a0
--- /dev/null
+++ b/cc/input/top_controls_manager_unittest.cc
@@ -0,0 +1,293 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/input/top_controls_manager.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "cc/input/top_controls_manager_client.h"
+#include "cc/layer_impl.h"
+#include "cc/layer_tree_impl.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+namespace {
+
+static const float kTopControlsHeight = 100;
+
+class MockTopControlsManagerClient : public TopControlsManagerClient {
+ public:
+ MockTopControlsManagerClient(float top_controls_show_threshold,
+ float top_controls_hide_threshold)
+ : host_impl_(&proxy_),
+ redraw_needed_(false),
+ update_draw_properties_needed_(false),
+ top_controls_show_threshold_(top_controls_show_threshold),
+ top_controls_hide_threshold_(top_controls_hide_threshold) {
+ active_tree_ = LayerTreeImpl::create(&host_impl_);
+ root_scroll_layer_ = LayerImpl::Create(active_tree_.get(), 1);
+ }
+
+ virtual ~MockTopControlsManagerClient() {}
+
+ virtual void setNeedsRedraw() OVERRIDE {
+ redraw_needed_ = true;
+ }
+
+ virtual void setActiveTreeNeedsUpdateDrawProperties() OVERRIDE {
+ update_draw_properties_needed_ = true;
+ }
+
+ virtual bool haveRootScrollLayer() const OVERRIDE {
+ return true;
+ }
+
+ LayerImpl* rootScrollLayer() {
+ return root_scroll_layer_.get();
+ }
+
+ TopControlsManager* manager() {
+ if (!manager_) {
+ manager_ = TopControlsManager::Create(this,
+ kTopControlsHeight,
+ top_controls_show_threshold_,
+ top_controls_hide_threshold_);
+ }
+ return manager_.get();
+ }
+
+ private:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ scoped_ptr<LayerTreeImpl> active_tree_;
+ scoped_ptr<LayerImpl> root_scroll_layer_;
+ scoped_ptr<TopControlsManager> manager_;
+ bool redraw_needed_;
+ bool update_draw_properties_needed_;
+
+ float top_controls_show_threshold_;
+ float top_controls_hide_threshold_;
+};
+
+TEST(TopControlsManagerTest, ensureScrollThresholdApplied) {
+ MockTopControlsManagerClient client(0.5f, 0.5f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBegin();
+
+ // Scroll down to hide the controls entirely.
+ manager->ScrollBy(gfx::Vector2dF(0.f, 30.f));
+ EXPECT_EQ(-30.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 30.f));
+ EXPECT_EQ(-60.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 100.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+
+ // Scroll back up a bit and ensure the controls don't move until we cross
+ // the threshold.
+ manager->ScrollBy(gfx::Vector2dF(0.f, -10.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -50.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+
+ // After hitting the threshold, further scrolling up should result in the top
+ // controls showing.
+ manager->ScrollBy(gfx::Vector2dF(0.f, -10.f));
+ EXPECT_EQ(-90.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -50.f));
+ EXPECT_EQ(-40.f, manager->controls_top_offset());
+
+ // Reset the scroll threshold by going further up the page than the initial
+ // threshold.
+ manager->ScrollBy(gfx::Vector2dF(0.f, -100.f));
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+
+ // See that scrolling down the page now will result in the controls hiding.
+ manager->ScrollBy(gfx::Vector2dF(0.f, 20.f));
+ EXPECT_EQ(-20.f, manager->controls_top_offset());
+
+ manager->ScrollEnd();
+}
+
+TEST(TopControlsManagerTest, partialShownHideAnimation) {
+ MockTopControlsManagerClient client(0.5f, 0.5f);
+ TopControlsManager* manager = client.manager();
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, 300.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, -15.f));
+ EXPECT_EQ(-85.f, manager->controls_top_offset());
+ EXPECT_EQ(15.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while(manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_LT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, partialShownShowAnimation) {
+ MockTopControlsManagerClient client(0.5f, 0.5f);
+ TopControlsManager* manager = client.manager();
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, 300.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, -70.f));
+ EXPECT_EQ(-30.f, manager->controls_top_offset());
+ EXPECT_EQ(70.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while(manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_GT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+ EXPECT_EQ(100.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, partialHiddenWithAmbiguousThresholdShows) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 20.f));
+ EXPECT_EQ(-20.f, manager->controls_top_offset());
+ EXPECT_EQ(80.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while(manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_GT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+ EXPECT_EQ(100.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, partialHiddenWithAmbiguousThresholdHides) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 30.f));
+ EXPECT_EQ(-30.f, manager->controls_top_offset());
+ EXPECT_EQ(70.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while(manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_LT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, partialShownWithAmbiguousThresholdHides) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 200.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -20.f));
+ EXPECT_EQ(-80.f, manager->controls_top_offset());
+ EXPECT_EQ(20.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while(manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_LT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, partialShownWithAmbiguousThresholdShows) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 200.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -30.f));
+ EXPECT_EQ(-70.f, manager->controls_top_offset());
+ EXPECT_EQ(30.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while(manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_GT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+ EXPECT_EQ(100.f, manager->content_top_offset());
+}
+
+} // namespace
+} // namespace cc