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

cc / input / snap_selection_strategy.h [blame]

// Copyright 2018 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_SNAP_SELECTION_STRATEGY_H_
#define CC_INPUT_SNAP_SELECTION_STRATEGY_H_

#include <memory>

#include "cc/input/scroll_snap_data.h"

namespace cc {

enum class SnapStopAlwaysFilter { kIgnore, kRequire };
enum class SnapTargetsPrioritization { kIgnore, kRequire };

// This class represents an abstract strategy that decide which snap selection
// should be considered valid. There are concrete implementations for three core
// scrolling types: scroll with end position only, scroll with direction only,
// and scroll with end position and direction.
class CC_EXPORT SnapSelectionStrategy {
 public:
  SnapSelectionStrategy() = default;
  virtual ~SnapSelectionStrategy() = default;
  static std::unique_ptr<SnapSelectionStrategy> CreateForEndPosition(
      const gfx::PointF& current_position,
      bool scrolled_x,
      bool scrolled_y);

  // |use_fractional_offsets| should be true when the current position is
  // provided in fractional pixels.
  static std::unique_ptr<SnapSelectionStrategy> CreateForDirection(
      gfx::PointF current_position,
      gfx::Vector2dF step,
      bool use_fractional_offsets,
      SnapStopAlwaysFilter filter = SnapStopAlwaysFilter::kIgnore);
  static std::unique_ptr<SnapSelectionStrategy> CreateForEndAndDirection(
      gfx::PointF current_position,
      gfx::Vector2dF displacement,
      bool use_fractional_offsets);

  // Creates a selection strategy that attempts to snap to previously snapped
  // targets if possible, but defaults to finding the closest snap point if
  // the target no longer exists.
  static std::unique_ptr<SnapSelectionStrategy> CreateForTargetElement(
      gfx::PointF current_position);

  // Returns whether it's snappable on x or y depending on the scroll performed.
  virtual bool ShouldSnapOnX() const = 0;
  virtual bool ShouldSnapOnY() const = 0;

  // Returns whether snapping should attempt to snap to the previously snapped
  // area if possible.
  virtual bool ShouldPrioritizeSnapTargets() const;

  // Returns the end position of the scroll if no snap interferes.
  virtual gfx::PointF intended_position() const = 0;
  // Returns the scroll position from which the snap position should minimize
  // its distance.
  virtual gfx::PointF base_position() const = 0;
  // Returns the current scroll position of the snap container.
  const gfx::PointF& current_position() const { return current_position_; }

  // Returns true if the selection strategy considers the given snap offset
  // valid for the current axis.
  virtual bool IsValidSnapPosition(SearchAxis axis, float position) const = 0;
  virtual bool IsValidSnapArea(SearchAxis axis, const SnapAreaData& data) const;

  virtual bool HasIntendedDirection() const;

  // Returns true if a snap area with scroll-snap-stop:always should not be
  // bypassed.
  virtual bool ShouldRespectSnapStop() const;

  // Returns the best result according to snap selection strategy. This method
  // is called at the end of selection process to make the final decision.
  //
  // -closest: snap search result representing closest match.
  // -covering: snap search result representing the original target if it makes
  //            a snaparea covering the snapport.
  virtual const std::optional<SnapSearchResult>& PickBestResult(
      const std::optional<SnapSearchResult>& closest,
      const std::optional<SnapSearchResult>& covering) const = 0;

  // Returns true when the current scroll offset is provided in fractional
  // pixels.
  virtual bool UsingFractionalOffsets() const;

  virtual std::unique_ptr<SnapSelectionStrategy> Clone() const = 0;

 protected:
  explicit SnapSelectionStrategy(const gfx::PointF& current_position)
      : current_position_(current_position) {}
  const gfx::PointF current_position_;
};

// Examples for intended end position scrolls include
// - a panning gesture, released without momentum
// - manupulating the scrollbar "thumb" explicitly
// - programmatically scrolling via APIs such as scrollTo()
// - tabbing through the document's focusable elements
// - navigating to an anchor within the page
// - homing operations such as the Home/End keys
// For this type of scrolls, we want to
// * Minimize the distance between the snap position and the end position.
// * Return the end position if that makes a snap area covers the snapport.
class EndPositionStrategy : public SnapSelectionStrategy {
 public:
  EndPositionStrategy(const gfx::PointF& current_position,
                      bool scrolled_x,
                      bool scrolled_y,
                      SnapTargetsPrioritization snap_targets_prioritization =
                          SnapTargetsPrioritization::kIgnore)
      : SnapSelectionStrategy(current_position),
        scrolled_x_(scrolled_x),
        scrolled_y_(scrolled_y),
        snap_targets_prioritization_(snap_targets_prioritization) {}
  EndPositionStrategy(const EndPositionStrategy& other) = default;
  ~EndPositionStrategy() override = default;

  bool ShouldSnapOnX() const override;
  bool ShouldSnapOnY() const override;

  gfx::PointF intended_position() const override;
  gfx::PointF base_position() const override;

  bool IsValidSnapPosition(SearchAxis axis, float position) const override;
  bool HasIntendedDirection() const override;
  bool ShouldPrioritizeSnapTargets() const override;

  const std::optional<SnapSearchResult>& PickBestResult(
      const std::optional<SnapSearchResult>& closest,
      const std::optional<SnapSearchResult>& covering) const override;
  std::unique_ptr<SnapSelectionStrategy> Clone() const override;

 private:
  // Whether the x axis and y axis have been scrolled in this scroll gesture.
  const bool scrolled_x_;
  const bool scrolled_y_;
  SnapTargetsPrioritization snap_targets_prioritization_;
};

// Examples for intended direction scrolls include
// - pressing an arrow key on the keyboard
// - a swiping gesture interpreted as a fixed (rather than inertial) scroll
// For this type of scrolls, we want to
// * Minimize the distance between the snap position and the starting position,
//   so that we stop at the first snap position in that direction.
// * Return the default intended position (using the default step) if that makes
//   a snap area covers the snapport.
class DirectionStrategy : public SnapSelectionStrategy {
 public:
  // |use_fractional_offsets| should be true when the current position is
  // provided in fractional pixels.
  DirectionStrategy(const gfx::PointF& current_position,
                    const gfx::Vector2dF& step,
                    SnapStopAlwaysFilter filter,
                    bool use_fractional_offsets)
      : SnapSelectionStrategy(current_position),
        step_(step),
        snap_stop_always_filter_(filter),
        use_fractional_offsets_(use_fractional_offsets) {}
  DirectionStrategy(const DirectionStrategy& other) = default;
  ~DirectionStrategy() override = default;

  bool ShouldSnapOnX() const override;
  bool ShouldSnapOnY() const override;

  gfx::PointF intended_position() const override;
  gfx::PointF base_position() const override;

  bool IsValidSnapPosition(SearchAxis axis, float position) const override;
  bool IsValidSnapArea(SearchAxis axis,
                       const SnapAreaData& area) const override;

  const std::optional<SnapSearchResult>& PickBestResult(
      const std::optional<SnapSearchResult>& closest,
      const std::optional<SnapSearchResult>& covering) const override;

  bool UsingFractionalOffsets() const override;

  std::unique_ptr<SnapSelectionStrategy> Clone() const override;

 private:
  // The default step for this DirectionStrategy.
  const gfx::Vector2dF step_;
  SnapStopAlwaysFilter snap_stop_always_filter_;
  bool use_fractional_offsets_;
};

// Examples for intended direction and end position scrolls include
// - a “fling” gesture, interpreted with momentum
// - programmatically scrolling via APIs such as scrollBy()
// - paging operations such as the PgUp/PgDn keys (or equivalent operations on
//   the scrollbar)
// For this type of scrolls, we want to
// * Minimize the distance between the snap position and the end position.
// * Return the end position if that makes a snap area covers the snapport.
class EndAndDirectionStrategy : public SnapSelectionStrategy {
 public:
  // |use_fractional_offsets| should be true when the current position is
  // provided in fractional pixels.
  EndAndDirectionStrategy(const gfx::PointF& current_position,
                          const gfx::Vector2dF& displacement,
                          bool use_fractional_offsets)
      : SnapSelectionStrategy(current_position),
        displacement_(displacement),
        use_fractional_offsets_(use_fractional_offsets) {}
  EndAndDirectionStrategy(const EndAndDirectionStrategy& other) = default;
  ~EndAndDirectionStrategy() override = default;

  bool ShouldSnapOnX() const override;
  bool ShouldSnapOnY() const override;

  gfx::PointF intended_position() const override;
  gfx::PointF base_position() const override;

  bool IsValidSnapPosition(SearchAxis axis, float position) const override;

  bool ShouldRespectSnapStop() const override;

  const std::optional<SnapSearchResult>& PickBestResult(
      const std::optional<SnapSearchResult>& closest,
      const std::optional<SnapSearchResult>& covering) const override;

  bool UsingFractionalOffsets() const override;

  std::unique_ptr<SnapSelectionStrategy> Clone() const override;

 private:
  const gfx::Vector2dF displacement_;
  bool use_fractional_offsets_;
};

}  // namespace cc

#endif  // CC_INPUT_SNAP_SELECTION_STRATEGY_H_