holding_space_animation_registry.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/system/holding_space/holding_space_animation_registry.h"
  5. #include <map>
  6. #include <memory>
  7. #include <set>
  8. #include <vector>
  9. #include "ash/public/cpp/holding_space/holding_space_controller.h"
  10. #include "ash/public/cpp/holding_space/holding_space_controller_observer.h"
  11. #include "ash/public/cpp/holding_space/holding_space_model.h"
  12. #include "ash/public/cpp/holding_space/holding_space_model_observer.h"
  13. #include "ash/system/progress_indicator/progress_icon_animation.h"
  14. #include "ash/system/progress_indicator/progress_ring_animation.h"
  15. #include "base/containers/contains.h"
  16. #include "base/containers/cxx20_erase_map.h"
  17. #include "base/memory/ptr_util.h"
  18. #include "base/no_destructor.h"
  19. namespace ash {
  20. namespace {
  21. // Helpers ---------------------------------------------------------------------
  22. // Returns the owner for the singleton `HoldingSpaceAnimationRegistry` instance.
  23. std::unique_ptr<HoldingSpaceAnimationRegistry>& GetInstanceOwner() {
  24. static base::NoDestructor<std::unique_ptr<HoldingSpaceAnimationRegistry>>
  25. instance_owner;
  26. return *instance_owner;
  27. }
  28. } // namespace
  29. // HoldingSpaceAnimationRegistry::ProgressIndicatorAnimationDelegate -----------
  30. // The delegate of `HoldingSpaceAnimationRegistry` responsible for creating and
  31. // curating progress indicator animations based on holding space model state.
  32. class HoldingSpaceAnimationRegistry::ProgressIndicatorAnimationDelegate
  33. : public HoldingSpaceControllerObserver,
  34. public HoldingSpaceModelObserver {
  35. public:
  36. ProgressIndicatorAnimationDelegate(
  37. ProgressIndicatorAnimationRegistry* registry,
  38. HoldingSpaceController* controller)
  39. : registry_(registry), controller_(controller) {
  40. controller_observation_.Observe(controller_);
  41. if (controller_->model())
  42. OnHoldingSpaceModelAttached(controller_->model());
  43. }
  44. ProgressIndicatorAnimationDelegate(
  45. const ProgressIndicatorAnimationDelegate&) = delete;
  46. ProgressIndicatorAnimationDelegate& operator=(
  47. const ProgressIndicatorAnimationDelegate&) = delete;
  48. ~ProgressIndicatorAnimationDelegate() override = default;
  49. private:
  50. // HoldingSpaceControllerObserver:
  51. void OnHoldingSpaceModelAttached(HoldingSpaceModel* model) override {
  52. model_ = model;
  53. model_observation_.Observe(model_);
  54. UpdateAnimations(/*for_removal=*/false);
  55. }
  56. void OnHoldingSpaceModelDetached(HoldingSpaceModel* model) override {
  57. model_ = nullptr;
  58. model_observation_.Reset();
  59. UpdateAnimations(/*for_removal=*/false);
  60. }
  61. // HoldingSpaceModelObserver:
  62. void OnHoldingSpaceItemsAdded(
  63. const std::vector<const HoldingSpaceItem*>& items) override {
  64. UpdateAnimations(/*for_removal=*/false);
  65. }
  66. void OnHoldingSpaceItemsRemoved(
  67. const std::vector<const HoldingSpaceItem*>& items) override {
  68. // The removal of `items` can be safely ignored if none were in progress.
  69. const bool removed_in_progress_item = std::any_of(
  70. items.begin(), items.end(), [](const HoldingSpaceItem* item) {
  71. return item->IsInitialized() && !item->progress().IsComplete();
  72. });
  73. if (removed_in_progress_item)
  74. UpdateAnimations(/*for_removal=*/true);
  75. }
  76. void OnHoldingSpaceItemInitialized(const HoldingSpaceItem* item) override {
  77. UpdateAnimations(/*for_removal=*/false);
  78. }
  79. void OnHoldingSpaceItemUpdated(const HoldingSpaceItem* item,
  80. uint32_t updated_fields) override {
  81. // The `item` update can be safely ignored if progress has not been updated.
  82. if (!(updated_fields & HoldingSpaceModelObserver::UpdatedField::kProgress))
  83. return;
  84. // If `item` has just progressed to completion, ensure that a pulse
  85. // animation is created and started.
  86. if (item->progress().IsComplete()) {
  87. EnsureRingAnimationOfTypeForKey(item,
  88. ProgressRingAnimation::Type::kPulse);
  89. }
  90. UpdateAnimations(/*for_removal=*/false);
  91. }
  92. // Erases the ring animation for the specified `key` if it is not of the
  93. // desired `type`, notifying any animation changed callbacks.
  94. void EraseRingAnimationIfNotOfTypeForKey(const void* key,
  95. ProgressRingAnimation::Type type) {
  96. auto* ring_animation = registry_->GetProgressRingAnimationForKey(key);
  97. if (ring_animation && ring_animation->type() != type)
  98. registry_->SetProgressRingAnimationForKey(key, nullptr);
  99. }
  100. // Ensures that the icon animation for the specified `key` exists. If
  101. // necessary, a new animation is created and started, notifying any animation
  102. // changed callbacks.
  103. void EnsureIconAnimationForKey(const void* key) {
  104. if (registry_->GetProgressIconAnimationForKey(key))
  105. return;
  106. auto* animation = registry_->SetProgressIconAnimationForKey(
  107. key, std::make_unique<ProgressIconAnimation>());
  108. // Only `Start()` the `animation` if it is associated with the holding space
  109. // `controller_`. In all other cases, the `animation` is associated with a
  110. // holding space item and will be started after the associated holding space
  111. // tray item preview has had the opportunity to animate in.
  112. if (key == controller_)
  113. animation->Start();
  114. }
  115. // Ensures that the ring animation for the specified `key` is of the desired
  116. // `type`. If necessary, a new animation is created and started, notifying any
  117. // animation changed callbacks.
  118. void EnsureRingAnimationOfTypeForKey(const void* key,
  119. ProgressRingAnimation::Type type) {
  120. auto* ring_animation = registry_->GetProgressRingAnimationForKey(key);
  121. if (ring_animation && ring_animation->type() == type)
  122. return;
  123. auto animation = ProgressRingAnimation::CreateOfType(type);
  124. animation->AddUnsafeAnimationUpdatedCallback(base::BindRepeating(
  125. &ProgressIndicatorAnimationDelegate::OnRingAnimationUpdatedForKey,
  126. base::Unretained(this), key, animation.get()));
  127. registry_->SetProgressRingAnimationForKey(key, std::move(animation))
  128. ->Start();
  129. }
  130. // Updates animation state for the current `model_` state. If `for_removal` is
  131. // `true`, the update was triggered by holding space item removal.
  132. void UpdateAnimations(bool for_removal) {
  133. // If no `model_` is currently attached, there should be no animations.
  134. // Animations will be updated if and when a `model_` is attached.
  135. if (model_ == nullptr) {
  136. cumulative_progress_ = HoldingSpaceProgress();
  137. registry_->EraseAllAnimations();
  138. return;
  139. }
  140. // Clean up all animations associated with holding space items that are no
  141. // longer present in the attached `model_`.
  142. registry_->EraseAllAnimationsForKeyIf(base::BindRepeating(
  143. [](const std::vector<std::unique_ptr<HoldingSpaceItem>>& items,
  144. const void* controller, const void* key) {
  145. return key != controller &&
  146. !base::Contains(items, key,
  147. &std::unique_ptr<HoldingSpaceItem>::get);
  148. },
  149. std::cref(model_->items()), base::Unretained(controller_)));
  150. HoldingSpaceProgress last_cumulative_progress = cumulative_progress_;
  151. cumulative_progress_ = HoldingSpaceProgress();
  152. // Iterate over each holding space item in the attached `model_`.
  153. for (const auto& item : model_->items()) {
  154. // If an `item` is not initialized or is not visibly in-progress, it
  155. // shouldn't contribute to `cumulative_progress_` nor have an animation.
  156. if (!item->IsInitialized() || item->progress().IsHidden()) {
  157. registry_->EraseAllAnimationsForKey(item.get());
  158. continue;
  159. }
  160. // If the `item` is complete, it should be allowed to continue a pulse
  161. // animation if one was previously created and started. This would only
  162. // have happened in response to the `item` transitioning to completion at
  163. // runtime, as items that are already complete on creation are not
  164. // animated. Any other type of animation should be cleared. Note that a
  165. // completed `item` does not contribute to `cumulative_progress_`.
  166. if (item->progress().IsComplete()) {
  167. registry_->SetProgressIconAnimationForKey(item.get(), nullptr);
  168. EraseRingAnimationIfNotOfTypeForKey(
  169. item.get(), ProgressRingAnimation::Type::kPulse);
  170. continue;
  171. }
  172. cumulative_progress_ += item->progress();
  173. // Because the `item` is in-progress, an icon animation should be
  174. // associated with it (if one does not already exist).
  175. EnsureIconAnimationForKey(item.get());
  176. // If the `item` is in an indeterminate state, an indeterminate animation
  177. // should be associated with it (if one does not already exist).
  178. if (item->progress().IsIndeterminate()) {
  179. EnsureRingAnimationOfTypeForKey(
  180. item.get(), ProgressRingAnimation::Type::kIndeterminate);
  181. continue;
  182. }
  183. // If `item` is not in an indeterminate state, it should not have an
  184. // associated ring animation.
  185. registry_->SetProgressRingAnimationForKey(item.get(), nullptr);
  186. }
  187. if (cumulative_progress_.IsComplete()) {
  188. // Because `cumulative_progress_` is complete, the `controller_` should
  189. // not have an associated icon animation.
  190. registry_->SetProgressIconAnimationForKey(controller_, nullptr);
  191. if (!last_cumulative_progress.IsComplete()) {
  192. if (for_removal) {
  193. // If `cumulative_progress_` has just become complete as a result of
  194. // one or more holding space items being removed, the `controller_`
  195. // should not have an associated ring animation.
  196. registry_->SetProgressRingAnimationForKey(controller_, nullptr);
  197. } else {
  198. // If `cumulative_progress_` has just become complete and is *not* due
  199. // to the removal of one or more holding space items, ensure that a
  200. // pulse animation is created and started.
  201. EnsureRingAnimationOfTypeForKey(controller_,
  202. ProgressRingAnimation::Type::kPulse);
  203. }
  204. } else {
  205. // If `cumulative_progress_` was already complete, it should be allowed
  206. // to continue a pulse animation if one was previously created and
  207. // started. Any other type of ring animation should be cleared.
  208. EraseRingAnimationIfNotOfTypeForKey(
  209. controller_, ProgressRingAnimation::Type::kPulse);
  210. }
  211. return;
  212. }
  213. // Because `cumulative_progress_` is in-progress, the `controller_` should
  214. // have an associated icon animation.
  215. EnsureIconAnimationForKey(controller_);
  216. // If `cumulative_progress_` is in an indeterminate state, an indeterminate
  217. // animation should be associated with the `controller_` (if one does not
  218. // already exist).
  219. if (cumulative_progress_.IsIndeterminate()) {
  220. EnsureRingAnimationOfTypeForKey(
  221. controller_, ProgressRingAnimation::Type::kIndeterminate);
  222. return;
  223. }
  224. // If `cumulative_progress_` is not in an indeterminate state, the
  225. // `controller_` should not have an associated ring animation.
  226. registry_->SetProgressRingAnimationForKey(controller_, nullptr);
  227. }
  228. // Invoked when the specified ring `animation` for the specified `key` has
  229. // been updated. This is used to clean up finished animations.
  230. void OnRingAnimationUpdatedForKey(const void* key,
  231. ProgressRingAnimation* animation) {
  232. if (animation->IsAnimating())
  233. return;
  234. // Once `animation` has finished, it can be removed from the registry. Note
  235. // that this needs to be posted as it is illegal to delete `animation` from
  236. // its update callback sequence.
  237. base::SequencedTaskRunnerHandle::Get()->PostTask(
  238. FROM_HERE,
  239. base::BindOnce(
  240. [](const base::WeakPtr<ProgressIndicatorAnimationDelegate>&
  241. delegate,
  242. const void* key, ProgressRingAnimation* animation) {
  243. if (!delegate)
  244. return;
  245. auto* registry = delegate->registry_;
  246. if (registry->GetProgressRingAnimationForKey(key) == animation)
  247. registry->SetProgressRingAnimationForKey(key, nullptr);
  248. },
  249. weak_factory_.GetWeakPtr(), key, animation));
  250. }
  251. ProgressIndicatorAnimationRegistry* const registry_;
  252. HoldingSpaceController* const controller_;
  253. HoldingSpaceModel* model_ = nullptr;
  254. // The cumulative progress for the attached `model_`, calculated and cached
  255. // with each call to `UpdateAnimations()`. This is used to determine when
  256. // cumulative progress changes from an incomplete to a completed state, at
  257. // which time a pulse animation is created and started.
  258. HoldingSpaceProgress cumulative_progress_;
  259. base::ScopedObservation<HoldingSpaceController,
  260. HoldingSpaceControllerObserver>
  261. controller_observation_{this};
  262. base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
  263. model_observation_{this};
  264. base::WeakPtrFactory<ProgressIndicatorAnimationDelegate> weak_factory_{this};
  265. };
  266. // HoldingSpaceAnimationRegistry -----------------------------------------------
  267. HoldingSpaceAnimationRegistry::HoldingSpaceAnimationRegistry() {
  268. progress_indicator_animation_delegate_ =
  269. std::make_unique<ProgressIndicatorAnimationDelegate>(
  270. this, HoldingSpaceController::Get());
  271. shell_observation_.Observe(Shell::Get());
  272. }
  273. HoldingSpaceAnimationRegistry::~HoldingSpaceAnimationRegistry() = default;
  274. // static
  275. HoldingSpaceAnimationRegistry* HoldingSpaceAnimationRegistry::GetInstance() {
  276. auto& instance_owner = GetInstanceOwner();
  277. if (!instance_owner.get() && Shell::HasInstance())
  278. instance_owner.reset(new HoldingSpaceAnimationRegistry());
  279. return instance_owner.get();
  280. }
  281. void HoldingSpaceAnimationRegistry::OnShellDestroying() {
  282. auto& instance_owner = GetInstanceOwner();
  283. DCHECK_EQ(instance_owner.get(), this);
  284. instance_owner.reset(); // Deletes `this`.
  285. }
  286. } // namespace ash