Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(260)

Unified Diff: content/browser/web_contents/aura/gesture_nav_simple.cc

Issue 2656463002: Implement MD simple gesture nav (Closed)
Patch Set: Simplified ThreePartImage test Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « content/browser/web_contents/aura/gesture_nav_simple.h ('k') | ui/base/cocoa/three_part_image_unittest.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: content/browser/web_contents/aura/gesture_nav_simple.cc
diff --git a/content/browser/web_contents/aura/gesture_nav_simple.cc b/content/browser/web_contents/aura/gesture_nav_simple.cc
index cef6749cae6cc9163613a7aee38f8e069d97eab5..e6c3601ecb7129d54fc003f2f688f6f05298029c 100644
--- a/content/browser/web_contents/aura/gesture_nav_simple.cc
+++ b/content/browser/web_contents/aura/gesture_nav_simple.cc
@@ -7,33 +7,59 @@
#include <utility>
#include "base/macros.h"
-#include "cc/layers/layer.h"
#include "cc/paint/paint_flags.h"
#include "content/browser/frame_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/overscroll_controller.h"
#include "content/browser/web_contents/web_contents_impl.h"
-#include "content/browser/web_contents/web_contents_view.h"
-#include "content/public/browser/browser_thread.h"
#include "content/public/browser/overscroll_configuration.h"
-#include "content/public/common/content_client.h"
+#include "third_party/skia/include/core/SkDrawLooper.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
-#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/compositor/paint_recorder.h"
-#include "ui/compositor/scoped_layer_animation_settings.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/image/image.h"
-#include "ui/resources/grit/ui_resources.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"
+#include "ui/vector_icons/vector_icons.h"
namespace content {
namespace {
-const int kArrowHeight = 280;
-const int kArrowWidth = 140;
-const float kMinOpacity = 0.25f;
+// Parameters defining the arrow icon inside the affordance.
+const int kArrowSize = 16;
+const SkColor kArrowColor = gfx::kGoogleBlue500;
+
+// 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, 0x33);
+const int kMaxRippleBurstRadius = 72;
+const gfx::Tween::Type kBurstAnimationTweenType = gfx::Tween::EASE_IN;
+const int kRippleBurstAnimationDuration = 160;
+
+// Offset of the affordance when it is at the maximum distance with content
+// border. 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 kMaxAffordanceOffset = 146;
+
+// Parameters defining animation when the affordance is aborted.
+const gfx::Tween::Type kAbortAnimationTweenType = gfx::Tween::EASE_IN;
+const int kAbortAnimationDuration = 300;
bool ShouldNavigateForward(const NavigationController& controller,
OverscrollMode mode) {
@@ -47,75 +73,259 @@ bool ShouldNavigateBack(const NavigationController& controller,
controller.CanGoBack();
}
-// An animation observers that deletes itself and a pointer after the end of the
-// animation.
-template <class T>
-class DeleteAfterAnimation : public ui::ImplicitAnimationObserver {
+} // namespace
+
+// This class is responsible for creating, painting, and positioning the layer
+// for the gesture nav affordance.
+class GestureNavSimple::Affordance : public ui::LayerDelegate,
+ public gfx::AnimationDelegate {
public:
- explicit DeleteAfterAnimation(std::unique_ptr<T> object)
- : object_(std::move(object)) {}
+ Affordance(OverscrollMode mode, const gfx::Rect& content_bounds);
+ ~Affordance() override;
+
+ // Sets progress of affordance drag as a value between 0 and 1.
+ 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() const { return root_layer_.get(); }
private:
- friend class base::DeleteHelper<DeleteAfterAnimation<T> >;
+ enum class State { DRAGGING, ABORTING, COMPLETING };
- ~DeleteAfterAnimation() override {}
+ void UpdateTransform();
+ void SchedulePaint();
+ void SetAbortProgress(float progress);
+ void SetCompleteProgress(float progress);
- // ui::ImplicitAnimationObserver:
- void OnImplicitAnimationsCompleted() override {
- // Deleting an observer when a ScopedLayerAnimationSettings is iterating
- // over them can cause a crash (which can happen during tests). So instead,
- // schedule this observer to be deleted soon.
- BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
- }
+ // ui::LayerDelegate:
+ void OnPaintLayer(const ui::PaintContext& context) override;
+ void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override;
+ void OnDeviceScaleFactorChanged(float 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;
- std::unique_ptr<T> object_;
- DISALLOW_COPY_AND_ASSIGN(DeleteAfterAnimation);
+ const OverscrollMode mode_;
+
+ // Root layer of the affordance. This is used to clip the affordance to the
+ // content bounds.
+ std::unique_ptr<ui::Layer> root_layer_;
+
+ // Layer that actually paints the affordance.
+ std::unique_ptr<ui::Layer> painted_layer_;
+
+ // Arrow image to be used for the affordance.
+ const gfx::Image image_;
+
+ // 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);
};
-} // namespace
+GestureNavSimple::Affordance::Affordance(OverscrollMode mode,
+ const gfx::Rect& content_bounds)
+ : mode_(mode),
+ root_layer_(base::MakeUnique<ui::Layer>(ui::LAYER_NOT_DRAWN)),
+ painted_layer_(base::MakeUnique<ui::Layer>(ui::LAYER_TEXTURED)),
+ image_(gfx::CreateVectorIcon(
+ mode == OVERSCROLL_EAST ? ui::kBackArrowIcon : ui::kForwardArrowIcon,
+ kArrowSize,
+ kArrowColor)) {
+ DCHECK(mode == OVERSCROLL_EAST || mode == OVERSCROLL_WEST);
+ DCHECK(!image_.IsEmpty());
+
+ root_layer_->SetBounds(content_bounds);
+ root_layer_->SetMasksToBounds(true);
+
+ painted_layer_->SetFillsBoundsOpaquely(false);
+ int x =
+ mode_ == OVERSCROLL_EAST
+ ? -kMaxRippleBurstRadius - kBackgroundRadius
+ : content_bounds.width() - kMaxRippleBurstRadius + kBackgroundRadius;
+ int y = std::max(0, content_bounds.height() / 2 - kMaxRippleBurstRadius);
+ painted_layer_->SetBounds(
+ gfx::Rect(x, y, 2 * kMaxRippleBurstRadius, 2 * kMaxRippleBurstRadius));
+ painted_layer_->set_delegate(this);
+
+ root_layer_->Add(painted_layer_.get());
+}
-// A layer delegate that paints the shield with the arrow in it.
-class ArrowLayerDelegate : public ui::LayerDelegate {
- public:
- explicit ArrowLayerDelegate(int resource_id)
- : image_(GetContentClient()->GetNativeImageNamed(resource_id)),
- left_arrow_(resource_id == IDR_BACK_ARROW) {
- CHECK(!image_.IsEmpty());
- }
+GestureNavSimple::Affordance::~Affordance() {}
- ~ArrowLayerDelegate() override {}
+void GestureNavSimple::Affordance::SetDragProgress(float progress) {
+ DCHECK_EQ(State::DRAGGING, state_);
+ DCHECK_LE(0.f, progress);
+ DCHECK_GE(1.f, progress);
- bool left() const { return left_arrow_; }
+ if (drag_progress_ == progress)
+ return;
+ drag_progress_ = progress;
- private:
- // ui::LayerDelegate:
- void OnPaintLayer(const ui::PaintContext& context) override {
- cc::PaintFlags paint;
- paint.setColor(SkColorSetARGB(0xa0, 0, 0, 0));
- paint.setStyle(cc::PaintFlags::kFill_Style);
- paint.setAntiAlias(true);
-
- // Set the recording size to be the size of the |arrow_| layer, and draw a
- // half circle (the other half will be clipped), then an arrow image inside
- // it.
- ui::PaintRecorder recorder(context, gfx::Size(kArrowWidth, kArrowHeight));
- recorder.canvas()->DrawCircle(
- gfx::Point(left_arrow_ ? 0 : kArrowWidth, kArrowHeight / 2),
- kArrowWidth, paint);
- recorder.canvas()->DrawImageInt(
- *image_.ToImageSkia(), left_arrow_ ? 0 : kArrowWidth - image_.Width(),
- (kArrowHeight - image_.Height()) / 2);
+ UpdateTransform();
+ SchedulePaint();
+}
+
+void GestureNavSimple::Affordance::Abort() {
+ DCHECK_EQ(State::DRAGGING, state_);
+
+ state_ = State::ABORTING;
+
+ animation_.reset(
+ new gfx::LinearAnimation(drag_progress_ * kAbortAnimationDuration,
+ gfx::LinearAnimation::kDefaultFrameRate, this));
+ animation_->Start();
+}
+
+void GestureNavSimple::Affordance::Complete() {
+ DCHECK_EQ(State::DRAGGING, state_);
+ DCHECK_EQ(1.f, drag_progress_);
+
+ state_ = State::COMPLETING;
+
+ animation_.reset(
+ new gfx::LinearAnimation(kRippleBurstAnimationDuration,
+ gfx::LinearAnimation::kDefaultFrameRate, this));
+ animation_->Start();
+}
+
+void GestureNavSimple::Affordance::UpdateTransform() {
+ float offset = (1 - abort_progress_) * drag_progress_ * kMaxAffordanceOffset;
+ gfx::Transform transform;
+ transform.Translate(mode_ == OVERSCROLL_EAST ? offset : -offset, 0);
+ painted_layer_->SetTransform(transform);
+}
+
+void GestureNavSimple::Affordance::SchedulePaint() {
+ painted_layer_->SchedulePaint(gfx::Rect(painted_layer_->size()));
+}
+
+void GestureNavSimple::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;
+
+ UpdateTransform();
+ SchedulePaint();
+}
+
+void GestureNavSimple::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(1 - complete_progress_);
+ SchedulePaint();
+}
+
+void GestureNavSimple::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 = (1 - abort_progress_) * drag_progress_;
+
+ // Draw the ripple.
+ cc::PaintFlags ripple_paint;
+ ripple_paint.setAntiAlias(true);
+ ripple_paint.setStyle(cc::PaintFlags::kFill_Style);
+ ripple_paint.setColor(kRippleColor);
+ float ripple_radius;
+ if (state_ == State::COMPLETING) {
+ ripple_radius =
+ kMaxRippleRadius +
+ complete_progress_ * (kMaxRippleBurstRadius - kMaxRippleRadius);
+ } else {
+ ripple_radius =
+ kBackgroundRadius + progress * (kMaxRippleRadius - kBackgroundRadius);
}
+ canvas->DrawCircle(center_point, ripple_radius, ripple_paint);
+
+ // Draw the arrow background circle with the shadow.
+ cc::PaintFlags bg_paint;
+ bg_paint.setAntiAlias(true);
+ bg_paint.setStyle(cc::PaintFlags::kFill_Style);
+ bg_paint.setColor(kBackgroundColor);
+ gfx::ShadowValues shadow;
+ shadow.emplace_back(gfx::Vector2d(0, kBgShadowOffsetY), kBgShadowBlurRadius,
+ kBgShadowColor);
+ bg_paint.setLooper(gfx::CreateShadowDrawLooperCorrectBlur(shadow));
+ canvas->DrawCircle(center_point, kBackgroundRadius, bg_paint);
+
+ // Draw the arrow.
+ float arrow_x = center_point.x() - kArrowSize / 2.f;
+ float arrow_y = center_point.y() - kArrowSize / 2.f;
+ // Calculate the offset for the arrow relative to its circular background.
+ float arrow_x_offset =
+ (1 - progress) * (-kBackgroundRadius + kArrowSize / 2.f);
+ arrow_x += mode_ == OVERSCROLL_EAST ? arrow_x_offset : -arrow_x_offset;
+ uint8_t arrow_alpha =
+ static_cast<uint8_t>(std::min(0xFF, static_cast<int>(progress * 0xFF)));
+ canvas->DrawImageInt(*image_.ToImageSkia(), static_cast<int>(arrow_x),
+ static_cast<int>(arrow_y), arrow_alpha);
+}
- void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
+void GestureNavSimple::Affordance::OnDelegatedFrameDamage(
+ const gfx::Rect& damage_rect_in_dip) {}
- void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
+void GestureNavSimple::Affordance::OnDeviceScaleFactorChanged(
+ float device_scale_factor) {}
- const gfx::Image& image_;
- const bool left_arrow_;
+void GestureNavSimple::Affordance::AnimationEnded(
+ const gfx::Animation* animation) {
+ delete this;
+}
+
+void GestureNavSimple::Affordance::AnimationProgressed(
+ const gfx::Animation* animation) {
+ switch (state_) {
+ case State::DRAGGING:
+ NOTREACHED();
+ break;
+ case State::ABORTING:
+ SetAbortProgress(gfx::Tween::CalculateValue(
+ kAbortAnimationTweenType, animation->GetCurrentValue()));
+ break;
+ case State::COMPLETING:
+ SetCompleteProgress(gfx::Tween::CalculateValue(
+ kBurstAnimationTweenType, animation->GetCurrentValue()));
+ break;
+ }
+}
+
+void GestureNavSimple::Affordance::AnimationCanceled(
+ const gfx::Animation* animation) {
+ NOTREACHED();
+}
- DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate);
-};
GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents)
: web_contents_(web_contents),
@@ -123,48 +333,22 @@ GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents)
GestureNavSimple::~GestureNavSimple() {}
-void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform& transform,
- float opacity) {
- ui::Layer* layer = arrow_.get();
- ui::ScopedLayerAnimationSettings settings(arrow_->GetAnimator());
- settings.AddObserver(
- new DeleteAfterAnimation<ArrowLayerDelegate>(std::move(arrow_delegate_)));
- settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(std::move(arrow_)));
- settings.AddObserver(
- new DeleteAfterAnimation<ui::Layer>(std::move(clip_layer_)));
- layer->SetTransform(transform);
- layer->SetOpacity(opacity);
-}
-
void GestureNavSimple::AbortGestureAnimation() {
- if (!arrow_)
+ if (!affordance_)
return;
- gfx::Transform transform;
- transform.Translate(arrow_delegate_->left() ? -kArrowWidth : kArrowWidth, 0);
- ApplyEffectsAndDestroy(transform, kMinOpacity);
+ // Release the unique pointer. The affordance will delete itself upon
+ // completion of animation.
+ Affordance* affordance = affordance_.release();
+ affordance->Abort();
}
void GestureNavSimple::CompleteGestureAnimation() {
- if (!arrow_)
+ if (!affordance_)
return;
- // Make sure the fade-out starts from the complete state.
- ApplyEffectsForDelta(completion_threshold_);
- ApplyEffectsAndDestroy(arrow_->transform(), 0.f);
-}
-
-bool GestureNavSimple::ApplyEffectsForDelta(float delta_x) {
- if (!arrow_)
- return false;
- CHECK_GT(completion_threshold_, 0.f);
- CHECK_GE(delta_x, 0.f);
- double complete = std::min(1.f, delta_x / completion_threshold_);
- float translate_x = gfx::Tween::FloatValueBetween(complete, -kArrowWidth, 0);
- gfx::Transform transform;
- transform.Translate(arrow_delegate_->left() ? translate_x : -translate_x,
- 0.f);
- arrow_->SetTransform(transform);
- arrow_->SetOpacity(gfx::Tween::FloatValueBetween(complete, kMinOpacity, 1.f));
- return true;
+ // Release the unique pointer. The affordance will delete itself upon
+ // completion of animation.
+ Affordance* affordance = affordance_.release();
+ affordance->Complete();
}
gfx::Rect GestureNavSimple::GetVisibleBounds() const {
@@ -172,7 +356,11 @@ gfx::Rect GestureNavSimple::GetVisibleBounds() const {
}
bool GestureNavSimple::OnOverscrollUpdate(float delta_x, float delta_y) {
- return ApplyEffectsForDelta(std::abs(delta_x) + 50.f);
+ if (!affordance_)
+ return false;
+ affordance_->SetDragProgress(
+ std::min(1.f, std::abs(delta_x) / completion_threshold_));
+ return true;
}
void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode) {
@@ -194,47 +382,24 @@ void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode,
return;
}
- arrow_.reset(new ui::Layer(ui::LAYER_TEXTURED));
- // Note that RTL doesn't affect the arrow that should be displayed.
- int resource_id = 0;
- if (new_mode == OVERSCROLL_WEST)
- resource_id = IDR_FORWARD_ARROW;
- else if (new_mode == OVERSCROLL_EAST)
- resource_id = IDR_BACK_ARROW;
- else
- NOTREACHED();
-
- arrow_delegate_.reset(new ArrowLayerDelegate(resource_id));
- arrow_->set_delegate(arrow_delegate_.get());
- arrow_->SetFillsBoundsOpaquely(false);
-
aura::Window* window = web_contents_->GetNativeView();
const gfx::Rect& window_bounds = window->bounds();
- completion_threshold_ = window_bounds.width() *
- GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE);
-
- // Align on the left or right edge.
- int x = (resource_id == IDR_BACK_ARROW) ? 0 :
- (window_bounds.width() - kArrowWidth);
- // Align in the center vertically.
- int y = std::max(0, (window_bounds.height() - kArrowHeight) / 2);
- arrow_->SetBounds(gfx::Rect(x, y, kArrowWidth, kArrowHeight));
- ApplyEffectsForDelta(0.f);
-
- // Adding the arrow 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 arrow
- // layer (e.g. when the navigated-to page is displayed while the completion
- // animation is in progress). So instead, a clip layer (that doesn't paint) is
- // installed on top of the content window as its sibling, and the arrow layer
- // is added to that clip layer.
- clip_layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN));
- clip_layer_->SetBounds(window->layer()->bounds());
- clip_layer_->SetMasksToBounds(true);
- clip_layer_->Add(arrow_.get());
-
+ completion_threshold_ =
+ window_bounds.width() *
+ GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE) -
+ GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN);
+
+ affordance_.reset(new Affordance(new_mode, window_bounds));
+
+ // 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(clip_layer_.get());
- parent->StackAtTop(clip_layer_.get());
+ parent->Add(affordance_->root_layer());
+ parent->StackAtTop(affordance_->root_layer());
}
} // namespace content
« no previous file with comments | « content/browser/web_contents/aura/gesture_nav_simple.h ('k') | ui/base/cocoa/three_part_image_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698