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
|
// Copyright 2019 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/layers/scrollbar_layer_impl_base.h"
#include <algorithm>
#include "base/cancelable_callback.h"
#include "cc/base/features.h"
#include "cc/base/math_util.h"
#include "cc/input/scroll_utils.h"
#include "cc/input/scrollbar.h"
#include "cc/input/scrollbar_controller.h"
#include "cc/layers/viewport.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/scroll_node.h"
namespace cc {
ScrollbarController::~ScrollbarController() {
if (cancelable_autoscroll_task_) {
cancelable_autoscroll_task_->Cancel();
cancelable_autoscroll_task_.reset();
}
}
ScrollbarController::ScrollbarController(
LayerTreeHostImpl* layer_tree_host_impl)
: layer_tree_host_impl_(layer_tree_host_impl),
scrollbar_scroll_is_active_(false),
last_known_pointer_position_(gfx::PointF(0, 0)),
drag_processed_for_current_frame_(false),
cancelable_autoscroll_task_(nullptr) {}
void ScrollbarController::WillBeginImplFrame() {
drag_processed_for_current_frame_ = false;
RecomputeAutoscrollStateIfNeeded();
}
// Retrieves the ScrollbarLayerImplBase corresponding to the stashed ElementId.
ScrollbarLayerImplBase* ScrollbarController::ScrollbarLayer() const {
if (!captured_scrollbar_metadata_.has_value())
return nullptr;
const ScrollbarSet scrollbars = layer_tree_host_impl_->ScrollbarsFor(
captured_scrollbar_metadata_->scroll_element_id);
for (ScrollbarLayerImplBase* scrollbar : scrollbars) {
if (captured_scrollbar_metadata_->orientation == scrollbar->orientation())
return scrollbar;
}
return nullptr;
}
const ScrollbarLayerImplBase* ScrollbarController::HitTest(
const gfx::PointF position_in_widget) const {
// If a non-custom scrollbar layer was not found, we return early as there is
// no point in setting additional state in the ScrollbarController. Return an
// empty InputHandlerPointerResult in this case so that when it is bubbled up
// to InputHandlerProxy::RouteToTypeSpecificHandler, the pointer event gets
// passed on to the main thread.
const LayerImpl* layer_impl = GetLayerHitByPoint(position_in_widget);
if (!(layer_impl && layer_impl->IsScrollbarLayer()))
return nullptr;
// If the scrollbar layer has faded out (eg: Overlay scrollbars), don't
// initiate a scroll.
const ScrollbarLayerImplBase* scrollbar = ToScrollbarLayer(layer_impl);
return scrollbar->OverlayScrollbarOpacity() == 0.f ? nullptr : scrollbar;
}
// Performs hit test and prepares scroll deltas that will be used by GSB and
// GSU.
InputHandlerPointerResult ScrollbarController::HandlePointerDown(
const gfx::PointF position_in_widget,
bool jump_key_modifier) {
const ScrollbarLayerImplBase* scrollbar = HitTest(position_in_widget);
if (!scrollbar) {
return InputHandlerPointerResult();
}
captured_scrollbar_metadata_ = CapturedScrollbarMetadata();
captured_scrollbar_metadata_->scroll_element_id =
scrollbar->scroll_element_id();
captured_scrollbar_metadata_->orientation = scrollbar->orientation();
InputHandlerPointerResult scroll_result;
scroll_result.target_scroller = scrollbar->scroll_element_id();
scroll_result.type = PointerResultType::kScrollbarScroll;
const ScrollbarPart scrollbar_part =
GetScrollbarPartFromPointerDown(position_in_widget);
const bool perform_jump_click_on_track =
scrollbar->JumpOnTrackClick() != jump_key_modifier;
scroll_result.scroll_delta = GetScrollDeltaForScrollbarPart(
scrollbar_part, perform_jump_click_on_track);
last_known_pointer_position_ = position_in_widget;
scrollbar_scroll_is_active_ = true;
scroll_result.scroll_units =
Granularity(scrollbar_part, perform_jump_click_on_track);
// Initialize drag state if either the scrollbar thumb is being dragged OR the
// user has initiated a jump click (since the thumb would have jumped under
// the pointer).
if (scrollbar_part == ScrollbarPart::kThumb || perform_jump_click_on_track) {
drag_state_ = DragState();
bool clipped = false;
drag_state_->drag_origin =
perform_jump_click_on_track
? DragOriginForJumpClick(scrollbar)
: GetScrollbarRelativePosition(position_in_widget, &clipped);
// If the point were clipped we shouldn't have hit tested to a valid
// part.
DCHECK(!clipped);
// Record the current scroller offset. This will be needed to snap the
// thumb back to its original position if the pointer moves too far away
// from the track during a thumb drag.
drag_state_->scroll_position_at_start_ = scrollbar->current_pos();
drag_state_->scroller_length_at_previous_move =
scrollbar->scroll_layer_length();
}
if (!scroll_result.scroll_delta.IsZero() && !perform_jump_click_on_track) {
// Thumb drag is the only scrollbar manipulation that cannot produce an
// autoscroll. All other interactions like clicking on arrows/trackparts
// have the potential of initiating an autoscroll (if held down for long
// enough).
DCHECK(scrollbar_part != ScrollbarPart::kThumb);
autoscroll_state_ = AutoScrollState();
autoscroll_state_->velocity =
InitialDeltaToAutoscrollVelocity(scroll_result.scroll_delta);
autoscroll_state_->pressed_scrollbar_part = scrollbar_part;
PostAutoscrollTask(kInitialAutoscrollTimerDelay);
}
return scroll_result;
}
void ScrollbarController::PostAutoscrollTask(const base::TimeDelta delay) {
cancelable_autoscroll_task_ =
std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
&ScrollbarController::StartAutoScroll, base::Unretained(this)));
layer_tree_host_impl_->GetTaskRunner()->PostDelayedTask(
FROM_HERE, cancelable_autoscroll_task_->callback(), delay);
}
gfx::PointF ScrollbarController::DragOriginForJumpClick(
const ScrollbarLayerImplBase* scrollbar) const {
// If the user initiated a jump click, create an artificial drag origin to the
// middle of the thumb's current position. This is to simulate a jump click as
// though the user had initiated a drag normally and pulled the thumb down to
// the jump click position. This also keeps consistency with
// scroll_position_at_start_ which is used both to calculate scroll positions
// as well as for snapping back to origin if the user moves their mouse away.
gfx::Rect thumb = scrollbar->ComputeThumbQuadRect();
return scrollbar->orientation() == ScrollbarOrientation::kHorizontal
? gfx::PointF(thumb.x() + thumb.width() / 2, 0)
: gfx::PointF(0, thumb.y() + thumb.height() / 2);
}
bool ScrollbarController::SnapToDragOrigin(
const gfx::PointF pointer_position_in_widget) const {
// Consult the ScrollbarTheme to check if thumb snapping is supported on the
// current platform.
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
if (!(scrollbar && scrollbar->SupportsDragSnapBack()))
return false;
bool clipped = false;
const gfx::PointF pointer_position_in_layer =
GetScrollbarRelativePosition(pointer_position_in_widget, &clipped);
if (clipped)
return false;
const ScrollbarOrientation orientation = scrollbar->orientation();
const gfx::Rect forward_track_rect = scrollbar->ForwardTrackRect();
// When dragging the thumb, there needs to exist "gutters" on either side of
// the track. The thickness of these gutters is a multiple of the track (or
// thumb) thickness. As long as the pointer remains within the bounds of these
// gutters in the non-scrolling direction, thumb drag proceeds as expected.
// The moment the pointer moves outside the bounds, the scroller needs to snap
// back to the drag_origin (aka the scroll offset of the parent scroller
// before the thumb drag initiated).
int track_thickness = orientation == ScrollbarOrientation::kVertical
? forward_track_rect.width()
: forward_track_rect.height();
if (!track_thickness) {
// For overlay scrollbars (or for tests that do not set up a track
// thickness), use the thumb_thickness instead to determine the gutters.
const int thumb_thickness = scrollbar->ThumbThickness();
// If the thumb doesn't have thickness, the gutters can't be determined.
// Snapping shouldn't occur in this case.
if (!thumb_thickness)
return false;
track_thickness = thumb_thickness;
}
const float gutter_thickness = kOffSideMultiplier * track_thickness;
const float gutter_min_bound =
orientation == ScrollbarOrientation::kVertical
? (forward_track_rect.x() - gutter_thickness)
: (forward_track_rect.y() - gutter_thickness);
const float gutter_max_bound =
orientation == ScrollbarOrientation::kVertical
? (forward_track_rect.x() + track_thickness + gutter_thickness)
: (forward_track_rect.y() + track_thickness + gutter_thickness);
const float pointer_location = orientation == ScrollbarOrientation::kVertical
? pointer_position_in_layer.x()
: pointer_position_in_layer.y();
return pointer_location < gutter_min_bound ||
pointer_location > gutter_max_bound;
}
ui::ScrollGranularity ScrollbarController::Granularity(
const ScrollbarPart scrollbar_part,
const bool jump_key_modifier) const {
const bool shift_click_on_scrollbar_track =
jump_key_modifier && (scrollbar_part == ScrollbarPart::kForwardTrack ||
scrollbar_part == ScrollbarPart::kBackTrack);
if (shift_click_on_scrollbar_track ||
scrollbar_part == ScrollbarPart::kThumb) {
return ui::ScrollGranularity::kScrollByPrecisePixel;
}
// TODO(arakeri): This needs to be updated to kLine once cc implements
// handling it. crbug.com/959441
return ui::ScrollGranularity::kScrollByPixel;
}
float ScrollbarController::GetScrollDistanceForAbsoluteJump() const {
bool clipped = false;
const gfx::PointF pointer_position_in_layer =
GetScrollbarRelativePosition(last_known_pointer_position_, &clipped);
if (clipped)
return 0;
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
const float pointer_location =
scrollbar->orientation() == ScrollbarOrientation::kVertical
? pointer_position_in_layer.y()
: pointer_position_in_layer.x();
// During a shift + click, the pointers current location (on the track) needs
// to be considered as the center of the thumb and the thumb origin needs to
// be calculated based on that. This will ensure that when shift + click is
// processed, the thumb will be centered on the pointer.
const int thumb_length = scrollbar->ThumbLength();
const float desired_thumb_origin = pointer_location - thumb_length / 2.f;
const gfx::Rect thumb_rect(scrollbar->ComputeThumbQuadRect());
const float current_thumb_origin =
scrollbar->orientation() == ScrollbarOrientation::kVertical
? thumb_rect.y()
: thumb_rect.x();
const float distance =
round(std::abs(desired_thumb_origin - current_thumb_origin));
return distance * GetScrollerToScrollbarRatio() *
GetPageScaleFactorForScroll();
}
float ScrollbarController::GetScrollDistanceForDragPosition(
const gfx::PointF pointer_position_in_widget) const {
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
// Convert the move position to scrollbar layer relative for comparison with
// |drag_state_| drag_origin. Ignore clipping as if we're within the region
// that doesn't cause snapping back, we do want the delta in the appropriate
// dimension to cause a scroll.
bool clipped = false;
const gfx::PointF scrollbar_relative_position(
GetScrollbarRelativePosition(pointer_position_in_widget, &clipped));
float pointer_delta =
scrollbar->orientation() == ScrollbarOrientation::kVertical
? scrollbar_relative_position.y() - drag_state_->drag_origin.y()
: scrollbar_relative_position.x() - drag_state_->drag_origin.x();
const float new_offset = pointer_delta * GetScrollerToScrollbarRatio();
float distance = drag_state_->scroll_position_at_start_ + new_offset -
scrollbar->current_pos();
// The scroll delta computed is layer relative. In order to scroll the
// correct amount, we have to convert the delta to be unscaled (i.e. multiply
// by the page scale factor), as GSU deltas are always unscaled.
distance *= GetPageScaleFactorForScroll();
return distance;
}
// Performs hit test and prepares scroll deltas that will be used by GSU.
InputHandlerPointerResult ScrollbarController::HandlePointerMove(
const gfx::PointF position_in_widget) {
last_known_pointer_position_ = position_in_widget;
RecomputeAutoscrollStateIfNeeded();
InputHandlerPointerResult scroll_result;
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
if (!scrollbar || !drag_state_.has_value())
return scroll_result;
// If the scrollbar thumb is being dragged, it qualifies as a kScrollbarScroll
// (although the delta might still be zero). Setting the "type" to
// kScrollbarScroll ensures that the correct event modifier (in
// InputHandlerProxy) is set which in-turn tells the main thread to invalidate
// the respective scrollbar parts. This needs to be done for all
// pointermove(s) since they are not VSync aligned.
scroll_result.type = PointerResultType::kScrollbarScroll;
// If a GSU was already produced for a thumb drag in this frame, there's no
// point in continuing on. Please see the header file for details.
if (drag_processed_for_current_frame_)
return scroll_result;
if (SnapToDragOrigin(position_in_widget)) {
const float delta =
scrollbar->current_pos() - drag_state_->scroll_position_at_start_;
scroll_result.scroll_units = ui::ScrollGranularity::kScrollByPrecisePixel;
scroll_result.scroll_delta =
scrollbar->orientation() == ScrollbarOrientation::kVertical
? gfx::Vector2dF(0, -delta)
: gfx::Vector2dF(-delta, 0);
drag_processed_for_current_frame_ = true;
return scroll_result;
}
// When initiating a thumb drag, a pointerdown and a pointermove can both
// arrive a the ScrollbarController in succession before a GSB would have
// been dispatched. So, querying LayerTreeHostImpl::CurrentlyScrollingNode()
// can potentially be null. Hence, a better way to look the target_node to be
// scrolled is by using ScrollbarLayerImplBase::scroll_element_id().
const ScrollNode* target_node =
layer_tree_host_impl_->active_tree()
->property_trees()
->scroll_tree()
.FindNodeFromElementId(scrollbar->scroll_element_id());
// If a scrollbar exists, it should always have an ElementId pointing to a
// valid ScrollNode.
DCHECK(target_node);
float distance = GetScrollDistanceForDragPosition(position_in_widget);
if (drag_state_->scroller_length_at_previous_move !=
scrollbar->scroll_layer_length()) {
drag_state_->scroller_displacement = distance;
drag_state_->scroller_length_at_previous_move =
scrollbar->scroll_layer_length();
// This is done to ensure that, when the scroller length changes mid thumb
// drag, the scroller shouldn't jump. We early out because the delta would
// be zero in this case anyway (since drag_state_->scroller_displacement =
// delta). So that means, in the worst case you'd miss 1 GSU every time the
// scroller expands while a thumb drag is in progress.
return scroll_result;
}
distance -= drag_state_->scroller_displacement;
// If scroll_offset can't be consumed, there's no point in continuing on.
const gfx::Vector2dF scroll_delta =
scrollbar->orientation() == ScrollbarOrientation::kVertical
? gfx::Vector2dF(0, distance)
: gfx::Vector2dF(distance, 0);
const gfx::Vector2dF clamped_scroll_delta =
ComputeClampedDelta(*target_node, scroll_delta);
if (clamped_scroll_delta.IsZero())
return scroll_result;
// Thumb drags have more granularity and are purely dependent on the pointer
// movement. Hence we use kPrecisePixel when dragging the thumb.
scroll_result.scroll_units = ui::ScrollGranularity::kScrollByPrecisePixel;
scroll_result.scroll_delta = clamped_scroll_delta;
drag_processed_for_current_frame_ = true;
return scroll_result;
}
gfx::Vector2dF ScrollbarController::ComputeClampedDelta(
const ScrollNode& target_node,
const gfx::Vector2dF& scroll_delta) const {
DCHECK(!target_node.scrolls_inner_viewport);
if (target_node.scrolls_outer_viewport)
return layer_tree_host_impl_->viewport().ComputeClampedDelta(scroll_delta);
// ComputeScrollDelta returns a delta accounting for the current page zoom
// level. Since we're producing a delta for an injected GSU, we need to get
// back to and unscaled delta (i.e. multiply by the page scale factor).
gfx::Vector2dF clamped_delta =
layer_tree_host_impl_->GetInputHandler().ComputeScrollDelta(target_node,
scroll_delta);
const float scale_factor = GetPageScaleFactorForScroll();
clamped_delta.Scale(scale_factor);
return clamped_delta;
}
float ScrollbarController::GetScrollerToScrollbarRatio() const {
// Calculating the delta by which the scroller layer should move when
// dragging the thumb depends on the following factors:
// - scrollbar_track_length
// - scrollbar_thumb_length
// - scroll_layer_length
// - viewport_length
// - position_in_widget
//
// When a thumb drag is in progress, for every pixel that the pointer moves,
// the delta for the corresponding scroll_layer needs to be scaled by the
// following ratio:
// scaled_scroller_to_scrollbar_ratio =
// (scroll_layer_length - viewport_length) /
// (scrollbar_track_length - scrollbar_thumb_length)
//
// PS: Note that since this is a "ratio", it need not be scaled by the DSF.
//
// |<--------------------- scroll_layer_length -------------------------->|
//
// +------------------------------------------------+......................
// | | .
// |<-------------- viewport_length --------------->| .
// | | .
// | | .
// | | .
// | | .
// | | .
// | | .
// | | .
// | | .
// | | .
// | |<------- scrollbar_track_length --------->| | .
// | | .
// +--+-----+----------------------------+-------+--+......................
// |<|| |############################| ||>|
// +--+-----+----------------------------+-------+--+
//
// |<- scrollbar_thumb_length ->|
//
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
float scroll_layer_length = scrollbar->scroll_layer_length();
float scrollbar_track_length = scrollbar->TrackLength();
gfx::Rect thumb_rect(scrollbar->ComputeThumbQuadRect());
float scrollbar_thumb_length =
scrollbar->orientation() == ScrollbarOrientation::kVertical
? thumb_rect.height()
: thumb_rect.width();
float viewport_length = GetViewportLength();
if (scrollbar_track_length == scrollbar_thumb_length)
return 0;
return (scroll_layer_length - viewport_length) /
(scrollbar_track_length - scrollbar_thumb_length);
}
void ScrollbarController::ResetState() {
drag_processed_for_current_frame_ = false;
drag_state_ = std::nullopt;
autoscroll_state_ = std::nullopt;
captured_scrollbar_metadata_ = std::nullopt;
if (cancelable_autoscroll_task_) {
cancelable_autoscroll_task_->Cancel();
cancelable_autoscroll_task_.reset();
}
}
void ScrollbarController::DidRegisterScrollbar(
ElementId element_id,
ScrollbarOrientation orientation) {
if (autoscroll_state_.has_value() &&
captured_scrollbar_metadata_->scroll_element_id == element_id &&
captured_scrollbar_metadata_->orientation == orientation &&
autoscroll_state_->status == AutoScrollStatus::kAutoscrollReady) {
// This is necessary, as when the scrollbar is being registered the layer
// tree will not yet have synced its layer properties and cannot update
// scrollbar geometries yet. We need to wait until the sync is over
PostAutoscrollTask(base::TimeDelta::Min());
}
}
void ScrollbarController::DidUnregisterScrollbar(
ElementId element_id,
ScrollbarOrientation orientation) {
if (autoscroll_state_.has_value() &&
captured_scrollbar_metadata_->scroll_element_id == element_id &&
captured_scrollbar_metadata_->orientation == orientation &&
autoscroll_state_->status == AutoScrollStatus::kAutoscrollScrolling) {
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(element_id);
autoscroll_state_->status = AutoScrollStatus::kAutoscrollReady;
}
}
void ScrollbarController::RecomputeAutoscrollStateIfNeeded() {
if (!autoscroll_state_.has_value() ||
!captured_scrollbar_metadata_.has_value() ||
autoscroll_state_->status != AutoScrollStatus::kAutoscrollScrolling) {
return;
}
bool clipped;
gfx::PointF scroller_relative_position(
GetScrollbarRelativePosition(last_known_pointer_position_, &clipped));
if (clipped)
return;
// Based on the orientation of the scrollbar and the direction of the
// autoscroll, the code below makes a decision of whether the track autoscroll
// should be canceled or not.
int thumb_start = 0;
int thumb_end = 0;
int pointer_position = 0;
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
const gfx::Rect thumb_quad = scrollbar->ComputeThumbQuadRect();
if (scrollbar->orientation() == ScrollbarOrientation::kVertical) {
thumb_start = thumb_quad.y();
thumb_end = thumb_quad.y() + thumb_quad.height();
pointer_position = scroller_relative_position.y();
} else {
thumb_start = thumb_quad.x();
thumb_end = thumb_quad.x() + thumb_quad.width();
pointer_position = scroller_relative_position.x();
}
// If the thumb reaches the pointer while autoscrolling, abort.
if ((autoscroll_state_->direction ==
AutoScrollDirection::kAutoscrollForward &&
thumb_end > pointer_position) ||
(autoscroll_state_->direction ==
AutoScrollDirection::kAutoscrollBackward &&
thumb_start < pointer_position)) {
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(
captured_scrollbar_metadata_->scroll_element_id);
}
// When the scroller is autoscrolling forward, its dimensions need to be
// monitored. If the length of the scroller layer increases, the old one needs
// to be aborted and a new autoscroll animation needs to start. This needs to
// be done only for the "autoscroll forward" case. Autoscrolling backward
// always has a constant value to animate to (which is '0'. See the function
// ScrollbarController::StartAutoScrollAnimation).
if (autoscroll_state_->direction == AutoScrollDirection::kAutoscrollForward) {
const float scroll_layer_length = scrollbar->scroll_layer_length();
if (autoscroll_state_->scroll_layer_length != scroll_layer_length) {
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(
scrollbar->scroll_element_id());
StartAutoScrollAnimation();
}
}
// The animations need to be aborted/restarted based on the pointer location
// (i.e leaving/entering the track/arrows, reaching the track end etc). The
// autoscroll_state_ however, needs to be reset on pointer changes.
const gfx::RectF scrollbar_part_rect(
GetRectForScrollbarPart(autoscroll_state_->pressed_scrollbar_part));
if (!scrollbar_part_rect.Contains(scroller_relative_position)) {
// Stop animating if pointer moves outside the rect bounds.
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(
scrollbar->scroll_element_id());
} else if (scrollbar_part_rect.Contains(scroller_relative_position) &&
!layer_tree_host_impl_->mutator_host()->IsElementAnimating(
scrollbar->scroll_element_id())) {
// Start animating if pointer re-enters the bounds.
StartAutoScrollAnimation();
}
}
// Helper to calculate the autoscroll velocity.
float ScrollbarController::InitialDeltaToAutoscrollVelocity(
gfx::Vector2dF scroll_delta) const {
DCHECK(captured_scrollbar_metadata_.has_value());
const float delta =
ScrollbarLayer()->orientation() == ScrollbarOrientation::kVertical
? scroll_delta.y()
: scroll_delta.x();
return delta * kAutoscrollMultiplier;
}
void ScrollbarController::StartAutoScroll() {
DCHECK(autoscroll_state_.has_value());
if (ScrollbarLayer()) {
autoscroll_state_->status = AutoScrollStatus::kAutoscrollScrolling;
StartAutoScrollAnimation();
} else {
autoscroll_state_->status = AutoScrollStatus::kAutoscrollReady;
}
}
void ScrollbarController::StartAutoScrollAnimation() {
// Autoscroll and thumb drag are mutually exclusive. Both can't be active at
// the same time.
DCHECK(!drag_state_.has_value());
DCHECK(captured_scrollbar_metadata_.has_value());
DCHECK(autoscroll_state_.has_value());
DCHECK(ScrollbarLayer());
DCHECK_EQ(autoscroll_state_->status, AutoScrollStatus::kAutoscrollScrolling);
DCHECK_NE(autoscroll_state_->velocity, 0);
// scroll_node is set up while handling GSB. If there's no node to scroll, we
// don't need to create any animation for it.
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
const ScrollTree& scroll_tree =
layer_tree_host_impl_->active_tree()->property_trees()->scroll_tree();
const ScrollNode* scroll_node =
scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id());
if (!(scroll_node && scrollbar_scroll_is_active_))
return;
float scroll_layer_length = scrollbar->scroll_layer_length();
gfx::PointF current_offset =
scroll_tree.current_scroll_offset(scroll_node->element_id);
// Determine the max offset for the scroll based on the scrolling direction.
// Negative scroll velocity indicates backwards scrolling whereas a positive
// value indicates forwards scrolling.
const float target_offset_in_orientation =
autoscroll_state_->velocity < 0 ? 0 : scroll_layer_length;
const gfx::PointF target_offset_2d =
scrollbar->orientation() == ScrollbarOrientation::kVertical
? gfx::PointF(current_offset.x(), target_offset_in_orientation)
: gfx::PointF(target_offset_in_orientation, current_offset.y());
autoscroll_state_->scroll_layer_length = scroll_layer_length;
autoscroll_state_->direction = autoscroll_state_->velocity < 0
? AutoScrollDirection::kAutoscrollBackward
: AutoScrollDirection::kAutoscrollForward;
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(
scroll_node->element_id);
layer_tree_host_impl_->AutoScrollAnimationCreate(
*scroll_node, target_offset_2d, std::abs(autoscroll_state_->velocity));
}
// Performs hit test and prepares scroll deltas that will be used by GSE.
InputHandlerPointerResult ScrollbarController::HandlePointerUp(
const gfx::PointF position_in_widget) {
InputHandlerPointerResult scroll_result;
if (scrollbar_scroll_is_active_) {
scrollbar_scroll_is_active_ = false;
scroll_result.type = PointerResultType::kScrollbarScroll;
}
// TODO(arakeri): This needs to be moved to ScrollOffsetAnimationsImpl as it
// has knowledge about what type of animation is running. crbug.com/976353
// Only abort the animation if it is an "autoscroll" animation.
if (autoscroll_state_.has_value() &&
autoscroll_state_->status == AutoScrollStatus::kAutoscrollScrolling) {
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(
captured_scrollbar_metadata_->scroll_element_id);
}
ResetState();
return scroll_result;
}
// Returns the layer that is hit by the position_in_widget.
LayerImpl* ScrollbarController::GetLayerHitByPoint(
const gfx::PointF position_in_widget) const {
LayerTreeImpl* active_tree = layer_tree_host_impl_->active_tree();
gfx::Point viewport_point(position_in_widget.x(), position_in_widget.y());
gfx::PointF device_viewport_point = gfx::ScalePoint(
gfx::PointF(viewport_point), active_tree->device_scale_factor());
LayerImpl* layer_impl =
active_tree->FindLayerThatIsHitByPoint(device_viewport_point);
return layer_impl;
}
float ScrollbarController::GetViewportLength() const {
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
const ScrollNode* scroll_node =
layer_tree_host_impl_->active_tree()
->property_trees()
->scroll_tree()
.FindNodeFromElementId(scrollbar->scroll_element_id());
DCHECK(scroll_node);
if (!scroll_node->scrolls_outer_viewport) {
float length = scrollbar->orientation() == ScrollbarOrientation::kVertical
? scroll_node->container_bounds.height()
: scroll_node->container_bounds.width();
return length;
}
gfx::SizeF viewport_size = layer_tree_host_impl_->viewport()
.GetInnerViewportSizeExcludingScrollbars();
float length = scrollbar->orientation() == ScrollbarOrientation::kVertical
? viewport_size.height()
: viewport_size.width();
return length / GetPageScaleFactorForScroll();
}
float ScrollbarController::GetPageScaleFactorForScroll() const {
return layer_tree_host_impl_->active_tree()->page_scale_factor_for_scroll();
}
float ScrollbarController::GetScrollDistanceForScrollbarPart(
const ScrollbarPart scrollbar_part,
const bool jump_key_modifier) const {
float scroll_delta = 0;
switch (scrollbar_part) {
case ScrollbarPart::kBackButton:
case ScrollbarPart::kForwardButton:
scroll_delta = kPixelsPerLineStep * ScreenSpaceScaleFactor();
break;
case ScrollbarPart::kBackTrack:
case ScrollbarPart::kForwardTrack: {
if (jump_key_modifier) {
scroll_delta = GetScrollDistanceForAbsoluteJump();
break;
}
int snapport_length = GetViewportLength();
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
const ScrollNode* target_node =
layer_tree_host_impl_->active_tree()
->property_trees()
->scroll_tree()
.FindNodeFromElementId(scrollbar->scroll_element_id());
if (target_node->snap_container_data) {
const gfx::RectF snapport =
target_node->snap_container_data.value().rect();
snapport_length =
scrollbar->orientation() == ScrollbarOrientation::kHorizontal
? snapport.x()
: snapport.y();
}
scroll_delta = ScrollUtils::CalculatePageStep(snapport_length);
break;
}
default:
scroll_delta = 0;
}
return scroll_delta;
}
float ScrollbarController::ScreenSpaceScaleFactor() const {
return layer_tree_host_impl_->active_tree()->painted_device_scale_factor();
}
gfx::PointF ScrollbarController::GetScrollbarRelativePosition(
const gfx::PointF position_in_widget,
bool* clipped) const {
// This is a speculative fix for crbug.com/1254865. On Mac, we occasionally
// enter into a state where a scrollbar layer becomes null mid interaction. If
// this happens, early out.
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
if (!scrollbar) {
// Set clipped to true so that GetScrollbarPartFromPointerDown returns early
// without trying to find a ScrollbarPart (since it wouldn't matter anyway).
*clipped = true;
return gfx::PointF(0, 0);
}
gfx::Transform inverse_screen_space_transform;
gfx::Transform scaled_screen_space_transform(
scrollbar->ScreenSpaceTransform());
if (!scaled_screen_space_transform.GetInverse(
&inverse_screen_space_transform))
return gfx::PointF(0, 0);
return gfx::PointF(MathUtil::ProjectPoint(inverse_screen_space_transform,
position_in_widget, clipped));
}
// Determines the ScrollbarPart based on the position_in_widget.
ScrollbarPart ScrollbarController::GetScrollbarPartFromPointerDown(
const gfx::PointF position_in_widget) const {
// position_in_widget needs to be transformed and made relative to the
// scrollbar layer because hit testing assumes layer relative coordinates.
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
bool clipped = false;
const gfx::PointF scroller_relative_position(
GetScrollbarRelativePosition(position_in_widget, &clipped));
if (clipped)
return ScrollbarPart::kNoPart;
return scrollbar->IdentifyScrollbarPart(scroller_relative_position);
}
// Determines the corresponding rect for the given scrollbar part.
gfx::Rect ScrollbarController::GetRectForScrollbarPart(
const ScrollbarPart scrollbar_part) const {
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
if (scrollbar_part == ScrollbarPart::kBackButton) {
return scrollbar->BackButtonRect();
}
if (scrollbar_part == ScrollbarPart::kForwardButton) {
return scrollbar->ForwardButtonRect();
}
if (scrollbar_part == ScrollbarPart::kBackTrack) {
return scrollbar->BackTrackRect();
}
if (scrollbar_part == ScrollbarPart::kForwardTrack) {
return scrollbar->ForwardTrackRect();
}
return gfx::Rect(0, 0);
}
// Determines the scroll delta as a gfx::Vector2dF based on the ScrollbarPart
// and the scrollbar orientation.
gfx::Vector2dF ScrollbarController::GetScrollDeltaForScrollbarPart(
const ScrollbarPart scrollbar_part,
const bool jump_key_modifier) const {
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
float distance =
GetScrollDistanceForScrollbarPart(scrollbar_part, jump_key_modifier);
// See CreateScrollStateForGesture for more information on how these values
// will be interpreted.
if (scrollbar_part == ScrollbarPart::kBackButton) {
return scrollbar->orientation() == ScrollbarOrientation::kVertical
? gfx::Vector2dF(0, -distance) // Up arrow
: gfx::Vector2dF(-distance, 0); // Left arrow
} else if (scrollbar_part == ScrollbarPart::kForwardButton) {
return scrollbar->orientation() == ScrollbarOrientation::kVertical
? gfx::Vector2dF(0, distance) // Down arrow
: gfx::Vector2dF(distance, 0); // Right arrow
} else if (scrollbar_part == ScrollbarPart::kBackTrack) {
return scrollbar->orientation() == ScrollbarOrientation::kVertical
? gfx::Vector2dF(0, -distance) // Track click up
: gfx::Vector2dF(-distance, 0); // Track click left
} else if (scrollbar_part == ScrollbarPart::kForwardTrack) {
return scrollbar->orientation() == ScrollbarOrientation::kVertical
? gfx::Vector2dF(0, distance) // Track click down
: gfx::Vector2dF(distance, 0); // Track click right
}
return gfx::Vector2dF(0, 0);
}
} // namespace cc
|