| // Copyright 2017 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. |
| |
| #ifndef CC_INPUT_SCROLL_SNAP_DATA_H_ |
| #define CC_INPUT_SCROLL_SNAP_DATA_H_ |
| |
| #include <vector> |
| |
| #include "base/optional.h" |
| #include "cc/cc_export.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/geometry/scroll_offset.h" |
| #include "ui/gfx/range/range_f.h" |
| |
| namespace cc { |
| |
| class SnapSelectionStrategy; |
| |
| // See https://www.w3.org/TR/css-scroll-snap-1/#snap-axis |
| enum class SnapAxis : unsigned { |
| kBoth, |
| kX, |
| kY, |
| kBlock, |
| kInline, |
| }; |
| |
| // A helper enum to specify the the axis when doing calculations. |
| enum class SearchAxis : unsigned { kX, kY }; |
| |
| // See https://www.w3.org/TR/css-scroll-snap-1/#snap-strictness |
| // TODO(sunyunjia): Add kNone for SnapStrictness to match the spec. |
| // crbug.com/791663 |
| enum class SnapStrictness : unsigned { kProximity, kMandatory }; |
| |
| // See https://www.w3.org/TR/css-scroll-snap-1/#scroll-snap-align |
| enum class SnapAlignment : unsigned { kNone, kStart, kEnd, kCenter }; |
| |
| struct ScrollSnapType { |
| ScrollSnapType() |
| : is_none(true), |
| axis(SnapAxis::kBoth), |
| strictness(SnapStrictness::kProximity) {} |
| |
| ScrollSnapType(bool snap_type_none, SnapAxis axis, SnapStrictness strictness) |
| : is_none(snap_type_none), axis(axis), strictness(strictness) {} |
| |
| bool operator==(const ScrollSnapType& other) const { |
| return is_none == other.is_none && axis == other.axis && |
| strictness == other.strictness; |
| } |
| |
| bool operator!=(const ScrollSnapType& other) const { |
| return !(*this == other); |
| } |
| |
| // Whether the scroll-snap-type is none or the snap-strictness field has the |
| // value None. |
| // TODO(sunyunjia): Consider combining is_none with SnapStrictness. |
| bool is_none; |
| |
| SnapAxis axis; |
| SnapStrictness strictness; |
| }; |
| |
| struct ScrollSnapAlign { |
| ScrollSnapAlign() |
| : alignment_block(SnapAlignment::kNone), |
| alignment_inline(SnapAlignment::kNone) {} |
| |
| explicit ScrollSnapAlign(SnapAlignment alignment) |
| : alignment_block(alignment), alignment_inline(alignment) {} |
| |
| ScrollSnapAlign(SnapAlignment b, SnapAlignment i) |
| : alignment_block(b), alignment_inline(i) {} |
| |
| bool operator==(const ScrollSnapAlign& other) const { |
| return alignment_block == other.alignment_block && |
| alignment_inline == other.alignment_inline; |
| } |
| |
| bool operator!=(const ScrollSnapAlign& other) const { |
| return !(*this == other); |
| } |
| |
| SnapAlignment alignment_block; |
| SnapAlignment alignment_inline; |
| }; |
| |
| // This class includes snap offset and visible range needed to perform a snap |
| // operation on one axis for a specific area. The data can be used to determine |
| // whether this snap area provides a valid snap position for the current scroll. |
| class SnapSearchResult { |
| public: |
| SnapSearchResult() {} |
| SnapSearchResult(float offset, const gfx::RangeF& range); |
| // Clips the |snap_offset| between 0 and |max_snap|. And clips the |
| // |visible_range| between 0 and |max_visible|. |
| void Clip(float max_snap, float max_visible); |
| |
| // Union the visible_range of the two SnapSearchResult if they represent two |
| // snap areas that are both covering the snapport at the current offset. |
| void Union(const SnapSearchResult& other); |
| |
| float snap_offset() const { return snap_offset_; } |
| void set_snap_offset(float offset) { snap_offset_ = offset; } |
| |
| gfx::RangeF visible_range() const { return visible_range_; } |
| void set_visible_range(const gfx::RangeF& range); |
| |
| private: |
| float snap_offset_; |
| // This is the range on the cross axis, within which the SnapArea generating |
| // this |snap_offset| is visible. We expect the range to be in order (as |
| // opposed to reversed), i.e., start() < end(). |
| gfx::RangeF visible_range_; |
| }; |
| |
| // Snap area is a bounding box that could be snapped to when a scroll happens in |
| // its scroll container. |
| // This data structure describes the data needed for SnapCoordinator if we want |
| // to snap to this snap area. |
| struct SnapAreaData { |
| // kInvalidScrollOffset is used to mark that the snap_position on a specific |
| // axis is not applicable, thus should not be considered when snapping on that |
| // axis. This is because the snap area has SnapAlignmentNone on that axis. |
| static const int kInvalidScrollPosition = -1; |
| |
| SnapAreaData() {} |
| |
| SnapAreaData(const ScrollSnapAlign& align, const gfx::RectF& rec, bool msnap) |
| : scroll_snap_align(align), rect(rec), must_snap(msnap) {} |
| |
| bool operator==(const SnapAreaData& other) const { |
| return (other.scroll_snap_align == scroll_snap_align) && |
| (other.rect == rect) && (other.must_snap == must_snap); |
| } |
| |
| bool operator!=(const SnapAreaData& other) const { return !(*this == other); } |
| |
| // Specifies how the snap area should be aligned with its snap container when |
| // snapped. The alignment_inline and alignment_block represent the alignments |
| // on x axis and y axis repectively. |
| ScrollSnapAlign scroll_snap_align; |
| |
| // The snap area rect relative to its snap container's boundary |
| gfx::RectF rect; |
| |
| // Whether this area has scroll-snap-stop: always. |
| // See https://www.w3.org/TR/css-scroll-snap-1/#scroll-snap-stop |
| bool must_snap; |
| }; |
| |
| typedef std::vector<SnapAreaData> SnapAreaList; |
| |
| // Snap container is a scroll container that has non-'none' value for |
| // scroll-snap-type. It can be snapped to one of its snap areas when a scroll |
| // happens. |
| // This data structure describes the data needed for SnapCoordinator to perform |
| // snapping in the snap container. |
| class CC_EXPORT SnapContainerData { |
| public: |
| SnapContainerData(); |
| explicit SnapContainerData(ScrollSnapType type); |
| SnapContainerData(ScrollSnapType type, |
| const gfx::RectF& rect, |
| const gfx::ScrollOffset& max); |
| SnapContainerData(const SnapContainerData& other); |
| SnapContainerData(SnapContainerData&& other); |
| ~SnapContainerData(); |
| |
| SnapContainerData& operator=(const SnapContainerData& other); |
| SnapContainerData& operator=(SnapContainerData&& other); |
| |
| bool operator==(const SnapContainerData& other) const { |
| return (other.scroll_snap_type_ == scroll_snap_type_) && |
| (other.rect_ == rect_) && (other.max_position_ == max_position_) && |
| (other.proximity_range_ == proximity_range_) && |
| (other.snap_area_list_ == snap_area_list_); |
| } |
| |
| bool operator!=(const SnapContainerData& other) const { |
| return !(*this == other); |
| } |
| |
| bool FindSnapPosition(const SnapSelectionStrategy& strategy, |
| gfx::ScrollOffset* snap_position) const; |
| |
| void AddSnapAreaData(SnapAreaData snap_area_data); |
| size_t size() const { return snap_area_list_.size(); } |
| const SnapAreaData& at(int index) const { return snap_area_list_[index]; } |
| |
| void set_scroll_snap_type(ScrollSnapType type) { scroll_snap_type_ = type; } |
| ScrollSnapType scroll_snap_type() const { return scroll_snap_type_; } |
| |
| void set_rect(const gfx::RectF& rect) { rect_ = rect; } |
| gfx::RectF rect() const { return rect_; } |
| |
| void set_max_position(gfx::ScrollOffset position) { |
| max_position_ = position; |
| } |
| gfx::ScrollOffset max_position() const { return max_position_; } |
| |
| void set_proximity_range(const gfx::ScrollOffset& range) { |
| proximity_range_ = range; |
| } |
| gfx::ScrollOffset proximity_range() const { return proximity_range_; } |
| |
| private: |
| // Finds the best SnapArea candidate that's optimal for the given selection |
| // strategy, while satisfying two invariants: |
| // - |candidate.snap_offset| is within |cross_axis_snap_result|'s visible |
| // range on |axis|. |
| // - |cross_axis_snap_result.snap_offset| is within |candidate|'s visible |
| // range on the cross axis. |
| // |cross_axis_snap_result| is what we've found to snap on the cross axis, |
| // or the original scroll offset if this is the first iteration of search. |
| // Returns the candidate as SnapSearchResult that includes the area's |
| // |snap_offset| and its visible range on the cross axis. |
| // When |should_consider_covering| is true, the current offset can be valid if |
| // it makes a snap area cover the snapport. |
| base::Optional<SnapSearchResult> FindClosestValidAreaInternal( |
| SearchAxis axis, |
| const SnapSelectionStrategy& strategy, |
| const SnapSearchResult& cross_axis_snap_result, |
| bool should_consider_covering = true) const; |
| |
| // A wrapper of FindClosestValidAreaInternal(). If |
| // FindClosestValidAreaInternal() doesn't return a valid result when the snap |
| // type is mandatory and the strategy has an intended direction, we relax the |
| // strategy to ignore the direction and find again. |
| base::Optional<SnapSearchResult> FindClosestValidArea( |
| SearchAxis axis, |
| const SnapSelectionStrategy& strategy, |
| const SnapSearchResult& cross_axis_snap_result) const; |
| |
| // Returns all the info needed to snap at this area on the given axis, |
| // including: |
| // - The offset at which the snap area and the snap container meet the |
| // requested alignment. |
| // - The visible range within which the snap area is visible on the cross |
| // axis. |
| SnapSearchResult GetSnapSearchResult(SearchAxis axis, |
| const SnapAreaData& data) const; |
| |
| bool IsSnapportCoveredOnAxis(SearchAxis axis, |
| float current_offset, |
| const gfx::RectF& area_rect) const; |
| |
| // Specifies whether a scroll container is a scroll snap container, how |
| // strictly it snaps, and which axes are considered. |
| // See https://www.w3.org/TR/css-scroll-snap-1/#scroll-snap-type for details. |
| ScrollSnapType scroll_snap_type_; |
| |
| // The rect of the snap_container relative to its boundary. |
| gfx::RectF rect_; |
| |
| // The maximal scroll position of the SnapContainer, in the same coordinate |
| // with blink's scroll position. |
| gfx::ScrollOffset max_position_; |
| |
| // A valid snap position should be within the |proximity_range_| of the |
| // current offset on the snapping axis. |
| gfx::ScrollOffset proximity_range_; |
| |
| // The SnapAreaData for the snap areas in this snap container. When a scroll |
| // happens, we iterate through the snap_area_list to find the best snap |
| // position. |
| std::vector<SnapAreaData> snap_area_list_; |
| }; |
| |
| CC_EXPORT std::ostream& operator<<(std::ostream&, const SnapAreaData&); |
| CC_EXPORT std::ostream& operator<<(std::ostream&, const SnapContainerData&); |
| |
| } // namespace cc |
| |
| #endif // CC_INPUT_SCROLL_SNAP_DATA_H_ |