blob: 96c7e83516318901bb7aed4aa6f9bf9a2fa52a57 [file] [log] [blame]
// 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/app_list/views/ghost_image_view.h"
#include <algorithm>
#include <vector>
#include "ash/app_list/model/app_list_folder_item.h"
#include "ash/app_list/model/app_list_item_list.h"
#include "ash/app_list/views/app_list_item_view.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
namespace app_list {
namespace {
constexpr int kGhostCircleStrokeWidth = 2;
constexpr int kGhostColor = SkColorSetARGB(0x4C, 232, 234, 237);
constexpr int kFolderGhostColor = SkColorSetARGB(0x4C, 95, 99, 104);
constexpr base::TimeDelta kGhostFadeInOutLength =
base::TimeDelta::FromMilliseconds(180);
constexpr int kInnerFolderGhostIconRadius = 14;
constexpr gfx::Tween::Type kGhostTween = gfx::Tween::FAST_OUT_SLOW_IN;
} // namespace
GhostImageView::GhostImageView(AppListItemView* drag_view,
bool is_folder,
bool is_in_folder,
const gfx::Rect& drop_target_bounds,
int page)
: is_hiding_(false),
is_in_folder_(is_in_folder),
is_folder_(is_folder),
page_(page),
drop_target_bounds_(drop_target_bounds),
icon_bounds_(drag_view->GetIconBounds()) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
layer()->SetOpacity(0.0f);
SetBoundsRect(drop_target_bounds);
if (is_folder_) {
AppListFolderItem* folder_item =
static_cast<AppListFolderItem*>(drag_view->item());
num_items_ = std::min(FolderImage::kNumFolderTopItems,
folder_item->item_list()->item_count());
}
}
GhostImageView::~GhostImageView() {
StopObservingImplicitAnimations();
}
void GhostImageView::FadeOut() {
if (is_hiding_)
return;
is_hiding_ = true;
DoAnimation(true /* fade out */);
}
void GhostImageView::FadeIn() {
DoAnimation(false /* fade in */);
}
void GhostImageView::SetTransitionOffset(
const gfx::Vector2d& transition_offset) {
SetPosition(drop_target_bounds_.origin() + transition_offset);
}
void GhostImageView::DoAnimation(bool hide) {
ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
animation.SetTransitionDuration(kGhostFadeInOutLength);
animation.SetTweenType(kGhostTween);
if (hide) {
animation.AddObserver(this);
animation.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
layer()->SetOpacity(0.0f);
return;
}
layer()->SetOpacity(1.0f);
}
void GhostImageView::OnPaint(gfx::Canvas* canvas) {
const gfx::PointF circle_center(icon_bounds_.CenterPoint());
const float ghost_radius = icon_bounds_.width() / 2;
// Draw a circle to represent the ghost image icon.
cc::PaintFlags circle_flags;
circle_flags.setAntiAlias(true);
circle_flags.setColor(is_in_folder_ ? kFolderGhostColor : kGhostColor);
circle_flags.setStyle(cc::PaintFlags::kStroke_Style);
circle_flags.setStrokeWidth(kGhostCircleStrokeWidth);
canvas->DrawCircle(circle_center, ghost_radius, circle_flags);
if (is_folder_) {
// Draw a mask so inner folder icons do not overlap the outer circle.
SkPath outer_circle_mask;
outer_circle_mask.addCircle(circle_center.x(), circle_center.y(),
ghost_radius - kGhostCircleStrokeWidth / 2);
canvas->ClipPath(outer_circle_mask, true);
// Returns the bounds for each inner icon in the folder icon.
std::vector<gfx::Rect> top_icon_bounds =
FolderImage::GetTopIconsBounds(icon_bounds_, num_items_.value());
// Draw ghost items within the ghost folder circle.
for (gfx::Rect bounds : top_icon_bounds) {
canvas->DrawCircle(gfx::PointF(bounds.CenterPoint()),
kInnerFolderGhostIconRadius, circle_flags);
}
}
ImageView::OnPaint(canvas);
}
void GhostImageView::OnImplicitAnimationsCompleted() {
// Delete this GhostImageView when the fade out animation is done.
delete this;
}
} // namespace app_list