diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-13 15:05:36 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-14 10:33:47 +0000 |
commit | e684a3455bcc29a6e3e66a004e352dea4e1141e7 (patch) | |
tree | d55b4003bde34d7d05f558f02cfd82b2a66a7aac /chromium/cc/input | |
parent | 2b94bfe47ccb6c08047959d1c26e392919550e86 (diff) | |
download | qtwebengine-chromium-e684a3455bcc29a6e3e66a004e352dea4e1141e7.tar.gz |
BASELINE: Update Chromium to 72.0.3626.110 and Ninja to 1.9.0
Change-Id: Ic57220b00ecc929a893c91f5cc552f5d3e99e922
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/cc/input')
-rw-r--r-- | chromium/cc/input/browser_controls_offset_manager.cc | 41 | ||||
-rw-r--r-- | chromium/cc/input/browser_controls_offset_manager.h | 10 | ||||
-rw-r--r-- | chromium/cc/input/browser_controls_offset_manager_client.h | 1 | ||||
-rw-r--r-- | chromium/cc/input/browser_controls_offset_manager_unittest.cc | 71 | ||||
-rw-r--r-- | chromium/cc/input/browser_controls_state.h | 3 | ||||
-rw-r--r-- | chromium/cc/input/main_thread_scrolling_reason.cc | 4 | ||||
-rw-r--r-- | chromium/cc/input/main_thread_scrolling_reason.h | 6 | ||||
-rw-r--r-- | chromium/cc/input/main_thread_scrolling_reason_unittest.cc | 5 | ||||
-rw-r--r-- | chromium/cc/input/scroll_snap_data.cc | 172 | ||||
-rw-r--r-- | chromium/cc/input/scroll_snap_data.h | 38 | ||||
-rw-r--r-- | chromium/cc/input/scroll_snap_data_unittest.cc | 81 | ||||
-rw-r--r-- | chromium/cc/input/snap_fling_controller.h | 4 | ||||
-rw-r--r-- | chromium/cc/input/snap_selection_strategy.cc | 151 | ||||
-rw-r--r-- | chromium/cc/input/snap_selection_strategy.h | 170 |
14 files changed, 603 insertions, 154 deletions
diff --git a/chromium/cc/input/browser_controls_offset_manager.cc b/chromium/cc/input/browser_controls_offset_manager.cc index e55bf940440..afbe9770086 100644 --- a/chromium/cc/input/browser_controls_offset_manager.cc +++ b/chromium/cc/input/browser_controls_offset_manager.cc @@ -38,6 +38,7 @@ BrowserControlsOffsetManager::BrowserControlsOffsetManager( float controls_show_threshold, float controls_hide_threshold) : client_(client), + animation_initialized_(false), animation_start_value_(0.f), animation_stop_value_(0.f), animation_direction_(NO_ANIMATION), @@ -47,7 +48,8 @@ BrowserControlsOffsetManager::BrowserControlsOffsetManager( baseline_bottom_content_offset_(0.f), controls_show_threshold_(controls_hide_threshold), controls_hide_threshold_(controls_show_threshold), - pinch_gesture_active_(false) { + pinch_gesture_active_(false), + constraint_changed_since_commit_(false) { CHECK(client_); } @@ -92,6 +94,17 @@ void BrowserControlsOffsetManager::UpdateBrowserControlsState( DCHECK(!(constraints == BrowserControlsState::kHidden && current == BrowserControlsState::kShown)); + TRACE_EVENT2("cc", "BrowserControlsOffsetManager::UpdateBrowserControlsState", + "constraints", static_cast<int>(constraints), "current", + static_cast<int>(current)); + + // If the constraints have changed we need to inform Blink about it since + // that'll affect main thread scrolling as well as layout. + if (permitted_state_ != constraints) { + constraint_changed_since_commit_ = true; + client_->SetNeedsCommit(); + } + permitted_state_ = constraints; // Don't do anything if it doesn't matter which state the controls are in. @@ -105,6 +118,7 @@ void BrowserControlsOffsetManager::UpdateBrowserControlsState( current == BrowserControlsState::kHidden) final_shown_ratio = 0.f; if (final_shown_ratio == TopControlsShownRatio()) { + TRACE_EVENT_INSTANT0("cc", "Ratio Unchanged", TRACE_EVENT_SCOPE_THREAD); ResetAnimations(); return; } @@ -113,10 +127,18 @@ void BrowserControlsOffsetManager::UpdateBrowserControlsState( SetupAnimation(final_shown_ratio ? SHOWING_CONTROLS : HIDING_CONTROLS); } else { ResetAnimations(); - // We depend on the main thread to push the new ratio. crbug.com/754346 . + client_->SetCurrentBrowserControlsShownRatio(final_shown_ratio); } } +BrowserControlsState BrowserControlsOffsetManager::PullConstraintForMainThread( + bool* out_changed_since_commit) { + DCHECK(out_changed_since_commit); + *out_changed_since_commit = constraint_changed_since_commit_; + constraint_changed_since_commit_ = false; + return permitted_state_; +} + void BrowserControlsOffsetManager::ScrollBegin() { if (pinch_gesture_active_) return; @@ -197,6 +219,16 @@ gfx::Vector2dF BrowserControlsOffsetManager::Animate( if (!has_animation() || !client_->HaveRootScrollNode()) return gfx::Vector2dF(); + if (!animation_initialized_) { + // Setup the animation start and time here so that they use the same clock + // as frame times. This is helpful for tests that mock time. + animation_start_time_ = monotonic_time; + animation_stop_time_ = + animation_start_time_ + + base::TimeDelta::FromMilliseconds(kShowHideMaxDurationMs); + animation_initialized_ = true; + } + float old_offset = ContentTopOffset(); float new_ratio = gfx::Tween::ClampedFloatValueBetween( monotonic_time, animation_start_time_, animation_start_value_, @@ -211,6 +243,7 @@ gfx::Vector2dF BrowserControlsOffsetManager::Animate( } void BrowserControlsOffsetManager::ResetAnimations() { + animation_initialized_ = false; animation_start_time_ = base::TimeTicks(); animation_start_value_ = 0.f; animation_stop_time_ = base::TimeTicks(); @@ -234,13 +267,9 @@ void BrowserControlsOffsetManager::SetupAnimation( return; } - animation_start_time_ = base::TimeTicks::Now(); animation_start_value_ = TopControlsShownRatio(); const float max_ending_ratio = (direction == SHOWING_CONTROLS ? 1 : -1); - animation_stop_time_ = - animation_start_time_ + - base::TimeDelta::FromMilliseconds(kShowHideMaxDurationMs); animation_stop_value_ = animation_start_value_ + max_ending_ratio; animation_direction_ = direction; diff --git a/chromium/cc/input/browser_controls_offset_manager.h b/chromium/cc/input/browser_controls_offset_manager.h index ae4d9e4268f..8d0435d2808 100644 --- a/chromium/cc/input/browser_controls_offset_manager.h +++ b/chromium/cc/input/browser_controls_offset_manager.h @@ -53,6 +53,9 @@ class CC_EXPORT BrowserControlsOffsetManager { BrowserControlsState current, bool animate); + BrowserControlsState PullConstraintForMainThread( + bool* out_changed_since_commit); + void ScrollBegin(); gfx::Vector2dF ScrollBy(const gfx::Vector2dF& pending_delta); void ScrollEnd(); @@ -81,6 +84,9 @@ class CC_EXPORT BrowserControlsOffsetManager { // The client manages the lifecycle of this. BrowserControlsOffsetManagerClient* client_; + // animation_initialized_ tracks if we've initialized the start and end + // times since that must happen at a BeginFrame. + bool animation_initialized_; base::TimeTicks animation_start_time_; float animation_start_value_; base::TimeTicks animation_stop_time_; @@ -106,6 +112,10 @@ class CC_EXPORT BrowserControlsOffsetManager { bool pinch_gesture_active_; + // Used to track whether the constraint has changed and we need up reflect + // the changes to Blink. + bool constraint_changed_since_commit_; + DISALLOW_COPY_AND_ASSIGN(BrowserControlsOffsetManager); }; diff --git a/chromium/cc/input/browser_controls_offset_manager_client.h b/chromium/cc/input/browser_controls_offset_manager_client.h index df1a742669d..5a2ce4cb5dc 100644 --- a/chromium/cc/input/browser_controls_offset_manager_client.h +++ b/chromium/cc/input/browser_controls_offset_manager_client.h @@ -15,6 +15,7 @@ class CC_EXPORT BrowserControlsOffsetManagerClient { virtual float CurrentBrowserControlsShownRatio() const = 0; virtual void DidChangeBrowserControlsPosition() = 0; virtual bool HaveRootScrollNode() const = 0; + virtual void SetNeedsCommit() = 0; protected: virtual ~BrowserControlsOffsetManagerClient() {} diff --git a/chromium/cc/input/browser_controls_offset_manager_unittest.cc b/chromium/cc/input/browser_controls_offset_manager_unittest.cc index 3caad9cd2ec..3a8cfdc7df8 100644 --- a/chromium/cc/input/browser_controls_offset_manager_unittest.cc +++ b/chromium/cc/input/browser_controls_offset_manager_unittest.cc @@ -71,6 +71,8 @@ class MockBrowserControlsOffsetManagerClient return top_controls_shown_ratio_; } + void SetNeedsCommit() override {} + LayerImpl* rootScrollLayer() { return root_scroll_layer_.get(); } BrowserControlsOffsetManager* manager() { @@ -167,7 +169,12 @@ TEST(BrowserControlsOffsetManagerTest, PartialShownHideAnimation) { EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -199,7 +206,12 @@ TEST(BrowserControlsOffsetManagerTest, EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->BottomControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -229,7 +241,12 @@ TEST(BrowserControlsOffsetManagerTest, PartialShownShowAnimation) { EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -256,7 +273,12 @@ TEST(BrowserControlsOffsetManagerTest, EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->BottomControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -283,7 +305,12 @@ TEST(BrowserControlsOffsetManagerTest, EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -310,7 +337,12 @@ TEST(BrowserControlsOffsetManagerTest, EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -341,7 +373,12 @@ TEST(BrowserControlsOffsetManagerTest, EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -372,7 +409,12 @@ TEST(BrowserControlsOffsetManagerTest, EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -435,7 +477,12 @@ TEST(BrowserControlsOffsetManagerTest, PinchBeginStartsAnimationIfNecessary) { EXPECT_TRUE(manager->has_animation()); base::TimeTicks time = base::TimeTicks::Now(); - float previous; + + // First animate will establish the animaion. + float previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; @@ -456,6 +503,12 @@ TEST(BrowserControlsOffsetManagerTest, PinchBeginStartsAnimationIfNecessary) { EXPECT_TRUE(manager->has_animation()); time = base::TimeTicks::Now(); + + // First animate will establish the animaion. + previous = manager->TopControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->TopControlsShownRatio(), previous); + while (manager->has_animation()) { previous = manager->TopControlsShownRatio(); time = base::TimeDelta::FromMicroseconds(100) + time; diff --git a/chromium/cc/input/browser_controls_state.h b/chromium/cc/input/browser_controls_state.h index 38310496c5a..8b2f93f4171 100644 --- a/chromium/cc/input/browser_controls_state.h +++ b/chromium/cc/input/browser_controls_state.h @@ -8,6 +8,7 @@ namespace cc { enum class BrowserControlsState { kShown = 1, kHidden = 2, kBoth = 3 }; -} + +} // namespace cc #endif // CC_INPUT_BROWSER_CONTROLS_STATE_H_ diff --git a/chromium/cc/input/main_thread_scrolling_reason.cc b/chromium/cc/input/main_thread_scrolling_reason.cc index a85b60f97a7..728708ce776 100644 --- a/chromium/cc/input/main_thread_scrolling_reason.cc +++ b/chromium/cc/input/main_thread_scrolling_reason.cc @@ -5,7 +5,7 @@ #include "cc/input/main_thread_scrolling_reason.h" #include "base/stl_util.h" -#include "base/trace_event/trace_event_argument.h" +#include "base/trace_event/traced_value.h" namespace cc { @@ -47,8 +47,6 @@ void MainThreadScrollingReason::AddToTracedValue( traced_value.AppendString("Has transform and LCD text"); if (reasons & kBackgroundNotOpaqueInRectAndLCDText) traced_value.AppendString("Background is not opaque in rect and LCD text"); - if (reasons & kHasBorderRadius) - traced_value.AppendString("Has border radius"); if (reasons & kHasClipRelatedProperty) traced_value.AppendString("Has clip related property"); if (reasons & kHasBoxShadowFromNonRootLayer) diff --git a/chromium/cc/input/main_thread_scrolling_reason.h b/chromium/cc/input/main_thread_scrolling_reason.h index cf2deff0ce8..732c3fa904b 100644 --- a/chromium/cc/input/main_thread_scrolling_reason.h +++ b/chromium/cc/input/main_thread_scrolling_reason.h @@ -46,7 +46,6 @@ struct CC_EXPORT MainThreadScrollingReason { kHasOpacityAndLCDText = 1 << 16, kHasTransformAndLCDText = 1 << 17, kBackgroundNotOpaqueInRectAndLCDText = 1 << 18, - kHasBorderRadius = 1 << 19, kHasClipRelatedProperty = 1 << 20, kHasBoxShadowFromNonRootLayer = 1 << 21, kIsNotStackingContextAndLCDText = 1 << 22, @@ -72,9 +71,8 @@ struct CC_EXPORT MainThreadScrollingReason { static const uint32_t kNonCompositedReasons = kHasOpacityAndLCDText | kHasTransformAndLCDText | - kBackgroundNotOpaqueInRectAndLCDText | kHasBorderRadius | - kHasClipRelatedProperty | kHasBoxShadowFromNonRootLayer | - kIsNotStackingContextAndLCDText; + kBackgroundNotOpaqueInRectAndLCDText | kHasClipRelatedProperty | + kHasBoxShadowFromNonRootLayer | kIsNotStackingContextAndLCDText; // Returns true if the given MainThreadScrollingReason can be set by the main // thread. diff --git a/chromium/cc/input/main_thread_scrolling_reason_unittest.cc b/chromium/cc/input/main_thread_scrolling_reason_unittest.cc index 9f8e6b641d8..7560dda71d9 100644 --- a/chromium/cc/input/main_thread_scrolling_reason_unittest.cc +++ b/chromium/cc/input/main_thread_scrolling_reason_unittest.cc @@ -16,13 +16,14 @@ TEST_F(MainThreadScrollingReasonTest, AsText) { "Has background-attachment:fixed," "Has non-layer viewport-constrained objects," "Threaded scrolling is disabled," - "Scrollbar scrolling,Page overlay," + "Scrollbar scrolling," + "Page overlay," "Handling scroll from main thread," "Custom scrollbar scrolling," "Has opacity and LCD text," "Has transform and LCD text," "Background is not opaque in rect and LCD text," - "Has border radius,Has clip related property," + "Has clip related property," "Has box shadow from non-root layer," "Is not stacking context and LCD text," "Non fast scrollable region," diff --git a/chromium/cc/input/scroll_snap_data.cc b/chromium/cc/input/scroll_snap_data.cc index 9e873aea523..3fc17474fc7 100644 --- a/chromium/cc/input/scroll_snap_data.cc +++ b/chromium/cc/input/scroll_snap_data.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "cc/input/scroll_snap_data.h" +#include "cc/input/snap_selection_strategy.h" #include <algorithm> #include <cmath> @@ -11,8 +12,8 @@ namespace cc { namespace { bool IsMutualVisible(const SnapSearchResult& a, const SnapSearchResult& b) { - return a.visible_range().Contains(b.snap_offset()) && - b.visible_range().Contains(a.snap_offset()); + return a.visible_range().Contains(gfx::RangeF(b.snap_offset())) && + b.visible_range().Contains(gfx::RangeF(a.snap_offset())); } bool SnappableOnAxis(const SnapAreaData& area, SearchAxis search_axis) { @@ -21,34 +22,36 @@ bool SnappableOnAxis(const SnapAreaData& area, SearchAxis search_axis) { : area.scroll_snap_align.alignment_block != SnapAlignment::kNone; } -} // namespace - -bool SnapVisibleRange::Contains(float value) const { - if (start_ < end_) - return start_ <= value && value <= end_; - return end_ <= value && value <= start_; +void SetOrUpdateResult(const SnapSearchResult& candidate, + base::Optional<SnapSearchResult>* result) { + if (result->has_value()) + result->value().Union(candidate); + else + *result = candidate; } -SnapSearchResult::SnapSearchResult(float offset, const SnapVisibleRange& range) +} // namespace + +SnapSearchResult::SnapSearchResult(float offset, const gfx::RangeF& range) : snap_offset_(offset) { set_visible_range(range); } -void SnapSearchResult::set_visible_range(const SnapVisibleRange& range) { +void SnapSearchResult::set_visible_range(const gfx::RangeF& range) { DCHECK(range.start() <= range.end()); visible_range_ = range; } void SnapSearchResult::Clip(float max_snap, float max_visible) { snap_offset_ = std::max(std::min(snap_offset_, max_snap), 0.0f); - visible_range_ = SnapVisibleRange( - std::max(std::min(visible_range_.start(), max_visible), 0.0f), - std::max(std::min(visible_range_.end(), max_visible), 0.0f)); + visible_range_ = + gfx::RangeF(std::max(std::min(visible_range_.start(), max_visible), 0.0f), + std::max(std::min(visible_range_.end(), max_visible), 0.0f)); } void SnapSearchResult::Union(const SnapSearchResult& other) { DCHECK(snap_offset_ == other.snap_offset_); - visible_range_ = SnapVisibleRange( + visible_range_ = gfx::RangeF( std::min(visible_range_.start(), other.visible_range_.start()), std::max(visible_range_.end(), other.visible_range_.end())); } @@ -88,13 +91,14 @@ void SnapContainerData::AddSnapAreaData(SnapAreaData snap_area_data) { } bool SnapContainerData::FindSnapPosition( - const gfx::ScrollOffset& current_position, - bool should_snap_on_x, - bool should_snap_on_y, + const SnapSelectionStrategy& strategy, gfx::ScrollOffset* snap_position) const { + gfx::ScrollOffset base_position = strategy.base_position(); SnapAxis axis = scroll_snap_type_.axis; - should_snap_on_x &= (axis == SnapAxis::kX || axis == SnapAxis::kBoth); - should_snap_on_y &= (axis == SnapAxis::kY || axis == SnapAxis::kBoth); + bool should_snap_on_x = strategy.ShouldSnapOnX() && + (axis == SnapAxis::kX || axis == SnapAxis::kBoth); + bool should_snap_on_y = strategy.ShouldSnapOnY() && + (axis == SnapAxis::kY || axis == SnapAxis::kBoth); if (!should_snap_on_x && !should_snap_on_y) return false; @@ -102,18 +106,18 @@ bool SnapContainerData::FindSnapPosition( // A region that includes every reachable scroll position. gfx::RectF scrollable_region(0, 0, max_position_.x(), max_position_.y()); if (should_snap_on_x) { - // Start from current offset in the cross axis and assume it's always + // Start from current position in the cross axis and assume it's always // visible. SnapSearchResult initial_snap_position_y = { - current_position.y(), SnapVisibleRange(0, max_position_.x())}; - closest_x = FindClosestValidArea(SearchAxis::kX, current_position.x(), - initial_snap_position_y); + base_position.y(), gfx::RangeF(0, max_position_.x())}; + closest_x = + FindClosestValidArea(SearchAxis::kX, strategy, initial_snap_position_y); } if (should_snap_on_y) { SnapSearchResult initial_snap_position_x = { - current_position.x(), SnapVisibleRange(0, max_position_.y())}; - closest_y = FindClosestValidArea(SearchAxis::kY, current_position.y(), - initial_snap_position_x); + base_position.x(), gfx::RangeF(0, max_position_.y())}; + closest_y = + FindClosestValidArea(SearchAxis::kY, strategy, initial_snap_position_x); } if (!closest_x.has_value() && !closest_y.has_value()) @@ -126,17 +130,18 @@ bool SnapContainerData::FindSnapPosition( if (closest_x.has_value() && closest_y.has_value() && !IsMutualVisible(closest_x.value(), closest_y.value())) { bool candidate_on_x_axis_is_closer = - std::abs(closest_x.value().snap_offset() - current_position.x()) <= - std::abs(closest_y.value().snap_offset() - current_position.y()); - if (candidate_on_x_axis_is_closer) - closest_y = FindClosestValidArea(SearchAxis::kY, current_position.y(), - closest_x.value()); - else - closest_x = FindClosestValidArea(SearchAxis::kX, current_position.x(), - closest_y.value()); + std::abs(closest_x.value().snap_offset() - base_position.x()) <= + std::abs(closest_y.value().snap_offset() - base_position.y()); + if (candidate_on_x_axis_is_closer) { + closest_y = + FindClosestValidArea(SearchAxis::kY, strategy, closest_x.value()); + } else { + closest_x = + FindClosestValidArea(SearchAxis::kX, strategy, closest_y.value()); + } } - *snap_position = current_position; + *snap_position = strategy.current_position(); if (closest_x.has_value()) snap_position->set_x(closest_x.value().snap_offset()); if (closest_y.has_value()) @@ -146,13 +151,36 @@ bool SnapContainerData::FindSnapPosition( base::Optional<SnapSearchResult> SnapContainerData::FindClosestValidArea( SearchAxis axis, - float current_offset, + const SnapSelectionStrategy& strategy, const SnapSearchResult& cros_axis_snap_result) const { - base::Optional<SnapSearchResult> result; - base::Optional<SnapSearchResult> inplace; - // The valid snap offsets immediately before and after the current offset. + // The search result from the snap area that's closest to the search origin. + base::Optional<SnapSearchResult> closest; + // The search result with the intended position if it makes a snap area cover + // the snapport. + base::Optional<SnapSearchResult> covering; + // The search result with the current position as a backup in case no other + // valid snap position exists. + base::Optional<SnapSearchResult> current; + + // The valid snap positions immediately before and after the current position. float prev = std::numeric_limits<float>::lowest(); float next = std::numeric_limits<float>::max(); + + // The current position before the scroll or snap happens. If no other snap + // position exists, this would become a backup option. + float current_position = axis == SearchAxis::kX + ? strategy.current_position().x() + : strategy.current_position().y(); + // The intended position of the scroll operation if there's no snap. This + // scroll position becomes the covering candidate if there is a snap area that + // fully covers the snapport if this position is scrolled to. + float intended_position = axis == SearchAxis::kX + ? strategy.intended_position().x() + : strategy.intended_position().y(); + // The position from which we search for the closest snap position. + float base_position = axis == SearchAxis::kX ? strategy.base_position().x() + : strategy.base_position().y(); + float smallest_distance = axis == SearchAxis::kX ? proximity_range_.x() : proximity_range_.y(); for (const SnapAreaData& area : snap_area_list_) { @@ -160,52 +188,54 @@ base::Optional<SnapSearchResult> SnapContainerData::FindClosestValidArea( continue; SnapSearchResult candidate = GetSnapSearchResult(axis, area); - if (IsSnapportCoveredOnAxis(axis, current_offset, area.rect)) { - // Since snap area is currently covering the snapport, we consider the - // current offset as a valid snap position. - SnapSearchResult inplace_candidate = candidate; - inplace_candidate.set_snap_offset(current_offset); - if (IsMutualVisible(inplace_candidate, cros_axis_snap_result)) { - // If we've already found a valid overflowing area before, we enlarge - // the area's visible region. - if (inplace.has_value()) - inplace.value().Union(inplace_candidate); - else - inplace = inplace_candidate; - // Even if a snap area covers the snapport, we need to continue this - // search to find previous and next snap positions and also to have - // alternative snap candidates if this inplace candidate is ultimately - // rejected. And this covering snap area has its own alignment that may - // generates a snap position rejecting the current inplace candidate. - } + if (IsSnapportCoveredOnAxis(axis, intended_position, area.rect)) { + // Since snap area will cover the snapport, we consider the intended + // position as a valid snap position. + SnapSearchResult covering_candidate = candidate; + covering_candidate.set_snap_offset(intended_position); + if (IsMutualVisible(covering_candidate, cros_axis_snap_result)) + SetOrUpdateResult(covering_candidate, &covering); + // Even if a snap area covers the snapport, we need to continue this + // search to find previous and next snap positions and also to have + // alternative snap candidates if this covering candidate is ultimately + // rejected. And this covering snap area has its own alignment that may + // generates a snap position rejecting the current inplace candidate. } if (!IsMutualVisible(candidate, cros_axis_snap_result)) continue; - float distance = std::abs(candidate.snap_offset() - current_offset); + if (!strategy.IsValidSnapPosition(axis, candidate.snap_offset())) { + if (candidate.snap_offset() == current_position && + scroll_snap_type_.strictness == SnapStrictness::kMandatory) { + SetOrUpdateResult(candidate, ¤t); + } + continue; + } + float distance = std::abs(candidate.snap_offset() - base_position); if (distance < smallest_distance) { smallest_distance = distance; - result = candidate; + closest = candidate; } - if (candidate.snap_offset() < current_offset && + if (candidate.snap_offset() < intended_position && candidate.snap_offset() > prev) prev = candidate.snap_offset(); - if (candidate.snap_offset() > current_offset && + if (candidate.snap_offset() > intended_position && candidate.snap_offset() < next) next = candidate.snap_offset(); } // According to the spec [1], if the snap area is covering the snapport, the - // scroll offset is a valid snap position only if the distance between the + // scroll position is a valid snap position only if the distance between the // geometrically previous and subsequent snap positions in that axis is larger // than size of the snapport in that axis. // [1] https://drafts.csswg.org/css-scroll-snap-1/#snap-overflow float size = axis == SearchAxis::kX ? rect_.width() : rect_.height(); - if (inplace.has_value() && - (prev == std::numeric_limits<float>::lowest() || - next == std::numeric_limits<float>::max() || next - prev > size)) { - return inplace; + if (prev != std::numeric_limits<float>::lowest() && + next != std::numeric_limits<float>::max() && next - prev <= size) { + covering = base::nullopt; } - return result; + const base::Optional<SnapSearchResult>& picked = + strategy.PickBestResult(closest, covering); + return picked.has_value() ? picked : current; } SnapSearchResult SnapContainerData::GetSnapSearchResult( @@ -213,8 +243,8 @@ SnapSearchResult SnapContainerData::GetSnapSearchResult( const SnapAreaData& area) const { SnapSearchResult result; if (axis == SearchAxis::kX) { - result.set_visible_range(SnapVisibleRange(area.rect.y() - rect_.bottom(), - area.rect.bottom() - rect_.y())); + result.set_visible_range(gfx::RangeF(area.rect.y() - rect_.bottom(), + area.rect.bottom() - rect_.y())); // https://www.w3.org/TR/css-scroll-snap-1/#scroll-snap-align switch (area.scroll_snap_align.alignment_inline) { case SnapAlignment::kStart: @@ -232,8 +262,8 @@ SnapSearchResult SnapContainerData::GetSnapSearchResult( } result.Clip(max_position_.x(), max_position_.y()); } else { - result.set_visible_range(SnapVisibleRange(area.rect.x() - rect_.right(), - area.rect.right() - rect_.x())); + result.set_visible_range(gfx::RangeF(area.rect.x() - rect_.right(), + area.rect.right() - rect_.x())); switch (area.scroll_snap_align.alignment_block) { case SnapAlignment::kStart: result.set_snap_offset(area.rect.y() - rect_.y()); diff --git a/chromium/cc/input/scroll_snap_data.h b/chromium/cc/input/scroll_snap_data.h index c18f7374229..3057acf339e 100644 --- a/chromium/cc/input/scroll_snap_data.h +++ b/chromium/cc/input/scroll_snap_data.h @@ -11,9 +11,12 @@ #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, @@ -85,28 +88,13 @@ struct ScrollSnapAlign { SnapAlignment alignment_inline; }; -// We should really use gfx::RangeF. However, it includes windows.h which would -// bring in complexity to the compilation. https://crbug.com/855717 -class SnapVisibleRange { - public: - SnapVisibleRange() {} - SnapVisibleRange(float start, float end) : start_(start), end_(end) {} - bool Contains(float value) const; - float start() const { return start_; } - float end() const { return end_; } - - private: - float start_; - float end_; -}; - // 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 SnapVisibleRange& range); + 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); @@ -118,15 +106,15 @@ class SnapSearchResult { float snap_offset() const { return snap_offset_; } void set_snap_offset(float offset) { snap_offset_ = offset; } - SnapVisibleRange visible_range() const { return visible_range_; } - void set_visible_range(const SnapVisibleRange& range); + 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. - SnapVisibleRange visible_range_; + // 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 @@ -196,9 +184,7 @@ class CC_EXPORT SnapContainerData { return !(*this == other); } - bool FindSnapPosition(const gfx::ScrollOffset& current_position, - bool should_snap_on_x, - bool should_snap_on_y, + bool FindSnapPosition(const SnapSelectionStrategy& strategy, gfx::ScrollOffset* snap_position) const; void AddSnapAreaData(SnapAreaData snap_area_data); @@ -222,8 +208,8 @@ class CC_EXPORT SnapContainerData { gfx::ScrollOffset proximity_range() const { return proximity_range_; } private: - // Finds the best SnapArea candidate that minimizes the distance between - // current and candidate positions, while satisfying two invariants: + // 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 @@ -234,7 +220,7 @@ class CC_EXPORT SnapContainerData { // |snap_offset| and its visible range on the cross axis. base::Optional<SnapSearchResult> FindClosestValidArea( SearchAxis axis, - float current_offset, + const SnapSelectionStrategy& strategy, const SnapSearchResult& cross_axis_snap_result) const; // Returns all the info needed to snap at this area on the given axis, diff --git a/chromium/cc/input/scroll_snap_data_unittest.cc b/chromium/cc/input/scroll_snap_data_unittest.cc index d2347148d0b..6f994d543a0 100644 --- a/chromium/cc/input/scroll_snap_data_unittest.cc +++ b/chromium/cc/input/scroll_snap_data_unittest.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "cc/input/scroll_snap_data.h" +#include "cc/input/snap_selection_strategy.h" #include "testing/gtest/include/gtest/gtest.h" @@ -17,10 +18,12 @@ TEST_F(ScrollSnapDataTest, StartAlignmentCalculation) { SnapAreaData area(ScrollSnapAlign(SnapAlignment::kStart), gfx::RectF(100, 150, 100, 100), false); container.AddSnapAreaData(area); - gfx::ScrollOffset current_position(0, 0); + gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(0, 0), true, + true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); EXPECT_EQ(90, snap_position.x()); EXPECT_EQ(140, snap_position.y()); } @@ -32,10 +35,12 @@ TEST_F(ScrollSnapDataTest, CenterAlignmentCalculation) { SnapAreaData area(ScrollSnapAlign(SnapAlignment::kCenter), gfx::RectF(100, 150, 100, 100), false); container.AddSnapAreaData(area); - gfx::ScrollOffset current_position(0, 0); + gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(0, 0), true, + true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); EXPECT_EQ(40, snap_position.x()); EXPECT_EQ(40, snap_position.y()); } @@ -47,10 +52,12 @@ TEST_F(ScrollSnapDataTest, EndAlignmentCalculation) { SnapAreaData area(ScrollSnapAlign(SnapAlignment::kEnd), gfx::RectF(150, 200, 100, 100), false); container.AddSnapAreaData(area); - gfx::ScrollOffset current_position(0, 0); + gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(0, 0), true, + true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); EXPECT_EQ(40, snap_position.x()); EXPECT_EQ(90, snap_position.y()); } @@ -62,10 +69,12 @@ TEST_F(ScrollSnapDataTest, UnreachableSnapPositionCalculation) { SnapAreaData area(ScrollSnapAlign(SnapAlignment::kEnd, SnapAlignment::kStart), gfx::RectF(200, 0, 100, 100), false); container.AddSnapAreaData(area); - gfx::ScrollOffset current_position(50, 50); + gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(50, 50), + true, true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); // Aligning to start on x would lead the scroll offset larger than max, and // aligning to end on y would lead the scroll offset smaller than zero. So // we expect these are clamped. @@ -85,13 +94,15 @@ TEST_F(ScrollSnapDataTest, FindsClosestSnapPositionIndependently) { gfx::RectF(0, 70, 150, 150), false); SnapAreaData snap_on_both(ScrollSnapAlign(SnapAlignment::kStart), gfx::RectF(50, 150, 150, 150), false); - gfx::ScrollOffset current_position(100, 100); container.AddSnapAreaData(snap_x_only); container.AddSnapAreaData(snap_y_only); container.AddSnapAreaData(snap_on_both); + gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(100, 100), + true, true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); EXPECT_EQ(80, snap_position.x()); EXPECT_EQ(70, snap_position.y()); } @@ -108,13 +119,15 @@ TEST_F(ScrollSnapDataTest, FindsClosestSnapPositionOnAxisValueBoth) { gfx::RectF(0, 70, 150, 150), false); SnapAreaData snap_on_both(ScrollSnapAlign(SnapAlignment::kStart), gfx::RectF(50, 150, 150, 150), false); - gfx::ScrollOffset current_position(40, 120); + container.AddSnapAreaData(snap_x_only); container.AddSnapAreaData(snap_y_only); container.AddSnapAreaData(snap_on_both); gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(40, 120), + true, true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); EXPECT_EQ(50, snap_position.x()); EXPECT_EQ(150, snap_position.y()); } @@ -129,12 +142,14 @@ TEST_F(ScrollSnapDataTest, DoesNotSnapOnNonScrolledAxis) { SnapAreaData snap_y_only( ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone), gfx::RectF(0, 70, 150, 150), false); - gfx::ScrollOffset current_position(100, 100); container.AddSnapAreaData(snap_x_only); container.AddSnapAreaData(snap_y_only); + gfx::ScrollOffset snap_position; - EXPECT_TRUE(container.FindSnapPosition(current_position, true, false, - &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(100, 100), + true, false); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); EXPECT_EQ(80, snap_position.x()); EXPECT_EQ(100, snap_position.y()); } @@ -149,19 +164,20 @@ TEST_F(ScrollSnapDataTest, DoesNotSnapOnNonVisibleAreas) { SnapAreaData snap_y_only( ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone), gfx::RectF(400, 300, 100, 100), false); - gfx::ScrollOffset current_position(0, 0); + container.AddSnapAreaData(snap_x_only); container.AddSnapAreaData(snap_y_only); gfx::ScrollOffset snap_position; - EXPECT_FALSE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(0, 0), true, + true); + EXPECT_FALSE(container.FindSnapPosition(*strategy, &snap_position)); } TEST_F(ScrollSnapDataTest, SnapOnClosestAxisFirstIfVisibilityConflicts) { SnapContainerData container( ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory), gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(600, 800)); - gfx::ScrollOffset current_position(0, 0); // Both the areas are currently visible. // However, if we snap to them on x and y independently, none is visible after @@ -180,9 +196,12 @@ TEST_F(ScrollSnapDataTest, SnapOnClosestAxisFirstIfVisibilityConflicts) { container.AddSnapAreaData(snap_x); container.AddSnapAreaData(snap_y1); container.AddSnapAreaData(snap_y2); + gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(0, 0), true, + true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); EXPECT_EQ(150, snap_position.x()); EXPECT_EQ(80, snap_position.y()); } @@ -193,13 +212,15 @@ TEST_F(ScrollSnapDataTest, DoesNotSnapToPositionsOutsideProximityRange) { gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(600, 800)); container.set_proximity_range(gfx::ScrollOffset(50, 50)); - gfx::ScrollOffset current_position(100, 100); SnapAreaData area(ScrollSnapAlign(SnapAlignment::kStart), gfx::RectF(80, 160, 100, 100), false); container.AddSnapAreaData(area); + gfx::ScrollOffset snap_position; - EXPECT_TRUE( - container.FindSnapPosition(current_position, true, true, &snap_position)); + std::unique_ptr<SnapSelectionStrategy> strategy = + SnapSelectionStrategy::CreateForEndPosition(gfx::ScrollOffset(100, 100), + true, true); + EXPECT_TRUE(container.FindSnapPosition(*strategy, &snap_position)); // The snap position on x, 80, is within the proximity range of [50, 150]. // However, the snap position on y, 160, is outside the proximity range of diff --git a/chromium/cc/input/snap_fling_controller.h b/chromium/cc/input/snap_fling_controller.h index 20f305651c2..eb07106796c 100644 --- a/chromium/cc/input/snap_fling_controller.h +++ b/chromium/cc/input/snap_fling_controller.h @@ -24,8 +24,8 @@ class SnapFlingCurve; class SnapFlingClient { public: virtual bool GetSnapFlingInfo(const gfx::Vector2dF& natural_displacement, - gfx::Vector2dF* out_initial_offset, - gfx::Vector2dF* out_target_offset) const = 0; + gfx::Vector2dF* out_initial_position, + gfx::Vector2dF* out_target_position) const = 0; virtual gfx::Vector2dF ScrollByForSnapFling(const gfx::Vector2dF& delta) = 0; virtual void ScrollEndForSnapFling() = 0; virtual void RequestAnimationForSnapFling() = 0; diff --git a/chromium/cc/input/snap_selection_strategy.cc b/chromium/cc/input/snap_selection_strategy.cc new file mode 100644 index 00000000000..ce0f2655ecf --- /dev/null +++ b/chromium/cc/input/snap_selection_strategy.cc @@ -0,0 +1,151 @@ +// Copyright 2018 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. + +#include "cc/input/snap_selection_strategy.h" + +namespace cc { + +std::unique_ptr<SnapSelectionStrategy> +SnapSelectionStrategy::CreateForEndPosition( + const gfx::ScrollOffset& current_position, + bool scrolled_x, + bool scrolled_y) { + return std::make_unique<EndPositionStrategy>(current_position, scrolled_x, + scrolled_y); +} + +std::unique_ptr<SnapSelectionStrategy> +SnapSelectionStrategy::CreateForDirection(gfx::ScrollOffset current_position, + gfx::ScrollOffset step) { + return std::make_unique<DirectionStrategy>(current_position, step); +} + +std::unique_ptr<SnapSelectionStrategy> +SnapSelectionStrategy::CreateForEndAndDirection( + gfx::ScrollOffset current_position, + gfx::ScrollOffset displacement) { + return std::make_unique<EndAndDirectionStrategy>(current_position, + displacement); +} + +bool EndPositionStrategy::ShouldSnapOnX() const { + return scrolled_x_; +} + +bool EndPositionStrategy::ShouldSnapOnY() const { + return scrolled_y_; +} + +gfx::ScrollOffset EndPositionStrategy::intended_position() const { + return current_position_; +} + +gfx::ScrollOffset EndPositionStrategy::base_position() const { + return current_position_; +} + +// |position| is unused in this method. +bool EndPositionStrategy::IsValidSnapPosition(SearchAxis axis, + float position) const { + return (scrolled_x_ && axis == SearchAxis::kX) || + (scrolled_y_ && axis == SearchAxis::kY); +} + +const base::Optional<SnapSearchResult>& EndPositionStrategy::PickBestResult( + const base::Optional<SnapSearchResult>& closest, + const base::Optional<SnapSearchResult>& covering) const { + return covering.has_value() ? covering : closest; +} + +bool DirectionStrategy::ShouldSnapOnX() const { + return step_.x() != 0; +} + +bool DirectionStrategy::ShouldSnapOnY() const { + return step_.y() != 0; +} + +gfx::ScrollOffset DirectionStrategy::intended_position() const { + return current_position_ + step_; +} + +gfx::ScrollOffset DirectionStrategy::base_position() const { + return current_position_; +} + +bool DirectionStrategy::IsValidSnapPosition(SearchAxis axis, + float position) const { + if (axis == SearchAxis::kX) { + return (step_.x() > 0 && + position > current_position_.x()) || // "Right" arrow + (step_.x() < 0 && position < current_position_.x()); // "Left" arrow + } else { + return (step_.y() > 0 && + position > current_position_.y()) || // "Down" arrow + (step_.y() < 0 && position < current_position_.y()); // "Up" arrow + } +} + +const base::Optional<SnapSearchResult>& DirectionStrategy::PickBestResult( + const base::Optional<SnapSearchResult>& closest, + const base::Optional<SnapSearchResult>& covering) const { + // We choose the |closest| result only if the default landing position (using + // the default step) is not a valid snap position (not making a snap area + // covering the snapport), or the |closest| is closer than the default landing + // position. + if (!closest.has_value()) + return covering; + if (!covering.has_value()) + return closest; + + // "Right" or "Down" arrow. + if ((step_.x() > 0 || step_.y() > 0) && + closest.value().snap_offset() < covering.value().snap_offset()) { + return closest; + } + // "Left" or "Up" arrow. + if ((step_.x() < 0 || step_.y() < 0) && + closest.value().snap_offset() > covering.value().snap_offset()) { + return closest; + } + + return covering; +} + +bool EndAndDirectionStrategy::ShouldSnapOnX() const { + return displacement_.x() != 0; +} + +bool EndAndDirectionStrategy::ShouldSnapOnY() const { + return displacement_.y() != 0; +} + +gfx::ScrollOffset EndAndDirectionStrategy::intended_position() const { + return current_position_ + displacement_; +} + +gfx::ScrollOffset EndAndDirectionStrategy::base_position() const { + return current_position_ + displacement_; +} + +bool EndAndDirectionStrategy::IsValidSnapPosition(SearchAxis axis, + float position) const { + if (axis == SearchAxis::kX) { + return (displacement_.x() > 0 && + position > current_position_.x()) || // Right + (displacement_.x() < 0 && position < current_position_.x()); // Left + } else { + return (displacement_.y() > 0 && + position > current_position_.y()) || // Down + (displacement_.y() < 0 && position < current_position_.y()); // Up + } +} + +const base::Optional<SnapSearchResult>& EndAndDirectionStrategy::PickBestResult( + const base::Optional<SnapSearchResult>& closest, + const base::Optional<SnapSearchResult>& covering) const { + return covering.has_value() ? covering : closest; +} + +} // namespace cc diff --git a/chromium/cc/input/snap_selection_strategy.h b/chromium/cc/input/snap_selection_strategy.h new file mode 100644 index 00000000000..cebc63e96b5 --- /dev/null +++ b/chromium/cc/input/snap_selection_strategy.h @@ -0,0 +1,170 @@ +// Copyright 2018 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_SNAP_SELECTION_STRATEGY_H_ +#define CC_INPUT_SNAP_SELECTION_STRATEGY_H_ + +#include "scroll_snap_data.h" + +#include <memory> + +namespace cc { + +// 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::ScrollOffset& current_position, + bool scrolled_x, + bool scrolled_y); + static std::unique_ptr<SnapSelectionStrategy> CreateForDirection( + gfx::ScrollOffset current_position, + gfx::ScrollOffset step); + static std::unique_ptr<SnapSelectionStrategy> CreateForEndAndDirection( + gfx::ScrollOffset current_position, + gfx::ScrollOffset displacement); + + // 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 the end position of the scroll if no snap interferes. + virtual gfx::ScrollOffset intended_position() const = 0; + // Returns the scroll position from which the snap position should minimize + // its distance. + virtual gfx::ScrollOffset base_position() const = 0; + // Returns the current scroll position of the snap container. + const gfx::ScrollOffset& 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; + + // 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 base::Optional<SnapSearchResult>& PickBestResult( + const base::Optional<SnapSearchResult>& closest, + const base::Optional<SnapSearchResult>& covering) const = 0; + + protected: + explicit SnapSelectionStrategy(const gfx::ScrollOffset& current_position) + : current_position_(current_position) {} + const gfx::ScrollOffset 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::ScrollOffset& current_position, + bool scrolled_x, + bool scrolled_y) + : SnapSelectionStrategy(current_position), + scrolled_x_(scrolled_x), + scrolled_y_(scrolled_y) {} + ~EndPositionStrategy() override = default; + + bool ShouldSnapOnX() const override; + bool ShouldSnapOnY() const override; + + gfx::ScrollOffset intended_position() const override; + gfx::ScrollOffset base_position() const override; + + bool IsValidSnapPosition(SearchAxis axis, float position) const override; + + const base::Optional<SnapSearchResult>& PickBestResult( + const base::Optional<SnapSearchResult>& closest, + const base::Optional<SnapSearchResult>& covering) 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_; +}; + +// 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: + DirectionStrategy(const gfx::ScrollOffset& current_position, + const gfx::ScrollOffset& step) + : SnapSelectionStrategy(current_position), step_(step) {} + ~DirectionStrategy() override = default; + + bool ShouldSnapOnX() const override; + bool ShouldSnapOnY() const override; + + gfx::ScrollOffset intended_position() const override; + gfx::ScrollOffset base_position() const override; + + bool IsValidSnapPosition(SearchAxis axis, float position) const override; + + const base::Optional<SnapSearchResult>& PickBestResult( + const base::Optional<SnapSearchResult>& closest, + const base::Optional<SnapSearchResult>& covering) const override; + + private: + // The default step for this DirectionStrategy. + const gfx::ScrollOffset step_; +}; + +// 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: + EndAndDirectionStrategy(const gfx::ScrollOffset& current_position, + const gfx::ScrollOffset& displacement) + : SnapSelectionStrategy(current_position), displacement_(displacement) {} + ~EndAndDirectionStrategy() override = default; + + bool ShouldSnapOnX() const override; + bool ShouldSnapOnY() const override; + + gfx::ScrollOffset intended_position() const override; + gfx::ScrollOffset base_position() const override; + + bool IsValidSnapPosition(SearchAxis axis, float position) const override; + + const base::Optional<SnapSearchResult>& PickBestResult( + const base::Optional<SnapSearchResult>& closest, + const base::Optional<SnapSearchResult>& covering) const override; + + private: + const gfx::ScrollOffset displacement_; +}; + +} // namespace cc + +#endif // CC_INPUT_SNAP_SELECTION_STRATEGY_H_ |