1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/input/browser_controls_offset_manager.h"
11 #include "base/logging.h"
12 #include "base/memory/ptr_util.h"
13 #include "cc/input/browser_controls_offset_manager_client.h"
14 #include "cc/trees/layer_tree_impl.h"
15 #include "components/viz/common/frame_sinks/begin_frame_args.h"
16 #include "ui/gfx/animation/tween.h"
17 #include "ui/gfx/geometry/vector2d_f.h"
18 #include "ui/gfx/transform.h"
22 // These constants were chosen empirically for their visually pleasant behavior.
23 // Contact tedchoc@chromium.org for questions about changing these values.
24 const int64_t kShowHideMaxDurationMs = 200;
28 std::unique_ptr<BrowserControlsOffsetManager>
29 BrowserControlsOffsetManager::Create(BrowserControlsOffsetManagerClient* client,
30 float controls_show_threshold,
31 float controls_hide_threshold) {
32 return base::WrapUnique(new BrowserControlsOffsetManager(
33 client, controls_show_threshold, controls_hide_threshold));
36 BrowserControlsOffsetManager::BrowserControlsOffsetManager(
37 BrowserControlsOffsetManagerClient* client,
38 float controls_show_threshold,
39 float controls_hide_threshold)
41 animation_initialized_(false),
42 animation_start_value_(0.f),
43 animation_stop_value_(0.f),
44 animation_direction_(NO_ANIMATION),
45 permitted_state_(BrowserControlsState::kBoth),
46 accumulated_scroll_delta_(0.f),
47 baseline_top_content_offset_(0.f),
48 baseline_bottom_content_offset_(0.f),
49 controls_show_threshold_(controls_hide_threshold),
50 controls_hide_threshold_(controls_show_threshold),
51 pinch_gesture_active_(false),
52 constraint_changed_since_commit_(false) {
56 BrowserControlsOffsetManager::~BrowserControlsOffsetManager() = default;
58 float BrowserControlsOffsetManager::ControlsTopOffset() const {
59 return ContentTopOffset() - TopControlsHeight();
62 float BrowserControlsOffsetManager::ContentTopOffset() const {
63 return TopControlsHeight() > 0
64 ? TopControlsShownRatio() * TopControlsHeight() : 0.0f;
67 float BrowserControlsOffsetManager::TopControlsShownRatio() const {
68 return client_->CurrentBrowserControlsShownRatio();
71 float BrowserControlsOffsetManager::TopControlsHeight() const {
72 return client_->TopControlsHeight();
75 float BrowserControlsOffsetManager::BottomControlsHeight() const {
76 return client_->BottomControlsHeight();
79 float BrowserControlsOffsetManager::ContentBottomOffset() const {
80 return BottomControlsHeight() > 0
81 ? BottomControlsShownRatio() * BottomControlsHeight() : 0.0f;
84 float BrowserControlsOffsetManager::BottomControlsShownRatio() const {
85 return TopControlsShownRatio();
88 void BrowserControlsOffsetManager::UpdateBrowserControlsState(
89 BrowserControlsState constraints,
90 BrowserControlsState current,
92 DCHECK(!(constraints == BrowserControlsState::kShown &&
93 current == BrowserControlsState::kHidden));
94 DCHECK(!(constraints == BrowserControlsState::kHidden &&
95 current == BrowserControlsState::kShown));
97 TRACE_EVENT2("cc", "BrowserControlsOffsetManager::UpdateBrowserControlsState",
98 "constraints", static_cast<int>(constraints), "current",
99 static_cast<int>(current));
101 // If the constraints have changed we need to inform Blink about it since
102 // that'll affect main thread scrolling as well as layout.
103 if (permitted_state_ != constraints) {
104 constraint_changed_since_commit_ = true;
105 client_->SetNeedsCommit();
108 permitted_state_ = constraints;
110 // Don't do anything if it doesn't matter which state the controls are in.
111 if (constraints == BrowserControlsState::kBoth &&
112 current == BrowserControlsState::kBoth)
115 // Don't do anything if there is no change in offset.
116 float final_shown_ratio = 1.f;
117 if (constraints == BrowserControlsState::kHidden ||
118 current == BrowserControlsState::kHidden)
119 final_shown_ratio = 0.f;
120 if (final_shown_ratio == TopControlsShownRatio()) {
121 TRACE_EVENT_INSTANT0("cc", "Ratio Unchanged", TRACE_EVENT_SCOPE_THREAD);
127 SetupAnimation(final_shown_ratio ? SHOWING_CONTROLS : HIDING_CONTROLS);
130 client_->SetCurrentBrowserControlsShownRatio(final_shown_ratio);
134 BrowserControlsState BrowserControlsOffsetManager::PullConstraintForMainThread(
135 bool* out_changed_since_commit) {
136 DCHECK(out_changed_since_commit);
137 *out_changed_since_commit = constraint_changed_since_commit_;
138 constraint_changed_since_commit_ = false;
139 return permitted_state_;
142 void BrowserControlsOffsetManager::ScrollBegin() {
143 if (pinch_gesture_active_)
150 gfx::Vector2dF BrowserControlsOffsetManager::ScrollBy(
151 const gfx::Vector2dF& pending_delta) {
152 // If one or both of the top/bottom controls are showing, the shown ratio
153 // needs to be computed.
154 float controls_height =
155 TopControlsHeight() ? TopControlsHeight() : BottomControlsHeight();
157 if (!controls_height)
158 return pending_delta;
160 if (pinch_gesture_active_)
161 return pending_delta;
163 if (permitted_state_ == BrowserControlsState::kShown && pending_delta.y() > 0)
164 return pending_delta;
165 else if (permitted_state_ == BrowserControlsState::kHidden &&
166 pending_delta.y() < 0)
167 return pending_delta;
169 accumulated_scroll_delta_ += pending_delta.y();
171 float old_top_offset = ContentTopOffset();
172 float baseline_content_offset = TopControlsHeight()
173 ? baseline_top_content_offset_ : baseline_bottom_content_offset_;
174 client_->SetCurrentBrowserControlsShownRatio(
175 (baseline_content_offset - accumulated_scroll_delta_) / controls_height);
177 // If the controls are fully visible, treat the current position as the
178 // new baseline even if the gesture didn't end.
179 if (TopControlsShownRatio() == 1.f)
184 // applied_delta will negate any scroll on the content if the top browser
185 // controls are showing in favor of hiding the controls and resizing the
186 // content. If the top controls have no height, the content should scroll
188 gfx::Vector2dF applied_delta(0.f, old_top_offset - ContentTopOffset());
189 return pending_delta - applied_delta;
192 void BrowserControlsOffsetManager::ScrollEnd() {
193 if (pinch_gesture_active_)
196 StartAnimationIfNecessary();
199 void BrowserControlsOffsetManager::PinchBegin() {
200 DCHECK(!pinch_gesture_active_);
201 pinch_gesture_active_ = true;
202 StartAnimationIfNecessary();
205 void BrowserControlsOffsetManager::PinchEnd() {
206 DCHECK(pinch_gesture_active_);
207 // Pinch{Begin,End} will always occur within the scope of Scroll{Begin,End},
208 // so return to a state expected by the remaining scroll sequence.
209 pinch_gesture_active_ = false;
213 void BrowserControlsOffsetManager::MainThreadHasStoppedFlinging() {
214 StartAnimationIfNecessary();
217 gfx::Vector2dF BrowserControlsOffsetManager::Animate(
218 base::TimeTicks monotonic_time) {
219 if (!has_animation() || !client_->HaveRootScrollNode())
220 return gfx::Vector2dF();
222 if (!animation_initialized_) {
223 // Setup the animation start and time here so that they use the same clock
224 // as frame times. This is helpful for tests that mock time.
225 animation_start_time_ = monotonic_time;
226 animation_stop_time_ =
227 animation_start_time_ +
228 base::TimeDelta::FromMilliseconds(kShowHideMaxDurationMs);
229 animation_initialized_ = true;
232 float old_offset = ContentTopOffset();
233 float new_ratio = gfx::Tween::ClampedFloatValueBetween(
234 monotonic_time, animation_start_time_, animation_start_value_,
235 animation_stop_time_, animation_stop_value_);
236 client_->SetCurrentBrowserControlsShownRatio(new_ratio);
238 if (IsAnimationComplete(new_ratio))
241 gfx::Vector2dF scroll_delta(0.f, ContentTopOffset() - old_offset);
245 void BrowserControlsOffsetManager::ResetAnimations() {
246 animation_initialized_ = false;
247 animation_start_time_ = base::TimeTicks();
248 animation_start_value_ = 0.f;
249 animation_stop_time_ = base::TimeTicks();
250 animation_stop_value_ = 0.f;
252 animation_direction_ = NO_ANIMATION;
255 void BrowserControlsOffsetManager::SetupAnimation(
256 AnimationDirection direction) {
257 DCHECK_NE(NO_ANIMATION, direction);
258 DCHECK(direction != HIDING_CONTROLS || TopControlsShownRatio() > 0.f);
259 DCHECK(direction != SHOWING_CONTROLS || TopControlsShownRatio() < 1.f);
261 if (has_animation() && animation_direction_ == direction)
264 if (!TopControlsHeight() && !BottomControlsHeight()) {
265 client_->SetCurrentBrowserControlsShownRatio(
266 direction == HIDING_CONTROLS ? 0.f : 1.f);
270 animation_start_value_ = TopControlsShownRatio();
272 const float max_ending_ratio = (direction == SHOWING_CONTROLS ? 1 : -1);
273 animation_stop_value_ = animation_start_value_ + max_ending_ratio;
275 animation_direction_ = direction;
276 client_->DidChangeBrowserControlsPosition();
279 void BrowserControlsOffsetManager::StartAnimationIfNecessary() {
280 if (TopControlsShownRatio() == 0.f || TopControlsShownRatio() == 1.f)
283 if (TopControlsShownRatio() >= 1.f - controls_hide_threshold_) {
284 // If we're showing so much that the hide threshold won't trigger, show.
285 SetupAnimation(SHOWING_CONTROLS);
286 } else if (TopControlsShownRatio() <= controls_show_threshold_) {
287 // If we're showing so little that the show threshold won't trigger, hide.
288 SetupAnimation(HIDING_CONTROLS);
290 // If we could be either showing or hiding, we determine which one to
291 // do based on whether or not the total scroll delta was moving up or
293 SetupAnimation(accumulated_scroll_delta_ <= 0.f ? SHOWING_CONTROLS
298 bool BrowserControlsOffsetManager::IsAnimationComplete(float new_ratio) {
299 return (animation_direction_ == SHOWING_CONTROLS && new_ratio >= 1.f) ||
300 (animation_direction_ == HIDING_CONTROLS && new_ratio <= 0.f);
303 void BrowserControlsOffsetManager::ResetBaseline() {
304 accumulated_scroll_delta_ = 0.f;
305 baseline_top_content_offset_ = ContentTopOffset();
306 baseline_bottom_content_offset_ = ContentBottomOffset();