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
|
// 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.
#ifndef CC_INPUT_SCROLLBAR_CONTROLLER_H_
#define CC_INPUT_SCROLLBAR_CONTROLLER_H_
#include <memory>
#include <optional>
#include "base/cancelable_callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "cc/cc_export.h"
#include "cc/input/input_handler.h"
#include "cc/input/scrollbar.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/painted_scrollbar_layer_impl.h"
// High level documentation:
// https://source.chromium.org/chromium/chromium/src/+/main:cc/input/README.md
// Click scrolling.
// - A click is considered as a kMouseDown and a kMouseUp in quick succession.
// Every click on a composited non-custom arrow leads to 3 GestureEvents in
// total.
// - GSB and GSU on get queued in the CTEQ on mousedown and a GSE on mouseup.
// - The delta scrolled is constant at 40px (scaled by the device_scale_factor)
// for scrollbar arrows and a function of the viewport length in the case of
// track autoscroll.
// Thumb dragging.
// - The sequence of events in the CTEQ would be something like GSB, GSU, GSU,
// GSU..., GSE
// - On every pointermove, the scroll delta is determined is as current pointer
// position - the point at which we got the initial mousedown.
// - The delta is then scaled by the scroller to scrollbar ratio so that
// dragging the thumb moves the scroller proportionately.
// - This ratio is calculated as:
// (scroll_layer_length - viewport_length) /
// (scrollbar_track_length - scrollbar_thumb_length)
// - On pointerup, the GSE clears state as mentioned above.
// VSync aligned autoscroll.
// - Autoscroll is implemented as a "scroll animation" which has a linear timing
// function (see cc::LinearTimingFunction) and a curve with a constant velocity.
// - The main thread does autoscrolling by pumping events at 50ms interval. To
// have a similar kind of behaviour on the compositor thread, the autoscroll
// velocity is set to 800px per second for scrollbar arrows.
// - For track autoscrolling however, the velocity is a function of the viewport
// length.
// - Based on this velocity, an autoscroll curve is created.
// - An autoscroll animation is set up. (via
// LayerTreeHostImpl::ScrollAnimationCreateInternal) on the the known
// scroll_node and the scroller starts animation when the pointer is held.
// Nuances:
// Thumb snapping.
// - During a thumb drag, if a pointer moves too far away from the scrollbar
// track, the thumb is supposed to snap back to it original place (i.e to the
// point before the thumb drag started).
// - This is done by having an imaginary no_snap_rect around the scrollbar
// track. This extends about 8 times the width of the track on either side. When
// a manipulation is in progress, the mouse is expected to stay within the
// bounds of this rect. Assuming a standard scrollbar, 17px wide, this is how
// it'd look like.
// https://github.com/rahul8805/CompositorThreadedScrollbarDocs/blob/master/snap.PNG?raw=true
// - When a pointerdown is received, record the original offset of the thumb.
// - On every pointermove, check if the pointer is within the bounds of the
// no_snap_rect. If false, snap to the initial_scroll_offset and stop processing
// pointermove(s) until the pointer reenters the bounds of the rect.
// - The moment the mouse re-enters the bounds of the no_snap_rect, we snap to
// the initial_scroll_offset + event.PositionInWidget.
// Thumb anchoring.
// - During a thumb drag, if the pointer runs off the track, there should be no
// additional scrolling until the pointer reenters the track and crosses the
// original mousedown point.
// - This is done by sending "clamped" deltas. The amount of scrollable delta is
// computed using LayerTreeHostImpl::ComputeScrollDelta.
// - Since the deltas are clamped, overscroll doesn't occur if it can't be
// consumed by the CurrentlyScrollingNode.
// Autoscroll play/pause.
// - When the pointer moves in and out of bounds of a scrollbar part that can
// initiate autoscrolls (like arrows or track), the autoscroll animation is
// expected to play or pause accordingly.
// - On every ScrollbarController::WillBeginMainFrame, the pointer location is
// constantly checked and if it is outside the bounds of the scrollbar part that
// initiated the autoscroll, the autoscroll is stopped.
// - Similarly, when the pointer reenters the bounds, autoscroll is restarted
// again. All the vital information during autoscrolling such the velocity,
// direction, scroll layer length etc is held in
// cc::ScrollbarController::AutoscrollState.
// Shift + click.
// - Doing a shift click on any part of a scrollbar track is supposed to do an
// instant scroll to that location (such that the thumb is still centered on the
// pointer).
// - When the MouseEvent reaches the
// InputHandlerProxy::RouteToTypeSpecificHandler, if the event is found to have
// a "Shift" modifier, the ScrollbarController calculates the offset based on
// the pointers current location on the track.
// - Once the offset is determined, the InputHandlerProxy creates a GSU with
// state that tells the LayerTreeHostImpl to perform a non-animated scroll to
// the offset.
// Continuous autoscrolling.
// - This builds on top of the autoscolling implementation. "Continuous"
// autoscrolling is when an autoscroll is in progress and the size of the
// content keeps increasing. For eg: When you keep the down arrow pressed on
// websites like Facebook, the autoscrolling is expected to keep on going until
// the mouse is released.
// - This is implemented by monitoring the length of the scroller layer at every
// frame and if the length increases (and if autoscroll in the forward direction
// is already in progress), the old animation is aborted and a new autoscroll
// animation with the new scroller length is kicked off.
namespace cc {
class LayerTreeHostImpl;
// This class is responsible for hit testing composited scrollbars, event
// handling and creating gesture scroll deltas.
class CC_EXPORT ScrollbarController {
public:
explicit ScrollbarController(LayerTreeHostImpl*);
virtual ~ScrollbarController();
// On Mac, the "jump to the spot that's clicked" setting can be dynamically
// set via System Preferences. When enabled, the expectation is that regular
// clicks on the scrollbar should make the scroller "jump" to the clicked
// location rather than animated scrolling. Additionally, when this is enabled
// and the user does an Option + click on the scrollbar, the scroller should
// *not* jump to that spot (i.e it should be treated as a regular track
// click). When this setting is disabled on the Mac, Option + click should
// make the scroller jump and a regular click should animate the scroll
// offset. On all other platforms, the "jump on click" option is available
// (via Shift + click) but is not configurable.
InputHandlerPointerResult HandlePointerDown(
const gfx::PointF position_in_widget,
const bool jump_key_modifier);
InputHandlerPointerResult HandlePointerMove(
const gfx::PointF position_in_widget);
InputHandlerPointerResult HandlePointerUp(
const gfx::PointF position_in_widget);
bool AutoscrollTaskIsScheduled() const {
return cancelable_autoscroll_task_ != nullptr;
}
bool ScrollbarScrollIsActive() const { return scrollbar_scroll_is_active_; }
void DidRegisterScrollbar(ElementId element_id,
ScrollbarOrientation orientation);
void DidUnregisterScrollbar(ElementId element_id,
ScrollbarOrientation orientation);
ScrollbarLayerImplBase* ScrollbarLayer() const;
void WillBeginImplFrame();
void ResetState();
const ScrollbarLayerImplBase* HitTest(
const gfx::PointF position_in_widget) const;
private:
FRIEND_TEST_ALL_PREFIXES(LayerTreeHostImplTest, ThumbDragAfterJumpClick);
FRIEND_TEST_ALL_PREFIXES(LayerTreeHostImplTest,
AbortAnimatedScrollBeforeStartingAutoscroll);
FRIEND_TEST_ALL_PREFIXES(LayerTreeHostImplTest, AutoscrollOnDeletedScrollbar);
FRIEND_TEST_ALL_PREFIXES(LayerTreeHostImplTest, ScrollOnLargeThumb);
// "Autoscroll" here means the continuous scrolling that occurs when the
// pointer is held down on a hit-testable area of the scrollbar such as an
// arrows of the track itself.
enum class AutoScrollDirection { kAutoscrollForward, kAutoscrollBackward };
enum class AutoScrollStatus {
// For when the 250ms delay before an autoscroll starts animating has not
// yet elapsed
kAutoscrollWaiting,
// For when the delay has elapsed, but the autoscroll cannot animate for
// some reason (the scrollbar being unregistered)
kAutoscrollReady,
// For when the autoscroll is animating
kAutoscrollScrolling
};
struct CC_EXPORT AutoScrollState {
// Can only be either kAutoscrollForward or kAutoscrollBackward.
AutoScrollDirection direction = AutoScrollDirection::kAutoscrollForward;
AutoScrollStatus status = AutoScrollStatus::kAutoscrollWaiting;
// Stores the autoscroll velocity. The sign is used to set the "direction".
float velocity = 0.f;
// Used to track the scroller length while autoscrolling. Helpful for
// setting up infinite scrolling.
float scroll_layer_length = 0.f;
// Used to lookup the rect corresponding to the ScrollbarPart so that
// autoscroll animations can be played/paused depending on the current
// pointer location.
ScrollbarPart pressed_scrollbar_part;
};
struct CC_EXPORT DragState {
// This marks the point at which the drag initiated (relative to the
// scrollbar layer).
gfx::PointF drag_origin;
// This is needed for thumb snapping when the pointer moves too far away
// from the track while scrolling.
float scroll_position_at_start_;
// The |scroller_displacement| indicates the scroll offset compensation that
// needs to be applied when the scroller's length changes dynamically mid
// thumb drag. This is needed done to ensure that the scroller does not jump
// while a thumb drag is in progress.
float scroller_displacement;
float scroller_length_at_previous_move;
};
struct CC_EXPORT CapturedScrollbarMetadata {
// Needed to retrieve the ScrollbarSet for a particular ElementId.
ElementId scroll_element_id;
// Needed to identify the correct scrollbar from the ScrollbarSet.
ScrollbarOrientation orientation;
};
// Posts an autoscroll task based on the autoscroll state, with the given
// delay
void PostAutoscrollTask(const base::TimeDelta delay);
// Initiates an autoscroll, setting the necessary status and starting the
// animation, if possible
void StartAutoScroll();
// Starts/restarts an autoscroll animation based off of the information in
// autoscroll_state_
void StartAutoScrollAnimation();
// Returns the DSF based on whether use-zoom-for-dsf is enabled.
float ScreenSpaceScaleFactor() const;
// Helper to convert scroll offset to autoscroll velocity.
float InitialDeltaToAutoscrollVelocity(gfx::Vector2dF scroll_delta) const;
// Returns the hit tested ScrollbarPart based on the position_in_widget.
ScrollbarPart GetScrollbarPartFromPointerDown(
const gfx::PointF position_in_widget) const;
// Clamps |scroll_delta| based on the available scrollable amount of
// |target_node|. The returned delta includes the page scale factor and is
// appropriate for use directly as a delta for GSU.
gfx::Vector2dF ComputeClampedDelta(const ScrollNode& target_node,
const gfx::Vector2dF& scroll_delta) const;
// Returns the rect for the ScrollbarPart.
gfx::Rect GetRectForScrollbarPart(const ScrollbarPart scrollbar_part) const;
LayerImpl* GetLayerHitByPoint(const gfx::PointF position_in_widget) const;
// Returns scroll delta as Vector2dF based on which ScrollbarPart was hit
// tested.
gfx::Vector2dF GetScrollDeltaForScrollbarPart(
const ScrollbarPart scrollbar_part,
const bool jump_key_modifier) const;
// Returns scroll delta in the direction of the scrollbar's orientation.
float GetScrollDistanceForScrollbarPart(const ScrollbarPart scrollbar_part,
const bool jump_key_modifier) const;
// Makes position_in_widget relative to the scrollbar.
gfx::PointF GetScrollbarRelativePosition(const gfx::PointF position_in_widget,
bool* clipped) const;
// Computes an aritificial drag origin for jump clicks, to give the scrollbar
// a proper place to snap back to on a jump click then drag
gfx::PointF DragOriginForJumpClick(
const ScrollbarLayerImplBase* scrollbar) const;
// Decides if the scroller should snap to the offset that it was
// originally at (i.e the offset before the thumb drag).
bool SnapToDragOrigin(const gfx::PointF pointer_position_in_widget) const;
// Decides whether a track autoscroll should be aborted (or restarted) due to
// the thumb reaching the pointer or the pointer leaving (or re-entering) the
// bounds.
void RecomputeAutoscrollStateIfNeeded();
// Shift (or "Option" in case of Mac) + click is expected to do a non-animated
// jump to a certain offset.
float GetScrollDistanceForAbsoluteJump() const;
// Determines if the delta needs to be animated.
ui::ScrollGranularity Granularity(const ScrollbarPart scrollbar_part,
bool jump_key_modifier) const;
// Calculates the distance based on position_in_widget and drag_origin.
float GetScrollDistanceForDragPosition(
const gfx::PointF pointer_position_in_widget) const;
// Returns the ratio of the scroller length to the scrollbar length. This is
// needed to scale the scroll delta for thumb drag.
float GetScrollerToScrollbarRatio() const;
float GetViewportLength() const;
// Returns the page scale factor (i.e. pinch zoom factor). This is relevant
// for root viewport scrollbar scrolling.
float GetPageScaleFactorForScroll() const;
raw_ptr<LayerTreeHostImpl> layer_tree_host_impl_;
// Used to safeguard against firing GSE without firing GSB and GSU. For
// example, if mouse is pressed outside the scrollbar but released after
// moving inside the scrollbar, a GSE will get queued up without this flag.
bool scrollbar_scroll_is_active_;
// This is relative to the RenderWidget's origin.
gfx::PointF last_known_pointer_position_;
// Set only while interacting with the scrollbar (eg: drag, click etc).
std::optional<CapturedScrollbarMetadata> captured_scrollbar_metadata_;
// Holds information pertaining to autoscrolling. This member is empty if and
// only if an autoscroll is *not* in progress or scheduled
std::optional<AutoScrollState> autoscroll_state_;
// Holds information pertaining to thumb drags. Useful while making decisions
// about thumb anchoring/snapping.
std::optional<DragState> drag_state_;
// Used to track if a GSU was processed for the current frame or not. Without
// this, thumb drag will appear jittery. The reason this happens is because
// when the first GSU is processed, it gets queued in the compositor thread
// event queue. So a second request within the same frame will end up
// calculating an incorrect delta (as ComputeThumbQuadRect would not have
// accounted for the delta in the first GSU that was not yet dispatched and
// pointermoves are not VSync aligned).
bool drag_processed_for_current_frame_;
std::unique_ptr<base::CancelableOnceClosure> cancelable_autoscroll_task_;
};
} // namespace cc
#endif // CC_INPUT_SCROLLBAR_CONTROLLER_H_
|