holding_space_tray.cc 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  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.h"
  5. #include <memory>
  6. #include "ash/accessibility/accessibility_controller_impl.h"
  7. #include "ash/drag_drop/scoped_drag_drop_observer.h"
  8. #include "ash/public/cpp/holding_space/holding_space_client.h"
  9. #include "ash/public/cpp/holding_space/holding_space_constants.h"
  10. #include "ash/public/cpp/holding_space/holding_space_item.h"
  11. #include "ash/public/cpp/holding_space/holding_space_metrics.h"
  12. #include "ash/public/cpp/holding_space/holding_space_prefs.h"
  13. #include "ash/public/cpp/system_tray_client.h"
  14. #include "ash/resources/vector_icons/vector_icons.h"
  15. #include "ash/session/session_controller_impl.h"
  16. #include "ash/shelf/shelf.h"
  17. #include "ash/shell.h"
  18. #include "ash/strings/grit/ash_strings.h"
  19. #include "ash/system/holding_space/holding_space_animation_registry.h"
  20. #include "ash/system/holding_space/holding_space_progress_indicator_util.h"
  21. #include "ash/system/holding_space/holding_space_tray_bubble.h"
  22. #include "ash/system/holding_space/holding_space_tray_icon.h"
  23. #include "ash/system/holding_space/pinned_files_section.h"
  24. #include "ash/system/progress_indicator/progress_indicator.h"
  25. #include "ash/system/tray/tray_constants.h"
  26. #include "ash/system/tray/tray_container.h"
  27. #include "base/bind.h"
  28. #include "base/callback_helpers.h"
  29. #include "base/check.h"
  30. #include "base/containers/adapters.h"
  31. #include "base/containers/contains.h"
  32. #include "base/containers/cxx20_erase.h"
  33. #include "base/pickle.h"
  34. #include "components/prefs/pref_change_registrar.h"
  35. #include "ui/base/clipboard/custom_data_helper.h"
  36. #include "ui/base/dragdrop/drag_drop_types.h"
  37. #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
  38. #include "ui/base/l10n/l10n_util.h"
  39. #include "ui/base/metadata/metadata_impl_macros.h"
  40. #include "ui/color/color_id.h"
  41. #include "ui/compositor/layer.h"
  42. #include "ui/compositor/scoped_layer_animation_settings.h"
  43. #include "ui/gfx/geometry/transform_util.h"
  44. #include "ui/gfx/paint_vector_icon.h"
  45. #include "ui/views/animation/animation_builder.h"
  46. #include "ui/views/animation/ink_drop.h"
  47. #include "ui/views/controls/image_view.h"
  48. #include "ui/views/layout/fill_layout.h"
  49. #include "ui/views/vector_icons.h"
  50. #include "ui/wm/core/coordinate_conversion.h"
  51. namespace ash {
  52. namespace {
  53. using ::ui::mojom::DragOperation;
  54. // Animation.
  55. constexpr base::TimeDelta kAnimationDuration = base::Milliseconds(167);
  56. constexpr base::TimeDelta kInProgressAnimationOpacityDuration =
  57. base::Milliseconds(100);
  58. constexpr base::TimeDelta kInProgressAnimationScaleDelay =
  59. base::Milliseconds(50);
  60. constexpr base::TimeDelta kInProgressAnimationScaleDuration =
  61. base::Milliseconds(166);
  62. constexpr float kInProgressAnimationScaleFactor = 0.875f;
  63. // Helpers ---------------------------------------------------------------------
  64. // Animates the specified `view` to the given `target_opacity`.
  65. void AnimateToTargetOpacity(views::View* view, float target_opacity) {
  66. DCHECK(view->layer());
  67. if (view->layer()->GetTargetOpacity() == target_opacity)
  68. return;
  69. ui::ScopedLayerAnimationSettings settings(view->layer()->GetAnimator());
  70. settings.SetPreemptionStrategy(
  71. ui::LayerAnimator::PreemptionStrategy::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  72. settings.SetTransitionDuration(kAnimationDuration);
  73. view->layer()->SetOpacity(target_opacity);
  74. }
  75. // Returns the file paths extracted from the specified `data` at the filenames
  76. // storage location. The Files app stores file paths but *not* directory paths
  77. // at this location.
  78. std::vector<base::FilePath> ExtractFilePathsFromFilenames(
  79. const ui::OSExchangeData& data) {
  80. if (!data.HasFile())
  81. return {};
  82. std::vector<ui::FileInfo> filenames;
  83. if (!data.GetFilenames(&filenames))
  84. return {};
  85. std::vector<base::FilePath> result;
  86. for (const ui::FileInfo& filename : filenames)
  87. result.push_back(base::FilePath(filename.path));
  88. return result;
  89. }
  90. // Returns the file paths extracted from the specified `data` at the file system
  91. // sources storage location. The Files app stores both file paths *and*
  92. // directory paths at this location.
  93. std::vector<base::FilePath> ExtractFilePathsFromFileSystemSources(
  94. const ui::OSExchangeData& data) {
  95. base::Pickle pickle;
  96. if (!data.GetPickledData(ui::ClipboardFormatType::WebCustomDataType(),
  97. &pickle)) {
  98. return {};
  99. }
  100. constexpr char16_t kFileSystemSourcesType[] = u"fs/sources";
  101. std::u16string file_system_sources;
  102. ui::ReadCustomDataForType(pickle.data(), pickle.size(),
  103. kFileSystemSourcesType, &file_system_sources);
  104. if (file_system_sources.empty())
  105. return {};
  106. HoldingSpaceClient* const client = HoldingSpaceController::Get()->client();
  107. std::vector<base::FilePath> result;
  108. for (const base::StringPiece16& file_system_source :
  109. base::SplitStringPiece(file_system_sources, u"\n", base::TRIM_WHITESPACE,
  110. base::SPLIT_WANT_NONEMPTY)) {
  111. base::FilePath file_path =
  112. client->CrackFileSystemUrl(GURL(file_system_source));
  113. if (!file_path.empty())
  114. result.push_back(file_path);
  115. }
  116. return result;
  117. }
  118. // Returns the file paths extracted from the specified `data` which are *not*
  119. // already pinned to the attached holding space model.
  120. std::vector<base::FilePath> ExtractUnpinnedFilePaths(
  121. const ui::OSExchangeData& data) {
  122. // Prefer extracting file paths from file system sources when possible. The
  123. // Files app populates both file system sources and filenames storage
  124. // locations, but only the former contains directory paths.
  125. std::vector<base::FilePath> unpinned_file_paths =
  126. ExtractFilePathsFromFileSystemSources(data);
  127. if (unpinned_file_paths.empty())
  128. unpinned_file_paths = ExtractFilePathsFromFilenames(data);
  129. HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  130. base::EraseIf(unpinned_file_paths, [model](const base::FilePath& file_path) {
  131. return model->ContainsItem(HoldingSpaceItem::Type::kPinnedFile, file_path);
  132. });
  133. return unpinned_file_paths;
  134. }
  135. // Returns whether previews are enabled.
  136. bool IsPreviewsEnabled() {
  137. auto* prefs = Shell::Get()->session_controller()->GetActivePrefService();
  138. return prefs && holding_space_prefs::IsPreviewsEnabled(prefs);
  139. }
  140. // Returns whether the holding space model contains any initialized items.
  141. bool ModelContainsInitializedItems(HoldingSpaceModel* model) {
  142. for (const auto& item : model->items()) {
  143. if (item->IsInitialized())
  144. return true;
  145. }
  146. return false;
  147. }
  148. // Creates the default tray icon.
  149. std::unique_ptr<views::ImageView> CreateDefaultTrayIcon() {
  150. auto icon = std::make_unique<views::ImageView>();
  151. icon->SetID(kHoldingSpaceTrayDefaultIconId);
  152. icon->SetPreferredSize(gfx::Size(kTrayItemSize, kTrayItemSize));
  153. icon->SetPaintToLayer();
  154. icon->layer()->SetFillsBoundsOpaquely(false);
  155. return icon;
  156. }
  157. // Creates the icon to be parented by the drop target overlay to indicate that
  158. // the parent view is a drop target and is capable of handling the current drag
  159. // payload.
  160. std::unique_ptr<views::ImageView> CreateDropTargetIcon() {
  161. auto icon = std::make_unique<views::ImageView>();
  162. icon->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
  163. icon->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
  164. icon->SetPreferredSize(
  165. gfx::Size(kHoldingSpaceIconSize, kHoldingSpaceIconSize));
  166. icon->SetPaintToLayer();
  167. icon->layer()->SetFillsBoundsOpaquely(false);
  168. return icon;
  169. }
  170. // Creates the overlay to be drawn over all other child views to indicate that
  171. // the parent view is a drop target and is capable of handling the current drag
  172. // payload.
  173. std::unique_ptr<views::View> CreateDropTargetOverlay() {
  174. auto drop_target_overlay = std::make_unique<views::View>();
  175. drop_target_overlay->SetID(kHoldingSpaceTrayDropTargetOverlayId);
  176. drop_target_overlay->SetLayoutManager(std::make_unique<views::FillLayout>());
  177. drop_target_overlay->SetPaintToLayer();
  178. drop_target_overlay->layer()->SetFillsBoundsOpaquely(false);
  179. return drop_target_overlay;
  180. }
  181. // Returns the `aura::client::DragDropClient` for the given `widget`. Note that
  182. // this may return `nullptr` if the browser is performing its shutdown sequence.
  183. aura::client::DragDropClient* GetDragDropClient(views::Widget* widget) {
  184. if (widget) {
  185. auto* native_window = widget->GetNativeWindow();
  186. if (native_window) {
  187. auto* root_window = native_window->GetRootWindow();
  188. if (root_window)
  189. return aura::client::GetDragDropClient(root_window);
  190. }
  191. }
  192. return nullptr;
  193. }
  194. } // namespace
  195. // HoldingSpaceTray ------------------------------------------------------------
  196. HoldingSpaceTray::HoldingSpaceTray(Shelf* shelf) : TrayBackgroundView(shelf) {
  197. // Ensure the existence of the singleton animation registry.
  198. HoldingSpaceAnimationRegistry::GetInstance();
  199. controller_observer_.Observe(HoldingSpaceController::Get());
  200. session_observer_.Observe(Shell::Get()->session_controller());
  201. SetVisible(false);
  202. // Default icon.
  203. default_tray_icon_ = tray_container()->AddChildView(CreateDefaultTrayIcon());
  204. // Previews icon.
  205. previews_tray_icon_ = tray_container()->AddChildView(
  206. std::make_unique<HoldingSpaceTrayIcon>(shelf));
  207. previews_tray_icon_->SetVisible(false);
  208. // Drop target overlay.
  209. // NOTE: The `drop_target_overlay_` will only be visible when:
  210. // * a drag is in progress,
  211. // * the drag payload contains pinnable files, and
  212. // * the cursor is sufficiently close to this view.
  213. drop_target_overlay_ = AddChildView(CreateDropTargetOverlay());
  214. drop_target_overlay_->layer()->SetOpacity(0.f);
  215. // Drop target icon.
  216. drop_target_icon_ =
  217. drop_target_overlay_->AddChildView(CreateDropTargetIcon());
  218. // Progress indicator.
  219. // NOTE: The `progress_indicator_` will only be visible when:
  220. // * there is at least one in-progress item in the attached model, and
  221. // * previews are hidden.
  222. progress_indicator_ =
  223. holding_space_util::CreateProgressIndicatorForController(
  224. HoldingSpaceController::Get());
  225. layer()->Add(progress_indicator_->CreateLayer());
  226. // Subscribe to receive notification of changes to the `progress_indicator_`'s
  227. // underlying progress. When progress changes, the `default_tray_icon_` may
  228. // need to be updated since it occupies the same space as the inner icon of
  229. // the `progress_indicator_`.
  230. progress_indicator_progress_changed_callback_list_subscription_ =
  231. progress_indicator_->AddProgressChangedCallback(base::BindRepeating(
  232. &HoldingSpaceTray::UpdateDefaultTrayIcon, base::Unretained(this)));
  233. UpdateDefaultTrayIcon();
  234. // Enable context menu, which supports an action to toggle item previews.
  235. SetContextMenuEnabled(true);
  236. }
  237. HoldingSpaceTray::~HoldingSpaceTray() = default;
  238. void HoldingSpaceTray::Initialize() {
  239. TrayBackgroundView::Initialize();
  240. UpdatePreviewsVisibility();
  241. // The preview icon is displayed conditionally, depending on user prefs state.
  242. auto* prefs = Shell::Get()->session_controller()->GetActivePrefService();
  243. if (prefs)
  244. ObservePrefService(prefs);
  245. // It's possible that this holding space tray was created after login, such as
  246. // would occur if the user connects an external display. In such situations
  247. // the holding space model will already have been attached.
  248. if (HoldingSpaceController::Get()->model())
  249. OnHoldingSpaceModelAttached(HoldingSpaceController::Get()->model());
  250. }
  251. void HoldingSpaceTray::ClickedOutsideBubble() {
  252. CloseBubble();
  253. }
  254. std::u16string HoldingSpaceTray::GetAccessibleNameForTray() {
  255. return l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_A11Y_NAME);
  256. }
  257. views::View* HoldingSpaceTray::GetTooltipHandlerForPoint(
  258. const gfx::Point& point) {
  259. // Tooltip events should be handled top level, not by descendents.
  260. return HitTestPoint(point) ? this : nullptr;
  261. }
  262. std::u16string HoldingSpaceTray::GetTooltipText(const gfx::Point& point) const {
  263. return l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_TITLE);
  264. }
  265. void HoldingSpaceTray::HandleLocaleChange() {
  266. TooltipTextChanged();
  267. }
  268. void HoldingSpaceTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {}
  269. void HoldingSpaceTray::AnchorUpdated() {
  270. if (bubble_)
  271. bubble_->AnchorUpdated();
  272. }
  273. void HoldingSpaceTray::UpdateAfterLoginStatusChange() {
  274. UpdateVisibility();
  275. }
  276. void HoldingSpaceTray::CloseBubble() {
  277. if (!bubble_)
  278. return;
  279. holding_space_metrics::RecordPodAction(
  280. holding_space_metrics::PodAction::kCloseBubble);
  281. widget_observer_.Reset();
  282. bubble_.reset();
  283. SetIsActive(false);
  284. }
  285. void HoldingSpaceTray::ShowBubble() {
  286. if (bubble_)
  287. return;
  288. holding_space_metrics::RecordPodAction(
  289. holding_space_metrics::PodAction::kShowBubble);
  290. DCHECK(tray_container());
  291. bubble_ = std::make_unique<HoldingSpaceTrayBubble>(this);
  292. // Observe the bubble widget so that we can do proper clean up when it is
  293. // being destroyed. If destruction is due to a call to `CloseBubble()` we will
  294. // have already cleaned up state but there are cases where the bubble widget
  295. // is destroyed independent of a call to `CloseBubble()`, e.g. ESC key press.
  296. widget_observer_.Observe(bubble_->GetBubbleWidget());
  297. SetIsActive(true);
  298. }
  299. TrayBubbleView* HoldingSpaceTray::GetBubbleView() {
  300. return bubble_ ? bubble_->GetBubbleView() : nullptr;
  301. }
  302. views::Widget* HoldingSpaceTray::GetBubbleWidget() const {
  303. return bubble_ ? bubble_->GetBubbleWidget() : nullptr;
  304. }
  305. void HoldingSpaceTray::SetVisiblePreferred(bool visible_preferred) {
  306. if (this->visible_preferred() == visible_preferred)
  307. return;
  308. holding_space_metrics::RecordPodAction(
  309. visible_preferred ? holding_space_metrics::PodAction::kShowPod
  310. : holding_space_metrics::PodAction::kHidePod);
  311. TrayBackgroundView::SetVisiblePreferred(visible_preferred);
  312. if (!visible_preferred)
  313. CloseBubble();
  314. }
  315. bool HoldingSpaceTray::GetDropFormats(
  316. int* formats,
  317. std::set<ui::ClipboardFormatType>* format_types) {
  318. *formats = ui::OSExchangeData::FILE_NAME;
  319. // Support custom web data so that file system sources can be retrieved from
  320. // pickled data. That is the storage location at which the Files app stores
  321. // both file paths *and* directory paths.
  322. format_types->insert(ui::ClipboardFormatType::WebCustomDataType());
  323. return true;
  324. }
  325. bool HoldingSpaceTray::AreDropTypesRequired() {
  326. return true;
  327. }
  328. bool HoldingSpaceTray::CanDrop(const ui::OSExchangeData& data) {
  329. return !ExtractUnpinnedFilePaths(data).empty();
  330. }
  331. int HoldingSpaceTray::OnDragUpdated(const ui::DropTargetEvent& event) {
  332. return ExtractUnpinnedFilePaths(event.data()).empty()
  333. ? ui::DragDropTypes::DRAG_NONE
  334. : ui::DragDropTypes::DRAG_COPY;
  335. }
  336. views::View::DropCallback HoldingSpaceTray::GetDropCallback(
  337. const ui::DropTargetEvent& event) {
  338. std::vector<base::FilePath> unpinned_file_paths(
  339. ExtractUnpinnedFilePaths(event.data()));
  340. if (unpinned_file_paths.empty())
  341. return base::NullCallback();
  342. return base::BindOnce(&HoldingSpaceTray::PerformDrop,
  343. weak_factory_.GetWeakPtr(),
  344. std::move(unpinned_file_paths));
  345. }
  346. void HoldingSpaceTray::PerformDrop(
  347. std::vector<base::FilePath> unpinned_file_paths,
  348. const ui::DropTargetEvent& event,
  349. ui::mojom::DragOperation& output_drag_op) {
  350. DCHECK(!unpinned_file_paths.empty());
  351. holding_space_metrics::RecordPodAction(
  352. holding_space_metrics::PodAction::kDragAndDropToPin);
  353. HoldingSpaceController::Get()->client()->PinFiles(unpinned_file_paths);
  354. did_drop_to_pin_ = true;
  355. output_drag_op = DragOperation::kCopy;
  356. }
  357. void HoldingSpaceTray::Layout() {
  358. TrayBackgroundView::Layout();
  359. // The `drop_target_overlay_` should always fill this view's bounds as they
  360. // are perceived by the user. Note that the user perceives the bounds of this
  361. // view to be its background bounds, not its local bounds.
  362. const gfx::Rect background_bounds(GetBackgroundBounds());
  363. drop_target_overlay_->SetBoundsRect(GetMirroredRect(background_bounds));
  364. // The `progress_indicator_` should also fill this view's bounds as they are
  365. // perceived by the user, but these bounds do not need to be mirrored since
  366. // they are in layer coordinates.
  367. progress_indicator_->layer()->SetBounds(background_bounds);
  368. }
  369. void HoldingSpaceTray::VisibilityChanged(views::View* starting_from,
  370. bool is_visible) {
  371. TrayBackgroundView::VisibilityChanged(starting_from, is_visible);
  372. if (!is_visible) {
  373. drag_drop_observer_.reset();
  374. return;
  375. }
  376. // It's possible that the `drag_drop_client` might be `nullptr` if the browser
  377. // is performing its shutdown sequence.
  378. auto* drag_drop_client = GetDragDropClient(GetWidget());
  379. if (!drag_drop_client)
  380. return;
  381. // Observe drag/drop events only when visible. Since the observer is owned by
  382. // `this` view, it's safe to bind to a raw pointer.
  383. drag_drop_observer_ = std::make_unique<ScopedDragDropObserver>(
  384. drag_drop_client,
  385. /*event_callback=*/base::BindRepeating(
  386. &HoldingSpaceTray::UpdateDropTargetState, base::Unretained(this)));
  387. }
  388. void HoldingSpaceTray::OnThemeChanged() {
  389. TrayBackgroundView::OnThemeChanged();
  390. const SkColor color = AshColorProvider::Get()->GetContentLayerColor(
  391. AshColorProvider::ContentLayerType::kIconColorPrimary);
  392. // Default tray icon.
  393. default_tray_icon_->SetImage(gfx::CreateVectorIcon(
  394. kHoldingSpaceIcon, kHoldingSpaceTrayIconSize, color));
  395. // Drop target icon.
  396. drop_target_icon_->SetImage(
  397. gfx::CreateVectorIcon(views::kUnpinIcon, kHoldingSpaceIconSize, color));
  398. // Progress indicator.
  399. progress_indicator_->InvalidateLayer();
  400. }
  401. void HoldingSpaceTray::UpdateVisibility() {
  402. // The holding space tray should not be visible if the `model` is not attached
  403. // or if the user session is blocked.
  404. HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  405. if (!model || Shell::Get()->session_controller()->IsUserSessionBlocked()) {
  406. SetVisiblePreferred(false);
  407. return;
  408. }
  409. // The holding space tray should always be shown if the `model` contains fully
  410. // initialized items. Otherwise, it should only be visible if the pinned files
  411. // section is going to show a placeholder.
  412. auto* prefs = Shell::Get()->session_controller()->GetActivePrefService();
  413. SetVisiblePreferred(
  414. ModelContainsInitializedItems(model) ||
  415. (prefs && PinnedFilesSection::ShouldShowPlaceholder(prefs)));
  416. }
  417. void HoldingSpaceTray::FirePreviewsUpdateTimerIfRunningForTesting() {
  418. if (previews_update_.IsRunning())
  419. previews_update_.FireNow();
  420. }
  421. std::u16string HoldingSpaceTray::GetAccessibleNameForBubble() {
  422. return GetAccessibleNameForTray();
  423. }
  424. bool HoldingSpaceTray::ShouldEnableExtraKeyboardAccessibility() {
  425. return Shell::Get()->accessibility_controller()->spoken_feedback().enabled();
  426. }
  427. void HoldingSpaceTray::HideBubble(const TrayBubbleView* bubble_view) {
  428. CloseBubble();
  429. }
  430. void HoldingSpaceTray::OnShouldShowAnimationChanged(bool should_animate) {
  431. previews_tray_icon_->set_should_animate_updates(should_animate);
  432. }
  433. std::unique_ptr<ui::SimpleMenuModel>
  434. HoldingSpaceTray::CreateContextMenuModel() {
  435. holding_space_metrics::RecordPodAction(
  436. holding_space_metrics::PodAction::kShowContextMenu);
  437. bool previews_enabled = holding_space_prefs::IsPreviewsEnabled(
  438. Shell::Get()->session_controller()->GetActivePrefService());
  439. auto context_menu_model = std::make_unique<ui::SimpleMenuModel>(this);
  440. if (previews_enabled) {
  441. context_menu_model->AddItemWithIcon(
  442. static_cast<int>(HoldingSpaceCommandId::kHidePreviews),
  443. l10n_util::GetStringUTF16(
  444. IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_HIDE_PREVIEWS),
  445. ui::ImageModel::FromVectorIcon(kVisibilityOffIcon,
  446. ui::kColorAshSystemUIMenuIcon,
  447. kHoldingSpaceIconSize));
  448. } else {
  449. context_menu_model->AddItemWithIcon(
  450. static_cast<int>(HoldingSpaceCommandId::kShowPreviews),
  451. l10n_util::GetStringUTF16(
  452. IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_SHOW_PREVIEWS),
  453. ui::ImageModel::FromVectorIcon(kVisibilityIcon,
  454. ui::kColorAshSystemUIMenuIcon,
  455. kHoldingSpaceIconSize));
  456. }
  457. return context_menu_model;
  458. }
  459. void HoldingSpaceTray::OnHoldingSpaceModelAttached(HoldingSpaceModel* model) {
  460. // When the `model` is attached the session is either being started/unlocked
  461. // or the active profile is being changed. It's also possible that the status
  462. // area is being initialized (such as is the case when a display is added at
  463. // runtime). The holding space tray should not bounce or animate previews
  464. // during this phase.
  465. SetShouldAnimate(false);
  466. model_observer_.Observe(model);
  467. UpdateVisibility();
  468. UpdatePreviewsState();
  469. }
  470. void HoldingSpaceTray::OnHoldingSpaceModelDetached(HoldingSpaceModel* model) {
  471. model_observer_.Reset();
  472. UpdateVisibility();
  473. UpdatePreviewsState();
  474. }
  475. void HoldingSpaceTray::OnHoldingSpaceItemsAdded(
  476. const std::vector<const HoldingSpaceItem*>& items) {
  477. // If an initialized holding space item is added to the model mid-session, the
  478. // holding space tray should bounce in (if it isn't already visible) and
  479. // previews should be animated.
  480. if (!Shell::Get()->session_controller()->IsUserSessionBlocked()) {
  481. const bool has_initialized_item = std::any_of(
  482. items.begin(), items.end(),
  483. [](const HoldingSpaceItem* item) { return item->IsInitialized(); });
  484. if (has_initialized_item)
  485. SetShouldAnimate(true);
  486. }
  487. UpdateVisibility();
  488. UpdatePreviewsState();
  489. }
  490. void HoldingSpaceTray::OnHoldingSpaceItemsRemoved(
  491. const std::vector<const HoldingSpaceItem*>& items) {
  492. // If an initialized holding space item is removed from the model mid-session,
  493. // the holding space tray should animate updates.
  494. if (!Shell::Get()->session_controller()->IsUserSessionBlocked()) {
  495. const bool has_initialized_item = std::any_of(
  496. items.begin(), items.end(),
  497. [](const HoldingSpaceItem* item) { return item->IsInitialized(); });
  498. if (has_initialized_item)
  499. SetShouldAnimate(true);
  500. }
  501. UpdateVisibility();
  502. UpdatePreviewsState();
  503. }
  504. void HoldingSpaceTray::OnHoldingSpaceItemInitialized(
  505. const HoldingSpaceItem* item) {
  506. UpdateVisibility();
  507. UpdatePreviewsState();
  508. }
  509. void HoldingSpaceTray::ExecuteCommand(int command_id, int event_flags) {
  510. switch (static_cast<HoldingSpaceCommandId>(command_id)) {
  511. case HoldingSpaceCommandId::kHidePreviews:
  512. holding_space_metrics::RecordPodAction(
  513. holding_space_metrics::PodAction::kHidePreviews);
  514. holding_space_prefs::SetPreviewsEnabled(
  515. Shell::Get()->session_controller()->GetActivePrefService(), false);
  516. break;
  517. case HoldingSpaceCommandId::kShowPreviews:
  518. SetShouldAnimate(true);
  519. holding_space_metrics::RecordPodAction(
  520. holding_space_metrics::PodAction::kShowPreviews);
  521. holding_space_prefs::SetPreviewsEnabled(
  522. Shell::Get()->session_controller()->GetActivePrefService(), true);
  523. break;
  524. default:
  525. NOTREACHED();
  526. break;
  527. }
  528. }
  529. void HoldingSpaceTray::OnWidgetDragWillStart(views::Widget* widget) {
  530. // The holding space bubble should be closed while dragging holding space
  531. // items so as not to obstruct drop targets. Post the task to close the bubble
  532. // so that we don't attempt to destroy the bubble widget before the associated
  533. // drag event has been fully initialized.
  534. base::SequencedTaskRunnerHandle::Get()->PostTask(
  535. FROM_HERE, base::BindOnce(&HoldingSpaceTray::CloseBubble,
  536. weak_factory_.GetWeakPtr()));
  537. }
  538. void HoldingSpaceTray::OnWidgetDestroying(views::Widget* widget) {
  539. CloseBubble();
  540. }
  541. void HoldingSpaceTray::OnActiveUserPrefServiceChanged(PrefService* prefs) {
  542. UpdatePreviewsState();
  543. ObservePrefService(prefs);
  544. }
  545. void HoldingSpaceTray::OnSessionStateChanged(
  546. session_manager::SessionState state) {
  547. UpdateVisibility();
  548. }
  549. void HoldingSpaceTray::ObservePrefService(PrefService* prefs) {
  550. pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  551. pref_change_registrar_->Init(prefs);
  552. // NOTE: The binding of these callbacks is scoped to `pref_change_registrar_`
  553. // which is owned by `this` so it is safe to bind with an unretained raw
  554. // pointer.
  555. holding_space_prefs::AddPreviewsEnabledChangedCallback(
  556. pref_change_registrar_.get(),
  557. base::BindRepeating(&HoldingSpaceTray::UpdatePreviewsState,
  558. base::Unretained(this)));
  559. holding_space_prefs::AddTimeOfFirstAddChangedCallback(
  560. pref_change_registrar_.get(), base::BindRepeating(
  561. [](HoldingSpaceTray* tray) {
  562. tray->SetShouldAnimate(true);
  563. tray->UpdateVisibility();
  564. },
  565. base::Unretained(this)));
  566. }
  567. void HoldingSpaceTray::UpdatePreviewsState() {
  568. UpdatePreviewsVisibility();
  569. SchedulePreviewsIconUpdate();
  570. if (PreviewsShown())
  571. return;
  572. // When previews are shown, progress icon animations are started on completion
  573. // of preview animations. When previews are *not* shown, there is nothing to
  574. // wait for so progress icon animations should be started immediately.
  575. if (auto* model = HoldingSpaceController::Get()->model(); model) {
  576. auto* registry = HoldingSpaceAnimationRegistry::GetInstance();
  577. for (const auto& item : model->items()) {
  578. auto* animation = registry->GetProgressIconAnimationForKey(item.get());
  579. if (animation && !animation->HasAnimated())
  580. animation->Start();
  581. }
  582. }
  583. }
  584. void HoldingSpaceTray::UpdatePreviewsVisibility() {
  585. const bool show_previews =
  586. IsPreviewsEnabled() && HoldingSpaceController::Get()->model() &&
  587. ModelContainsInitializedItems(HoldingSpaceController::Get()->model());
  588. if (PreviewsShown() == show_previews)
  589. return;
  590. default_tray_icon_->SetVisible(!show_previews);
  591. previews_tray_icon_->SetVisible(show_previews);
  592. progress_indicator_->layer()->SetVisible(!show_previews);
  593. if (!show_previews) {
  594. previews_tray_icon_->Clear();
  595. previews_update_.Stop();
  596. }
  597. }
  598. void HoldingSpaceTray::SchedulePreviewsIconUpdate() {
  599. if (previews_update_.IsRunning())
  600. return;
  601. // Schedule async task with a short (somewhat arbitrary) delay to update
  602. // previews so items added in quick succession are handled together.
  603. base::TimeDelta delay = use_zero_previews_update_delay_
  604. ? base::TimeDelta()
  605. : base::Milliseconds(50);
  606. previews_update_.Start(FROM_HERE, delay,
  607. base::BindOnce(&HoldingSpaceTray::UpdatePreviewsIcon,
  608. base::Unretained(this)));
  609. }
  610. void HoldingSpaceTray::UpdatePreviewsIcon() {
  611. if (!PreviewsShown()) {
  612. previews_tray_icon_->Clear();
  613. return;
  614. }
  615. std::vector<const HoldingSpaceItem*> items_with_previews;
  616. std::set<base::FilePath> paths_with_previews;
  617. for (const auto& item :
  618. base::Reversed(HoldingSpaceController::Get()->model()->items())) {
  619. if (!item->IsInitialized())
  620. continue;
  621. if (base::Contains(paths_with_previews, item->file_path()))
  622. continue;
  623. items_with_previews.push_back(item.get());
  624. paths_with_previews.insert(item->file_path());
  625. }
  626. previews_tray_icon_->UpdatePreviews(items_with_previews);
  627. }
  628. bool HoldingSpaceTray::PreviewsShown() const {
  629. return previews_tray_icon_->GetVisible();
  630. }
  631. void HoldingSpaceTray::UpdateDefaultTrayIcon() {
  632. const absl::optional<float>& progress = progress_indicator_->progress();
  633. // If `progress` is not `complete`, there is potential for overlap between the
  634. // `default_tray_icon_` and the `progress_indicator_`'s inner icon. To address
  635. // this, hide the `default_tray_icon_` when `progress` is being indicated.
  636. bool complete = progress == ProgressIndicator::kProgressComplete;
  637. float target_opacity = complete ? 1.f : 0.f;
  638. // If `target_opacity` is already set there's nothing to do.
  639. ui::Layer* const layer = default_tray_icon_->layer();
  640. if (layer->GetTargetOpacity() == target_opacity)
  641. return;
  642. // If `target_opacity` is zero, it should be set immediately w/o animation.
  643. if (target_opacity == 0.f) {
  644. layer->GetAnimator()->StopAnimating();
  645. layer->SetOpacity(0.f);
  646. return;
  647. }
  648. // If `bounds` are empty, the `default_tray_icon_` has not yet been laid out
  649. // and therefore any changes to its visual state need not be animated.
  650. const gfx::Rect& bounds = default_tray_icon_->bounds();
  651. if (bounds.IsEmpty()) {
  652. layer->SetOpacity(1.f);
  653. layer->SetTransform(gfx::Transform());
  654. return;
  655. }
  656. const auto preemption_strategy =
  657. ui::LayerAnimator::PreemptionStrategy::IMMEDIATELY_ANIMATE_TO_NEW_TARGET;
  658. const auto transform = gfx::GetScaleTransform(
  659. bounds.CenterPoint(), kInProgressAnimationScaleFactor);
  660. const auto tween_type = gfx::Tween::Type::FAST_OUT_SLOW_IN_3;
  661. views::AnimationBuilder()
  662. .SetPreemptionStrategy(preemption_strategy)
  663. .Once()
  664. .SetDuration(base::TimeDelta())
  665. .SetOpacity(layer, 0.f)
  666. .SetTransform(layer, transform)
  667. .Then()
  668. .SetDuration(kInProgressAnimationOpacityDuration)
  669. .SetOpacity(layer, 1.f)
  670. .Offset(kInProgressAnimationScaleDelay)
  671. .SetDuration(kInProgressAnimationScaleDuration)
  672. .SetTransform(layer, gfx::Transform(), tween_type);
  673. }
  674. void HoldingSpaceTray::UpdateDropTargetState(const ui::DropTargetEvent* event) {
  675. bool is_drop_target = false;
  676. if (event && !ExtractUnpinnedFilePaths(event->data()).empty()) {
  677. // If the `event` contains pinnable files and is within range of this view,
  678. // indicate this view is a drop target to increase discoverability.
  679. constexpr int kProximityThreshold = 20;
  680. gfx::Rect drop_target_bounds_in_screen(GetBoundsInScreen());
  681. drop_target_bounds_in_screen.Inset(gfx::Insets(-kProximityThreshold));
  682. gfx::Point event_location_in_screen(event->root_location());
  683. ::wm::ConvertPointToScreen(
  684. static_cast<aura::Window*>(event->target())->GetRootWindow(),
  685. &event_location_in_screen);
  686. is_drop_target =
  687. drop_target_bounds_in_screen.Contains(event_location_in_screen);
  688. }
  689. AnimateToTargetOpacity(drop_target_overlay_, is_drop_target ? 1.f : 0.f);
  690. AnimateToTargetOpacity(default_tray_icon_, is_drop_target ? 0.f : 1.f);
  691. AnimateToTargetOpacity(previews_tray_icon_, is_drop_target ? 0.f : 1.f);
  692. previews_tray_icon_->UpdateDropTargetState(is_drop_target, did_drop_to_pin_);
  693. did_drop_to_pin_ = false;
  694. const views::InkDropState target_ink_drop_state =
  695. is_drop_target ? views::InkDropState::ACTION_PENDING
  696. : views::InkDropState::HIDDEN;
  697. if (views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState() ==
  698. target_ink_drop_state)
  699. return;
  700. // Do *not* pass in an event as the origin for the ink drop. Since the user is
  701. // not directly over this view, it would look strange to give the ink drop an
  702. // out-of-bounds origin.
  703. views::InkDrop::Get(this)->AnimateToState(target_ink_drop_state,
  704. /*event=*/nullptr);
  705. }
  706. void HoldingSpaceTray::SetShouldAnimate(bool should_animate) {
  707. if (!should_animate) {
  708. if (!animation_disabler_) {
  709. animation_disabler_ =
  710. std::make_unique<base::ScopedClosureRunner>(DisableShowAnimation());
  711. }
  712. } else if (animation_disabler_) {
  713. animation_disabler_.reset();
  714. }
  715. }
  716. BEGIN_METADATA(HoldingSpaceTray, TrayBackgroundView)
  717. END_METADATA
  718. } // namespace ash