1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
cc / input / browser_controls_offset_manager.cc [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/input/browser_controls_offset_manager.h"
#include <stdint.h>
#include <algorithm>
#include <utility>
#include "base/check_op.h"
#include "base/memory/ptr_util.h"
#include "base/types/optional_ref.h"
#include "cc/input/browser_controls_offset_manager_client.h"
#include "cc/input/browser_controls_offset_tags_info.h"
#include "cc/trees/layer_tree_impl.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/quads/offset_tag.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/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_t kShowHideMaxDurationMs = 200;
// TODO(sinansahin): Temporary value, pending UX guidance probably.
const int64_t kHeightChangeDurationMs = 200;
}
// static
std::unique_ptr<BrowserControlsOffsetManager>
BrowserControlsOffsetManager::Create(BrowserControlsOffsetManagerClient* client,
float controls_show_threshold,
float controls_hide_threshold) {
return base::WrapUnique(new BrowserControlsOffsetManager(
client, controls_show_threshold, controls_hide_threshold));
}
BrowserControlsOffsetManager::BrowserControlsOffsetManager(
BrowserControlsOffsetManagerClient* client,
float controls_show_threshold,
float controls_hide_threshold)
: client_(client),
permitted_state_(BrowserControlsState::kBoth),
accumulated_scroll_delta_(0.f),
baseline_top_content_offset_(0.f),
baseline_bottom_content_offset_(0.f),
controls_show_threshold_(controls_hide_threshold),
controls_hide_threshold_(controls_show_threshold),
pinch_gesture_active_(false),
constraint_changed_since_commit_(false),
top_min_height_change_in_progress_(false),
bottom_min_height_change_in_progress_(false),
top_controls_min_height_offset_(0.f),
bottom_controls_min_height_offset_(0.f) {
CHECK(client_);
UpdateOldBrowserControlsParams();
}
BrowserControlsOffsetManager::~BrowserControlsOffsetManager() = default;
float BrowserControlsOffsetManager::ControlsTopOffset() const {
return ContentTopOffset() - TopControlsHeight();
}
float BrowserControlsOffsetManager::ContentTopOffset() const {
return TopControlsHeight() > 0
? TopControlsShownRatio() * TopControlsHeight() : 0.0f;
}
float BrowserControlsOffsetManager::TopControlsShownRatio() const {
return client_->CurrentTopControlsShownRatio();
}
float BrowserControlsOffsetManager::TopControlsHeight() const {
return client_->TopControlsHeight();
}
float BrowserControlsOffsetManager::TopControlsMinHeight() const {
return client_->TopControlsMinHeight();
}
int BrowserControlsOffsetManager::TopControlsHairlineHeight() const {
return top_controls_hairline_height_;
}
int BrowserControlsOffsetManager::BottomControlsAdditionalHeight() const {
return bottom_controls_additional_height_;
}
viz::OffsetTag BrowserControlsOffsetManager::BottomControlsOffsetTag() const {
return bottom_controls_offset_tag_;
}
viz::OffsetTag BrowserControlsOffsetManager::ContentOffsetTag() const {
return content_offset_tag_;
}
viz::OffsetTag BrowserControlsOffsetManager::TopControlsOffsetTag() const {
return top_controls_offset_tag_;
}
float BrowserControlsOffsetManager::TopControlsMinShownRatio() const {
return TopControlsHeight() ? TopControlsMinHeight() / TopControlsHeight()
: 0.f;
}
float BrowserControlsOffsetManager::BottomControlsHeight() const {
return client_->BottomControlsHeight();
}
float BrowserControlsOffsetManager::BottomControlsMinHeight() const {
return client_->BottomControlsMinHeight();
}
float BrowserControlsOffsetManager::BottomControlsMinShownRatio() const {
return BottomControlsHeight()
? BottomControlsMinHeight() / BottomControlsHeight()
: 0.f;
}
float BrowserControlsOffsetManager::ContentBottomOffset() const {
return BottomControlsHeight() > 0
? BottomControlsShownRatio() * BottomControlsHeight() : 0.0f;
}
float BrowserControlsOffsetManager::BottomControlsShownRatio() const {
return client_->CurrentBottomControlsShownRatio();
}
float BrowserControlsOffsetManager::TopControlsMinHeightOffset() const {
return top_controls_min_height_offset_;
}
float BrowserControlsOffsetManager::BottomControlsMinHeightOffset() const {
return bottom_controls_min_height_offset_;
}
std::pair<float, float>
BrowserControlsOffsetManager::TopControlsShownRatioRange() {
if (top_controls_animation_.IsInitialized())
return std::make_pair(top_controls_animation_.min_value(),
top_controls_animation_.max_value());
return std::make_pair(0.f, 1.f);
}
std::pair<float, float>
BrowserControlsOffsetManager::BottomControlsShownRatioRange() {
if (bottom_controls_animation_.IsInitialized())
return std::make_pair(bottom_controls_animation_.min_value(),
bottom_controls_animation_.max_value());
return std::make_pair(0.f, 1.f);
}
void BrowserControlsOffsetManager::UpdateBrowserControlsState(
BrowserControlsState constraints,
BrowserControlsState current,
bool animate,
base::optional_ref<const BrowserControlsOffsetTagsInfo> offset_tags_info) {
DCHECK(!(constraints == BrowserControlsState::kShown &&
current == BrowserControlsState::kHidden));
DCHECK(!(constraints == BrowserControlsState::kHidden &&
current == BrowserControlsState::kShown));
TRACE_EVENT2("cc", "BrowserControlsOffsetManager::UpdateBrowserControlsState",
"constraints", static_cast<int>(constraints), "current",
static_cast<int>(current));
// If the constraints have changed we need to inform Blink about it since
// that'll affect main thread scrolling as well as layout.
if (permitted_state_ != constraints) {
constraint_changed_since_commit_ = true;
client_->SetNeedsCommit();
}
permitted_state_ = constraints;
if (offset_tags_info.has_value()) {
bottom_controls_offset_tag_ =
offset_tags_info.value().bottom_controls_offset_tag;
bottom_controls_additional_height_ =
offset_tags_info.value().bottom_controls_additional_height;
content_offset_tag_ = offset_tags_info.value().content_offset_tag;
top_controls_offset_tag_ = offset_tags_info.value().top_controls_offset_tag;
top_controls_hairline_height_ =
offset_tags_info.value().top_controls_hairline_height;
}
// Don't do anything if it doesn't matter which state the controls are in.
if (constraints == BrowserControlsState::kBoth &&
current == BrowserControlsState::kBoth)
return;
// Don't do anything if there is no change in offset.
float final_top_shown_ratio = 1.f;
float final_bottom_shown_ratio = 1.f;
AnimationDirection direction = AnimationDirection::kShowingControls;
if (constraints == BrowserControlsState::kHidden ||
current == BrowserControlsState::kHidden) {
final_top_shown_ratio = TopControlsMinShownRatio();
final_bottom_shown_ratio = BottomControlsMinShownRatio();
direction = AnimationDirection::kHidingControls;
}
if (final_top_shown_ratio == TopControlsShownRatio() &&
final_bottom_shown_ratio == BottomControlsShownRatio()) {
TRACE_EVENT_INSTANT0("cc", "Ratios Unchanged", TRACE_EVENT_SCOPE_THREAD);
ResetAnimations();
return;
}
// Don't do anything if the currently running animations end in our desired
// state.
float animated_top_shown_ratio = top_controls_animation_.IsInitialized()
? top_controls_animation_.FinalValue()
: TopControlsShownRatio();
float animated_bottom_shown_ratio =
bottom_controls_animation_.IsInitialized()
? bottom_controls_animation_.FinalValue()
: BottomControlsShownRatio();
if (animated_top_shown_ratio == final_top_shown_ratio &&
animated_bottom_shown_ratio == final_bottom_shown_ratio) {
return;
}
ResetAnimations();
// If we're about to animate the controls in, then restart the animation after
// the scroll completes. We don't know if a scroll is in progress, but that's
// okay; the flag will be reset when a scroll starts next in that case.
if (animate && direction == AnimationDirection::kShowingControls) {
show_controls_when_scroll_completes_ = true;
}
if (animate)
SetupAnimation(direction);
else
client_->SetCurrentBrowserControlsShownRatio(final_top_shown_ratio,
final_bottom_shown_ratio);
}
BrowserControlsState BrowserControlsOffsetManager::PullConstraintForMainThread(
bool* out_changed_since_commit) {
DCHECK(out_changed_since_commit);
*out_changed_since_commit = constraint_changed_since_commit_;
return permitted_state_;
}
void BrowserControlsOffsetManager::NotifyConstraintSyncedToMainThread() {
constraint_changed_since_commit_ = false;
}
void BrowserControlsOffsetManager::OnBrowserControlsParamsChanged(
bool animate_changes) {
if (old_browser_controls_params_.top_controls_height == TopControlsHeight() &&
old_browser_controls_params_.top_controls_min_height ==
TopControlsMinHeight() &&
old_browser_controls_params_.bottom_controls_height ==
BottomControlsHeight() &&
old_browser_controls_params_.bottom_controls_min_height ==
BottomControlsMinHeight()) {
return;
}
// We continue to update both top and bottom controls even if one has a height
// of 0 so that animations work properly. So here, we should preserve the
// ratios even if the controls height is 0.
float old_top_height = old_browser_controls_params_.top_controls_height;
float new_top_ratio =
TopControlsHeight()
? TopControlsShownRatio() * old_top_height / TopControlsHeight()
: TopControlsShownRatio();
float old_bottom_height = old_browser_controls_params_.bottom_controls_height;
float new_bottom_ratio = BottomControlsHeight()
? BottomControlsShownRatio() *
old_bottom_height / BottomControlsHeight()
: BottomControlsShownRatio();
if (!animate_changes) {
// If the min-heights changed when the controls were at the min-height, the
// shown ratios need to be snapped to the new min-shown-ratio to keep the
// controls at the min height. If the controls were fully shown, we want to
// keep them fully shown even after the heights changed. For any other
// cases, we should update the shown ratio so the visible height remains the
// same.
if (TopControlsShownRatio() == OldTopControlsMinShownRatio())
new_top_ratio = TopControlsMinShownRatio();
else if (TopControlsShownRatio() == 1.f)
new_top_ratio = 1.f;
if (BottomControlsShownRatio() == OldBottomControlsMinShownRatio())
new_bottom_ratio = BottomControlsMinShownRatio();
else if (BottomControlsShownRatio() == 1.f)
new_bottom_ratio = 1.f;
}
// Browser controls height change animations
// If the browser controls heights (and/or min-heights) changed and need to be
// animated, the setup is done here. All the animations done in this class
// involve changing the shown ratios smoothly.
//
// There are several cases to handle:
// 1- The controls shown ratio was at the minimum ratio
// - If the min-height changed, we will run an animation from
// old-min-height / new-total-height to new-min-height / new-total-height
// - If the min-height didn't change, we should update the shown ratio to
// min-height / new-total-height so that the controls keep the same
// visible height and don't jump. No animation needed in this case.
// 2- The controls shown ratio was at the highest ratio (should be 1 here)
// - If the total height changed, we will run an animation from
// old-total-height / new-total-height to 1.
// - If the total height didn't change, we don't need to do anything.
// 3- The controls shown ratio is between the minimum and the maximum.
// - If an animation is running to the old min-height, start a new
// animation to min-height / total-height.
// - Otherwise don't start an animation. We're either animating the
// controls to their expanded state, in which case we can let that
// animation continue, or we're scrolling and no animation is needed.
// Update the shown ratio so the visible height remains the same (see
// new_{top,bottom}_ratio above).
//
// When this method is called as a result of a height change,
// TopControlsHeight(), TopControlsMinHeight(), BottomControlsHeight(), and
// BottomControlsMinHeight() will already be returning the new values.
// However, the shown ratios aren't updated.
bool top_controls_need_animation = animate_changes;
bool bottom_controls_need_animation = animate_changes;
// To handle the case where the min-height changes while we're animating to
// the previous min-height, base our "are we at the minimum shown ratio"
// check on the post-animation ratio if an animation is running, rather than
// its current value.
float effective_top_shown_ratio = TopControlsShownRatio();
if (top_controls_animation_.IsInitialized()) {
effective_top_shown_ratio = top_controls_animation_.FinalValue();
}
float effective_bottom_shown_ratio = BottomControlsShownRatio();
if (bottom_controls_animation_.IsInitialized()) {
effective_bottom_shown_ratio = bottom_controls_animation_.FinalValue();
}
float top_target_ratio;
// We can't animate if we don't have top controls.
if (!TopControlsHeight()) {
top_controls_need_animation = false;
// If the top controls height changed when they were fully shown.
} else if (TopControlsShownRatio() == 1.f &&
TopControlsHeight() != old_top_height) {
top_target_ratio = 1.f; // i.e. new_height / new_height
// If the top controls min-height changed when they were at the minimum
// shown ratio. For example, the min height changed from 0 to a positive
// value while the top controls were completely hidden.
} else if (effective_top_shown_ratio == OldTopControlsMinShownRatio() &&
TopControlsMinHeight() !=
old_browser_controls_params_.top_controls_min_height) {
top_target_ratio = TopControlsMinShownRatio();
} else {
top_controls_need_animation = false;
}
float bottom_target_ratio;
// We can't animate if we don't have bottom controls.
if (!BottomControlsHeight()) {
bottom_controls_need_animation = false;
// If the bottom controls height changed when they were fully shown.
} else if (BottomControlsShownRatio() == 1.f &&
BottomControlsHeight() != old_bottom_height) {
bottom_target_ratio = 1.f; // i.e. new_height / new_height
// If the bottom controls min-height changed when they were at the minimum
// shown ratio.
} else if (effective_bottom_shown_ratio == OldBottomControlsMinShownRatio() &&
BottomControlsMinHeight() !=
old_browser_controls_params_.bottom_controls_min_height) {
bottom_target_ratio = BottomControlsMinShownRatio();
} else {
bottom_controls_need_animation = false;
}
if (top_controls_need_animation) {
InitAnimationForHeightChange(&top_controls_animation_, new_top_ratio,
top_target_ratio);
if (old_browser_controls_params_.top_controls_min_height !=
TopControlsMinHeight()) {
top_controls_min_height_offset_ =
old_browser_controls_params_.top_controls_min_height;
top_min_height_change_in_progress_ = true;
SetTopMinHeightOffsetAnimationRange(top_controls_min_height_offset_,
TopControlsMinHeight());
}
} else {
top_controls_min_height_offset_ = TopControlsMinHeight();
}
if (bottom_controls_need_animation) {
InitAnimationForHeightChange(&bottom_controls_animation_, new_bottom_ratio,
bottom_target_ratio);
if (old_browser_controls_params_.bottom_controls_min_height !=
BottomControlsMinHeight()) {
bottom_controls_min_height_offset_ =
old_browser_controls_params_.bottom_controls_min_height;
int height_delta = BottomControlsHeight() - old_bottom_height;
int min_height_delta =
BottomControlsMinHeight() -
old_browser_controls_params_.bottom_controls_min_height;
// Currently, browser controls animate purely based on the change in the
// height, not on the change in minHeight. This works fine when that
// change is the same, but causes issues if the minHeight has been changed
// by a different value than the height has. This is mitigated by
// "stepping up" or "down" the starting min height offset such that the
// effective change is the same for both the height and minHeight.
if (min_height_delta > height_delta) {
bottom_controls_min_height_offset_ += min_height_delta - height_delta;
}
bottom_min_height_change_in_progress_ = true;
SetBottomMinHeightOffsetAnimationRange(bottom_controls_min_height_offset_,
BottomControlsMinHeight());
}
} else {
bottom_controls_min_height_offset_ = BottomControlsMinHeight();
}
// We won't run any animations if the controls are in an in-between state.
// Examples: a show/hide animation is running, shown ratio is some value
// between min-shown-ratio and 1 because of a scroll event.
UpdateOldBrowserControlsParams();
client_->SetCurrentBrowserControlsShownRatio(new_top_ratio, new_bottom_ratio);
}
void BrowserControlsOffsetManager::ScrollBegin() {
if (pinch_gesture_active_)
return;
// If an animation to show the controls is in progress, re-order the animation
// to start after the scroll completes. This ensures that the user doesn't
// accidentally hide the controls with a gesture that would not normally be
// enough to hide them.
show_controls_when_scroll_completes_ = IsAnimatingToShowControls();
ResetAnimations();
ResetBaseline();
}
gfx::Vector2dF BrowserControlsOffsetManager::ScrollBy(
const gfx::Vector2dF& pending_delta) {
// If one or both of the top/bottom controls are showing, the shown ratio
// needs to be computed.
if (!TopControlsHeight() && !BottomControlsHeight())
return pending_delta;
if (pinch_gesture_active_)
return pending_delta;
if ((permitted_state_ == BrowserControlsState::kShown &&
pending_delta.y() > 0) ||
(permitted_state_ == BrowserControlsState::kHidden &&
pending_delta.y() < 0))
return pending_delta;
// Scroll the page up before expanding the browser controls if
// OnlyExpandTopControlsAtPageTop() returns true.
float viewport_offset_y = client_->ViewportScrollOffset().y();
if (client_->OnlyExpandTopControlsAtPageTop() && pending_delta.y() < 0 &&
viewport_offset_y > 0) {
// Reset the baseline so the controls will immediately begin to scroll
// once we're at the top.
ResetBaseline();
// Only scroll the controls by the amount remaining after the page contents
// have been scrolled to the top.
accumulated_scroll_delta_ =
std::min(0.f, pending_delta.y() + viewport_offset_y);
} else {
accumulated_scroll_delta_ += pending_delta.y();
}
// We want to base our calculations on top or bottom controls. After consuming
// the scroll delta, we will calculate a shown ratio for the controls. The
// top controls have the priority because they need to visually be in sync
// with the web contents.
bool base_on_top_controls = TopControlsHeight();
float old_top_offset = ContentTopOffset();
float baseline_content_offset = base_on_top_controls
? baseline_top_content_offset_
: baseline_bottom_content_offset_;
// The top and bottom controls ratios can be calculated independently.
// However, we want the (normalized) ratios to be equal when scrolling.
// Having normalized ratios in this context means the top and bottom controls
// reach the min and max ratios at the same time when scrolling or during
// the usual show/hide animations, but they can have different shown ratios at
// any time.
float shown_ratio =
(baseline_content_offset - accumulated_scroll_delta_) /
(base_on_top_controls ? TopControlsHeight() : BottomControlsHeight());
float min_ratio = base_on_top_controls ? TopControlsMinShownRatio()
: BottomControlsMinShownRatio();
float normalized_shown_ratio =
(std::clamp(shown_ratio, min_ratio, 1.f) - min_ratio) / (1.f - min_ratio);
// Even though the real shown ratios (shown height / total height) of the top
// and bottom controls can be different, they share the same
// relative/normalized ratio to keep them in sync.
client_->SetCurrentBrowserControlsShownRatio(
TopControlsMinShownRatio() +
normalized_shown_ratio * (1.f - TopControlsMinShownRatio()),
BottomControlsMinShownRatio() +
normalized_shown_ratio * (1.f - BottomControlsMinShownRatio()));
// If the controls are fully visible, treat the current position as the
// new baseline even if the gesture didn't end.
if (TopControlsShownRatio() == 1.f && BottomControlsShownRatio() == 1.f) {
ResetBaseline();
// Once the controls are fully visible, then any cancelled animation to show
// them isn't relevant; the user definitely sees the controls and can decide
// if they'd like to keep them.
show_controls_when_scroll_completes_ = false;
}
ResetAnimations();
// applied_delta will negate any scroll on the content if the top browser
// controls are showing in favor of hiding the controls and resizing the
// content. If the top controls have no height, the content should scroll
// immediately.
gfx::Vector2dF applied_delta(0.f, old_top_offset - ContentTopOffset());
return pending_delta - applied_delta;
}
void BrowserControlsOffsetManager::ScrollEnd() {
if (pinch_gesture_active_)
return;
// See if we should animate the top bar in, in case there was a race between
// chrome showing the controls and the user performing a scroll. We only need
// to animate the top control if it's not fully shown.
if (show_controls_when_scroll_completes_ && TopControlsShownRatio() != 1.f) {
SetupAnimation(AnimationDirection::kShowingControls);
return;
}
StartAnimationIfNecessary();
}
void BrowserControlsOffsetManager::PinchBegin() {
DCHECK(!pinch_gesture_active_);
pinch_gesture_active_ = true;
StartAnimationIfNecessary();
}
void BrowserControlsOffsetManager::PinchEnd() {
DCHECK(pinch_gesture_active_);
// Pinch{Begin,End} will always occur within the scope of Scroll{Begin,End},
// so return to a state expected by the remaining scroll sequence.
pinch_gesture_active_ = false;
ScrollBegin();
}
gfx::Vector2dF BrowserControlsOffsetManager::Animate(
base::TimeTicks monotonic_time) {
if (!HasAnimation() || !client_->HaveRootScrollNode())
return gfx::Vector2dF();
float old_top_offset = ContentTopOffset();
float old_bottom_offset = ContentBottomOffset();
std::optional<float> new_top_ratio =
top_controls_animation_.Tick(monotonic_time);
if (!new_top_ratio.has_value())
new_top_ratio = TopControlsShownRatio();
std::optional<float> new_bottom_ratio =
bottom_controls_animation_.Tick(monotonic_time);
if (!new_bottom_ratio.has_value())
new_bottom_ratio = BottomControlsShownRatio();
client_->SetCurrentBrowserControlsShownRatio(new_top_ratio.value(),
new_bottom_ratio.value());
float top_offset_delta = ContentTopOffset() - old_top_offset;
float bottom_offset_delta = ContentBottomOffset() - old_bottom_offset;
if (top_min_height_change_in_progress_) {
// The change in top offset may be larger than the min-height, resulting in
// too low or too high |top_controls_min_height_offset_| values. So, we
// should clamp it to a valid range.
top_controls_min_height_offset_ =
std::clamp(top_controls_min_height_offset_ + top_offset_delta,
top_min_height_offset_animation_range_->first,
top_min_height_offset_animation_range_->second);
// Ticking the animation might reset it if it's at the final value.
top_min_height_change_in_progress_ =
top_controls_animation_.IsInitialized();
}
if (bottom_min_height_change_in_progress_) {
// The change in bottom offset may be larger than the min-height, resulting
// in too low or too high |bottom_controls_min_height_offset_| values. So,
// we should clamp it to a valid range.
bottom_controls_min_height_offset_ =
std::clamp(bottom_controls_min_height_offset_ + bottom_offset_delta,
bottom_min_height_offset_animation_range_->first,
bottom_min_height_offset_animation_range_->second);
// Ticking the animation might reset it if it's at the final value.
bottom_min_height_change_in_progress_ =
bottom_controls_animation_.IsInitialized();
// When shrinking the bottom controls, there may be a remaining offset
// mistake if the min height was decreased by more than the height was. This
// can be fixed by simply "resetting" the offset to the final minHeight
// value at the end of the animation. This only applies to shrinking
// animations, since this adjustment happens at the beginning for growing
// animations. This is done to avoid the bottom controls "lagging behind"
// the changes to the web content and exposing a blank space right above the
// bottom controls.
if (!bottom_min_height_change_in_progress_) {
bottom_controls_min_height_offset_ = BottomControlsMinHeight();
}
}
gfx::Vector2dF scroll_delta(0.f, top_offset_delta);
return scroll_delta;
}
bool BrowserControlsOffsetManager::HasAnimation() {
return top_controls_animation_.IsInitialized() ||
bottom_controls_animation_.IsInitialized();
}
void BrowserControlsOffsetManager::ResetAnimations() {
// If the animation doesn't need to jump to the end, Animation::Reset() will
// return |std::nullopt|.
std::optional<float> top_ratio = top_controls_animation_.Reset();
std::optional<float> bottom_ratio = bottom_controls_animation_.Reset();
if (top_ratio.has_value() || bottom_ratio.has_value()) {
client_->SetCurrentBrowserControlsShownRatio(
top_ratio.has_value() ? top_ratio.value() : TopControlsShownRatio(),
bottom_ratio.has_value() ? bottom_ratio.value()
: BottomControlsShownRatio());
if (top_min_height_change_in_progress_) {
DCHECK(top_ratio.has_value());
top_controls_min_height_offset_ = TopControlsMinHeight();
}
if (bottom_min_height_change_in_progress_) {
DCHECK(bottom_ratio.has_value());
bottom_controls_min_height_offset_ = BottomControlsMinHeight();
}
}
top_min_height_change_in_progress_ = false;
bottom_min_height_change_in_progress_ = false;
top_min_height_offset_animation_range_.reset();
bottom_min_height_offset_animation_range_.reset();
}
void BrowserControlsOffsetManager::SetupAnimation(
AnimationDirection direction) {
DCHECK_NE(AnimationDirection::kNoAnimation, direction);
DCHECK(direction != AnimationDirection::kHidingControls ||
TopControlsShownRatio() > 0.f);
DCHECK(direction != AnimationDirection::kShowingControls ||
TopControlsShownRatio() < 1.f);
if (top_controls_animation_.IsInitialized() &&
top_controls_animation_.Direction() == direction &&
bottom_controls_animation_.IsInitialized() &&
bottom_controls_animation_.Direction() == direction) {
return;
}
if (!TopControlsHeight() && !BottomControlsHeight()) {
float ratio = direction == AnimationDirection::kHidingControls ? 0.f : 1.f;
client_->SetCurrentBrowserControlsShownRatio(ratio, ratio);
return;
}
// Providing artificially larger/smaller stop ratios to make the animation
// faster if the start ratio is closer to stop ratio.
const float max_stop_ratio =
direction == AnimationDirection::kShowingControls ? 1 : -1;
float top_start_ratio = TopControlsShownRatio();
float top_stop_ratio = top_start_ratio + max_stop_ratio;
top_controls_animation_.Initialize(direction, top_start_ratio, top_stop_ratio,
kShowHideMaxDurationMs,
/*jump_to_end_on_reset=*/false);
top_controls_animation_.SetBounds(TopControlsMinShownRatio(), 1.f);
float bottom_start_ratio = BottomControlsShownRatio();
float bottom_stop_ratio = bottom_start_ratio + max_stop_ratio;
bottom_controls_animation_.Initialize(
direction, bottom_start_ratio, bottom_stop_ratio, kShowHideMaxDurationMs,
/*jump_to_end_on_reset=*/false);
bottom_controls_animation_.SetBounds(BottomControlsMinShownRatio(), 1.f);
client_->DidChangeBrowserControlsPosition();
}
void BrowserControlsOffsetManager::StartAnimationIfNecessary() {
if ((TopControlsShownRatio() == TopControlsMinShownRatio() ||
TopControlsShownRatio() == 1.f) &&
(BottomControlsShownRatio() == BottomControlsMinShownRatio() ||
BottomControlsShownRatio() == 1.f))
return;
float normalized_top_ratio =
(TopControlsShownRatio() - TopControlsMinShownRatio()) /
(1.f - TopControlsMinShownRatio());
if (normalized_top_ratio >= 1.f - controls_hide_threshold_) {
// If we're showing so much that the hide threshold won't trigger, show.
SetupAnimation(AnimationDirection::kShowingControls);
} else if (normalized_top_ratio <= controls_show_threshold_) {
// If we're showing so little that the show threshold won't trigger, hide.
SetupAnimation(AnimationDirection::kHidingControls);
} 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.
SetupAnimation(accumulated_scroll_delta_ <= 0.f
? AnimationDirection::kShowingControls
: AnimationDirection::kHidingControls);
}
}
void BrowserControlsOffsetManager::ResetBaseline() {
accumulated_scroll_delta_ = 0.f;
baseline_top_content_offset_ = ContentTopOffset();
baseline_bottom_content_offset_ = ContentBottomOffset();
}
void BrowserControlsOffsetManager::InitAnimationForHeightChange(
Animation* animation,
float start_ratio,
float stop_ratio) {
AnimationDirection direction = start_ratio < stop_ratio
? AnimationDirection::kShowingControls
: AnimationDirection::kHidingControls;
animation->Initialize(direction, start_ratio, stop_ratio,
kHeightChangeDurationMs, /*jump_to_end_on_reset=*/true);
}
float BrowserControlsOffsetManager::OldTopControlsMinShownRatio() {
return old_browser_controls_params_.top_controls_height
? old_browser_controls_params_.top_controls_min_height /
old_browser_controls_params_.top_controls_height
: 0.f;
}
float BrowserControlsOffsetManager::OldBottomControlsMinShownRatio() {
return old_browser_controls_params_.bottom_controls_height
? old_browser_controls_params_.bottom_controls_min_height /
old_browser_controls_params_.bottom_controls_height
: 0.f;
}
void BrowserControlsOffsetManager::UpdateOldBrowserControlsParams() {
// No need to update the other two bool members as they aren't useful for this
// class.
old_browser_controls_params_.top_controls_height = TopControlsHeight();
old_browser_controls_params_.top_controls_min_height = TopControlsMinHeight();
old_browser_controls_params_.bottom_controls_height = BottomControlsHeight();
old_browser_controls_params_.bottom_controls_min_height =
BottomControlsMinHeight();
}
void BrowserControlsOffsetManager::SetTopMinHeightOffsetAnimationRange(
float from,
float to) {
top_min_height_offset_animation_range_ =
std::make_pair(std::min(from, to), std::max(from, to));
}
void BrowserControlsOffsetManager::SetBottomMinHeightOffsetAnimationRange(
float from,
float to) {
bottom_min_height_offset_animation_range_ =
std::make_pair(std::min(from, to), std::max(from, to));
}
double BrowserControlsOffsetManager::PredictViewportBoundsDelta(
double current_bounds_delta,
gfx::Vector2dF scroll_distance) {
double adjustment = current_bounds_delta;
if (scroll_distance.y() > 0 && adjustment > 0) {
// We're scrolling down and started to hide controls. Let's assume they're
// going to be fully hidden by the end of the fling.
if (TopControlsShownRatio() < 1) {
adjustment += ContentTopOffset();
}
if (BottomControlsShownRatio() < 1) {
adjustment += ContentBottomOffset();
}
}
if (scroll_distance.y() < 0 && adjustment < 0) {
// We're scrolling up and started to show controls. Let's assume they're
// going to be fully shown by the end of the fling.
if (TopControlsShownRatio() > 0) {
adjustment -= TopControlsHeight() - ContentTopOffset();
}
if (BottomControlsShownRatio() > 0) {
adjustment -= BottomControlsHeight() - ContentBottomOffset();
}
}
return adjustment;
}
// class Animation
BrowserControlsOffsetManager::Animation::Animation() {}
void BrowserControlsOffsetManager::Animation::Initialize(
AnimationDirection direction,
float start_value,
float stop_value,
int64_t duration,
bool jump_to_end_on_reset) {
direction_ = direction;
start_value_ = start_value;
stop_value_ = stop_value;
duration_ = base::Milliseconds(duration);
initialized_ = true;
jump_to_end_on_reset_ = jump_to_end_on_reset;
SetBounds(std::min(start_value_, stop_value_),
std::max(start_value_, stop_value_));
}
std::optional<float> BrowserControlsOffsetManager::Animation::Tick(
base::TimeTicks monotonic_time) {
if (!IsInitialized())
return std::nullopt;
if (!started_) {
start_time_ = monotonic_time;
stop_time_ = start_time_ + duration_;
started_ = true;
}
float value = gfx::Tween::ClampedFloatValueBetween(
monotonic_time, start_time_, start_value_, stop_time_, stop_value_);
if (IsComplete(value)) {
value = FinalValue();
Reset();
}
return value;
}
void BrowserControlsOffsetManager::Animation::SetBounds(float min, float max) {
min_value_ = min;
max_value_ = max;
}
std::optional<float> BrowserControlsOffsetManager::Animation::Reset() {
auto ret =
jump_to_end_on_reset_
? std::make_optional(std::clamp(stop_value_, min_value_, max_value_))
: std::nullopt;
started_ = false;
initialized_ = false;
start_time_ = base::TimeTicks();
start_value_ = 0.f;
stop_time_ = base::TimeTicks();
stop_value_ = 0.f;
direction_ = AnimationDirection::kNoAnimation;
duration_ = base::TimeDelta();
min_value_ = 0.f;
max_value_ = 1.f;
jump_to_end_on_reset_ = false;
return ret;
}
bool BrowserControlsOffsetManager::Animation::IsComplete(float value) {
return (direction_ == AnimationDirection::kShowingControls &&
(value >= stop_value_ || value >= max_value_)) ||
(direction_ == AnimationDirection::kHidingControls &&
(value <= stop_value_ || value <= min_value_));
}
float BrowserControlsOffsetManager::Animation::FinalValue() {
return std::clamp(stop_value_, min_value_, max_value_);
}
} // namespace cc