blob: 3016a6027883403b345bad2bdf9933bb538bd38d [file] [log] [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;
// Strategy for scrolling to a particular position in a non-directional
// manner.
static std::unique_ptr<SnapSelectionStrategy> CreateForEndPosition(
const gfx::PointF& current_position,
bool scrolled_x,
bool scrolled_y);
// Strategy for scrolling in a direction by some small amount,
// giving preference to stop at snap areas.
// |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);
// Strategy for scrolling by some large offset in a particular direction.
// Unlike CreateForDirection, prefers scrolling by the given displacement
// over snapping to nearby points.
// |use_fractional_offsets| should be true when the current position is
// provided in fractional pixels.
static std::unique_ptr<SnapSelectionStrategy> CreateForDisplacement(
gfx::PointF current_position,
gfx::Vector2dF displacement,
bool use_fractional_offsets,
SnapStopAlwaysFilter filter = SnapStopAlwaysFilter::kIgnore);
// This prefers scrolling by the optimal displacement of about a page size
// in the given displacement unit vector direction.
// It additionally prefers snap points that scroll at most a page.
// |use_fractional_offsets| should be true when the current position is
// provided in fractional pixels.
static std::unique_ptr<SnapSelectionStrategy> CreateForPageScroll(
gfx::PointF current_position,
gfx::Vector2dF direction,
gfx::Size page_size,
bool use_fractional_offsets,
SnapStopAlwaysFilter filter = SnapStopAlwaysFilter::kIgnore);
// 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 given snap offset matches the strategy's preference.
virtual bool IsPreferredSnapPosition(SearchAxis axis,
float position) const = 0;
// 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 IsPreferredSnapPosition(SearchAxis axis, float 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
// - 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 scroll, we want to
// * Minimize the distance between the snap position and
// the starting position if we only prefer the direction
// so that we stop at the first snap position in that direction.
// * When the step distance is preferred, prefer skipping snap positions
// to scroll closer to the step distance.
// * Return the default intended position (using the default step) if that makes
// a snap area covers the snapport.
class DirectionStrategy : public SnapSelectionStrategy {
public:
enum class StepPreference {
// Prefer only the direction, but otherwise choose a closer snap position.
kDirection,
// Prefer snap areas close to the specified step distance.
kDistance
};
// |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,
StepPreference preferred_step,
const gfx::Vector2dF preferred_min_displacement,
const gfx::Vector2dF preferred_max_displacement,
SnapStopAlwaysFilter filter,
bool use_fractional_offsets)
: SnapSelectionStrategy(current_position),
step_(step),
preferred_step_(preferred_step),
preferred_min_displacement_(preferred_min_displacement),
preferred_max_displacement_(preferred_max_displacement),
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 IsPreferredSnapPosition(SearchAxis axis, float position) const override;
bool IsValidSnapPosition(SearchAxis axis, float position) const override;
bool IsValidSnapArea(SearchAxis axis,
const SnapAreaData& area) 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:
// The default step for this DirectionStrategy.
const gfx::Vector2dF step_;
// How strictly to prefer the step's magnitude.
const StepPreference preferred_step_;
// Some operations, e.g. scrolling by a page, prefer snap areas that
// scroll no more than a certain amount and at least a certain amount.
// 0 represents unrestricted displacement.
const gfx::Vector2dF preferred_min_displacement_;
const gfx::Vector2dF preferred_max_displacement_;
SnapStopAlwaysFilter snap_stop_always_filter_;
bool use_fractional_offsets_;
};
} // namespace cc
#endif // CC_INPUT_SNAP_SELECTION_STRATEGY_H_