123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- // 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 "ash/app_list/views/expand_arrow_view.h"
- #include <algorithm>
- #include <memory>
- #include <utility>
- #include "ash/app_list/views/app_list_view.h"
- #include "ash/app_list/views/apps_container_view.h"
- #include "ash/app_list/views/contents_view.h"
- #include "ash/public/cpp/app_list/app_list_color_provider.h"
- #include "ash/public/cpp/app_list/app_list_config.h"
- #include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "base/bind.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/numerics/safe_conversions.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/compositor/scoped_animation_duration_scale_mode.h"
- #include "ui/gfx/animation/slide_animation.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/color_palette.h"
- #include "ui/gfx/paint_vector_icon.h"
- #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
- #include "ui/views/animation/ink_drop.h"
- #include "ui/views/animation/ink_drop_impl.h"
- #include "ui/views/controls/highlight_path_generator.h"
- namespace ash {
- namespace {
- // The dimensions of this view.
- constexpr int kTileHeight = 72;
- constexpr int kTileWidth = 60;
- // The arrow dimension of expand arrow.
- constexpr int kArrowDimension = 12;
- constexpr int kInkDropRadius = 18;
- constexpr int kCircleRadius = 18;
- // The y position of circle center in closed, peeking and fullscreen state.
- constexpr int kCircleCenterClosedY = 18;
- constexpr int kCircleCenterPeekingY = 42;
- constexpr int kCircleCenterFullscreenY = 8;
- // The arrow's y position in peeking and fullscreen state.
- constexpr int kArrowClosedY = 12;
- constexpr int kArrowPeekingY = 36;
- constexpr int kArrowFullscreenY = 2;
- // The stroke width of the arrow.
- constexpr int kExpandArrowStrokeWidth = 2;
- // The three points of arrow in peeking and fullscreen state.
- constexpr size_t kPointCount = 3;
- constexpr gfx::PointF kPeekingPoints[] = {
- gfx::PointF(0, kArrowDimension / 4 * 3),
- gfx::PointF(kArrowDimension / 2, kArrowDimension / 4),
- gfx::PointF(kArrowDimension, kArrowDimension / 4 * 3)};
- constexpr gfx::PointF kFullscreenPoints[] = {
- gfx::PointF(0, kArrowDimension / 2),
- gfx::PointF(kArrowDimension / 2, kArrowDimension / 2),
- gfx::PointF(kArrowDimension, kArrowDimension / 2)};
- // Arrow vertical transiton animation related constants.
- constexpr int kTotalArrowYOffset = 24;
- 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 auto kCycleDuration = base::Milliseconds(1000);
- constexpr auto kCycleInterval = base::Milliseconds(500);
- constexpr int kFocusRingWidth = 2;
- // THe bounds for the tap target of the expand arrow button.
- constexpr int kTapTargetWidth = 156;
- constexpr int kTapTargetHeight = 72;
- float GetCircleCenterYForAppListProgress(float progress) {
- if (progress <= 1) {
- return gfx::Tween::FloatValueBetween(progress, kCircleCenterClosedY,
- kCircleCenterPeekingY);
- }
- return gfx::Tween::FloatValueBetween(std::min(1.0f, progress - 1),
- kCircleCenterPeekingY,
- kCircleCenterFullscreenY);
- }
- float GetArrowYForAppListProgress(float progress) {
- if (progress <= 1) {
- return gfx::Tween::FloatValueBetween(progress, kArrowClosedY,
- kArrowPeekingY);
- }
- return gfx::Tween::FloatValueBetween(std::min(1.0f, progress - 1),
- kArrowPeekingY, kArrowFullscreenY);
- }
- // Returns the location of the circle, relative to the view's local bounds.
- gfx::Rect GetCircleBounds() {
- const gfx::Point circle_center(kTileWidth / 2, kCircleCenterPeekingY);
- const gfx::Rect circle_bounds(
- circle_center - gfx::Vector2d(kCircleRadius, kCircleRadius),
- gfx::Size(2 * kCircleRadius, 2 * kCircleRadius));
- return circle_bounds;
- }
- class ExpandArrowHighlightPathGenerator : public views::HighlightPathGenerator {
- public:
- ExpandArrowHighlightPathGenerator() = default;
- ExpandArrowHighlightPathGenerator(const ExpandArrowHighlightPathGenerator&) =
- delete;
- ExpandArrowHighlightPathGenerator& operator=(
- const ExpandArrowHighlightPathGenerator&) = delete;
- // views::HighlightPathGenerator:
- absl::optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override {
- return gfx::RRectF(gfx::RectF(GetCircleBounds()), kInkDropRadius);
- }
- };
- } // namespace
- ExpandArrowView::ExpandArrowView(ContentsView* contents_view,
- AppListView* app_list_view)
- : views::Button(base::BindRepeating(&ExpandArrowView::OnButtonPressed,
- base::Unretained(this))),
- contents_view_(contents_view),
- app_list_view_(app_list_view) {
- SetFocusBehavior(FocusBehavior::ALWAYS);
- SetPaintToLayer();
- layer()->SetFillsBoundsOpaquely(false);
- // ExpandArrowView draws its own focus, removing FocusRing prevents double
- // focus.
- // TODO(pbos): Replace ::OnPaint focus painting with FocusRing +
- // HighlightPathGenerator usage.
- SetInstallFocusRingOnFocus(false);
- views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
- views::HighlightPathGenerator::Install(
- this, std::make_unique<ExpandArrowHighlightPathGenerator>());
- views::InkDrop::UseInkDropWithoutAutoHighlight(views::InkDrop::Get(this),
- /*highlight_on_hover=*/false);
- views::InkDrop::Get(this)->SetCreateRippleCallback(base::BindRepeating(
- [](Button* host) -> std::unique_ptr<views::InkDropRipple> {
- const AppListColorProvider* color_provider =
- AppListColorProvider::Get();
- return std::make_unique<views::FloodFillInkDropRipple>(
- host->size(), host->GetLocalBounds().InsetsFrom(GetCircleBounds()),
- views::InkDrop::Get(host)->GetInkDropCenterBasedOnLastEvent(),
- color_provider->GetInkDropBaseColor(),
- color_provider->GetInkDropOpacity());
- },
- this));
- SetAccessibleName(l10n_util::GetStringUTF16(IDS_APP_LIST_EXPAND_BUTTON));
- animation_ = std::make_unique<gfx::SlideAnimation>(this);
- animation_->SetTweenType(gfx::Tween::LINEAR);
- animation_->SetSlideDuration(kCycleDuration * 2 + kCycleInterval);
- SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
- }
- ExpandArrowView::~ExpandArrowView() = default;
- void ExpandArrowView::PaintButtonContents(gfx::Canvas* canvas) {
- gfx::PointF circle_center(kTileWidth / 2, kCircleCenterPeekingY);
- gfx::PointF arrow_origin((kTileWidth - kArrowDimension) / 2, kArrowPeekingY);
- gfx::PointF arrow_points[kPointCount];
- for (size_t i = 0; i < kPointCount; ++i)
- arrow_points[i] = kPeekingPoints[i];
- SkColor circle_color =
- AppListColorProvider::Get()->GetExpandArrowIconBackgroundColor();
- const float progress = app_list_view_->GetAppListTransitionProgress(
- AppListView::kProgressFlagNone);
- circle_center.set_y(GetCircleCenterYForAppListProgress(progress));
- arrow_origin.set_y(GetArrowYForAppListProgress(progress));
- // If transition progress is between peeking and fullscreen state, change
- // the shape of the arrow and the opacity of the circle in addition to
- // changing the circle and arrow position.
- if (progress > 1) {
- const float peeking_to_full_progress = progress - 1;
- for (size_t i = 0; i < kPointCount; ++i) {
- arrow_points[i].set_y(gfx::Tween::FloatValueBetween(
- peeking_to_full_progress, kPeekingPoints[i].y(),
- kFullscreenPoints[i].y()));
- }
- circle_color = gfx::Tween::ColorValueBetween(
- peeking_to_full_progress, circle_color, SK_ColorTRANSPARENT);
- }
- if (animation_->is_animating() && progress <= 1) {
- // If app list is peeking state or below peeking state, the arrow should
- // keep runing transition animation.
- arrow_origin.Offset(0, arrow_y_offset_);
- }
- // Draw a circle.
- cc::PaintFlags circle_flags;
- circle_flags.setAntiAlias(true);
- circle_flags.setColor(circle_color);
- circle_flags.setStyle(cc::PaintFlags::kFill_Style);
- canvas->DrawCircle(circle_center, kCircleRadius, circle_flags);
- if (HasFocus()) {
- cc::PaintFlags focus_ring_flags;
- focus_ring_flags.setAntiAlias(true);
- focus_ring_flags.setColor(AppListColorProvider::Get()->GetFocusRingColor());
- focus_ring_flags.setStyle(cc::PaintFlags::Style::kStroke_Style);
- focus_ring_flags.setStrokeWidth(kFocusRingWidth);
- // Creates a focus ring with 1px wider radius to create a border.
- canvas->DrawCircle(circle_center, kCircleRadius + 1, focus_ring_flags);
- }
- if (animation_->is_animating() && progress <= 1) {
- // Draw a pulse that expands around the circle.
- cc::PaintFlags pulse_flags;
- pulse_flags.setStyle(cc::PaintFlags::kStroke_Style);
- pulse_flags.setColor(
- SkColorSetA(AppListColorProvider::Get()->GetExpandArrowIconBaseColor(),
- static_cast<U8CPU>(255 * pulse_opacity_)));
- pulse_flags.setAntiAlias(true);
- canvas->DrawCircle(circle_center, pulse_radius_, pulse_flags);
- }
- // Add a clip path so that arrow will only be shown within the circular
- // highlight area.
- SkPath arrow_mask_path;
- arrow_mask_path.addCircle(circle_center.x(), circle_center.y(),
- kCircleRadius);
- canvas->ClipPath(arrow_mask_path, true);
- // Draw an arrow. (It becomes a horizontal line in fullscreen state.)
- for (auto& point : arrow_points)
- point.Offset(arrow_origin.x(), arrow_origin.y());
- cc::PaintFlags arrow_flags;
- arrow_flags.setAntiAlias(true);
- arrow_flags.setColor(
- AppListColorProvider::Get()->GetExpandArrowIconBaseColor());
- arrow_flags.setStrokeWidth(kExpandArrowStrokeWidth);
- arrow_flags.setStrokeCap(cc::PaintFlags::Cap::kRound_Cap);
- arrow_flags.setStrokeJoin(cc::PaintFlags::Join::kRound_Join);
- arrow_flags.setStyle(cc::PaintFlags::kStroke_Style);
- SkPath arrow_path;
- arrow_path.moveTo(arrow_points[0].x(), arrow_points[0].y());
- for (size_t i = 1; i < kPointCount; ++i)
- arrow_path.lineTo(arrow_points[i].x(), arrow_points[i].y());
- canvas->DrawPath(arrow_path, arrow_flags);
- }
- gfx::Size ExpandArrowView::CalculatePreferredSize() const {
- return gfx::Size(kTileWidth, kTileHeight);
- }
- 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();
- }
- const char* ExpandArrowView::GetClassName() const {
- return "ExpandArrowView";
- }
- void ExpandArrowView::AnimationProgressed(const gfx::Animation* animation) {
- // There are two cycles in one animation.
- constexpr auto kAnimationDuration = kCycleDuration * 2 + kCycleInterval;
- constexpr auto kFirstCycleEndTime = kCycleDuration;
- constexpr auto kIntervalEndTime = kCycleDuration + kCycleInterval;
- constexpr auto kSecondCycleEndTime = kCycleDuration * 2 + kCycleInterval;
- base::TimeDelta time = animation->GetCurrentValue() * kAnimationDuration;
- if (time > kFirstCycleEndTime && time <= kIntervalEndTime) {
- // There's no animation in the interval between cycles.
- return;
- }
- if (time > kIntervalEndTime && time <= kSecondCycleEndTime) {
- // Convert to time in one single cycle.
- time -= kIntervalEndTime;
- }
- // Update pulse opacity.
- constexpr auto kPulseOpacityShowBeginTime = base::Milliseconds(100);
- constexpr auto kPulseOpacityShowEndTime = base::Milliseconds(200);
- constexpr auto kPulseOpacityHideBeginTime = base::Milliseconds(800);
- constexpr auto kPulseOpacityHideEndTime = base::Milliseconds(1000);
- if (time > kPulseOpacityShowBeginTime && time <= kPulseOpacityShowEndTime) {
- pulse_opacity_ =
- kPulseMinOpacity +
- (kPulseMaxOpacity - kPulseMinOpacity) *
- (time - kPulseOpacityShowBeginTime) /
- (kPulseOpacityShowEndTime - kPulseOpacityShowBeginTime);
- } else if (time > kPulseOpacityHideBeginTime &&
- time <= kPulseOpacityHideEndTime) {
- pulse_opacity_ =
- kPulseMaxOpacity -
- (kPulseMaxOpacity - kPulseMinOpacity) *
- (time - kPulseOpacityHideBeginTime) /
- (kPulseOpacityHideEndTime - kPulseOpacityHideBeginTime);
- }
- // Update pulse radius.
- pulse_radius_ =
- base::ClampRound((kPulseMaxRadius - kPulseMinRadius) *
- gfx::Tween::CalculateValue(gfx::Tween::EASE_IN_OUT,
- time / kCycleDuration));
- // Update y position offset of the arrow.
- constexpr auto kArrowMoveOutBeginTime = base::Milliseconds(100);
- constexpr auto kArrowMoveOutEndTime = base::Milliseconds(500);
- constexpr auto kArrowMoveInBeginTime = base::Milliseconds(500);
- constexpr auto kArrowMoveInEndTime = base::Milliseconds(900);
- if (time > kArrowMoveOutBeginTime && time <= kArrowMoveOutEndTime) {
- const double progress = (time - kArrowMoveOutBeginTime) /
- (kArrowMoveOutEndTime - kArrowMoveOutBeginTime);
- arrow_y_offset_ = base::ClampRound(
- -kTotalArrowYOffset *
- gfx::Tween::CalculateValue(gfx::Tween::EASE_IN, progress));
- } else if (time > kArrowMoveInBeginTime && time <= kArrowMoveInEndTime) {
- const double progress = (time - kArrowMoveInBeginTime) /
- (kArrowMoveInEndTime - kArrowMoveInBeginTime);
- arrow_y_offset_ = base::ClampRound(
- kTotalArrowYOffset *
- (1 - gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, progress)));
- }
- // Apply updates.
- SchedulePaint();
- }
- void ExpandArrowView::AnimationEnded(const gfx::Animation* /*animation*/) {
- ResetHintingAnimation();
- // Only reschedule hinting animation if app list is not fullscreen. Once the
- // user has made the app_list fullscreen, a hint to do so is no longer needed
- if (!app_list_view_->is_fullscreen())
- ScheduleHintingAnimation(false);
- }
- void ExpandArrowView::OnButtonPressed() {
- button_pressed_ = true;
- ResetHintingAnimation();
- TransitToFullscreenAllAppsState();
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::ACTION_TRIGGERED);
- }
- void ExpandArrowView::TransitToFullscreenAllAppsState() {
- UMA_HISTOGRAM_ENUMERATION("Apps.AppListPageOpened", AppListState::kStateApps,
- AppListState::kStateLast);
- UMA_HISTOGRAM_ENUMERATION(kAppListPeekingToFullscreenHistogram, kExpandArrow,
- kMaxPeekingToFullscreen);
- contents_view_->SetActiveState(AppListState::kStateApps);
- app_list_view_->SetState(AppListViewState::kFullscreenAllApps);
- }
- void ExpandArrowView::MaybeEnableHintingAnimation(bool enabled) {
- button_pressed_ = false;
- ResetHintingAnimation();
- // When side shelf or tablet mode is enabled, the peeking launcher won't be
- // shown, so the hint animation is unnecessary. Also, do not run the animation
- // during test since we are not testing the animation and it might cause msan
- // crash when spoken feedback is enabled (See https://crbug.com/926038).
- if (enabled && !app_list_view_->is_side_shelf() &&
- !app_list_view_->is_tablet_mode() &&
- !ui::ScopedAnimationDurationScaleMode::is_zero()) {
- ScheduleHintingAnimation(true);
- } else {
- hinting_animation_timer_.Stop();
- }
- }
- float ExpandArrowView::CalculateOffsetFromCurrentAppListProgress(
- double progress) const {
- const float current_progress = app_list_view_->GetAppListTransitionProgress(
- AppListView::kProgressFlagNone);
- return GetCircleCenterYForAppListProgress(progress) -
- GetCircleCenterYForAppListProgress(current_progress);
- }
- void ExpandArrowView::ScheduleHintingAnimation(bool is_first_time) {
- int delay_in_sec = kAnimationIntervalInSec;
- if (is_first_time)
- delay_in_sec = kAnimationInitialWaitTimeInSec;
- hinting_animation_timer_.Start(FROM_HERE, base::Seconds(delay_in_sec), this,
- &ExpandArrowView::StartHintingAnimation);
- }
- void ExpandArrowView::StartHintingAnimation() {
- if (!button_pressed_)
- animation_->Show();
- }
- void ExpandArrowView::ResetHintingAnimation() {
- pulse_opacity_ = kPulseMinOpacity;
- pulse_radius_ = kPulseMinRadius;
- animation_->Reset();
- Layout();
- }
- bool ExpandArrowView::DoesIntersectRect(const views::View* target,
- const gfx::Rect& rect) const {
- DCHECK_EQ(target, this);
- gfx::Rect button_bounds = GetLocalBounds();
- // Increase clickable area for the button from
- // (kTileWidth x height) to
- // (kTapTargetWidth x kTapTargetHeight).
- const int horizontal_padding = (kTapTargetWidth - button_bounds.width()) / 2;
- const int vertical_padding = (kTapTargetHeight - button_bounds.height()) / 2;
- button_bounds.Inset(gfx::Insets::VH(-vertical_padding, -horizontal_padding));
- return button_bounds.Intersects(rect);
- }
- } // namespace ash
|