scroll_snap_data.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // Copyright 2017 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_SCROLL_SNAP_DATA_H_
  5. #define CC_INPUT_SCROLL_SNAP_DATA_H_
  6. #include <vector>
  7. #include "base/gtest_prod_util.h"
  8. #include "cc/cc_export.h"
  9. #include "cc/paint/element_id.h"
  10. #include "third_party/abseil-cpp/absl/types/optional.h"
  11. #include "ui/gfx/geometry/rect_f.h"
  12. #include "ui/gfx/geometry/vector2d_f.h"
  13. #include "ui/gfx/range/range_f.h"
  14. namespace cc {
  15. class SnapSelectionStrategy;
  16. // See https://www.w3.org/TR/css-scroll-snap-1/#snap-axis
  17. enum class SnapAxis : unsigned {
  18. kBoth,
  19. kX,
  20. kY,
  21. kBlock,
  22. kInline,
  23. };
  24. // A helper enum to specify the the axis when doing calculations.
  25. enum class SearchAxis : unsigned { kX, kY };
  26. // See https://www.w3.org/TR/css-scroll-snap-1/#snap-strictness
  27. enum class SnapStrictness : unsigned { kProximity, kMandatory };
  28. // See https://www.w3.org/TR/css-scroll-snap-1/#scroll-snap-align
  29. enum class SnapAlignment : unsigned { kNone, kStart, kEnd, kCenter };
  30. struct ScrollSnapType {
  31. ScrollSnapType()
  32. : is_none(true),
  33. axis(SnapAxis::kBoth),
  34. strictness(SnapStrictness::kProximity) {}
  35. ScrollSnapType(bool snap_type_none, SnapAxis axis, SnapStrictness strictness)
  36. : is_none(snap_type_none), axis(axis), strictness(strictness) {}
  37. bool operator==(const ScrollSnapType& other) const {
  38. return is_none == other.is_none && axis == other.axis &&
  39. strictness == other.strictness;
  40. }
  41. bool operator!=(const ScrollSnapType& other) const {
  42. return !(*this == other);
  43. }
  44. // Represents whether the scroll-snap-type is none.
  45. bool is_none;
  46. SnapAxis axis;
  47. SnapStrictness strictness;
  48. };
  49. struct ScrollSnapAlign {
  50. ScrollSnapAlign()
  51. : alignment_block(SnapAlignment::kNone),
  52. alignment_inline(SnapAlignment::kNone) {}
  53. explicit ScrollSnapAlign(SnapAlignment alignment)
  54. : alignment_block(alignment), alignment_inline(alignment) {}
  55. ScrollSnapAlign(SnapAlignment b, SnapAlignment i)
  56. : alignment_block(b), alignment_inline(i) {}
  57. bool operator==(const ScrollSnapAlign& other) const {
  58. return alignment_block == other.alignment_block &&
  59. alignment_inline == other.alignment_inline;
  60. }
  61. bool operator!=(const ScrollSnapAlign& other) const {
  62. return !(*this == other);
  63. }
  64. SnapAlignment alignment_block;
  65. SnapAlignment alignment_inline;
  66. };
  67. // This class includes snap offset and visible range needed to perform a snap
  68. // operation on one axis for a specific area. The data can be used to determine
  69. // whether this snap area provides a valid snap position for the current scroll.
  70. class SnapSearchResult {
  71. public:
  72. SnapSearchResult() {}
  73. SnapSearchResult(float offset, const gfx::RangeF& range);
  74. // Clips the |snap_offset| between 0 and |max_snap|. And clips the
  75. // |visible_range| between 0 and |max_visible|.
  76. void Clip(float max_snap, float max_visible);
  77. // Union the visible_range of the two SnapSearchResult if they represent two
  78. // snap areas that are both covering the snapport at the current offset.
  79. // The |element_id_| of this is arbitrarily chosen because both snap areas
  80. // cover the snapport and are therefore both valid.
  81. void Union(const SnapSearchResult& other);
  82. float snap_offset() const { return snap_offset_; }
  83. void set_snap_offset(float offset) { snap_offset_ = offset; }
  84. gfx::RangeF visible_range() const { return visible_range_; }
  85. void set_visible_range(const gfx::RangeF& range);
  86. ElementId element_id() const { return element_id_; }
  87. void set_element_id(ElementId id) { element_id_ = id; }
  88. private:
  89. float snap_offset_;
  90. // This is the range on the cross axis, within which the SnapArea generating
  91. // this |snap_offset| is visible. We expect the range to be in order (as
  92. // opposed to reversed), i.e., start() < end().
  93. gfx::RangeF visible_range_;
  94. // The ElementId of the snap area that corresponds to this SnapSearchResult.
  95. ElementId element_id_;
  96. };
  97. // Snap area is a bounding box that could be snapped to when a scroll happens in
  98. // its scroll container.
  99. // This data structure describes the data needed for SnapCoordinator if we want
  100. // to snap to this snap area.
  101. struct SnapAreaData {
  102. // kInvalidScrollOffset is used to mark that the snap_position on a specific
  103. // axis is not applicable, thus should not be considered when snapping on that
  104. // axis. This is because the snap area has SnapAlignmentNone on that axis.
  105. static const int kInvalidScrollPosition = -1;
  106. SnapAreaData() {}
  107. SnapAreaData(const ScrollSnapAlign& align,
  108. const gfx::RectF& rec,
  109. bool msnap,
  110. ElementId id)
  111. : scroll_snap_align(align), rect(rec), must_snap(msnap), element_id(id) {}
  112. bool operator==(const SnapAreaData& other) const {
  113. return (other.element_id == element_id) &&
  114. (other.scroll_snap_align == scroll_snap_align) &&
  115. (other.rect == rect) && (other.must_snap == must_snap);
  116. }
  117. bool operator!=(const SnapAreaData& other) const { return !(*this == other); }
  118. // Specifies how the snap area should be aligned with its snap container when
  119. // snapped. The alignment_inline and alignment_block represent the alignments
  120. // on x axis and y axis repectively.
  121. ScrollSnapAlign scroll_snap_align;
  122. // The snap area rect relative to its snap container's boundary
  123. gfx::RectF rect;
  124. // Whether this area has scroll-snap-stop: always.
  125. // See https://www.w3.org/TR/css-scroll-snap-1/#scroll-snap-stop
  126. bool must_snap;
  127. // ElementId of the corresponding snap area.
  128. ElementId element_id;
  129. };
  130. struct TargetSnapAreaElementIds {
  131. TargetSnapAreaElementIds() = default;
  132. TargetSnapAreaElementIds(ElementId x_id, ElementId y_id) : x(x_id), y(y_id) {}
  133. bool operator==(const TargetSnapAreaElementIds& other) const {
  134. return (other.x == x) && (other.y == y);
  135. }
  136. bool operator!=(const TargetSnapAreaElementIds& other) const {
  137. return !(*this == other);
  138. }
  139. // Note that the same element can be snapped to on both the x and y axes.
  140. ElementId x;
  141. ElementId y;
  142. };
  143. typedef std::vector<SnapAreaData> SnapAreaList;
  144. // Snap container is a scroll container that at least one snap area assigned to
  145. // it. If the snap-type is not 'none', then it can be snapped to one of its
  146. // snap areas when a scroll happens.
  147. // This data structure describes the data needed for SnapCoordinator to perform
  148. // snapping in the snap container.
  149. //
  150. // Note that the snap area data should only be used when snap-type is not 'none'
  151. // There is not guarantee that this information is up-to-date otherwise. In
  152. // fact, we skip updating these info as an optiomization.
  153. class CC_EXPORT SnapContainerData {
  154. public:
  155. SnapContainerData();
  156. explicit SnapContainerData(ScrollSnapType type);
  157. SnapContainerData(ScrollSnapType type,
  158. const gfx::RectF& rect,
  159. const gfx::PointF& max);
  160. SnapContainerData(const SnapContainerData& other);
  161. SnapContainerData(SnapContainerData&& other);
  162. ~SnapContainerData();
  163. SnapContainerData& operator=(const SnapContainerData& other);
  164. SnapContainerData& operator=(SnapContainerData&& other);
  165. bool operator==(const SnapContainerData& other) const {
  166. return (other.scroll_snap_type_ == scroll_snap_type_) &&
  167. (other.rect_ == rect_) && (other.max_position_ == max_position_) &&
  168. (other.proximity_range_ == proximity_range_) &&
  169. (other.snap_area_list_ == snap_area_list_) &&
  170. (other.target_snap_area_element_ids_ ==
  171. target_snap_area_element_ids_);
  172. }
  173. bool operator!=(const SnapContainerData& other) const {
  174. return !(*this == other);
  175. }
  176. // Returns true if a snap position was found.
  177. bool FindSnapPosition(const SnapSelectionStrategy& strategy,
  178. gfx::PointF* snap_position,
  179. TargetSnapAreaElementIds* target_element_ids,
  180. const ElementId& active_element_id = ElementId()) const;
  181. const TargetSnapAreaElementIds& GetTargetSnapAreaElementIds() const;
  182. // Returns true if the target snap area element ids were changed.
  183. bool SetTargetSnapAreaElementIds(TargetSnapAreaElementIds ids);
  184. void AddSnapAreaData(SnapAreaData snap_area_data);
  185. size_t size() const { return snap_area_list_.size(); }
  186. const SnapAreaData& at(size_t index) const { return snap_area_list_[index]; }
  187. void set_scroll_snap_type(ScrollSnapType type) { scroll_snap_type_ = type; }
  188. ScrollSnapType scroll_snap_type() const { return scroll_snap_type_; }
  189. void set_rect(const gfx::RectF& rect) { rect_ = rect; }
  190. gfx::RectF rect() const { return rect_; }
  191. void set_max_position(gfx::PointF position) { max_position_ = position; }
  192. gfx::PointF max_position() const { return max_position_; }
  193. void set_proximity_range(const gfx::PointF& range) {
  194. proximity_range_ = range;
  195. }
  196. gfx::PointF proximity_range() const { return proximity_range_; }
  197. private:
  198. // Finds the best SnapArea candidate that's optimal for the given selection
  199. // strategy, while satisfying two invariants:
  200. // - |candidate.snap_offset| is within |cross_axis_snap_result|'s visible
  201. // range on |axis|.
  202. // - |cross_axis_snap_result.snap_offset| is within |candidate|'s visible
  203. // range on the cross axis.
  204. // |cross_axis_snap_result| is what we've found to snap on the cross axis,
  205. // or the original scroll offset if this is the first iteration of search.
  206. // Returns the candidate as SnapSearchResult that includes the area's
  207. // |snap_offset| and its visible range on the cross axis.
  208. // When |should_consider_covering| is true, the current offset can be valid if
  209. // it makes a snap area cover the snapport.
  210. absl::optional<SnapSearchResult> FindClosestValidAreaInternal(
  211. SearchAxis axis,
  212. const SnapSelectionStrategy& strategy,
  213. const SnapSearchResult& cross_axis_snap_result,
  214. const ElementId& active_element_id,
  215. bool should_consider_covering = true) const;
  216. // A wrapper of FindClosestValidAreaInternal(). If
  217. // FindClosestValidAreaInternal() doesn't return a valid result when the snap
  218. // type is mandatory and the strategy has an intended direction, we relax the
  219. // strategy to ignore the direction and find again.
  220. absl::optional<SnapSearchResult> FindClosestValidArea(
  221. SearchAxis axis,
  222. const SnapSelectionStrategy& strategy,
  223. const SnapSearchResult& cross_axis_snap_result,
  224. const ElementId& active_element_id) const;
  225. bool FindSnapPositionForMutualSnap(const SnapSelectionStrategy& strategy,
  226. gfx::PointF* snap_position) const;
  227. // Finds the snap area associated with the target snap area element id for the
  228. // given axis.
  229. absl::optional<SnapSearchResult> GetTargetSnapAreaSearchResult(
  230. SearchAxis axis) const;
  231. // Returns all the info needed to snap at this area on the given axis,
  232. // including:
  233. // - The offset at which the snap area and the snap container meet the
  234. // requested alignment.
  235. // - The visible range within which the snap area is visible on the cross
  236. // axis.
  237. SnapSearchResult GetSnapSearchResult(SearchAxis axis,
  238. const SnapAreaData& data) const;
  239. bool IsSnapportCoveredOnAxis(SearchAxis axis,
  240. float current_offset,
  241. const gfx::RectF& area_rect) const;
  242. void UpdateSnapAreaForTesting(ElementId element_id,
  243. SnapAreaData snap_area_data);
  244. // Specifies whether a scroll container is a scroll snap container, how
  245. // strictly it snaps, and which axes are considered.
  246. // See https://www.w3.org/TR/css-scroll-snap-1/#scroll-snap-type for details.
  247. ScrollSnapType scroll_snap_type_;
  248. // The rect of the snap_container relative to its boundary.
  249. gfx::RectF rect_;
  250. // The maximal scroll position of the SnapContainer, in the same coordinate
  251. // with blink's scroll position.
  252. gfx::PointF max_position_;
  253. // A valid snap position should be within the |proximity_range_| of the
  254. // current offset on the snapping axis.
  255. gfx::PointF proximity_range_;
  256. // The SnapAreaData for the snap areas in this snap container. When a scroll
  257. // happens, we iterate through the snap_area_list to find the best snap
  258. // position.
  259. std::vector<SnapAreaData> snap_area_list_;
  260. // Represents the ElementId(s) of the latest targeted snap areas.
  261. // ElementId(s) will be invalid (ElementId::kInvalidElementId) if the snap
  262. // container is not snapped to a position.
  263. TargetSnapAreaElementIds target_snap_area_element_ids_;
  264. FRIEND_TEST_ALL_PREFIXES(ScrollSnapDataTest, SnapToFocusedElementHorizontal);
  265. FRIEND_TEST_ALL_PREFIXES(ScrollSnapDataTest, SnapToFocusedElementVertical);
  266. FRIEND_TEST_ALL_PREFIXES(ScrollSnapDataTest, SnapToFocusedElementBoth);
  267. };
  268. CC_EXPORT std::ostream& operator<<(std::ostream&, const SnapAreaData&);
  269. CC_EXPORT std::ostream& operator<<(std::ostream&, const SnapContainerData&);
  270. } // namespace cc
  271. #endif // CC_INPUT_SCROLL_SNAP_DATA_H_