holding_space_tray_icon_preview.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. // Copyright 2020 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_tray_icon_preview.h"
  5. #include <algorithm>
  6. #include "ash/constants/ash_features.h"
  7. #include "ash/public/cpp/holding_space/holding_space_constants.h"
  8. #include "ash/public/cpp/holding_space/holding_space_image.h"
  9. #include "ash/public/cpp/holding_space/holding_space_item.h"
  10. #include "ash/public/cpp/holding_space/holding_space_model.h"
  11. #include "ash/public/cpp/holding_space/holding_space_model_observer.h"
  12. #include "ash/public/cpp/shelf_config.h"
  13. #include "ash/shelf/shelf.h"
  14. #include "ash/style/ash_color_provider.h"
  15. #include "ash/style/dark_light_mode_controller_impl.h"
  16. #include "ash/system/holding_space/holding_space_animation_registry.h"
  17. #include "ash/system/holding_space/holding_space_progress_indicator_util.h"
  18. #include "ash/system/holding_space/holding_space_tray_icon.h"
  19. #include "ash/system/progress_indicator/progress_indicator.h"
  20. #include "ash/system/tray/tray_constants.h"
  21. #include "base/bind.h"
  22. #include "base/i18n/rtl.h"
  23. #include "ui/compositor/layer.h"
  24. #include "ui/compositor/layer_animation_sequence.h"
  25. #include "ui/compositor/paint_recorder.h"
  26. #include "ui/compositor/scoped_layer_animation_settings.h"
  27. #include "ui/gfx/canvas.h"
  28. #include "ui/gfx/geometry/transform_util.h"
  29. #include "ui/gfx/image/image_skia.h"
  30. #include "ui/gfx/image/image_skia_operations.h"
  31. #include "ui/gfx/image/image_skia_source.h"
  32. #include "ui/gfx/shadow_util.h"
  33. #include "ui/gfx/skia_paint_util.h"
  34. namespace ash {
  35. namespace {
  36. // Appearance.
  37. constexpr int kElevation = 1;
  38. // In-progress animation.
  39. constexpr base::TimeDelta kInProgressAnimationDuration =
  40. base::Milliseconds(150);
  41. constexpr float kInProgressAnimationScaleFactor = 0.7f;
  42. // The duration of each of the preview icon bounce animation.
  43. constexpr base::TimeDelta kBounceAnimationSegmentDuration =
  44. base::Milliseconds(250);
  45. // The delay with which preview icon is dropped into the holding space tray
  46. // icon.
  47. constexpr base::TimeDelta kBounceAnimationBaseDelay = base::Milliseconds(150);
  48. // The duration of shift animation.
  49. constexpr base::TimeDelta kShiftAnimationDuration = base::Milliseconds(250);
  50. // Helpers ---------------------------------------------------------------------
  51. // Convenience helper to allow a `closure` to be used in a context which is
  52. // expecting a callback with arguments.
  53. template <typename... T>
  54. base::RepeatingCallback<void(T...)> IgnoreArgs(base::RepeatingClosure closure) {
  55. return base::BindRepeating([](T...) {}).Then(std::move(closure));
  56. }
  57. // Returns true if small previews should be used given the current shelf
  58. // configuration, false otherwise.
  59. bool ShouldUseSmallPreviews() {
  60. ShelfConfig* const shelf_config = ShelfConfig::Get();
  61. return shelf_config->in_tablet_mode() && shelf_config->is_in_app();
  62. }
  63. // Returns the size for previews. If `use_small_previews` is absent it will be
  64. // determined from the current shelf configuration.
  65. gfx::Size GetPreviewSize(
  66. const absl::optional<bool>& use_small_previews = absl::nullopt) {
  67. return use_small_previews.value_or(ShouldUseSmallPreviews())
  68. ? gfx::Size(kHoldingSpaceTrayIconSmallPreviewSize,
  69. kHoldingSpaceTrayIconSmallPreviewSize)
  70. : gfx::Size(kHoldingSpaceTrayIconDefaultPreviewSize,
  71. kHoldingSpaceTrayIconDefaultPreviewSize);
  72. }
  73. // Returns the shadow details for painting elevation.
  74. const gfx::ShadowDetails& GetShadowDetails() {
  75. const gfx::Size size(GetPreviewSize());
  76. const int radius = std::min(size.height(), size.width()) / 2;
  77. return gfx::ShadowDetails::Get(kElevation, radius);
  78. }
  79. // Adjust the specified `origin` for shadow margins.
  80. void AdjustOriginForShadowMargins(gfx::Point& origin, const Shelf* shelf) {
  81. const gfx::ShadowValues& values(GetShadowDetails().values);
  82. const gfx::Insets margins(gfx::ShadowValue::GetMargin(values));
  83. if (shelf->IsHorizontalAlignment()) {
  84. // When the `shelf` is horizontally aligned the `origin` will already have
  85. // been offset to center the preview `layer()` vertically within its parent
  86. // container so no further vertical offset is needed.
  87. const int offset = margins.width() / 2;
  88. origin.Offset(base::i18n::IsRTL() ? -offset : offset, 0);
  89. } else {
  90. origin.Offset(margins.width() / 2, margins.height() / 2);
  91. }
  92. }
  93. // Enlarges the specified `size` for shadow margins.
  94. void EnlargeForShadowMargins(gfx::Size& size) {
  95. const gfx::ShadowValues& values(GetShadowDetails().values);
  96. const gfx::Insets margins(gfx::ShadowValue::GetMargin(values));
  97. size.Enlarge(-margins.width(), -margins.height());
  98. }
  99. // Returns whether the specified `shelf_alignment` is horizontal.
  100. bool IsHorizontal(ShelfAlignment shelf_alignment) {
  101. switch (shelf_alignment) {
  102. case ShelfAlignment::kBottom:
  103. case ShelfAlignment::kBottomLocked:
  104. return true;
  105. case ShelfAlignment::kLeft:
  106. case ShelfAlignment::kRight:
  107. return false;
  108. }
  109. }
  110. // Performs set up of the specified `animation_settings`.
  111. void SetUpAnimation(ui::ScopedLayerAnimationSettings* animation_settings) {
  112. animation_settings->SetPreemptionStrategy(
  113. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  114. animation_settings->SetTransitionDuration(
  115. ShelfConfig::Get()->shelf_animation_duration());
  116. animation_settings->SetTweenType(gfx::Tween::EASE_OUT);
  117. }
  118. // OneShotLayerAnimationObserver -----------------------------------------------
  119. // A `ui::LayerAnimationObserver` which invokes a callback on animation
  120. // completion. The callback will be run whether the animation ends or aborts.
  121. class CallbackLayerAnimationObserver : public ui::LayerAnimationObserver {
  122. public:
  123. CallbackLayerAnimationObserver() = default;
  124. CallbackLayerAnimationObserver(const CallbackLayerAnimationObserver&) =
  125. delete;
  126. CallbackLayerAnimationObserver& operator=(
  127. const CallbackLayerAnimationObserver&) = delete;
  128. ~CallbackLayerAnimationObserver() override = default;
  129. // Sets the callback to invoke on animation completion. The callback will be
  130. // run whether the animation ends or aborts.
  131. // NOTE: It is safe to delete `this` from callback.
  132. void SetAnimationCompletedCallback(
  133. base::OnceClosure animation_completed_callback) {
  134. animation_completed_callback_ = std::move(animation_completed_callback);
  135. }
  136. private:
  137. // ui::LayerAnimationObserver:
  138. bool RequiresNotificationWhenAnimatorDestroyed() const override {
  139. // Ensure that `OnLayerAnimationAborted()` is invoked on animator
  140. // destruction if an observed animation sequence is in progress.
  141. return true;
  142. }
  143. void OnLayerAnimationScheduled(ui::LayerAnimationSequence*) override {}
  144. void OnLayerAnimationEnded(ui::LayerAnimationSequence*) override {
  145. OnLayerAnimationCompleted();
  146. }
  147. void OnLayerAnimationAborted(ui::LayerAnimationSequence*) override {
  148. OnLayerAnimationCompleted();
  149. }
  150. void OnLayerAnimationCompleted() {
  151. // NOTE: `this` may be deleted by running `animation_completed_callback_`.
  152. if (animation_completed_callback_)
  153. std::move(animation_completed_callback_).Run();
  154. }
  155. base::OnceClosure animation_completed_callback_;
  156. };
  157. } // namespace
  158. // HoldingSpaceTrayIconPreview::ImageLayerOwner --------------------------------
  159. // Class which owns the `layer()` to which the image representation for the
  160. // associated holding space `item_` is painted.
  161. class HoldingSpaceTrayIconPreview::ImageLayerOwner
  162. : public ui::LayerOwner,
  163. public ui::LayerDelegate,
  164. public HoldingSpaceModelObserver {
  165. public:
  166. explicit ImageLayerOwner(const HoldingSpaceItem* item) : item_(item) {
  167. item_deletion_subscription_ = item->AddDeletionCallback(base::BindRepeating(
  168. &ImageLayerOwner::OnHoldingSpaceItemDeleted, base::Unretained(this)));
  169. item_image_skia_subscription_ =
  170. item->image().AddImageSkiaChangedCallback(base::BindRepeating(
  171. &ImageLayerOwner::OnHoldingSpaceItemImageSkiaChanged,
  172. base::Unretained(this)));
  173. progress_ring_animation_changed_subscription_ =
  174. HoldingSpaceAnimationRegistry::GetInstance()
  175. ->AddProgressRingAnimationChangedCallbackForKey(
  176. /*animation_key=*/item_,
  177. IgnoreArgs<ProgressRingAnimation*>(
  178. base::BindRepeating(&ImageLayerOwner::UpdateTransform,
  179. base::Unretained(this))));
  180. model_observer_.Observe(HoldingSpaceController::Get()->model());
  181. }
  182. ImageLayerOwner(const ImageLayerOwner&) = delete;
  183. ImageLayerOwner& operator=(const ImageLayerOwner&) = delete;
  184. ~ImageLayerOwner() override = default;
  185. // Creates and returns the `layer()` owned by this class. Note that this may
  186. // only be called if `layer()` does not already exist.
  187. ui::Layer* CreateLayer() {
  188. DCHECK(!layer());
  189. auto layer = std::make_unique<ui::Layer>(ui::LAYER_TEXTURED);
  190. layer->set_delegate(this);
  191. layer->SetFillsBoundsOpaquely(false);
  192. layer->SetName(HoldingSpaceTrayIconPreview::kImageLayerName);
  193. Reset(std::move(layer));
  194. UpdateOpacity();
  195. UpdateTransform();
  196. return this->layer();
  197. }
  198. // Destroys the `layer()` which is owned by this class. Note that this will
  199. // no-op if `layer()` does not exist.
  200. void DestroyLayer() {
  201. if (layer())
  202. ReleaseLayer();
  203. }
  204. // Invoke to schedule repaint of the entire `layer()`.
  205. void InvalidateLayer() {
  206. // Clear cache.
  207. image_skia_ = gfx::ImageSkia();
  208. // Schedule repaint.
  209. if (layer())
  210. layer()->SchedulePaint(gfx::Rect(layer()->size()));
  211. }
  212. private:
  213. // ui::LayerDelegate:
  214. void OnPaintLayer(const ui::PaintContext& context) override {
  215. if (image_skia_.isNull())
  216. return;
  217. // Copy `image_skia_` since retrieving a representation at the appropriate
  218. // scale may result in a series of events in which `image_skia_` is deleted.
  219. // Note that `gfx::ImageSkia`'s shared storage makes this a cheap copy.
  220. gfx::ImageSkia image_skia(image_skia_);
  221. // Paint `image_skia`.
  222. ui::PaintRecorder recorder(context, layer()->size());
  223. gfx::Canvas* canvas = recorder.canvas();
  224. canvas->DrawImageInt(image_skia, 0, 0);
  225. }
  226. void OnDeviceScaleFactorChanged(float old_device_scale_factor,
  227. float new_device_scale_factor) override {
  228. // Clear cache and schedule repaint.
  229. InvalidateLayer();
  230. }
  231. void OnLayerBoundsChanged(const gfx::Rect& old_bounds,
  232. ui::PropertyChangeReason reason) override {
  233. // Corner radius.
  234. const gfx::Size& size = layer()->size();
  235. const float corner_radius = std::min(size.width(), size.height()) / 2.f;
  236. layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(corner_radius));
  237. // Transform.
  238. UpdateTransform();
  239. // Clear cache and schedule repaint.
  240. InvalidateLayer();
  241. }
  242. void UpdateVisualState() override {
  243. if (item_ && image_skia_.isNull()) {
  244. image_skia_ = item_->image().GetImageSkia(
  245. layer()->size(),
  246. DarkLightModeControllerImpl::Get()->IsDarkModeEnabled());
  247. }
  248. }
  249. // HoldingSpaceModelObserver:
  250. void OnHoldingSpaceItemUpdated(const HoldingSpaceItem* item,
  251. uint32_t updated_fields) override {
  252. if (item_ != item)
  253. return;
  254. if (updated_fields & HoldingSpaceModelObserver::UpdatedField::kProgress) {
  255. UpdateOpacity();
  256. UpdateTransform();
  257. }
  258. }
  259. void OnHoldingSpaceItemDeleted() { item_ = nullptr; }
  260. void OnHoldingSpaceItemImageSkiaChanged() { InvalidateLayer(); }
  261. void UpdateOpacity() {
  262. // Opacity need not be updated if:
  263. // * `item_` is destroyed and is being animated out,
  264. // * `layer()` does not exist.
  265. if (!item_ || !layer())
  266. return;
  267. const bool is_item_visibly_in_progress =
  268. !item_->progress().IsHidden() && !item_->progress().IsComplete();
  269. const float target_opacity = is_item_visibly_in_progress ? 0.f : 1.f;
  270. if (layer()->GetTargetOpacity() == target_opacity)
  271. return;
  272. // If `layer()` should be hidden, do so immediately without animation so as
  273. // to avoid clashing with other UI elements.
  274. if (target_opacity == 0.f) {
  275. layer()->SetOpacity(0.f);
  276. return;
  277. }
  278. // If `layer()` should be visible, animate the transition.
  279. ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
  280. settings.SetTransitionDuration(kInProgressAnimationDuration);
  281. settings.SetTweenType(gfx::Tween::Type::LINEAR_OUT_SLOW_IN);
  282. layer()->SetOpacity(1.f);
  283. }
  284. void UpdateTransform() {
  285. // Transform need not be updated if:
  286. // * `item_` is destroyed and is being animated out,
  287. // * `layer()` does not exist.
  288. if (!item_ || !layer())
  289. return;
  290. const bool is_item_visibly_in_progress =
  291. !item_->progress().IsHidden() && !item_->progress().IsComplete();
  292. const bool is_item_animating_progress_ring =
  293. HoldingSpaceAnimationRegistry::GetInstance()
  294. ->GetProgressRingAnimationForKey(item_);
  295. const gfx::Transform target_transform =
  296. is_item_visibly_in_progress || is_item_animating_progress_ring
  297. ? gfx::GetScaleTransform(gfx::Rect(layer()->size()).CenterPoint(),
  298. kInProgressAnimationScaleFactor)
  299. : gfx::Transform();
  300. if (layer()->GetTargetTransform() == target_transform)
  301. return;
  302. // If `layer()` should be scaled, do so immediately without animation so as
  303. // to avoid clashing with other UI elements.
  304. if (!target_transform.IsIdentity()) {
  305. layer()->SetTransform(target_transform);
  306. return;
  307. }
  308. // If `layer()` should not be scaled, animate the transition.
  309. ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
  310. settings.SetTransitionDuration(kInProgressAnimationDuration);
  311. settings.SetTweenType(gfx::Tween::Type::LINEAR_OUT_SLOW_IN);
  312. layer()->SetTransform(target_transform);
  313. }
  314. const HoldingSpaceItem* item_ = nullptr;
  315. base::CallbackListSubscription item_deletion_subscription_;
  316. base::CallbackListSubscription item_image_skia_subscription_;
  317. base::CallbackListSubscription progress_ring_animation_changed_subscription_;
  318. // Cached image representation of the associated holding space `item_` that is
  319. // painted to the `layer()` owned by this class.
  320. gfx::ImageSkia image_skia_;
  321. base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
  322. model_observer_{this};
  323. };
  324. // HoldingSpaceTrayIconPreview -------------------------------------------------
  325. // static
  326. constexpr char HoldingSpaceTrayIconPreview::kClassName[];
  327. constexpr char HoldingSpaceTrayIconPreview::kImageLayerName[];
  328. HoldingSpaceTrayIconPreview::HoldingSpaceTrayIconPreview(
  329. Shelf* shelf,
  330. views::View* container,
  331. const HoldingSpaceItem* item)
  332. : shelf_(shelf),
  333. container_(container),
  334. image_layer_owner_(std::make_unique<ImageLayerOwner>(item)),
  335. progress_indicator_(
  336. holding_space_util::CreateProgressIndicatorForItem(item)),
  337. use_small_previews_(ShouldUseSmallPreviews()) {
  338. container_observer_.Observe(container_);
  339. }
  340. HoldingSpaceTrayIconPreview::~HoldingSpaceTrayIconPreview() = default;
  341. void HoldingSpaceTrayIconPreview::AnimateIn(base::TimeDelta additional_delay) {
  342. DCHECK(transform_.IsIdentity());
  343. DCHECK(!index_.has_value());
  344. DCHECK(pending_index_.has_value());
  345. index_ = *pending_index_;
  346. pending_index_.reset();
  347. const gfx::Size preview_size = GetPreviewSize();
  348. if (*index_ > 0u) {
  349. gfx::Vector2dF translation(*index_ * preview_size.width() / 2, 0);
  350. AdjustForShelfAlignmentAndTextDirection(&translation);
  351. transform_.Translate(translation);
  352. }
  353. if (!NeedsLayer()) {
  354. // Since the holding space tray icon preview will not be animated, any
  355. // associated progress icon animation can `Start()` immediately.
  356. auto* key = progress_indicator_->animation_key();
  357. auto* registry = HoldingSpaceAnimationRegistry::GetInstance();
  358. auto* animation = registry->GetProgressIconAnimationForKey(key);
  359. if (animation && !animation->HasAnimated())
  360. animation->Start();
  361. return;
  362. }
  363. int pre_translate_y = -preview_size.height();
  364. if (IsHorizontal(shelf_->alignment())) {
  365. const gfx::Size& container_size = container_->size();
  366. pre_translate_y = -container_size.height() +
  367. (container_size.height() - preview_size.height()) / 2;
  368. }
  369. gfx::Transform pre_transform;
  370. pre_transform.Translate(transform_.To2dTranslation().x(), pre_translate_y);
  371. CreateLayer(pre_transform);
  372. gfx::Transform mid_transform(transform_);
  373. mid_transform.Translate(0, preview_size.height() * 0.25f);
  374. ui::ScopedLayerAnimationSettings scoped_settings(layer()->GetAnimator());
  375. scoped_settings.SetPreemptionStrategy(
  376. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  377. std::unique_ptr<ui::LayerAnimationSequence> sequence =
  378. std::make_unique<ui::LayerAnimationSequence>();
  379. sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement(
  380. ui::LayerAnimationElement::TRANSFORM,
  381. kBounceAnimationBaseDelay + additional_delay));
  382. std::unique_ptr<ui::LayerAnimationElement> initial_drop =
  383. ui::LayerAnimationElement::CreateTransformElement(
  384. mid_transform, kBounceAnimationSegmentDuration);
  385. initial_drop->set_tween_type(gfx::Tween::EASE_OUT_4);
  386. sequence->AddElement(std::move(initial_drop));
  387. std::unique_ptr<ui::LayerAnimationElement> rebound =
  388. ui::LayerAnimationElement::CreateTransformElement(
  389. transform_, kBounceAnimationSegmentDuration);
  390. rebound->set_tween_type(gfx::Tween::FAST_OUT_SLOW_IN_3);
  391. sequence->AddElement(std::move(rebound));
  392. // Any associated progress icon animation should `Start()` only after
  393. // completion of the holding space tray icon preview animation.
  394. auto observer = std::make_unique<CallbackLayerAnimationObserver>();
  395. sequence->AddObserver(observer.get());
  396. observer->SetAnimationCompletedCallback(base::BindOnce(
  397. [](CallbackLayerAnimationObserver* observer, const void* key) {
  398. auto* registry = HoldingSpaceAnimationRegistry::GetInstance();
  399. auto* animation = registry->GetProgressIconAnimationForKey(key);
  400. if (animation && !animation->HasAnimated())
  401. animation->Start();
  402. },
  403. base::Owned(std::move(observer)), progress_indicator_->animation_key()));
  404. layer()->GetAnimator()->StartAnimation(sequence.release());
  405. }
  406. void HoldingSpaceTrayIconPreview::AnimateOut(
  407. base::OnceClosure animate_out_closure) {
  408. animate_out_closure_ = std::move(animate_out_closure);
  409. pending_index_.reset();
  410. index_.reset();
  411. if (!layer()) {
  412. std::move(animate_out_closure_).Run();
  413. return;
  414. }
  415. ui::ScopedLayerAnimationSettings animation_settings(layer()->GetAnimator());
  416. SetUpAnimation(&animation_settings);
  417. animation_settings.AddObserver(this);
  418. layer()->SetOpacity(0.f);
  419. layer()->SetVisible(false);
  420. }
  421. void HoldingSpaceTrayIconPreview::AnimateShift(base::TimeDelta delay) {
  422. DCHECK(index_.has_value());
  423. DCHECK(pending_index_.has_value());
  424. index_ = *pending_index_;
  425. pending_index_.reset();
  426. bool created_layer = false;
  427. if (!layer() && NeedsLayer()) {
  428. CreateLayer(transform_);
  429. created_layer = true;
  430. }
  431. // Calculate the target preview transform for the new position in the icon.
  432. // Avoid adjustments based on relative index change, as the current transform
  433. // may not match the previous index in case the icon view has been resized
  434. // since last update - see `AdjustTransformForContainerSizeChange()`.
  435. transform_ = gfx::Transform();
  436. gfx::Vector2dF translation(index_.value() * GetPreviewSize().width() / 2, 0);
  437. AdjustForShelfAlignmentAndTextDirection(&translation);
  438. transform_.Translate(translation);
  439. if (!layer())
  440. return;
  441. // If the `layer()` has just been created because it is shifting into the
  442. // viewport, animate in its opacity.
  443. if (created_layer)
  444. layer()->SetOpacity(0.f);
  445. ui::ScopedLayerAnimationSettings scoped_settings(layer()->GetAnimator());
  446. scoped_settings.AddObserver(this);
  447. scoped_settings.SetPreemptionStrategy(
  448. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  449. auto opacity_sequence = std::make_unique<ui::LayerAnimationSequence>();
  450. if (created_layer) {
  451. opacity_sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement(
  452. ui::LayerAnimationElement::OPACITY, delay));
  453. opacity_sequence->AddElement(
  454. ui::LayerAnimationElement::CreateOpacityElement(
  455. 1.f, kShiftAnimationDuration));
  456. }
  457. auto transform_sequence = std::make_unique<ui::LayerAnimationSequence>();
  458. transform_sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement(
  459. ui::LayerAnimationElement::TRANSFORM, delay));
  460. std::unique_ptr<ui::LayerAnimationElement> shift =
  461. ui::LayerAnimationElement::CreateTransformElement(
  462. transform_, kShiftAnimationDuration);
  463. shift->set_tween_type(gfx::Tween::FAST_OUT_SLOW_IN);
  464. transform_sequence->AddElement(std::move(shift));
  465. layer()->GetAnimator()->StartTogether(
  466. {opacity_sequence.release(), transform_sequence.release()});
  467. }
  468. void HoldingSpaceTrayIconPreview::AdjustTransformForContainerSizeChange(
  469. const gfx::Vector2d& size_change) {
  470. if (!index_.has_value())
  471. return;
  472. int direction = base::i18n::IsRTL() ? -1 : 1;
  473. transform_.Translate(direction * size_change.x(), size_change.y());
  474. if (layer()) {
  475. // Update the layer transform. The current layer transform may be different
  476. // from `transform_` if a transform animation is in progress, so calculate
  477. // the new target transform using the current layer transform as the base.
  478. gfx::Transform layer_transform = layer()->transform();
  479. layer_transform.Translate(direction * size_change.x(), size_change.y());
  480. layer()->SetTransform(layer_transform);
  481. }
  482. }
  483. void HoldingSpaceTrayIconPreview::OnShelfAlignmentChanged(
  484. ShelfAlignment old_shelf_alignment,
  485. ShelfAlignment new_shelf_alignment) {
  486. // If shelf orientation has not changed, no action needs to be taken.
  487. if (IsHorizontal(old_shelf_alignment) == IsHorizontal(new_shelf_alignment))
  488. return;
  489. // Because shelf orientation has changed, the target `transform_` needs to be
  490. // updated. First stop the current animation to immediately advance to target
  491. // end values.
  492. const auto weak_ptr = weak_factory_.GetWeakPtr();
  493. if (layer() && layer()->GetAnimator()->is_animating())
  494. layer()->GetAnimator()->StopAnimating();
  495. // This instance may have been deleted as a result of stopping the current
  496. // animation if it was in the process of animating out.
  497. if (!weak_ptr)
  498. return;
  499. // Swap x-coordinate and y-coordinate of the target `transform_` since the
  500. // shelf has changed orientation from horizontal to vertical or vice versa.
  501. gfx::Vector2dF translation = transform_.To2dTranslation();
  502. // In LTR, `translation` is always a positive offset. With a horizontal shelf,
  503. // offset is relative to the parent layer's left bound while with a vertical
  504. // shelf, offset is relative to the parent layer's top bound. In RTL, positive
  505. // offset is still used for vertical shelf but with a horizontal shelf the
  506. // `translation` is a negative offset from the parent layer's right bound. For
  507. // this reason, a change in shelf orientation in RTL requires a negation of
  508. // the current `translation`.
  509. if (base::i18n::IsRTL())
  510. translation = -translation;
  511. gfx::Transform swapped_transform;
  512. swapped_transform.Translate(translation.y(), translation.x());
  513. transform_ = swapped_transform;
  514. if (layer()) {
  515. UpdateLayerBounds();
  516. layer()->SetTransform(transform_);
  517. }
  518. }
  519. void HoldingSpaceTrayIconPreview::OnShelfConfigChanged() {
  520. // If the change in shelf configuration hasn't affected whether or not small
  521. // previews should be used, no action needs to be taken.
  522. const bool use_small_previews = ShouldUseSmallPreviews();
  523. if (use_small_previews_ == use_small_previews)
  524. return;
  525. use_small_previews_ = use_small_previews;
  526. // Because the size of previews is changing, the target `transform_` needs to
  527. // be updated. First stop the current animation to immediately advance to
  528. // target end values.
  529. const auto weak_ptr = weak_factory_.GetWeakPtr();
  530. if (layer() && layer()->GetAnimator()->is_animating())
  531. layer()->GetAnimator()->StopAnimating();
  532. // This instance may have been deleted as a result of stopping the current
  533. // animation if it was in the process of animating out.
  534. if (!weak_ptr)
  535. return;
  536. // Adjust `translation` to account for the change in size.
  537. DCHECK(index_);
  538. gfx::Vector2dF translation(*index_ * GetPreviewSize().width() / 2, 0);
  539. AdjustForShelfAlignmentAndTextDirection(&translation);
  540. transform_.MakeIdentity();
  541. transform_.Translate(translation);
  542. if (layer()) {
  543. UpdateLayerBounds();
  544. layer()->SetTransform(transform_);
  545. }
  546. }
  547. void HoldingSpaceTrayIconPreview::OnThemeChanged() {
  548. InvalidateLayer();
  549. image_layer_owner_->InvalidateLayer();
  550. progress_indicator_->InvalidateLayer();
  551. }
  552. void HoldingSpaceTrayIconPreview::OnPaintLayer(
  553. const ui::PaintContext& context) {
  554. ui::PaintRecorder recorder(context, layer()->size());
  555. gfx::Canvas* canvas = recorder.canvas();
  556. // The `layer()` was enlarged so that the shadow would be painted outside of
  557. // desired preview bounds. Content bounds should be clamped to preview size.
  558. gfx::Rect contents_bounds = gfx::Rect(layer()->size());
  559. contents_bounds.ClampToCenteredSize(GetPreviewSize());
  560. // Background.
  561. // NOTE: The background radius is shrunk by a single pixel to avoid being
  562. // painted outside `image_layer_owner_` layer bounds as might otherwise occur
  563. // due to pixel rounding. Failure to do so could result in paint artifacts.
  564. cc::PaintFlags flags;
  565. flags.setAntiAlias(true);
  566. flags.setColor(AshColorProvider::Get()->GetBaseLayerColor(
  567. AshColorProvider::BaseLayerType::kOpaque));
  568. flags.setLooper(gfx::CreateShadowDrawLooper(GetShadowDetails().values));
  569. canvas->DrawCircle(
  570. gfx::PointF(contents_bounds.CenterPoint()),
  571. std::min(contents_bounds.width(), contents_bounds.height()) / 2.f - 0.5f,
  572. flags);
  573. }
  574. void HoldingSpaceTrayIconPreview::OnDeviceScaleFactorChanged(
  575. float old_device_scale_factor,
  576. float new_device_scale_factor) {
  577. InvalidateLayer();
  578. }
  579. void HoldingSpaceTrayIconPreview::OnImplicitAnimationsCompleted() {
  580. if (!NeedsLayer())
  581. DestroyLayer();
  582. // NOTE: Running `animate_out_closure_` may delete `this`.
  583. if (animate_out_closure_)
  584. std::move(animate_out_closure_).Run();
  585. }
  586. void HoldingSpaceTrayIconPreview::OnViewBoundsChanged(views::View* view) {
  587. DCHECK_EQ(container_, view);
  588. if (layer())
  589. UpdateLayerBounds();
  590. }
  591. void HoldingSpaceTrayIconPreview::OnViewIsDeleting(views::View* view) {
  592. DCHECK_EQ(container_, view);
  593. container_observer_.Reset();
  594. }
  595. void HoldingSpaceTrayIconPreview::CreateLayer(
  596. const gfx::Transform& initial_transform) {
  597. DCHECK(!layer());
  598. DCHECK(!layer_owner_.OwnsLayer());
  599. auto new_layer = std::make_unique<ui::Layer>(ui::LAYER_TEXTURED);
  600. new_layer->set_delegate(this);
  601. new_layer->SetFillsBoundsOpaquely(false);
  602. new_layer->SetName(kClassName);
  603. new_layer->SetTransform(initial_transform);
  604. new_layer->Add(image_layer_owner_->CreateLayer());
  605. new_layer->Add(progress_indicator_->CreateLayer());
  606. layer_owner_.Reset(std::move(new_layer));
  607. UpdateLayerBounds();
  608. container_->layer()->Add(layer());
  609. }
  610. void HoldingSpaceTrayIconPreview::DestroyLayer() {
  611. if (layer())
  612. layer_owner_.ReleaseLayer();
  613. image_layer_owner_->DestroyLayer();
  614. progress_indicator_->DestroyLayer();
  615. }
  616. bool HoldingSpaceTrayIconPreview::NeedsLayer() const {
  617. return index_ && *index_ < kHoldingSpaceTrayIconMaxVisiblePreviews;
  618. }
  619. void HoldingSpaceTrayIconPreview::InvalidateLayer() {
  620. if (layer())
  621. layer()->SchedulePaint(gfx::Rect(layer()->size()));
  622. }
  623. void HoldingSpaceTrayIconPreview::AdjustForShelfAlignmentAndTextDirection(
  624. gfx::Vector2dF* vector_2df) {
  625. if (!shelf_->IsHorizontalAlignment()) {
  626. const float x = vector_2df->x();
  627. vector_2df->set_x(vector_2df->y());
  628. vector_2df->set_y(x);
  629. return;
  630. }
  631. // With a horizontal shelf in RTL, translation is a negative offset relative
  632. // to the parent layer's right bound. This requires negation of `vector_2df`.
  633. if (base::i18n::IsRTL())
  634. vector_2df->Scale(-1.f);
  635. }
  636. void HoldingSpaceTrayIconPreview::UpdateLayerBounds() {
  637. DCHECK(layer());
  638. // The shadow for `layer()` should be painted outside desired preview bounds.
  639. gfx::Size size = GetPreviewSize();
  640. EnlargeForShadowMargins(size);
  641. // With a horizontal shelf in RTL, `layer()` is aligned with its parent
  642. // layer's right bound and translated with a negative offset. In all other
  643. // cases, `layer()` is aligned with its parent layer's left/top bound and
  644. // translated with a positive offset.
  645. gfx::Point origin;
  646. if (shelf_->IsHorizontalAlignment()) {
  647. gfx::Rect container_bounds = container_->GetLocalBounds();
  648. if (base::i18n::IsRTL())
  649. origin = container_bounds.top_right() - gfx::Vector2d(size.width(), 0);
  650. origin.Offset(0, (container_bounds.height() - size.height()) / 2);
  651. }
  652. AdjustOriginForShadowMargins(origin, shelf_);
  653. gfx::Rect bounds(origin, size);
  654. if (bounds == layer()->bounds())
  655. return;
  656. layer()->SetBounds(bounds);
  657. // The `layer()` was enlarged so that the shadow would be painted outside of
  658. // desired preview bounds. The image layer and progress indicator bounds are
  659. // clamped to preview size.
  660. bounds = gfx::Rect(layer()->size());
  661. bounds.ClampToCenteredSize(GetPreviewSize());
  662. image_layer_owner_->layer()->SetBounds(bounds);
  663. progress_indicator_->layer()->SetBounds(bounds);
  664. }
  665. } // namespace ash