Commit 74394f7a authored by minch's avatar minch Committed by Commit Bot

back_gesture: Add affordance.

This is an initial cl for back gesture affordance. Many other issues
will be solved in following up cls. e.g, fling event support;
make affordance can be moved on y-axis movement; affordance should not
under frame area, etc.

Recorded video:
https://drive.google.com/file/d/1p7T-RiHU8nhaKmyAgrqA_bqITkCDBi7p/view?usp=sharing

Bug: 1002733
Change-Id: I89c02372babf2d562e56717c7966df2e8e5405f7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1834826
Commit-Queue: Min Chen <minch@chromium.org>
Reviewed-by: default avatarXiaoqian Dai <xdai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#702635}
parent 55cb653e
......@@ -1085,6 +1085,8 @@ component("ash") {
"wm/always_on_top_controller.h",
"wm/ash_focus_rules.cc",
"wm/ash_focus_rules.h",
"wm/back_gesture_affordance.cc",
"wm/back_gesture_affordance.h",
"wm/base_state.cc",
"wm/base_state.h",
"wm/client_controlled_state.cc",
......
// Copyright 2019 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 "ash/wm/back_gesture_affordance.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/events/event.h"
#include "ui/gfx/animation/animation_delegate.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"
namespace ash {
namespace {
// Distance of the arrow that above the drag start position.
constexpr int kArrowAboveStartPosition = 64;
constexpr int kArrowSize = 20;
// Radius of the arrow's rounded background.
constexpr int kBackgroundRadius = 20;
// Radius of the ripple while affordance transform achieves |kMaxTransform|.
constexpr int kRippleMaxRadius = 32;
// Radius of the ripple while affordance transform achieves |kBurstTransform|.
constexpr int kRippleBurstRadius = 40;
// Transform for the affordance to go from outside of the display to the
// position that it needs to update the arrow and background colors.
constexpr float kMaxTransform = 100.f;
// |kMaxTransform| plus the transform for the ripple of the affordance to
// increase from |kRippleMaxRadius| to |kRippleBurstRadius|.
constexpr float kBurstTransform = 116.f;
constexpr base::TimeDelta kAbortAnimationTimeout =
base::TimeDelta::FromMilliseconds(300);
constexpr base::TimeDelta kCompleteAnimationTimeout =
base::TimeDelta::FromMilliseconds(200);
constexpr SkColor kRippleColor = SkColorSetA(gfx::kGoogleBlue600, 0x4C); // 30%
} // namespace
BackGestureAffordance::BackGestureAffordance(const gfx::Point& location)
: painted_layer_(ui::LAYER_TEXTURED) {
painted_layer_.SetFillsBoundsOpaquely(false);
gfx::Rect painted_layer_bounds(
gfx::Rect(2 * kRippleBurstRadius, 2 * kRippleBurstRadius));
gfx::Point origin;
// TODO(crbug.com/1002733): Handle the case while origin y is outside display.
origin.set_y(location.y() - kArrowAboveStartPosition - kRippleBurstRadius);
origin.set_x(-kRippleBurstRadius - kBackgroundRadius);
painted_layer_bounds.set_origin(origin);
painted_layer_.SetBounds(painted_layer_bounds);
painted_layer_.set_delegate(this);
}
BackGestureAffordance::~BackGestureAffordance() {}
void BackGestureAffordance::SetDragProgress(int x_location) {
DCHECK_EQ(State::DRAGGING, state_);
DCHECK_LE(0.f, drag_progress_);
const float progress = (x_location + kBackgroundRadius) / kMaxTransform;
if (drag_progress_ == progress)
return;
drag_progress_ = std::min(progress, kBurstTransform / kMaxTransform);
UpdateTransform();
SchedulePaint();
}
void BackGestureAffordance::Abort() {
DCHECK_EQ(State::DRAGGING, state_);
state_ = State::ABORTING;
animation_ = std::make_unique<gfx::LinearAnimation>(
drag_progress_ * kAbortAnimationTimeout,
gfx::LinearAnimation::kDefaultFrameRate, this);
animation_->Start();
}
void BackGestureAffordance::Complete() {
DCHECK_EQ(State::DRAGGING, state_);
DCHECK_LE(1.f, drag_progress_);
state_ = State::COMPLETING;
animation_ = std::make_unique<gfx::LinearAnimation>(
kCompleteAnimationTimeout, gfx::LinearAnimation::kDefaultFrameRate, this);
animation_->Start();
}
void BackGestureAffordance::UpdateTransform() {
const float offset = (1 - abort_progress_) * drag_progress_ * kMaxTransform;
gfx::Transform transform;
transform.Translate(offset, 0);
painted_layer_.SetTransform(transform);
}
void BackGestureAffordance::SchedulePaint() {
painted_layer_.SchedulePaint(gfx::Rect(painted_layer_.size()));
}
void BackGestureAffordance::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 BackGestureAffordance::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(
gfx::Tween::FAST_OUT_SLOW_IN, 1 - complete_progress_));
SchedulePaint();
}
void BackGestureAffordance::OnPaintLayer(const ui::PaintContext& context) {
ui::PaintRecorder recorder(context, painted_layer_.size());
gfx::Canvas* canvas = recorder.canvas();
const gfx::PointF center_point(kRippleBurstRadius, kRippleBurstRadius);
const float progress = (1 - abort_progress_) * drag_progress_;
// Draw the ripple.
cc::PaintFlags ripple_flags;
ripple_flags.setAntiAlias(true);
ripple_flags.setStyle(cc::PaintFlags::kFill_Style);
ripple_flags.setColor(kRippleColor);
const bool exceed_full_progress = progress >= 1.f;
float ripple_radius = 0.f;
if (exceed_full_progress) {
const float factor = (kRippleBurstRadius - kRippleMaxRadius) /
(kBurstTransform / kMaxTransform - 1);
ripple_radius = (kRippleMaxRadius - factor) + factor * progress;
} else {
ripple_radius =
kBackgroundRadius + progress * (kRippleMaxRadius - kBackgroundRadius);
}
canvas->DrawCircle(center_point, ripple_radius, ripple_flags);
// Draw the arrow background circle.
cc::PaintFlags bg_flags;
bg_flags.setAntiAlias(true);
bg_flags.setStyle(cc::PaintFlags::kFill_Style);
bg_flags.setColor(exceed_full_progress ? gfx::kGoogleBlue600 : SK_ColorWHITE);
canvas->DrawCircle(center_point, kBackgroundRadius, bg_flags);
// Draw the arrow.
const float arrow_x = center_point.x() - kArrowSize / 2.f;
const float arrow_y = center_point.y() - kArrowSize / 2.f;
if (exceed_full_progress) {
canvas->DrawImageInt(gfx::CreateVectorIcon(vector_icons::kBackArrowIcon,
kArrowSize, SK_ColorWHITE),
static_cast<int>(arrow_x), static_cast<int>(arrow_y));
} else {
canvas->DrawImageInt(gfx::CreateVectorIcon(vector_icons::kBackArrowIcon,
kArrowSize, gfx::kGoogleBlue600),
static_cast<int>(arrow_x), static_cast<int>(arrow_y));
}
}
void BackGestureAffordance::OnDeviceScaleFactorChanged(
float old_device_scale_factor,
float new_device_scale_factor) {}
void BackGestureAffordance::AnimationEnded(const gfx::Animation* animation) {}
void BackGestureAffordance::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 BackGestureAffordance::AnimationCanceled(const gfx::Animation* animation) {
NOTREACHED();
}
} // namespace ash
// Copyright 2019 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 ASH_WM_BACK_GESTURE_AFFORDANCE_H_
#define ASH_WM_BACK_GESTURE_AFFORDANCE_H_
#include <memory>
#include "base/macros.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/linear_animation.h"
namespace ash {
// This class is responsible for creating, painting, and positioning the layer
// for the back gesture affordance.
class BackGestureAffordance : public ui::LayerDelegate,
public gfx::AnimationDelegate {
public:
explicit BackGestureAffordance(const gfx::Point& location);
~BackGestureAffordance() override;
// Sets drag progress. 0 means no progress. 1 means full progress and drag
// will be completed with full progress. Values more than 1 are also allowed
// for achieving burst ripple. But the maximium value is limited as
// |kBurstTransform| / |kMaxTransform|.
void SetDragProgress(int x_location);
// Aborts the affordance and animates it back.
void Abort();
// Completes the affordance and fading it out.
void Complete();
ui::Layer* painted_layer() { return &painted_layer_; }
private:
enum class State { DRAGGING, ABORTING, COMPLETING };
void UpdateTransform();
void SchedulePaint();
void SetAbortProgress(float progress);
void SetCompleteProgress(float progress);
// 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;
// Layer that actually paints the affordance.
ui::Layer painted_layer_;
// 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(BackGestureAffordance);
};
} // namespace ash
#endif // ASH_WM_BACK_GESTURE_AFFORDANCE_H_
......@@ -7,6 +7,7 @@
#include "ash/public/cpp/app_types.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/shell.h"
#include "ash/wm/back_gesture_affordance.h"
#include "ash/wm/resize_shadow_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_resizer.h"
......@@ -841,19 +842,30 @@ bool ToplevelWindowEventHandler::HandleGoingBackFromLeftEdge(
return false;
switch (event->type()) {
case ui::ET_GESTURE_SCROLL_BEGIN:
case ui::ET_GESTURE_SCROLL_BEGIN: {
going_back_started_ = StartedAwayFromLeftArea(event);
if (going_back_started_)
return true;
break;
if (!going_back_started_)
break;
back_gesture_affordance_ =
std::make_unique<BackGestureAffordance>(event->location());
aura::Window* window = static_cast<aura::Window*>(event->target());
DCHECK(window);
ui::Layer* parent = window->layer()->parent();
parent->Add(back_gesture_affordance_->painted_layer());
parent->StackAtTop(back_gesture_affordance_->painted_layer());
return true;
}
case ui::ET_GESTURE_SCROLL_UPDATE:
if (!going_back_started_)
break;
// TODO(crbug.com/1002733): Update the arrow animation.
DCHECK(back_gesture_affordance_);
back_gesture_affordance_->SetDragProgress(event->location().x());
return true;
case ui::ET_GESTURE_SCROLL_END:
if (!going_back_started_)
break;
DCHECK(back_gesture_affordance_);
if (event->location().x() >= kSwipingDistanceForGoingBack) {
aura::Window* root_window =
window_util::GetRootWindowAt(event->location());
......@@ -865,8 +877,9 @@ bool ToplevelWindowEventHandler::HandleGoingBackFromLeftEdge(
ui::VKEY_BROWSER_BACK, ui::EF_NONE);
ignore_result(
root_window->GetHost()->SendEventToSink(&release_key_event));
back_gesture_affordance_->Complete();
} else {
// TODO(crbug.com/1002733): Revert going back animation.
back_gesture_affordance_->Abort();
}
going_back_started_ = false;
return true;
......
......@@ -30,6 +30,7 @@ class GestureEvent;
} // namespace ui
namespace ash {
class BackGestureAffordance;
namespace mojom {
enum class WindowStateType;
}
......@@ -187,6 +188,10 @@ class ASH_EXPORT ToplevelWindowEventHandler
// True if swiping from left edge to go to previous page is in progress.
bool going_back_started_ = false;
// Used to show the affordance while swiping from left edge to go to the
// previout page.
std::unique_ptr<BackGestureAffordance> back_gesture_affordance_;
base::WeakPtrFactory<ToplevelWindowEventHandler> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ToplevelWindowEventHandler);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment