Commit 3825e706 authored by Nicholas Hollingum's avatar Nicholas Hollingum Committed by Commit Bot

Added a fade-out animation to shelf spinners.

This change works by keeping track of a requested removal time and
fading out the spinner over that duration. This is a continuation of
crrev.com/c/1830254.

The external API of this class is preserved, w.r.t. HasApp() calls and
the like, but internally the controller will only actually evict its
items once the fade-out animation has completed.

Bug: 922977
Change-Id: Ief07b33157403d2141187558dabd866a7ebbbd5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1837323Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Commit-Queue: Nic Hollingum <hollingum@google.com>
Cr-Commit-Position: refs/heads/master@{#704036}
parent c3d3fbea
......@@ -25,22 +25,88 @@ namespace {
constexpr int kUpdateIconIntervalMs = 40; // 40ms for 25 frames per second.
// Controls the spinner animation. See crbug.com/922977 for details.
constexpr double kFadeInDurationMs = 200;
constexpr base::TimeDelta kFadeInDuration =
base::TimeDelta::FromMilliseconds(200);
constexpr base::TimeDelta kFadeOutDuration =
base::TimeDelta::FromMilliseconds(200);
constexpr base::TimeDelta kMinimumShowDuration =
base::TimeDelta::FromMilliseconds(200);
constexpr int kSpinningGapPercent = 25;
constexpr color_utils::HSL kInactiveHslShift = {-1, 0, 0.25};
constexpr double kInactiveTransparency = 0.5;
// Returns the proportion of the duration |d| from |t1| to |t2|, where 0 means
// |t2| is before or at |t1| and 1 means it is |d| or further ahead.
double TimeProportionSince(const base::Time& t1,
const base::Time& t2,
const base::TimeDelta& d) {
return std::max(
0.0, std::min(1.0, (t2 - t1).InMillisecondsF() / d.InMillisecondsF()));
}
} // namespace
class ShelfSpinnerController::ShelfSpinnerData {
public:
explicit ShelfSpinnerData(ShelfSpinnerItemController* controller)
: controller_(controller),
creation_time_(controller->start_time()),
removal_time_() {}
~ShelfSpinnerData() = default;
// Returns true if we are currently fading the spinner in. This will also
// return true when the spinner is animating but has finished fading in.
bool IsFadingIn() const {
return !IsKilled() || (base::Time::Now() < removal_time_);
}
// Returns true if we have completed the fade-out animation.
bool IsFinished() const {
return IsKilled() && base::Time::Now() >= removal_time_ + kFadeOutDuration;
}
// Returns true if this spinner has been killed (no matter what stage of the
// animation it is up to).
bool IsKilled() const { return controller_ == nullptr; }
// Marks the spinner as completed, which begins the fade out animation
// either now, or at a point in the future when the minimum show duration
// has been met.
void Kill() {
removal_time_ =
std::max(base::Time::Now(), creation_time_ + kMinimumShowDuration);
controller_ = nullptr;
}
ShelfSpinnerItemController* controller() const { return controller_; }
// Get a timestamp for when the spinner was started.
base::Time creation_time() const { return creation_time_; }
// Get a timestamp for when the spinner's fade-out animation begins. This
// will be in the future if the spiiner was Kill()ed before the minimum show
// duration was reached.
base::Time removal_time() const { return removal_time_; }
private:
ShelfSpinnerItemController* controller_;
base::Time creation_time_;
base::Time removal_time_;
};
namespace {
class SpinningEffectSource : public gfx::CanvasImageSource {
public:
SpinningEffectSource(const base::WeakPtr<ShelfSpinnerController>& host,
const std::string& app_id,
SpinningEffectSource(ShelfSpinnerController::ShelfSpinnerData data,
const gfx::ImageSkia& image,
bool is_pinned)
: gfx::CanvasImageSource(image.size()),
host_(host),
app_id_(app_id),
data_(std::move(data)),
active_image_(
is_pinned
(is_pinned || !data_.IsFadingIn())
? image
: gfx::ImageSkiaOperations::CreateTransparentImage(image, 0)),
inactive_image_(gfx::ImageSkiaOperations::CreateTransparentImage(
......@@ -52,16 +118,11 @@ class SpinningEffectSource : public gfx::CanvasImageSource {
// gfx::CanvasImageSource override.
void Draw(gfx::Canvas* canvas) override {
if (!host_)
return;
base::TimeDelta active_time = host_->GetActiveTime(app_id_);
double fade_in_lirp =
std::min(kFadeInDurationMs, active_time.InMillisecondsF()) /
kFadeInDurationMs;
base::Time now = base::Time::Now();
double animation_lirp = GetAnimationStage(now);
canvas->DrawImageInt(gfx::ImageSkiaOperations::CreateBlendedImage(
active_image_, inactive_image_, fade_in_lirp),
inactive_image_, active_image_, animation_lirp),
0, 0);
const int gap = kSpinningGapPercent * inactive_image_.width() / 100;
......@@ -69,17 +130,31 @@ class SpinningEffectSource : public gfx::CanvasImageSource {
canvas,
gfx::Rect(gap, gap, inactive_image_.width() - 2 * gap,
inactive_image_.height() - 2 * gap),
SkColorSetA(SK_ColorWHITE, 0xFF * fade_in_lirp), active_time);
SkColorSetA(SK_ColorWHITE, 0xFF * (1.0 - std::abs(animation_lirp))),
now - data_.creation_time());
}
private:
base::WeakPtr<ShelfSpinnerController> host_;
const std::string app_id_;
// Returns a number in the range [0, 1] where:
// - 0 -> spinner image is completely shown.
// - 0.5 -> spinner image is half-way gone.
// - 1 -> normal image is shown.
double GetAnimationStage(const base::Time& now) {
if (data_.IsFadingIn()) {
return 1.0 -
TimeProportionSince(data_.creation_time(), now, kFadeInDuration);
} else {
return TimeProportionSince(data_.removal_time(), now, kFadeOutDuration);
}
}
ShelfSpinnerController::ShelfSpinnerData data_;
const gfx::ImageSkia active_image_;
const gfx::ImageSkia inactive_image_;
DISALLOW_COPY_AND_ASSIGN(SpinningEffectSource);
};
} // namespace
ShelfSpinnerController::ShelfSpinnerController(ChromeLauncherController* owner)
......@@ -102,12 +177,12 @@ ShelfSpinnerController::~ShelfSpinnerController() {
void ShelfSpinnerController::MaybeApplySpinningEffect(const std::string& app_id,
gfx::ImageSkia* image) {
DCHECK(image);
if (app_controller_map_.find(app_id) == app_controller_map_.end())
auto it = app_controller_map_.find(app_id);
if (it == app_controller_map_.end())
return;
*image = gfx::ImageSkia(std::make_unique<SpinningEffectSource>(
weak_ptr_factory_.GetWeakPtr(), app_id, *image,
owner_->IsAppPinned(app_id)),
it->second, *image, owner_->IsAppPinned(app_id)),
image->size());
}
......@@ -148,7 +223,8 @@ bool ShelfSpinnerController::RemoveSpinnerFromControllerMap(
return false;
const ash::ShelfID shelf_id(app_id);
DCHECK_EQ(it->second, owner_->shelf_model()->GetShelfItemDelegate(shelf_id));
DCHECK_EQ(it->second.controller(),
owner_->shelf_model()->GetShelfItemDelegate(shelf_id));
app_controller_map_.erase(it);
return true;
......@@ -169,7 +245,8 @@ void ShelfSpinnerController::CloseCrostiniSpinners() {
}
bool ShelfSpinnerController::HasApp(const std::string& app_id) const {
return app_controller_map_.count(app_id);
auto it = app_controller_map_.find(app_id);
return it != app_controller_map_.end() && !it->second.IsKilled();
}
base::TimeDelta ShelfSpinnerController::GetActiveTime(
......@@ -178,7 +255,7 @@ base::TimeDelta ShelfSpinnerController::GetActiveTime(
if (it == app_controller_map_.end())
return base::TimeDelta();
return it->second->GetActiveTime();
return base::Time::Now() - it->second.creation_time();
}
Profile* ShelfSpinnerController::OwnerProfile() {
......@@ -191,8 +268,7 @@ void ShelfSpinnerController::ShelfItemDelegateChanged(
ash::ShelfItemDelegate* delegate) {
auto it = app_controller_map_.find(id.app_id);
if (it != app_controller_map_.end()) {
app_controller_map_.erase(it);
UpdateShelfItemIcon(id.app_id);
it->second.Kill();
}
}
......@@ -238,8 +314,16 @@ void ShelfSpinnerController::UpdateApps() {
return;
RegisterNextUpdate();
for (const auto pair : app_controller_map_)
std::vector<std::string> app_ids_to_close;
for (const auto& pair : app_controller_map_) {
UpdateShelfItemIcon(pair.first);
if (pair.second.IsFinished())
app_ids_to_close.emplace_back(pair.first);
}
for (const auto& app_id : app_ids_to_close) {
if (RemoveSpinnerFromControllerMap(app_id))
UpdateShelfItemIcon(app_id);
}
}
void ShelfSpinnerController::RegisterNextUpdate() {
......@@ -273,5 +357,5 @@ void ShelfSpinnerController::AddSpinnerToShelf(
if (app_controller_map_.empty())
RegisterNextUpdate();
app_controller_map_[app_id] = item_controller;
app_controller_map_.emplace(app_id, ShelfSpinnerData(item_controller));
}
......@@ -32,6 +32,10 @@ class ImageSkia;
// waiting for ARC or Crostini to be ready.
class ShelfSpinnerController : public ash::ShelfModelObserver {
public:
// ShelfSpinnerData holds the information used to draw the spinner, including
// animating the spinner after it has been dismissed.
class ShelfSpinnerData;
explicit ShelfSpinnerController(ChromeLauncherController* owner);
~ShelfSpinnerController() override;
......@@ -70,9 +74,10 @@ class ShelfSpinnerController : public ash::ShelfModelObserver {
ash::ShelfItemDelegate* delegate) override;
private:
// Defines mapping of a shelf app id to a corresponding controller. Shelf app
// id is optional mapping (for example, Play Store to ARC Host Support).
using AppControllerMap = std::map<std::string, ShelfSpinnerItemController*>;
// Defines mapping of a shelf app id to a corresponding controller's data.
// Shelf app id is optional mapping (for example, Play Store to ARC Host
// Support).
using AppControllerMap = std::map<std::string, ShelfSpinnerData>;
// Defines a mapping from account id to (app id, ShelfSpinnerItemController)
// for spinners that are not currently on the shelf. Taking ownership of these
// delegates allows us to reuse them if we need to add the spinner back on to
......
......@@ -25,10 +25,6 @@ void ShelfSpinnerItemController::SetHost(
host_ = host;
}
base::TimeDelta ShelfSpinnerItemController::GetActiveTime() const {
return base::Time::Now() - start_time_;
}
void ShelfSpinnerItemController::ExecuteCommand(bool from_context_menu,
int64_t command_id,
int32_t event_flags,
......
......@@ -29,7 +29,7 @@ class ShelfSpinnerItemController : public ash::ShelfItemDelegate {
virtual void SetHost(const base::WeakPtr<ShelfSpinnerController>& host);
base::TimeDelta GetActiveTime() const;
base::Time start_time() const { return start_time_; }
// ash::ShelfItemDelegate:
void ExecuteCommand(bool from_context_menu,
......
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