blob: 2bbebeb9c01f4360fca252d92847924a4792bcb6 [file] [log] [blame]
// 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