blob: 7cd1f44f02af041174ba20cdd27e9bfbc5e8cc7a [file] [log] [blame]
// Copyright 2019 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/layers/scrollbar_layer_impl_base.h"
#include <algorithm>
#include "base/cancelable_callback.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;
}
PointerResultType 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 PointerResultType::kUnhandled;
// If the scrollbar layer has faded out (eg: Overlay scrollbars), don't
// initiate a scroll.
const ScrollbarLayerImplBase* scrollbar = ToScrollbarLayer(layer_impl);
if (scrollbar->OverlayScrollbarOpacity() == 0.f)
return PointerResultType::kUnhandled;
// If the scroll_node has a main_thread_scrolling_reason, don't initiate a
// scroll.
const ScrollNode* target_node =
layer_tree_host_impl_->active_tree()
->property_trees()
->scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id());
if (target_node->main_thread_scrolling_reasons)
return PointerResultType::kUnhandled;
return PointerResultType::kScrollbarScroll;
}
// 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) {
if (HitTest(position_in_widget) != PointerResultType::kScrollbarScroll)
return InputHandlerPointerResult();
// TODO(arakeri): GetLayerHitByPoint should ideally be called only once per
// pointerdown. This needs to be optimized. See crbug.com/1156922.
const ScrollbarLayerImplBase* scrollbar =
ToScrollbarLayer(GetLayerHitByPoint(position_in_widget));
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;
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
const ScrollbarPart scrollbar_part =
GetScrollbarPartFromPointerDown(position_in_widget);
const bool perform_jump_click_on_track =
scrollbar->JumpOnTrackClick() != jump_key_modifier;
scroll_result.scroll_offset = GetScrollOffsetForScrollbarPart(
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::THUMB || perform_jump_click_on_track) {
drag_state_ = DragState();
bool clipped = false;
drag_state_->drag_origin =
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. Additionally, if a thumb drag is
// being initiated *after* a jump click, scroll_position_at_start_ needs
// to account for that.
const float jump_click_thumb_drag_offset =
scrollbar->orientation() == ScrollbarOrientation::HORIZONTAL
? scroll_result.scroll_offset.x()
: scroll_result.scroll_offset.y();
drag_state_->scroll_position_at_start_ =
scrollbar->current_pos() +
(perform_jump_click_on_track ? jump_click_thumb_drag_offset : 0);
drag_state_->scroller_length_at_previous_move =
scrollbar->scroll_layer_length();
}
if (!scroll_result.scroll_offset.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::THUMB);
cancelable_autoscroll_task_ =
std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
&ScrollbarController::StartAutoScrollAnimation,
base::Unretained(this),
InitialDeltaToAutoscrollVelocity(scroll_result.scroll_offset),
scrollbar_part));
layer_tree_host_impl_->GetTaskRunner()->PostDelayedTask(
FROM_HERE, cancelable_autoscroll_task_->callback(),
kInitialAutoscrollTimerDelay);
}
return scroll_result;
}
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;
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
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::VERTICAL
? 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::VERTICAL
? (forward_track_rect.x() - gutter_thickness)
: (forward_track_rect.y() - gutter_thickness);
const float gutter_max_bound =
orientation == ScrollbarOrientation::VERTICAL
? (forward_track_rect.x() + track_thickness + gutter_thickness)
: (forward_track_rect.y() + track_thickness + gutter_thickness);
const float pointer_location = orientation == ScrollbarOrientation::VERTICAL
? 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::FORWARD_TRACK ||
scrollbar_part == ScrollbarPart::BACK_TRACK);
if (shift_click_on_scrollbar_track || scrollbar_part == ScrollbarPart::THUMB)
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::GetScrollDeltaForAbsoluteJump() const {
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
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::VERTICAL
? 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::VERTICAL
? thumb_rect.y()
: thumb_rect.x();
const float delta =
round(std::abs(desired_thumb_origin - current_thumb_origin));
return delta * GetScrollerToScrollbarRatio() * GetPageScaleFactorForScroll();
}
float ScrollbarController::GetScrollDeltaForDragPosition(
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::VERTICAL
? 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 scroll_delta = 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.
scroll_delta *= GetPageScaleFactorForScroll();
return scroll_delta;
}
// 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_offset =
scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? gfx::ScrollOffset(0, -delta)
: gfx::ScrollOffset(-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 delta = GetScrollDeltaForDragPosition(position_in_widget);
if (drag_state_->scroller_length_at_previous_move !=
scrollbar->scroll_layer_length()) {
drag_state_->scroller_displacement = delta;
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;
}
delta -= drag_state_->scroller_displacement;
// If scroll_offset can't be consumed, there's no point in continuing on.
const gfx::ScrollOffset scroll_offset(scrollbar->orientation() ==
ScrollbarOrientation::VERTICAL
? gfx::ScrollOffset(0, delta)
: gfx::ScrollOffset(delta, 0));
const gfx::Vector2dF clamped_scroll_offset =
ComputeClampedDelta(*target_node, ScrollOffsetToVector2dF(scroll_offset));
if (clamped_scroll_offset.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_offset = gfx::ScrollOffset(clamped_scroll_offset);
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 ->|
//
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
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::VERTICAL
? thumb_rect.height()
: thumb_rect.width();
float viewport_length = GetViewportLength();
return (scroll_layer_length - viewport_length) /
(scrollbar_track_length - scrollbar_thumb_length);
}
void ScrollbarController::ResetState() {
drag_processed_for_current_frame_ = false;
drag_state_ = base::nullopt;
autoscroll_state_ = base::nullopt;
captured_scrollbar_metadata_ = base::nullopt;
if (cancelable_autoscroll_task_) {
cancelable_autoscroll_task_->Cancel();
cancelable_autoscroll_task_.reset();
}
}
void ScrollbarController::DidUnregisterScrollbar(
ElementId element_id,
ScrollbarOrientation orientation) {
if (captured_scrollbar_metadata_.has_value() &&
captured_scrollbar_metadata_->scroll_element_id == element_id &&
captured_scrollbar_metadata_->orientation == orientation)
ResetState();
}
void ScrollbarController::RecomputeAutoscrollStateIfNeeded() {
if (!autoscroll_state_.has_value() ||
!captured_scrollbar_metadata_.has_value())
return;
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
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::VERTICAL) {
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::AUTOSCROLL_FORWARD &&
thumb_end > pointer_position) ||
(autoscroll_state_->direction ==
AutoScrollDirection::AUTOSCROLL_BACKWARD &&
thumb_start < pointer_position))
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort();
// 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::AUTOSCROLL_FORWARD) {
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();
StartAutoScrollAnimation(autoscroll_state_->velocity,
autoscroll_state_->pressed_scrollbar_part);
}
}
// 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();
} 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(autoscroll_state_->velocity,
autoscroll_state_->pressed_scrollbar_part);
}
}
// Helper to calculate the autoscroll velocity.
float ScrollbarController::InitialDeltaToAutoscrollVelocity(
gfx::ScrollOffset scroll_offset) const {
DCHECK(captured_scrollbar_metadata_.has_value());
const float scroll_delta =
ScrollbarLayer()->orientation() == ScrollbarOrientation::VERTICAL
? scroll_offset.y()
: scroll_offset.x();
return scroll_delta * kAutoscrollMultiplier;
}
void ScrollbarController::StartAutoScrollAnimation(
const float velocity,
ScrollbarPart pressed_scrollbar_part) {
// 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_NE(velocity, 0);
DCHECK(ScrollbarLayer());
// 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();
ScrollTree& scroll_tree =
layer_tree_host_impl_->active_tree()->property_trees()->scroll_tree;
ScrollNode* scroll_node =
scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id());
if (!(scroll_node && scrollbar_scroll_is_active_))
return;
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
float scroll_layer_length = scrollbar->scroll_layer_length();
gfx::ScrollOffset 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 = velocity < 0 ? 0 : scroll_layer_length;
const gfx::Vector2dF target_offset_vector =
scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? gfx::Vector2dF(current_offset.x(), target_offset)
: gfx::Vector2dF(target_offset, current_offset.y());
autoscroll_state_ = AutoScrollState();
autoscroll_state_->velocity = velocity;
autoscroll_state_->scroll_layer_length = scroll_layer_length;
autoscroll_state_->pressed_scrollbar_part = pressed_scrollbar_part;
autoscroll_state_->direction = velocity < 0
? AutoScrollDirection::AUTOSCROLL_BACKWARD
: AutoScrollDirection::AUTOSCROLL_FORWARD;
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort();
layer_tree_host_impl_->AutoScrollAnimationCreate(
*scroll_node, target_offset_vector, std::abs(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())
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort();
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::VERTICAL
? 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::VERTICAL
? viewport_size.height()
: viewport_size.width();
return length / GetPageScaleFactorForScroll();
}
float ScrollbarController::GetScrollDeltaForPercentBasedScroll() 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);
const gfx::Vector2dF scroll_delta =
scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? gfx::Vector2dF(0, kPercentDeltaForDirectionalScroll)
: gfx::Vector2dF(kPercentDeltaForDirectionalScroll, 0);
const gfx::Vector2dF pixel_delta =
layer_tree_host_impl_->GetInputHandler().ResolveScrollGranularityToPixels(
*scroll_node, scroll_delta,
ui::ScrollGranularity::kScrollByPercentage);
return scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? pixel_delta.y()
: pixel_delta.x();
}
float ScrollbarController::GetPageScaleFactorForScroll() const {
return layer_tree_host_impl_->active_tree()->page_scale_factor_for_scroll();
}
float ScrollbarController::GetScrollDeltaForScrollbarPart(
const ScrollbarPart scrollbar_part,
const bool jump_key_modifier) const {
float scroll_delta = 0;
switch (scrollbar_part) {
case ScrollbarPart::BACK_BUTTON:
case ScrollbarPart::FORWARD_BUTTON:
if (layer_tree_host_impl_->settings().percent_based_scrolling) {
scroll_delta = GetScrollDeltaForPercentBasedScroll();
} else {
scroll_delta = kPixelsPerLineStep * ScreenSpaceScaleFactor();
}
break;
case ScrollbarPart::BACK_TRACK:
case ScrollbarPart::FORWARD_TRACK: {
if (jump_key_modifier) {
scroll_delta = GetScrollDeltaForAbsoluteJump();
break;
}
// TODO(savella) Use snapport length instead of viewport length to match
// main thread behaviour. See https://crbug.com/1098383.
scroll_delta = GetViewportLength() * kMinFractionToStepWhenPaging;
break;
}
default:
scroll_delta = 0;
}
return scroll_delta;
}
float ScrollbarController::ScreenSpaceScaleFactor() const {
// TODO(arakeri): When crbug.com/716231 is fixed, this needs to be updated.
// If use_zoom_for_dsf is false, the click deltas and thumb drag ratios
// shouldn't be scaled. For example: On Mac, when the use_zoom_for_dsf is
// false and the device_scale_factor is 2, the scroll delta for pointer clicks
// on arrows would be incorrectly calculated as 80px instead of 40px. This is
// also necessary to ensure that hit testing works as intended.
return layer_tree_host_impl_->settings().use_zoom_for_dsf
? layer_tree_host_impl_->active_tree()
->painted_device_scale_factor()
: 1.f;
}
gfx::PointF ScrollbarController::GetScrollbarRelativePosition(
const gfx::PointF position_in_widget,
bool* clipped) const {
gfx::Transform inverse_screen_space_transform(
gfx::Transform::kSkipInitialization);
// If use_zoom_for_dsf is false, the ScreenSpaceTransform needs to be scaled
// down by the DSF to ensure that position_in_widget is transformed correctly.
const float scale =
!layer_tree_host_impl_->settings().use_zoom_for_dsf
? 1.f / layer_tree_host_impl_->active_tree()->device_scale_factor()
: 1.f;
gfx::Transform scaled_screen_space_transform(
ScrollbarLayer()->ScreenSpaceTransform());
scaled_screen_space_transform.PostScale(scale, scale);
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::NO_PART;
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::BACK_BUTTON)
return scrollbar->BackButtonRect();
if (scrollbar_part == ScrollbarPart::FORWARD_BUTTON)
return scrollbar->ForwardButtonRect();
if (scrollbar_part == ScrollbarPart::BACK_TRACK)
return scrollbar->BackTrackRect();
if (scrollbar_part == ScrollbarPart::FORWARD_TRACK)
return scrollbar->ForwardTrackRect();
return gfx::Rect(0, 0);
}
// Determines the scroll offsets based on the ScrollbarPart and the scrollbar
// orientation.
gfx::ScrollOffset ScrollbarController::GetScrollOffsetForScrollbarPart(
const ScrollbarPart scrollbar_part,
const bool jump_key_modifier) const {
const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer();
float scroll_delta =
GetScrollDeltaForScrollbarPart(scrollbar_part, jump_key_modifier);
// See CreateScrollStateForGesture for more information on how these values
// will be interpreted.
if (scrollbar_part == ScrollbarPart::BACK_BUTTON) {
return scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? gfx::ScrollOffset(0, -scroll_delta) // Up arrow
: gfx::ScrollOffset(-scroll_delta, 0); // Left arrow
} else if (scrollbar_part == ScrollbarPart::FORWARD_BUTTON) {
return scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? gfx::ScrollOffset(0, scroll_delta) // Down arrow
: gfx::ScrollOffset(scroll_delta, 0); // Right arrow
} else if (scrollbar_part == ScrollbarPart::BACK_TRACK) {
return scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? gfx::ScrollOffset(0, -scroll_delta) // Track click up
: gfx::ScrollOffset(-scroll_delta, 0); // Track click left
} else if (scrollbar_part == ScrollbarPart::FORWARD_TRACK) {
return scrollbar->orientation() == ScrollbarOrientation::VERTICAL
? gfx::ScrollOffset(0, scroll_delta) // Track click down
: gfx::ScrollOffset(scroll_delta, 0); // Track click right
}
return gfx::ScrollOffset(0, 0);
}
} // namespace cc