scrollbar_controller.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #ifndef CC_INPUT_SCROLLBAR_CONTROLLER_H_
  5. #define CC_INPUT_SCROLLBAR_CONTROLLER_H_
  6. #include <memory>
  7. #include "base/cancelable_callback.h"
  8. #include "base/gtest_prod_util.h"
  9. #include "base/memory/raw_ptr.h"
  10. #include "base/time/time.h"
  11. #include "cc/cc_export.h"
  12. #include "cc/input/input_handler.h"
  13. #include "cc/input/scrollbar.h"
  14. #include "cc/layers/layer_impl.h"
  15. #include "cc/layers/painted_scrollbar_layer_impl.h"
  16. #include "third_party/abseil-cpp/absl/types/optional.h"
  17. // High level documentation:
  18. // https://source.chromium.org/chromium/chromium/src/+/main:cc/input/README.md
  19. // Click scrolling.
  20. // - A click is considered as a kMouseDown and a kMouseUp in quick succession.
  21. // Every click on a composited non-custom arrow leads to 3 GestureEvents in
  22. // total.
  23. // - GSB and GSU on get queued in the CTEQ on mousedown and a GSE on mouseup.
  24. // - The delta scrolled is constant at 40px (scaled by the device_scale_factor)
  25. // for scrollbar arrows and a function of the viewport length in the case of
  26. // track autoscroll.
  27. // Thumb dragging.
  28. // - The sequence of events in the CTEQ would be something like GSB, GSU, GSU,
  29. // GSU..., GSE
  30. // - On every pointermove, the scroll delta is determined is as current pointer
  31. // position - the point at which we got the initial mousedown.
  32. // - The delta is then scaled by the scroller to scrollbar ratio so that
  33. // dragging the thumb moves the scroller proportionately.
  34. // - This ratio is calculated as:
  35. // (scroll_layer_length - viewport_length) /
  36. // (scrollbar_track_length - scrollbar_thumb_length)
  37. // - On pointerup, the GSE clears state as mentioned above.
  38. // VSync aligned autoscroll.
  39. // - Autoscroll is implemented as a "scroll animation" which has a linear timing
  40. // function (see cc::LinearTimingFunction) and a curve with a constant velocity.
  41. // - The main thread does autoscrolling by pumping events at 50ms interval. To
  42. // have a similar kind of behaviour on the compositor thread, the autoscroll
  43. // velocity is set to 800px per second for scrollbar arrows.
  44. // - For track autoscrolling however, the velocity is a function of the viewport
  45. // length.
  46. // - Based on this velocity, an autoscroll curve is created.
  47. // - An autoscroll animation is set up. (via
  48. // LayerTreeHostImpl::ScrollAnimationCreateInternal) on the the known
  49. // scroll_node and the scroller starts animation when the pointer is held.
  50. // Nuances:
  51. // Thumb snapping.
  52. // - During a thumb drag, if a pointer moves too far away from the scrollbar
  53. // track, the thumb is supposed to snap back to it original place (i.e to the
  54. // point before the thumb drag started).
  55. // - This is done by having an imaginary no_snap_rect around the scrollbar
  56. // track. This extends about 8 times the width of the track on either side. When
  57. // a manipulation is in progress, the mouse is expected to stay within the
  58. // bounds of this rect. Assuming a standard scrollbar, 17px wide, this is how
  59. // it'd look like.
  60. // https://github.com/rahul8805/CompositorThreadedScrollbarDocs/blob/master/snap.PNG?raw=true
  61. // - When a pointerdown is received, record the original offset of the thumb.
  62. // - On every pointermove, check if the pointer is within the bounds of the
  63. // no_snap_rect. If false, snap to the initial_scroll_offset and stop processing
  64. // pointermove(s) until the pointer reenters the bounds of the rect.
  65. // - The moment the mouse re-enters the bounds of the no_snap_rect, we snap to
  66. // the initial_scroll_offset + event.PositionInWidget.
  67. // Thumb anchoring.
  68. // - During a thumb drag, if the pointer runs off the track, there should be no
  69. // additional scrolling until the pointer reenters the track and crosses the
  70. // original mousedown point.
  71. // - This is done by sending "clamped" deltas. The amount of scrollable delta is
  72. // computed using LayerTreeHostImpl::ComputeScrollDelta.
  73. // - Since the deltas are clamped, overscroll doesn't occur if it can't be
  74. // consumed by the CurrentlyScrollingNode.
  75. // Autoscroll play/pause.
  76. // - When the pointer moves in and out of bounds of a scrollbar part that can
  77. // initiate autoscrolls (like arrows or track), the autoscroll animation is
  78. // expected to play or pause accordingly.
  79. // - On every ScrollbarController::WillBeginMainFrame, the pointer location is
  80. // constantly checked and if it is outside the bounds of the scrollbar part that
  81. // initiated the autoscroll, the autoscroll is stopped.
  82. // - Similarly, when the pointer reenters the bounds, autoscroll is restarted
  83. // again. All the vital information during autoscrolling such the velocity,
  84. // direction, scroll layer length etc is held in
  85. // cc::ScrollbarController::AutoscrollState.
  86. // Shift + click.
  87. // - Doing a shift click on any part of a scrollbar track is supposed to do an
  88. // instant scroll to that location (such that the thumb is still centered on the
  89. // pointer).
  90. // - When the MouseEvent reaches the
  91. // InputHandlerProxy::RouteToTypeSpecificHandler, if the event is found to have
  92. // a "Shift" modifier, the ScrollbarController calculates the offset based on
  93. // the pointers current location on the track.
  94. // - Once the offset is determined, the InputHandlerProxy creates a GSU with
  95. // state that tells the LayerTreeHostImpl to perform a non-animated scroll to
  96. // the offset.
  97. // Continuous autoscrolling.
  98. // - This builds on top of the autoscolling implementation. "Continuous"
  99. // autoscrolling is when an autoscroll is in progress and the size of the
  100. // content keeps increasing. For eg: When you keep the down arrow pressed on
  101. // websites like Facebook, the autoscrolling is expected to keep on going until
  102. // the mouse is released.
  103. // - This is implemented by monitoring the length of the scroller layer at every
  104. // frame and if the length increases (and if autoscroll in the forward direction
  105. // is already in progress), the old animation is aborted and a new autoscroll
  106. // animation with the new scroller length is kicked off.
  107. namespace cc {
  108. class LayerTreeHostImpl;
  109. // This class is responsible for hit testing composited scrollbars, event
  110. // handling and creating gesture scroll deltas.
  111. class CC_EXPORT ScrollbarController {
  112. public:
  113. explicit ScrollbarController(LayerTreeHostImpl*);
  114. virtual ~ScrollbarController();
  115. // On Mac, the "jump to the spot that's clicked" setting can be dynamically
  116. // set via System Preferences. When enabled, the expectation is that regular
  117. // clicks on the scrollbar should make the scroller "jump" to the clicked
  118. // location rather than animated scrolling. Additionally, when this is enabled
  119. // and the user does an Option + click on the scrollbar, the scroller should
  120. // *not* jump to that spot (i.e it should be treated as a regular track
  121. // click). When this setting is disabled on the Mac, Option + click should
  122. // make the scroller jump and a regular click should animate the scroll
  123. // offset. On all other platforms, the "jump on click" option is available
  124. // (via Shift + click) but is not configurable.
  125. InputHandlerPointerResult HandlePointerDown(
  126. const gfx::PointF position_in_widget,
  127. const bool jump_key_modifier);
  128. InputHandlerPointerResult HandlePointerMove(
  129. const gfx::PointF position_in_widget);
  130. InputHandlerPointerResult HandlePointerUp(
  131. const gfx::PointF position_in_widget);
  132. bool AutoscrollTaskIsScheduled() const {
  133. return cancelable_autoscroll_task_ != nullptr;
  134. }
  135. bool ScrollbarScrollIsActive() const { return scrollbar_scroll_is_active_; }
  136. void DidRegisterScrollbar(ElementId element_id,
  137. ScrollbarOrientation orientation);
  138. void DidUnregisterScrollbar(ElementId element_id,
  139. ScrollbarOrientation orientation);
  140. ScrollbarLayerImplBase* ScrollbarLayer() const;
  141. void WillBeginImplFrame();
  142. void ResetState();
  143. PointerResultType HitTest(const gfx::PointF position_in_widget) const;
  144. private:
  145. FRIEND_TEST_ALL_PREFIXES(ScrollUnifiedLayerTreeHostImplTest,
  146. ThumbDragAfterJumpClick);
  147. FRIEND_TEST_ALL_PREFIXES(ScrollUnifiedLayerTreeHostImplTest,
  148. AbortAnimatedScrollBeforeStartingAutoscroll);
  149. FRIEND_TEST_ALL_PREFIXES(ScrollUnifiedLayerTreeHostImplTest,
  150. AutoscrollOnDeletedScrollbar);
  151. FRIEND_TEST_ALL_PREFIXES(ScrollUnifiedLayerTreeHostImplTest,
  152. ScrollOnLargeThumb);
  153. // "Autoscroll" here means the continuous scrolling that occurs when the
  154. // pointer is held down on a hit-testable area of the scrollbar such as an
  155. // arrows of the track itself.
  156. enum class AutoScrollDirection { AUTOSCROLL_FORWARD, AUTOSCROLL_BACKWARD };
  157. enum class AutoScrollStatus {
  158. // For when the 250ms delay before an autoscroll starts animating has not
  159. // yet elapsed
  160. AUTOSCROLL_WAITING,
  161. // For when the delay has elapsed, but the autoscroll cannot animate for
  162. // some reason (the scrollbar being unregistered)
  163. AUTOSCROLL_READY,
  164. // For when the autoscroll is animating
  165. AUTOSCROLL_SCROLLING
  166. };
  167. struct CC_EXPORT AutoScrollState {
  168. // Can only be either AUTOSCROLL_FORWARD or AUTOSCROLL_BACKWARD.
  169. AutoScrollDirection direction = AutoScrollDirection::AUTOSCROLL_FORWARD;
  170. AutoScrollStatus status = AutoScrollStatus::AUTOSCROLL_WAITING;
  171. // Stores the autoscroll velocity. The sign is used to set the "direction".
  172. float velocity = 0.f;
  173. // Used to track the scroller length while autoscrolling. Helpful for
  174. // setting up infinite scrolling.
  175. float scroll_layer_length = 0.f;
  176. // Used to lookup the rect corresponding to the ScrollbarPart so that
  177. // autoscroll animations can be played/paused depending on the current
  178. // pointer location.
  179. ScrollbarPart pressed_scrollbar_part;
  180. };
  181. struct CC_EXPORT DragState {
  182. // This marks the point at which the drag initiated (relative to the
  183. // scrollbar layer).
  184. gfx::PointF drag_origin;
  185. // This is needed for thumb snapping when the pointer moves too far away
  186. // from the track while scrolling.
  187. float scroll_position_at_start_;
  188. // The |scroller_displacement| indicates the scroll offset compensation that
  189. // needs to be applied when the scroller's length changes dynamically mid
  190. // thumb drag. This is needed done to ensure that the scroller does not jump
  191. // while a thumb drag is in progress.
  192. float scroller_displacement;
  193. float scroller_length_at_previous_move;
  194. };
  195. struct CC_EXPORT CapturedScrollbarMetadata {
  196. // Needed to retrieve the ScrollbarSet for a particular ElementId.
  197. ElementId scroll_element_id;
  198. // Needed to identify the correct scrollbar from the ScrollbarSet.
  199. ScrollbarOrientation orientation;
  200. };
  201. // Posts an autoscroll task based on the autoscroll state, with the given
  202. // delay
  203. void PostAutoscrollTask(const base::TimeDelta delay);
  204. // Initiates an autoscroll, setting the necessary status and starting the
  205. // animation, if possible
  206. void StartAutoScroll();
  207. // Starts/restarts an autoscroll animation based off of the information in
  208. // autoscroll_state_
  209. void StartAutoScrollAnimation();
  210. // Returns the DSF based on whether use-zoom-for-dsf is enabled.
  211. float ScreenSpaceScaleFactor() const;
  212. // Helper to convert scroll offset to autoscroll velocity.
  213. float InitialDeltaToAutoscrollVelocity(gfx::Vector2dF scroll_delta) const;
  214. // Returns the hit tested ScrollbarPart based on the position_in_widget.
  215. ScrollbarPart GetScrollbarPartFromPointerDown(
  216. const gfx::PointF position_in_widget) const;
  217. // Clamps |scroll_delta| based on the available scrollable amount of
  218. // |target_node|. The returned delta includes the page scale factor and is
  219. // appropriate for use directly as a delta for GSU.
  220. gfx::Vector2dF ComputeClampedDelta(const ScrollNode& target_node,
  221. const gfx::Vector2dF& scroll_delta) const;
  222. // Returns the rect for the ScrollbarPart.
  223. gfx::Rect GetRectForScrollbarPart(const ScrollbarPart scrollbar_part) const;
  224. LayerImpl* GetLayerHitByPoint(const gfx::PointF position_in_widget) const;
  225. // Returns scroll delta as Vector2dF based on which ScrollbarPart was hit
  226. // tested.
  227. gfx::Vector2dF GetScrollDeltaForScrollbarPart(
  228. const ScrollbarPart scrollbar_part,
  229. const bool jump_key_modifier) const;
  230. // Returns scroll delta in the direction of the scrollbar's orientation.
  231. float GetScrollDistanceForScrollbarPart(const ScrollbarPart scrollbar_part,
  232. const bool jump_key_modifier) const;
  233. // Makes position_in_widget relative to the scrollbar.
  234. gfx::PointF GetScrollbarRelativePosition(const gfx::PointF position_in_widget,
  235. bool* clipped) const;
  236. // Computes an aritificial drag origin for jump clicks, to give the scrollbar
  237. // a proper place to snap back to on a jump click then drag
  238. gfx::PointF DragOriginForJumpClick(
  239. const ScrollbarLayerImplBase* scrollbar) const;
  240. // Decides if the scroller should snap to the offset that it was
  241. // originally at (i.e the offset before the thumb drag).
  242. bool SnapToDragOrigin(const gfx::PointF pointer_position_in_widget) const;
  243. // Decides whether a track autoscroll should be aborted (or restarted) due to
  244. // the thumb reaching the pointer or the pointer leaving (or re-entering) the
  245. // bounds.
  246. void RecomputeAutoscrollStateIfNeeded();
  247. // Shift (or "Option" in case of Mac) + click is expected to do a non-animated
  248. // jump to a certain offset.
  249. float GetScrollDistanceForAbsoluteJump() const;
  250. // Determines if the delta needs to be animated.
  251. ui::ScrollGranularity Granularity(const ScrollbarPart scrollbar_part,
  252. bool jump_key_modifier) const;
  253. // Calculates the distance based on position_in_widget and drag_origin.
  254. float GetScrollDistanceForDragPosition(
  255. const gfx::PointF pointer_position_in_widget) const;
  256. // Returns the ratio of the scroller length to the scrollbar length. This is
  257. // needed to scale the scroll delta for thumb drag.
  258. float GetScrollerToScrollbarRatio() const;
  259. float GetViewportLength() const;
  260. // Returns the pixel distance for a percent-based scroll of the scrollbar
  261. float GetScrollDistanceForPercentBasedScroll() const;
  262. // Returns the page scale factor (i.e. pinch zoom factor). This is relevant
  263. // for root viewport scrollbar scrolling.
  264. float GetPageScaleFactorForScroll() const;
  265. raw_ptr<LayerTreeHostImpl> layer_tree_host_impl_;
  266. // Used to safeguard against firing GSE without firing GSB and GSU. For
  267. // example, if mouse is pressed outside the scrollbar but released after
  268. // moving inside the scrollbar, a GSE will get queued up without this flag.
  269. bool scrollbar_scroll_is_active_;
  270. // This is relative to the RenderWidget's origin.
  271. gfx::PointF last_known_pointer_position_;
  272. // Set only while interacting with the scrollbar (eg: drag, click etc).
  273. absl::optional<CapturedScrollbarMetadata> captured_scrollbar_metadata_;
  274. // Holds information pertaining to autoscrolling. This member is empty if and
  275. // only if an autoscroll is *not* in progress or scheduled
  276. absl::optional<AutoScrollState> autoscroll_state_;
  277. // Holds information pertaining to thumb drags. Useful while making decisions
  278. // about thumb anchoring/snapping.
  279. absl::optional<DragState> drag_state_;
  280. // Used to track if a GSU was processed for the current frame or not. Without
  281. // this, thumb drag will appear jittery. The reason this happens is because
  282. // when the first GSU is processed, it gets queued in the compositor thread
  283. // event queue. So a second request within the same frame will end up
  284. // calculating an incorrect delta (as ComputeThumbQuadRect would not have
  285. // accounted for the delta in the first GSU that was not yet dispatched and
  286. // pointermoves are not VSync aligned).
  287. bool drag_processed_for_current_frame_;
  288. std::unique_ptr<base::CancelableOnceClosure> cancelable_autoscroll_task_;
  289. };
  290. } // namespace cc
  291. #endif // CC_INPUT_SCROLLBAR_CONTROLLER_H_