blob: 4fb6d2dfe0d7a3d9c3f1883434b6d6421d339bdb [file] [log] [blame]
// Copyright 2017 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 "ui/app_list/views/expand_arrow_view.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/vector_icons/vector_icons.h"
#include "ui/app_list/views/app_list_view.h"
#include "ui/app_list/views/contents_view.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/animation/ink_drop_painted_layer_delegates.h"
#include "ui/views/controls/image_view.h"
namespace app_list {
namespace {
constexpr int kExpandArrowTileSize = 60;
constexpr int kExpandArrowIconSize = 12;
constexpr int kClipViewSize = 36;
constexpr int kInkDropRadius = 18;
constexpr int kSelectedRadius = 18;
// Animation related constants
constexpr int kPulseMinRadius = 2;
constexpr int kPulseMaxRadius = 30;
constexpr float kPulseMinOpacity = 0.f;
constexpr float kPulseMaxOpacity = 0.3f;
constexpr int kAnimationInitialWaitTimeInSec = 3;
constexpr int kAnimationIntervalInSec = 10;
constexpr int kCycleDurationInMs = 1000;
constexpr int kCycleIntervalInMs = 500;
constexpr int kPulseOpacityShowBeginTimeInMs = 100;
constexpr int kPulseOpacityShowEndTimeInMs = 200;
constexpr int kPulseOpacityHideBeginTimeInMs = 800;
constexpr int kPulseOpacityHideEndTimeInMs = 1000;
constexpr int kArrowMoveOutBeginTimeInMs = 100;
constexpr int kArrowMoveOutEndTimeInMs = 500;
constexpr int kArrowMoveInBeginTimeInMs = 500;
constexpr int kArrowMoveInEndTimeInMs = 900;
constexpr SkColor kExpandArrowColor = SK_ColorWHITE;
constexpr SkColor kPulseColor = SK_ColorWHITE;
constexpr SkColor kUnFocusedBackgroundColor =
SkColorSetARGBMacro(0xF, 0xFF, 0xFF, 0xFF);
constexpr SkColor kFocusedBackgroundColor =
SkColorSetARGBMacro(0x3D, 0xFF, 0xFF, 0xFF);
constexpr SkColor kInkDropRippleColor =
SkColorSetARGBMacro(0x14, 0xFF, 0xFF, 0xFF);
} // namespace
ExpandArrowView::ExpandArrowView(ContentsView* contents_view,
AppListView* app_list_view)
: views::Button(this),
contents_view_(contents_view),
app_list_view_(app_list_view),
weak_ptr_factory_(this) {
SetFocusBehavior(FocusBehavior::ALWAYS);
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
icon_ = new views::ImageView;
icon_->SetVerticalAlignment(views::ImageView::CENTER);
icon_->SetImage(gfx::CreateVectorIcon(kIcArrowUpIcon, kExpandArrowIconSize,
kExpandArrowColor));
clip_view_ = new views::View;
clip_view_->AddChildView(icon_);
AddChildView(clip_view_);
SetInkDropMode(InkDropHostView::InkDropMode::ON);
SetAccessibleName(l10n_util::GetStringUTF16(IDS_APP_LIST_EXPAND_BUTTON));
animation_.reset(new gfx::SlideAnimation(this));
animation_->SetTweenType(gfx::Tween::LINEAR);
animation_->SetSlideDuration(kCycleDurationInMs * 2 + kCycleIntervalInMs);
ResetHintingAnimation();
ScheduleHintingAnimation(true);
}
ExpandArrowView::~ExpandArrowView() = default;
void ExpandArrowView::PaintButtonContents(gfx::Canvas* canvas) {
gfx::Rect rect(GetContentsBounds());
// Draw focused or unfocused background.
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setColor(HasFocus() ? kFocusedBackgroundColor
: kUnFocusedBackgroundColor);
flags.setStyle(cc::PaintFlags::kFill_Style);
canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), kSelectedRadius, flags);
if (animation_->is_animating()) {
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setColor(SkColorSetA(kPulseColor, 255 * pulse_opacity_));
flags.setAntiAlias(true);
canvas->DrawCircle(rect.CenterPoint(), pulse_radius_, flags);
}
}
void ExpandArrowView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
button_pressed_ = true;
ResetHintingAnimation();
TransitToFullscreenAllAppsState();
GetInkDrop()->AnimateToState(views::InkDropState::ACTION_TRIGGERED);
}
gfx::Size ExpandArrowView::CalculatePreferredSize() const {
return gfx::Size(kExpandArrowTileSize, kExpandArrowTileSize);
}
void ExpandArrowView::Layout() {
gfx::Rect clip_view_rect(GetContentsBounds());
clip_view_rect.ClampToCenteredSize(gfx::Size(kClipViewSize, kClipViewSize));
clip_view_->SetBoundsRect(clip_view_rect);
gfx::Rect icon_rect(clip_view_->GetContentsBounds());
icon_rect.ClampToCenteredSize(
gfx::Size(kExpandArrowIconSize, kExpandArrowIconSize));
icon_->SetBoundsRect(icon_rect);
}
bool ExpandArrowView::OnKeyPressed(const ui::KeyEvent& event) {
if (event.key_code() != ui::VKEY_RETURN)
return false;
TransitToFullscreenAllAppsState();
return true;
}
void ExpandArrowView::OnFocus() {
SchedulePaint();
Button::OnFocus();
}
void ExpandArrowView::OnBlur() {
SchedulePaint();
Button::OnBlur();
}
std::unique_ptr<views::InkDrop> ExpandArrowView::CreateInkDrop() {
std::unique_ptr<views::InkDropImpl> ink_drop =
Button::CreateDefaultInkDropImpl();
ink_drop->SetShowHighlightOnHover(false);
ink_drop->SetShowHighlightOnFocus(false);
ink_drop->SetAutoHighlightMode(views::InkDropImpl::AutoHighlightMode::NONE);
return std::move(ink_drop);
}
std::unique_ptr<views::InkDropMask> ExpandArrowView::CreateInkDropMask() const {
return std::make_unique<views::CircleInkDropMask>(
size(), GetLocalBounds().CenterPoint(), kInkDropRadius);
}
std::unique_ptr<views::InkDropRipple> ExpandArrowView::CreateInkDropRipple()
const {
gfx::Point center = GetLocalBounds().CenterPoint();
gfx::Rect bounds(center.x() - kInkDropRadius, center.y() - kInkDropRadius,
2 * kInkDropRadius, 2 * kInkDropRadius);
return std::make_unique<views::FloodFillInkDropRipple>(
size(), GetLocalBounds().InsetsFrom(bounds),
GetInkDropCenterBasedOnLastEvent(), kInkDropRippleColor, 1.0f);
}
void ExpandArrowView::AnimationProgressed(const gfx::Animation* animation) {
// There are two cycles in one animation.
const int animation_duration = kCycleDurationInMs * 2 + kCycleIntervalInMs;
const int first_cycle_end_time = kCycleDurationInMs;
const int interval_end_time = kCycleDurationInMs + kCycleIntervalInMs;
const int second_cycle_end_time = kCycleDurationInMs * 2 + kCycleIntervalInMs;
int time_in_ms = animation->GetCurrentValue() * animation_duration;
if (time_in_ms > first_cycle_end_time && time_in_ms <= interval_end_time) {
// There's no animation in the interval between cycles.
return;
} else if (time_in_ms > interval_end_time &&
time_in_ms <= second_cycle_end_time) {
// Convert to time in one single cycle.
time_in_ms -= interval_end_time;
}
// Update pulse opacity.
if (time_in_ms > kPulseOpacityShowBeginTimeInMs &&
time_in_ms <= kPulseOpacityShowEndTimeInMs) {
pulse_opacity_ =
kPulseMinOpacity +
(kPulseMaxOpacity - kPulseMinOpacity) *
(time_in_ms - kPulseOpacityShowBeginTimeInMs) /
(kPulseOpacityShowEndTimeInMs - kPulseOpacityShowBeginTimeInMs);
} else if (time_in_ms > kPulseOpacityHideBeginTimeInMs &&
time_in_ms <= kPulseOpacityHideEndTimeInMs) {
pulse_opacity_ =
kPulseMaxOpacity -
(kPulseMaxOpacity - kPulseMinOpacity) *
(time_in_ms - kPulseOpacityHideBeginTimeInMs) /
(kPulseOpacityHideEndTimeInMs - kPulseOpacityHideBeginTimeInMs);
}
// Update pulse radius.
pulse_radius_ = static_cast<float>(kPulseMaxRadius - kPulseMinRadius) *
gfx::Tween::CalculateValue(
gfx::Tween::EASE_IN_OUT,
static_cast<double>(time_in_ms) / kCycleDurationInMs);
// Update y position of the arrow icon.
int y_position = icon_->bounds().y();
const int total_y_delta = (kExpandArrowTileSize - kExpandArrowIconSize) / 2;
if (time_in_ms > kArrowMoveOutBeginTimeInMs &&
time_in_ms <= kArrowMoveOutEndTimeInMs) {
const double progress =
static_cast<double>(time_in_ms - kArrowMoveOutBeginTimeInMs) /
(kArrowMoveOutEndTimeInMs - kArrowMoveOutBeginTimeInMs);
y_position = (kClipViewSize - kExpandArrowIconSize) / 2 -
total_y_delta *
gfx::Tween::CalculateValue(gfx::Tween::EASE_IN, progress);
} else if (time_in_ms > kArrowMoveInBeginTimeInMs &&
time_in_ms <= kArrowMoveInEndTimeInMs) {
const double progress =
static_cast<double>(time_in_ms - kArrowMoveInBeginTimeInMs) /
(kArrowMoveInEndTimeInMs - kArrowMoveInBeginTimeInMs);
y_position = (kClipViewSize - kExpandArrowIconSize) / 2 +
total_y_delta * (1 - gfx::Tween::CalculateValue(
gfx::Tween::EASE_OUT, progress));
}
// Apply updates.
gfx::Rect icon_rect(icon_->bounds());
icon_rect.set_y(y_position);
icon_->SetBoundsRect(icon_rect);
SchedulePaint();
}
void ExpandArrowView::AnimationEnded(const gfx::Animation* animation) {
ResetHintingAnimation();
if (!button_pressed_)
ScheduleHintingAnimation(false);
}
void ExpandArrowView::TransitToFullscreenAllAppsState() {
UMA_HISTOGRAM_ENUMERATION(kPageOpenedHistogram, ash::AppListState::kStateApps,
ash::AppListState::kStateLast);
UMA_HISTOGRAM_ENUMERATION(kAppListPeekingToFullscreenHistogram, kExpandArrow,
kMaxPeekingToFullscreen);
contents_view_->SetActiveState(ash::AppListState::kStateApps);
app_list_view_->SetState(AppListViewState::FULLSCREEN_ALL_APPS);
}
void ExpandArrowView::ScheduleHintingAnimation(bool is_first_time) {
int delay_in_sec = kAnimationIntervalInSec;
if (is_first_time)
delay_in_sec = kAnimationInitialWaitTimeInSec;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ExpandArrowView::StartHintingAnimation,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(delay_in_sec));
}
void ExpandArrowView::StartHintingAnimation() {
if (!button_pressed_)
animation_->Show();
}
void ExpandArrowView::ResetHintingAnimation() {
pulse_opacity_ = kPulseMinOpacity;
pulse_radius_ = kPulseMinRadius;
animation_->Reset();
Layout();
}
} // namespace app_list