| // Copyright 2014 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 "content/browser/web_contents/aura/gesture_nav_simple.h" |
| |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "cc/paint/paint_flags.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/browser/frame_host/navigation_controller_impl.h" |
| #include "content/browser/renderer_host/overscroll_controller.h" |
| #include "content/browser/web_contents/aura/types.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/overscroll_configuration.h" |
| #include "third_party/skia/include/core/SkDrawLooper.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_delegate.h" |
| #include "ui/compositor/paint_recorder.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/animation/animation_delegate.h" |
| #include "ui/gfx/animation/linear_animation.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/shadow_value.h" |
| #include "ui/gfx/skia_paint_util.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Parameters defining the arrow icon inside the affordance. |
| const int kArrowSize = 16; |
| const SkColor kArrowColor = gfx::kGoogleBlue500; |
| const float kArrowFullOpacity = 1.f; |
| const float kArrowInitialOpacity = .3f; |
| const float kReloadArrowInitialRotation = -180.f; |
| |
| // The arrow opacity remains constant until progress reaches this threshold, |
| // then increases quickly as the progress increases beyond the threshold |
| const float kArrowOpacityProgressThreshold = .9f; |
| |
| // Parameters defining the background circle of the affordance. |
| const int kBackgroundRadius = 18; |
| const SkColor kBackgroundColor = SK_ColorWHITE; |
| const int kBgShadowOffsetY = 2; |
| const int kBgShadowBlurRadius = 8; |
| const SkColor kBgShadowColor = SkColorSetA(SK_ColorBLACK, 0x4D); |
| |
| // Parameters defining the affordance ripple. The ripple fades in and grows as |
| // the user drags the affordance until it reaches |kMaxRippleRadius|. If the |
| // overscroll is successful, the ripple will burst by fading out and growing to |
| // |kMaxRippleBurstRadius|. |
| const int kMaxRippleRadius = 54; |
| const SkColor kRippleColor = SkColorSetA(gfx::kGoogleBlue500, 0x66); |
| const int kMaxRippleBurstRadius = 72; |
| const gfx::Tween::Type kBurstAnimationTweenType = gfx::Tween::EASE_IN; |
| constexpr auto kRippleBurstAnimationDuration = |
| base::TimeDelta::FromMilliseconds(160); |
| |
| // Offset of the affordance when it is at the activation threshold. Since the |
| // affordance is initially out of content bounds, this is the offset of the |
| // farther side of the affordance (which equals 128 + 18). |
| const int kAffordanceActivationOffset = 146; |
| |
| // Extra offset of the affordance when it is dragged past the activation |
| // threshold. |
| const int kAffordanceExtraOffset = 72; |
| const gfx::Tween::Type kExtraDragTweenType = gfx::Tween::Type::EASE_OUT; |
| |
| constexpr float kExtraAffordanceRatio = |
| static_cast<float>(kAffordanceExtraOffset) / |
| static_cast<float>(kAffordanceActivationOffset); |
| |
| // Parameters defining animation when the affordance is aborted. |
| const gfx::Tween::Type kAbortAnimationTweenType = gfx::Tween::EASE_IN; |
| constexpr auto kAbortAnimationDuration = base::TimeDelta::FromMilliseconds(300); |
| |
| bool ShouldNavigateForward(NavigationController* controller, |
| OverscrollMode mode) { |
| return mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST) && |
| controller->CanGoForward(); |
| } |
| |
| bool ShouldNavigateBack(NavigationController* controller, OverscrollMode mode) { |
| return mode == (base::i18n::IsRTL() ? OVERSCROLL_WEST : OVERSCROLL_EAST) && |
| controller->CanGoBack(); |
| } |
| |
| bool ShouldReload(const NavigationController& controller, OverscrollMode mode) { |
| return mode == OVERSCROLL_SOUTH; |
| } |
| |
| NavigationDirection GetDirectionFromMode(OverscrollMode mode) { |
| if (mode == (base::i18n::IsRTL() ? OVERSCROLL_WEST : OVERSCROLL_EAST)) |
| return NavigationDirection::BACK; |
| if (mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST)) |
| return NavigationDirection::FORWARD; |
| if (mode == OVERSCROLL_SOUTH) |
| return NavigationDirection::RELOAD; |
| return NavigationDirection::NONE; |
| } |
| |
| // Records UMA histogram and also user action for the cancelled overscroll. |
| void RecordGestureOverscrollCancelled(NavigationDirection direction, |
| OverscrollSource source) { |
| DCHECK_NE(direction, NavigationDirection::NONE); |
| DCHECK_NE(source, OverscrollSource::NONE); |
| UMA_HISTOGRAM_ENUMERATION("Overscroll.Cancelled3", |
| GetUmaNavigationType(direction, source), |
| NAVIGATION_TYPE_COUNT); |
| if (direction == NavigationDirection::BACK) |
| RecordAction(base::UserMetricsAction("Overscroll_Cancelled.Back")); |
| else if (direction == NavigationDirection::FORWARD) |
| RecordAction(base::UserMetricsAction("Overscroll_Cancelled.Forward")); |
| else |
| RecordAction(base::UserMetricsAction("Overscroll_Cancelled.Reload")); |
| } |
| |
| // Responsible for drawing the affordance arrow. Depending on the overscroll |
| // mode, it will draw back, forward, or reload arrow. |
| class Arrow : public ui::LayerDelegate { |
| public: |
| explicit Arrow(OverscrollMode mode); |
| ~Arrow() override; |
| |
| ui::Layer* layer() { return &layer_; } |
| |
| private: |
| // ui::LayerDelegate: |
| void OnPaintLayer(const ui::PaintContext& context) override; |
| void OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override; |
| |
| const OverscrollMode mode_; |
| ui::Layer layer_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Arrow); |
| }; |
| |
| Arrow::Arrow(OverscrollMode mode) : mode_(mode), layer_(ui::LAYER_TEXTURED) { |
| DCHECK(mode_ == OVERSCROLL_EAST || mode_ == OVERSCROLL_WEST || |
| mode_ == OVERSCROLL_SOUTH); |
| gfx::Rect bounds(kArrowSize, kArrowSize); |
| bounds.set_x(kMaxRippleBurstRadius - kArrowSize / 2); |
| bounds.set_y(kMaxRippleBurstRadius - kArrowSize / 2); |
| layer_.SetBounds(bounds); |
| layer_.SetFillsBoundsOpaquely(false); |
| layer_.set_delegate(this); |
| } |
| |
| Arrow::~Arrow() {} |
| |
| void Arrow::OnPaintLayer(const ui::PaintContext& context) { |
| const gfx::VectorIcon* icon = nullptr; |
| switch (mode_) { |
| case OVERSCROLL_EAST: |
| icon = &vector_icons::kBackArrowIcon; |
| break; |
| case OVERSCROLL_WEST: |
| icon = &vector_icons::kForwardArrowIcon; |
| break; |
| case OVERSCROLL_SOUTH: |
| icon = &vector_icons::kReloadIcon; |
| break; |
| case OVERSCROLL_NORTH: |
| case OVERSCROLL_NONE: |
| NOTREACHED(); |
| } |
| const gfx::ImageSkia& image = |
| gfx::CreateVectorIcon(*icon, kArrowSize, kArrowColor); |
| |
| ui::PaintRecorder recorder(context, layer_.size()); |
| gfx::Canvas* canvas = recorder.canvas(); |
| canvas->DrawImageInt(image, 0, 0); |
| } |
| |
| void Arrow::OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) {} |
| |
| } // namespace |
| |
| // This class is responsible for creating, painting, and positioning the layer |
| // for the gesture nav affordance. |
| class Affordance : public ui::LayerDelegate, public gfx::AnimationDelegate { |
| public: |
| Affordance(GestureNavSimple* owner, |
| OverscrollMode mode, |
| const gfx::Rect& content_bounds, |
| float max_drag_progress); |
| ~Affordance() override; |
| |
| // Sets drag progress. 0 means no progress. 1 means full progress. Values more |
| // than 1 are also allowed for when the user drags beyond the completion |
| // threshold. |
| void SetDragProgress(float progress); |
| |
| // Aborts the affordance and animates it back. This will delete |this| |
| // instance after the animation. |
| void Abort(); |
| |
| // Completes the affordance by doing a ripple burst animation. This will |
| // delete |this| instance after the animation. |
| void Complete(); |
| |
| // Returns the root layer of the affordance. |
| ui::Layer* root_layer() { return &root_layer_; } |
| |
| // Returns whether the affordance is performing abort or complete animation. |
| bool IsFinishing() const { return state_ != State::DRAGGING; } |
| |
| private: |
| enum class State { DRAGGING, ABORTING, COMPLETING }; |
| |
| // Helper function that returns the origin for painted layer according to the |
| // overscroll mode. |
| gfx::Point GetPaintedLayerOrigin(const gfx::Rect& content_bounds) const; |
| |
| void UpdatePaintedLayer(); |
| void UpdateArrowLayer(); |
| void UpdateLayers(); |
| void SchedulePaint(); |
| void SetAbortProgress(float progress); |
| void SetCompleteProgress(float progress); |
| |
| // Helper function that returns the affordance progress according to the |
| // current values of different progress values (drag progress and abort |
| // progress). 1 means the affordance is at the activation threshold. |
| float GetAffordanceProgress() const; |
| |
| // ui::LayerDelegate: |
| void OnPaintLayer(const ui::PaintContext& context) override; |
| void OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override; |
| |
| // gfx::AnimationDelegate: |
| void AnimationEnded(const gfx::Animation* animation) override; |
| void AnimationProgressed(const gfx::Animation* animation) override; |
| void AnimationCanceled(const gfx::Animation* animation) override; |
| |
| GestureNavSimple* const owner_; |
| |
| const OverscrollMode mode_; |
| |
| // Maximum value for drag progress that can be reached if the user drags |
| // entire width of the page/screen. |
| const float max_drag_progress_; |
| |
| // Root layer of the affordance. This is used to clip the affordance to the |
| // content bounds. |
| ui::Layer root_layer_; |
| |
| // Layer that actually paints the affordance. |
| ui::Layer painted_layer_; |
| |
| // The arrow for the affordance. |
| Arrow arrow_; |
| |
| // Values that determine current state of the affordance. |
| State state_ = State::DRAGGING; |
| float drag_progress_ = 0.f; |
| float abort_progress_ = 0.f; |
| float complete_progress_ = 0.f; |
| |
| std::unique_ptr<gfx::LinearAnimation> animation_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Affordance); |
| }; |
| |
| Affordance::Affordance(GestureNavSimple* owner, |
| OverscrollMode mode, |
| const gfx::Rect& content_bounds, |
| float max_drag_progress) |
| : owner_(owner), |
| mode_(mode), |
| max_drag_progress_(max_drag_progress), |
| root_layer_(ui::LAYER_NOT_DRAWN), |
| painted_layer_(ui::LAYER_TEXTURED), |
| arrow_(mode) { |
| DCHECK(mode_ == OVERSCROLL_EAST || mode_ == OVERSCROLL_WEST || |
| mode_ == OVERSCROLL_SOUTH); |
| |
| root_layer_.SetBounds(content_bounds); |
| root_layer_.SetMasksToBounds(true); |
| |
| painted_layer_.SetFillsBoundsOpaquely(false); |
| gfx::Rect painted_layer_bounds(2 * kMaxRippleBurstRadius, |
| 2 * kMaxRippleBurstRadius); |
| painted_layer_bounds.set_origin(GetPaintedLayerOrigin(content_bounds)); |
| painted_layer_.SetBounds(painted_layer_bounds); |
| painted_layer_.set_delegate(this); |
| |
| painted_layer_.Add(arrow_.layer()); |
| root_layer_.Add(&painted_layer_); |
| } |
| |
| Affordance::~Affordance() {} |
| |
| void Affordance::SetDragProgress(float progress) { |
| DCHECK_EQ(State::DRAGGING, state_); |
| DCHECK_LE(0.f, progress); |
| |
| if (drag_progress_ == progress) |
| return; |
| drag_progress_ = progress; |
| |
| UpdateLayers(); |
| SchedulePaint(); |
| } |
| |
| void Affordance::Abort() { |
| DCHECK_EQ(State::DRAGGING, state_); |
| |
| state_ = State::ABORTING; |
| |
| animation_ = std::make_unique<gfx::LinearAnimation>( |
| GetAffordanceProgress() * kAbortAnimationDuration, |
| gfx::LinearAnimation::kDefaultFrameRate, this); |
| animation_->Start(); |
| } |
| |
| void Affordance::Complete() { |
| DCHECK_EQ(State::DRAGGING, state_); |
| DCHECK_LE(1.f, drag_progress_); |
| |
| state_ = State::COMPLETING; |
| |
| animation_ = std::make_unique<gfx::LinearAnimation>( |
| kRippleBurstAnimationDuration, gfx::LinearAnimation::kDefaultFrameRate, |
| this); |
| animation_->Start(); |
| } |
| |
| gfx::Point Affordance::GetPaintedLayerOrigin( |
| const gfx::Rect& content_bounds) const { |
| DCHECK_NE(OVERSCROLL_NONE, mode_); |
| gfx::Point origin; |
| if (mode_ == OVERSCROLL_SOUTH) { |
| origin.set_x( |
| std::max(0, content_bounds.width() / 2 - kMaxRippleBurstRadius)); |
| origin.set_y(-kMaxRippleBurstRadius - kBackgroundRadius); |
| } else { |
| origin.set_y( |
| std::max(0, content_bounds.height() / 2 - kMaxRippleBurstRadius)); |
| if (mode_ == OVERSCROLL_EAST) { |
| origin.set_x(-kMaxRippleBurstRadius - kBackgroundRadius); |
| } else { |
| origin.set_x(content_bounds.width() - kMaxRippleBurstRadius + |
| kBackgroundRadius); |
| } |
| } |
| return origin; |
| } |
| |
| void Affordance::UpdatePaintedLayer() { |
| const float offset = GetAffordanceProgress() * kAffordanceActivationOffset; |
| gfx::Transform transform; |
| if (mode_ == OVERSCROLL_EAST) |
| transform.Translate(offset, 0); |
| else if (mode_ == OVERSCROLL_WEST) |
| transform.Translate(-offset, 0); |
| else |
| transform.Translate(0, offset); |
| painted_layer_.SetTransform(transform); |
| } |
| |
| void Affordance::UpdateArrowLayer() { |
| const float progress = GetAffordanceProgress(); |
| const float capped_progress = std::min(1.f, progress); |
| gfx::Transform transform; |
| if (mode_ == OVERSCROLL_SOUTH) { |
| // For pull-to-refresh, the arrow should rotate starting from |
| // |kReloadArrowInitialRotation| until it reaches 0 when |progress| reaches |
| // 1; i.e., activation threshold. The arrow will continue rotation after |
| // activation threshold. |
| gfx::Vector2dF offset(kArrowSize / 2.f, kArrowSize / 2.f); |
| transform.Translate(offset); |
| transform.Rotate(kReloadArrowInitialRotation * (1 - progress)); |
| transform.Translate(-offset); |
| } else { |
| // Calculate the offset for the arrow relative to its final position. |
| const float offset = |
| (1 - capped_progress) * (-kBackgroundRadius + kArrowSize / 2.f); |
| transform.Translate( |
| gfx::Vector2dF(mode_ == OVERSCROLL_EAST ? offset : -offset, 0)); |
| } |
| arrow_.layer()->SetTransform(transform); |
| |
| // The arrow opacity is fixed before progress reaches |
| // kArrowOpacityProgressThreshold and after that increases linearly to 1; |
| // essentially, making a quick bump at the end. |
| float opacity = kArrowInitialOpacity; |
| if (capped_progress > kArrowOpacityProgressThreshold) { |
| const float max_opacity_bump = kArrowFullOpacity - kArrowInitialOpacity; |
| const float opacity_bump_ratio = |
| std::min(1.f, (capped_progress - kArrowOpacityProgressThreshold) / |
| (1.f - kArrowOpacityProgressThreshold)); |
| opacity += opacity_bump_ratio * max_opacity_bump; |
| } |
| arrow_.layer()->SetOpacity(opacity); |
| } |
| |
| void Affordance::UpdateLayers() { |
| UpdatePaintedLayer(); |
| UpdateArrowLayer(); |
| } |
| |
| void Affordance::SchedulePaint() { |
| painted_layer_.SchedulePaint(gfx::Rect(painted_layer_.size())); |
| } |
| |
| void Affordance::SetAbortProgress(float progress) { |
| DCHECK_EQ(State::ABORTING, state_); |
| DCHECK_LE(0.f, progress); |
| DCHECK_GE(1.f, progress); |
| |
| if (abort_progress_ == progress) |
| return; |
| abort_progress_ = progress; |
| |
| UpdateLayers(); |
| SchedulePaint(); |
| } |
| |
| void Affordance::SetCompleteProgress(float progress) { |
| DCHECK_EQ(State::COMPLETING, state_); |
| DCHECK_LE(0.f, progress); |
| DCHECK_GE(1.f, progress); |
| |
| if (complete_progress_ == progress) |
| return; |
| complete_progress_ = progress; |
| |
| painted_layer_.SetOpacity(gfx::Tween::CalculateValue(kBurstAnimationTweenType, |
| 1 - complete_progress_)); |
| SchedulePaint(); |
| } |
| |
| float Affordance::GetAffordanceProgress() const { |
| // Apply tween on the drag progress. |
| float affordance_progress = drag_progress_; |
| if (drag_progress_ >= 1.f) { |
| float extra_progress = max_drag_progress_ == 1.f |
| ? 1.f |
| : std::min(1.f, (drag_progress_ - 1.f) / |
| (max_drag_progress_ - 1.f)); |
| affordance_progress = |
| 1 + gfx::Tween::CalculateValue(kExtraDragTweenType, extra_progress) * |
| kExtraAffordanceRatio; |
| } |
| |
| // Apply abort progress, if any. |
| affordance_progress *= |
| 1 - gfx::Tween::CalculateValue(kAbortAnimationTweenType, abort_progress_); |
| |
| return affordance_progress; |
| } |
| |
| void Affordance::OnPaintLayer(const ui::PaintContext& context) { |
| DCHECK(drag_progress_ >= 1.f || state_ != State::COMPLETING); |
| DCHECK(abort_progress_ == 0.f || state_ == State::ABORTING); |
| DCHECK(complete_progress_ == 0.f || state_ == State::COMPLETING); |
| |
| ui::PaintRecorder recorder(context, painted_layer_.size()); |
| gfx::Canvas* canvas = recorder.canvas(); |
| |
| gfx::PointF center_point(kMaxRippleBurstRadius, kMaxRippleBurstRadius); |
| float progress = std::min(1.f, GetAffordanceProgress()); |
| |
| // Draw the ripple. |
| cc::PaintFlags ripple_flags; |
| ripple_flags.setAntiAlias(true); |
| ripple_flags.setStyle(cc::PaintFlags::kFill_Style); |
| ripple_flags.setColor(kRippleColor); |
| float ripple_radius; |
| if (state_ == State::COMPLETING) { |
| const float burst_progress = gfx::Tween::CalculateValue( |
| kBurstAnimationTweenType, complete_progress_); |
| ripple_radius = kMaxRippleRadius + |
| burst_progress * (kMaxRippleBurstRadius - kMaxRippleRadius); |
| } else { |
| ripple_radius = |
| kBackgroundRadius + progress * (kMaxRippleRadius - kBackgroundRadius); |
| } |
| canvas->DrawCircle(center_point, ripple_radius, ripple_flags); |
| |
| // Draw the arrow background circle with the shadow. |
| cc::PaintFlags bg_flags; |
| bg_flags.setAntiAlias(true); |
| bg_flags.setStyle(cc::PaintFlags::kFill_Style); |
| bg_flags.setColor(kBackgroundColor); |
| gfx::ShadowValues shadow; |
| shadow.emplace_back(gfx::Vector2d(0, kBgShadowOffsetY), kBgShadowBlurRadius, |
| kBgShadowColor); |
| bg_flags.setLooper(gfx::CreateShadowDrawLooper(shadow)); |
| canvas->DrawCircle(center_point, kBackgroundRadius, bg_flags); |
| } |
| |
| void Affordance::OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) {} |
| |
| void Affordance::AnimationEnded(const gfx::Animation* animation) { |
| owner_->OnAffordanceAnimationEnded(); |
| } |
| |
| void Affordance::AnimationProgressed(const gfx::Animation* animation) { |
| switch (state_) { |
| case State::DRAGGING: |
| NOTREACHED(); |
| break; |
| case State::ABORTING: |
| SetAbortProgress(animation->GetCurrentValue()); |
| break; |
| case State::COMPLETING: |
| SetCompleteProgress(animation->GetCurrentValue()); |
| break; |
| } |
| } |
| |
| void Affordance::AnimationCanceled(const gfx::Animation* animation) { |
| NOTREACHED(); |
| } |
| |
| GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents) |
| : web_contents_(web_contents) {} |
| |
| GestureNavSimple::~GestureNavSimple() {} |
| |
| void GestureNavSimple::OnAffordanceAnimationEnded() { |
| affordance_ = nullptr; |
| } |
| |
| gfx::Size GestureNavSimple::GetDisplaySize() const { |
| return display::Screen::GetScreen() |
| ->GetDisplayNearestView(web_contents_->GetNativeView()) |
| .size(); |
| } |
| |
| bool GestureNavSimple::OnOverscrollUpdate(float delta_x, float delta_y) { |
| if (!affordance_ || affordance_->IsFinishing()) |
| return false; |
| float delta = std::abs(mode_ == OVERSCROLL_SOUTH ? delta_y : delta_x); |
| DCHECK_LE(delta, max_delta_); |
| affordance_->SetDragProgress(delta / completion_threshold_); |
| return true; |
| } |
| |
| void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode) { |
| if (mode_ == OverscrollMode::OVERSCROLL_NONE) { |
| // Previous mode change has been ignored because of overscroll-behavior |
| // value; so, ignore this, too. |
| return; |
| } |
| |
| DCHECK_EQ(mode_, overscroll_mode); |
| |
| mode_ = OVERSCROLL_NONE; |
| OverscrollSource overscroll_source = source_; |
| source_ = OverscrollSource::NONE; |
| if (!affordance_ || affordance_->IsFinishing()) |
| return; |
| |
| affordance_->Complete(); |
| |
| NavigationControllerImpl& controller = web_contents_->GetController(); |
| NavigationDirection direction = NavigationDirection::NONE; |
| if (ShouldNavigateForward(&controller, overscroll_mode)) { |
| controller.GoForward(); |
| direction = NavigationDirection::FORWARD; |
| } else if (ShouldNavigateBack(&controller, overscroll_mode)) { |
| controller.GoBack(); |
| direction = NavigationDirection::BACK; |
| } else if (ShouldReload(controller, overscroll_mode)) { |
| controller.Reload(ReloadType::NORMAL, true); |
| direction = NavigationDirection::RELOAD; |
| } |
| |
| if (direction != NavigationDirection::NONE) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Overscroll.Navigated3", |
| GetUmaNavigationType(direction, overscroll_source), |
| UmaNavigationType::NAVIGATION_TYPE_COUNT); |
| if (direction == NavigationDirection::BACK) |
| RecordAction(base::UserMetricsAction("Overscroll_Navigated.Back")); |
| else if (direction == NavigationDirection::FORWARD) |
| RecordAction(base::UserMetricsAction("Overscroll_Navigated.Forward")); |
| else |
| RecordAction(base::UserMetricsAction("Overscroll_Navigated.Reload")); |
| } else { |
| RecordGestureOverscrollCancelled(GetDirectionFromMode(overscroll_mode), |
| overscroll_source); |
| } |
| } |
| |
| void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode, |
| OverscrollMode new_mode, |
| OverscrollSource source, |
| cc::OverscrollBehavior behavior) { |
| DCHECK(old_mode == OverscrollMode::OVERSCROLL_NONE || |
| new_mode == OverscrollMode::OVERSCROLL_NONE); |
| |
| // Do not start a new gesture-nav if overscroll-behavior-x is not auto. |
| if ((new_mode == OverscrollMode::OVERSCROLL_EAST || |
| new_mode == OverscrollMode::OVERSCROLL_WEST) && |
| behavior.x != cc::OverscrollBehavior::kOverscrollBehaviorTypeAuto) { |
| return; |
| } |
| |
| // Do not start a new pull-to-refresh if overscroll-behavior-y is not auto. |
| if (new_mode == OverscrollMode::OVERSCROLL_SOUTH && |
| behavior.y != cc::OverscrollBehavior::kOverscrollBehaviorTypeAuto) { |
| return; |
| } |
| |
| if (old_mode != OverscrollMode::OVERSCROLL_NONE && |
| mode_ == OverscrollMode::OVERSCROLL_NONE) { |
| // Previous mode change has been ignored because of overscroll-behavior |
| // value; so, ignore this one, too. |
| return; |
| } |
| |
| DCHECK_EQ(mode_, old_mode); |
| if (mode_ == new_mode) |
| return; |
| mode_ = new_mode; |
| |
| NavigationControllerImpl& controller = web_contents_->GetController(); |
| if (!ShouldNavigateForward(&controller, mode_) && |
| !ShouldNavigateBack(&controller, mode_) && |
| !ShouldReload(controller, mode_)) { |
| // If there is an overscroll in progress - record its cancellation. |
| if (affordance_ && !affordance_->IsFinishing()) { |
| RecordGestureOverscrollCancelled(GetDirectionFromMode(old_mode), source_); |
| affordance_->Abort(); |
| } |
| source_ = OverscrollSource::NONE; |
| return; |
| } |
| |
| DCHECK_NE(OverscrollSource::NONE, source); |
| source_ = source; |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "Overscroll.Started3", |
| GetUmaNavigationType(GetDirectionFromMode(mode_), source_), |
| UmaNavigationType::NAVIGATION_TYPE_COUNT); |
| |
| const bool is_touchpad = source == OverscrollSource::TOUCHPAD; |
| const float start_threshold = OverscrollConfig::GetThreshold( |
| is_touchpad ? OverscrollConfig::Threshold::kStartTouchpad |
| : OverscrollConfig::Threshold::kStartTouchscreen); |
| const gfx::Size size = GetDisplaySize(); |
| const int max_size = std::max(size.width(), size.height()); |
| completion_threshold_ = |
| max_size * OverscrollConfig::GetThreshold( |
| is_touchpad |
| ? OverscrollConfig::Threshold::kCompleteTouchpad |
| : OverscrollConfig::Threshold::kCompleteTouchscreen) - |
| start_threshold; |
| DCHECK_LE(0, completion_threshold_); |
| |
| max_delta_ = max_size - start_threshold; |
| DCHECK_LE(0, max_delta_); |
| |
| aura::Window* window = web_contents_->GetNativeView(); |
| affordance_ = std::make_unique<Affordance>( |
| this, mode_, window->bounds(), max_delta_ / completion_threshold_); |
| |
| // Adding the affordance as a child of the content window is not sufficient, |
| // because it is possible for a new layer to be parented on top of the |
| // affordance layer (e.g. when the navigated-to page is displayed while the |
| // completion animation is in progress). So instead, it is installed on top of |
| // the content window as its sibling. Note that the affordance itself makes |
| // sure that its contents are clipped to the bounds given to it. |
| ui::Layer* parent = window->layer()->parent(); |
| parent->Add(affordance_->root_layer()); |
| parent->StackAtTop(affordance_->root_layer()); |
| } |
| |
| base::Optional<float> GestureNavSimple::GetMaxOverscrollDelta() const { |
| if (affordance_) |
| return max_delta_; |
| return base::nullopt; |
| } |
| |
| } // namespace content |