holding_space_item_view.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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_item_view.h"
  5. #include "ash/public/cpp/holding_space/holding_space_client.h"
  6. #include "ash/public/cpp/holding_space/holding_space_constants.h"
  7. #include "ash/public/cpp/holding_space/holding_space_controller.h"
  8. #include "ash/public/cpp/holding_space/holding_space_item.h"
  9. #include "ash/public/cpp/holding_space/holding_space_progress.h"
  10. #include "ash/public/cpp/holding_space/holding_space_util.h"
  11. #include "ash/public/cpp/shelf_config.h"
  12. #include "ash/resources/vector_icons/vector_icons.h"
  13. #include "ash/style/ash_color_provider.h"
  14. #include "ash/style/dark_light_mode_controller_impl.h"
  15. #include "ash/system/holding_space/holding_space_util.h"
  16. #include "ash/system/holding_space/holding_space_view_delegate.h"
  17. #include "base/bind.h"
  18. #include "ui/base/class_property.h"
  19. #include "ui/base/dragdrop/drag_drop_types.h"
  20. #include "ui/base/metadata/metadata_impl_macros.h"
  21. #include "ui/compositor/layer.h"
  22. #include "ui/gfx/canvas.h"
  23. #include "ui/gfx/color_palette.h"
  24. #include "ui/gfx/paint_vector_icon.h"
  25. #include "ui/views/accessibility/view_accessibility.h"
  26. #include "ui/views/background.h"
  27. #include "ui/views/controls/button/image_button.h"
  28. #include "ui/views/controls/focus_ring.h"
  29. #include "ui/views/controls/image_view.h"
  30. #include "ui/views/layout/fill_layout.h"
  31. #include "ui/views/painter.h"
  32. #include "ui/views/vector_icons.h"
  33. #include "ui/views/widget/widget.h"
  34. namespace ash {
  35. namespace {
  36. // A UI class property used to identify if a view is an instance of
  37. // `HoldingSpaceItemView`. Class name is not an adequate identifier as it may be
  38. // overridden by subclasses.
  39. DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsHoldingSpaceItemViewProperty, false)
  40. // Appearance.
  41. constexpr size_t kCheckmarkBackgroundSize = 18;
  42. // Helpers ---------------------------------------------------------------------
  43. // Schedules repaint of `layer`.
  44. void InvalidateLayer(ui::Layer* layer) {
  45. layer->SchedulePaint(gfx::Rect(layer->size()));
  46. }
  47. // CallbackPainter -------------------------------------------------------------
  48. // A painter which delegates painting to a callback.
  49. class CallbackPainter : public views::Painter {
  50. public:
  51. using Callback = base::RepeatingCallback<void(gfx::Canvas*, gfx::Size)>;
  52. CallbackPainter(const CallbackPainter&) = delete;
  53. CallbackPainter& operator=(const CallbackPainter&) = delete;
  54. ~CallbackPainter() override = default;
  55. // Creates a painted layer which delegates painting to `callback`.
  56. static std::unique_ptr<ui::LayerOwner> CreatePaintedLayer(Callback callback) {
  57. auto owner = views::Painter::CreatePaintedLayer(
  58. base::WrapUnique(new CallbackPainter(callback)));
  59. owner->layer()->SetFillsBoundsOpaquely(false);
  60. return owner;
  61. }
  62. private:
  63. explicit CallbackPainter(Callback callback) : callback_(callback) {}
  64. // views::Painter:
  65. gfx::Size GetMinimumSize() const override { return gfx::Size(); }
  66. void Paint(gfx::Canvas* canvas, const gfx::Size& size) override {
  67. callback_.Run(canvas, size);
  68. }
  69. Callback callback_;
  70. };
  71. // MinimumSizableView ---------------------------------------------------------
  72. // A view which respects a minimum size restriction.
  73. class MinimumSizableView : public views::View {
  74. public:
  75. explicit MinimumSizableView(const gfx::Size& min_size)
  76. : min_size_(min_size) {}
  77. MinimumSizableView(const MinimumSizableView&) = delete;
  78. MinimumSizableView& operator=(const MinimumSizableView&) = delete;
  79. ~MinimumSizableView() override = default;
  80. private:
  81. // views::View:
  82. gfx::Size CalculatePreferredSize() const override {
  83. gfx::Size preferred_size(views::View::CalculatePreferredSize());
  84. preferred_size.SetToMax(min_size_);
  85. return preferred_size;
  86. }
  87. int GetHeightForWidth(int width) const override {
  88. return std::max(views::View::GetHeightForWidth(width), min_size_.height());
  89. }
  90. const gfx::Size min_size_;
  91. };
  92. } // namespace
  93. // HoldingSpaceItemView --------------------------------------------------------
  94. HoldingSpaceItemView::HoldingSpaceItemView(HoldingSpaceViewDelegate* delegate,
  95. const HoldingSpaceItem* item)
  96. : delegate_(delegate), item_(item), item_id_(item->id()) {
  97. // Subscribe to be notified of `item_` deletion. Note that it is safe to use a
  98. // raw pointer here since `this` owns the callback.
  99. item_deletion_subscription_ = item_->AddDeletionCallback(base::BindRepeating(
  100. [](HoldingSpaceItemView* view) { view->item_ = nullptr; },
  101. base::Unretained(this)));
  102. model_observer_.Observe(HoldingSpaceController::Get()->model());
  103. SetProperty(kIsHoldingSpaceItemViewProperty, true);
  104. set_context_menu_controller(delegate_);
  105. set_drag_controller(delegate_);
  106. SetNotifyEnterExitOnChild(true);
  107. // Accessibility.
  108. GetViewAccessibility().OverrideRole(ax::mojom::Role::kListItem);
  109. GetViewAccessibility().OverrideName(item->GetAccessibleName());
  110. // When the description is not specified, tooltip text will be used.
  111. // That text is redundant to the name, but different enough that it is
  112. // still exposed to assistive technologies which may then present both.
  113. // To avoid that redundant presentation, set the description explicitly
  114. // to the empty string. See crrev.com/c/3218112.
  115. GetViewAccessibility().OverrideDescription(
  116. std::u16string(), ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty);
  117. // Layer.
  118. SetPaintToLayer();
  119. layer()->SetFillsBoundsOpaquely(false);
  120. // Focus.
  121. SetFocusBehavior(FocusBehavior::ALWAYS);
  122. set_suppress_default_focus_handling();
  123. focused_layer_owner_ =
  124. CallbackPainter::CreatePaintedLayer(base::BindRepeating(
  125. &HoldingSpaceItemView::OnPaintFocus, base::Unretained(this)));
  126. layer()->Add(focused_layer_owner_->layer());
  127. // Selection.
  128. selected_layer_owner_ =
  129. CallbackPainter::CreatePaintedLayer(base::BindRepeating(
  130. &HoldingSpaceItemView::OnPaintSelect, base::Unretained(this)));
  131. layer()->Add(selected_layer_owner_->layer());
  132. // This view's `selected_` state is represented differently depending on
  133. // `delegate_`'s selection UI. Register to be notified of changes.
  134. selection_ui_changed_subscription_ =
  135. delegate_->AddSelectionUiChangedCallback(base::BindRepeating(
  136. &HoldingSpaceItemView::OnSelectionUiChanged, base::Unretained(this)));
  137. delegate_->OnHoldingSpaceItemViewCreated(this);
  138. }
  139. HoldingSpaceItemView::~HoldingSpaceItemView() {
  140. if (delegate_)
  141. delegate_->OnHoldingSpaceItemViewDestroying(this);
  142. }
  143. // static
  144. HoldingSpaceItemView* HoldingSpaceItemView::Cast(views::View* view) {
  145. return const_cast<HoldingSpaceItemView*>(
  146. Cast(const_cast<const views::View*>(view)));
  147. }
  148. // static
  149. const HoldingSpaceItemView* HoldingSpaceItemView::Cast(
  150. const views::View* view) {
  151. DCHECK(HoldingSpaceItemView::IsInstance(view));
  152. return static_cast<const HoldingSpaceItemView*>(view);
  153. }
  154. // static
  155. bool HoldingSpaceItemView::IsInstance(const views::View* view) {
  156. return view->GetProperty(kIsHoldingSpaceItemViewProperty);
  157. }
  158. void HoldingSpaceItemView::Reset() {
  159. delegate_ = nullptr;
  160. }
  161. bool HoldingSpaceItemView::HandleAccessibleAction(
  162. const ui::AXActionData& action_data) {
  163. return (delegate_ && delegate_->OnHoldingSpaceItemViewAccessibleAction(
  164. this, action_data)) ||
  165. views::View::HandleAccessibleAction(action_data);
  166. }
  167. void HoldingSpaceItemView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  168. gfx::Rect bounds = GetLocalBounds();
  169. // Selection ring.
  170. selected_layer_owner_->layer()->SetBounds(bounds);
  171. InvalidateLayer(selected_layer_owner_->layer());
  172. // Focus ring.
  173. // NOTE: The focus ring is painted just outside the bounds for this view.
  174. bounds.Inset(gfx::Insets(kHoldingSpaceFocusInsets));
  175. focused_layer_owner_->layer()->SetBounds(bounds);
  176. InvalidateLayer(focused_layer_owner_->layer());
  177. }
  178. void HoldingSpaceItemView::OnFocus() {
  179. InvalidateLayer(focused_layer_owner_->layer());
  180. }
  181. void HoldingSpaceItemView::OnBlur() {
  182. InvalidateLayer(focused_layer_owner_->layer());
  183. }
  184. void HoldingSpaceItemView::OnGestureEvent(ui::GestureEvent* event) {
  185. if (delegate_ && delegate_->OnHoldingSpaceItemViewGestureEvent(this, *event))
  186. event->SetHandled();
  187. }
  188. bool HoldingSpaceItemView::OnKeyPressed(const ui::KeyEvent& event) {
  189. return delegate_ && delegate_->OnHoldingSpaceItemViewKeyPressed(this, event);
  190. }
  191. void HoldingSpaceItemView::OnMouseEvent(ui::MouseEvent* event) {
  192. switch (event->type()) {
  193. case ui::ET_MOUSE_ENTERED:
  194. case ui::ET_MOUSE_EXITED:
  195. UpdatePrimaryAction();
  196. break;
  197. default:
  198. break;
  199. }
  200. views::View::OnMouseEvent(event);
  201. }
  202. bool HoldingSpaceItemView::OnMousePressed(const ui::MouseEvent& event) {
  203. return delegate_ &&
  204. delegate_->OnHoldingSpaceItemViewMousePressed(this, event);
  205. }
  206. void HoldingSpaceItemView::OnMouseReleased(const ui::MouseEvent& event) {
  207. if (delegate_)
  208. delegate_->OnHoldingSpaceItemViewMouseReleased(this, event);
  209. }
  210. void HoldingSpaceItemView::OnThemeChanged() {
  211. views::View::OnThemeChanged();
  212. AshColorProvider* const ash_color_provider = AshColorProvider::Get();
  213. // Background.
  214. SetBackground(views::CreateRoundedRectBackground(
  215. ash_color_provider->GetControlsLayerColor(
  216. AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive),
  217. kHoldingSpaceCornerRadius));
  218. // Checkmark.
  219. checkmark_->SetBackground(holding_space_util::CreateCircleBackground(
  220. ash_color_provider->GetControlsLayerColor(
  221. AshColorProvider::ControlsLayerType::kFocusRingColor),
  222. kCheckmarkBackgroundSize));
  223. checkmark_->SetImage(gfx::CreateVectorIcon(
  224. kCheckIcon, kHoldingSpaceIconSize,
  225. DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
  226. ? gfx::kGoogleGrey900
  227. : SK_ColorWHITE));
  228. // Focused/selected layers.
  229. InvalidateLayer(focused_layer_owner_->layer());
  230. InvalidateLayer(selected_layer_owner_->layer());
  231. if (!primary_action_container_)
  232. return;
  233. // Cancel.
  234. const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
  235. AshColorProvider::ContentLayerType::kButtonIconColor);
  236. primary_action_cancel_->SetImage(
  237. views::Button::STATE_NORMAL,
  238. gfx::CreateVectorIcon(kCancelIcon, kHoldingSpaceIconSize, icon_color));
  239. // Pin.
  240. const gfx::ImageSkia unpinned_icon = gfx::CreateVectorIcon(
  241. views::kUnpinIcon, kHoldingSpaceIconSize, icon_color);
  242. const gfx::ImageSkia pinned_icon =
  243. gfx::CreateVectorIcon(views::kPinIcon, kHoldingSpaceIconSize, icon_color);
  244. primary_action_pin_->SetImage(views::Button::STATE_NORMAL, unpinned_icon);
  245. primary_action_pin_->SetToggledImage(views::Button::STATE_NORMAL,
  246. &pinned_icon);
  247. }
  248. void HoldingSpaceItemView::OnHoldingSpaceItemUpdated(
  249. const HoldingSpaceItem* item,
  250. uint32_t updated_fields) {
  251. if (item_ != item)
  252. return;
  253. // Accessibility.
  254. if (updated_fields & UpdatedField::kAccessibleName) {
  255. GetViewAccessibility().OverrideName(item_->GetAccessibleName());
  256. NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
  257. }
  258. // Primary action.
  259. UpdatePrimaryAction();
  260. }
  261. void HoldingSpaceItemView::StartDrag(const ui::LocatedEvent& event,
  262. ui::mojom::DragEventSource source) {
  263. int drag_operations = GetDragOperations(event.location());
  264. if (drag_operations == ui::DragDropTypes::DRAG_NONE)
  265. return;
  266. views::Widget* widget = GetWidget();
  267. DCHECK(widget);
  268. if (widget->dragged_view())
  269. return;
  270. auto data = std::make_unique<ui::OSExchangeData>();
  271. WriteDragData(event.location(), data.get());
  272. gfx::Point widget_location(event.location());
  273. views::View::ConvertPointToWidget(this, &widget_location);
  274. widget->RunShellDrag(this, std::move(data), widget_location, drag_operations,
  275. source);
  276. }
  277. void HoldingSpaceItemView::SetSelected(bool selected) {
  278. if (selected_ == selected)
  279. return;
  280. selected_ = selected;
  281. InvalidateLayer(selected_layer_owner_->layer());
  282. if (delegate_)
  283. delegate_->OnHoldingSpaceItemViewSelectedChanged(this);
  284. OnSelectionUiChanged();
  285. }
  286. views::Builder<views::ImageView>
  287. HoldingSpaceItemView::CreateCheckmarkBuilder() {
  288. DCHECK(!checkmark_);
  289. auto checkmark = views::Builder<views::ImageView>();
  290. checkmark.CopyAddressTo(&checkmark_)
  291. .SetID(kHoldingSpaceItemCheckmarkId)
  292. .SetVisible(selected());
  293. return checkmark;
  294. }
  295. views::Builder<views::View> HoldingSpaceItemView::CreatePrimaryActionBuilder(
  296. const gfx::Size& min_size) {
  297. DCHECK(!primary_action_container_);
  298. DCHECK(!primary_action_cancel_);
  299. DCHECK(!primary_action_pin_);
  300. using HorizontalAlignment = views::ImageButton::HorizontalAlignment;
  301. using VerticalAlignment = views::ImageButton::VerticalAlignment;
  302. gfx::Size preferred_size(kHoldingSpaceIconSize, kHoldingSpaceIconSize);
  303. preferred_size.SetToMax(min_size);
  304. auto primary_action = views::Builder<views::View>();
  305. primary_action.CopyAddressTo(&primary_action_container_)
  306. .SetID(kHoldingSpaceItemPrimaryActionContainerId)
  307. .SetUseDefaultFillLayout(true)
  308. .SetVisible(false)
  309. .AddChild(
  310. views::Builder<views::ImageButton>()
  311. .CopyAddressTo(&primary_action_cancel_)
  312. .SetID(kHoldingSpaceItemCancelButtonId)
  313. .SetCallback(base::BindRepeating(
  314. &HoldingSpaceItemView::OnPrimaryActionPressed,
  315. base::Unretained(this)))
  316. .SetFocusBehavior(views::View::FocusBehavior::NEVER)
  317. .SetImageHorizontalAlignment(HorizontalAlignment::ALIGN_CENTER)
  318. .SetImageVerticalAlignment(VerticalAlignment::ALIGN_MIDDLE)
  319. .SetPreferredSize(preferred_size)
  320. .SetVisible(false))
  321. .AddChild(
  322. views::Builder<views::ToggleImageButton>()
  323. .CopyAddressTo(&primary_action_pin_)
  324. .SetID(kHoldingSpaceItemPinButtonId)
  325. .SetCallback(base::BindRepeating(
  326. &HoldingSpaceItemView::OnPrimaryActionPressed,
  327. base::Unretained(this)))
  328. .SetFocusBehavior(views::View::FocusBehavior::NEVER)
  329. .SetImageHorizontalAlignment(HorizontalAlignment::ALIGN_CENTER)
  330. .SetImageVerticalAlignment(VerticalAlignment::ALIGN_MIDDLE)
  331. .SetPreferredSize(preferred_size)
  332. .SetVisible(false));
  333. return primary_action;
  334. }
  335. void HoldingSpaceItemView::OnSelectionUiChanged() {
  336. const bool multiselect =
  337. delegate_ && delegate_->selection_ui() ==
  338. HoldingSpaceViewDelegate::SelectionUi::kMultiSelect;
  339. checkmark_->SetVisible(selected() && multiselect);
  340. }
  341. void HoldingSpaceItemView::OnPaintFocus(gfx::Canvas* canvas, gfx::Size size) {
  342. if (!HasFocus())
  343. return;
  344. cc::PaintFlags flags;
  345. flags.setAntiAlias(true);
  346. flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
  347. AshColorProvider::ControlsLayerType::kFocusRingColor));
  348. flags.setStrokeWidth(views::FocusRing::kDefaultHaloThickness);
  349. flags.setStyle(cc::PaintFlags::kStroke_Style);
  350. gfx::Rect bounds = gfx::Rect(size);
  351. bounds.Inset(gfx::Insets(flags.getStrokeWidth() / 2));
  352. canvas->DrawRoundRect(bounds, kHoldingSpaceFocusCornerRadius, flags);
  353. }
  354. void HoldingSpaceItemView::OnPaintSelect(gfx::Canvas* canvas, gfx::Size size) {
  355. if (!selected_)
  356. return;
  357. const SkColor color =
  358. SkColorSetA(AshColorProvider::Get()->GetControlsLayerColor(
  359. AshColorProvider::ControlsLayerType::kFocusRingColor),
  360. kHoldingSpaceSelectedOverlayOpacity * 0xFF);
  361. cc::PaintFlags flags;
  362. flags.setAntiAlias(true);
  363. flags.setColor(color);
  364. canvas->DrawRoundRect(gfx::Rect(size), kHoldingSpaceCornerRadius, flags);
  365. }
  366. void HoldingSpaceItemView::OnPrimaryActionPressed() {
  367. // If the associated `item()` has been deleted then `this` is in the process
  368. // of being destroyed and no action needs to be taken.
  369. if (!item())
  370. return;
  371. DCHECK_NE(primary_action_cancel_->GetVisible(),
  372. primary_action_pin_->GetVisible());
  373. if (delegate())
  374. delegate()->OnHoldingSpaceItemViewPrimaryActionPressed(this);
  375. // Cancel.
  376. if (primary_action_cancel_->GetVisible()) {
  377. if (!holding_space_util::ExecuteInProgressCommand(
  378. item(), HoldingSpaceCommandId::kCancelItem)) {
  379. NOTREACHED();
  380. }
  381. return;
  382. }
  383. // Pin.
  384. const bool is_item_pinned =
  385. HoldingSpaceController::Get()->model()->ContainsItem(
  386. HoldingSpaceItem::Type::kPinnedFile, item()->file_path());
  387. // Unpinning `item()` may result in the destruction of this view.
  388. auto weak_ptr = weak_factory_.GetWeakPtr();
  389. if (is_item_pinned)
  390. HoldingSpaceController::Get()->client()->UnpinItems({item()});
  391. else
  392. HoldingSpaceController::Get()->client()->PinItems({item()});
  393. if (weak_ptr)
  394. UpdatePrimaryAction();
  395. }
  396. void HoldingSpaceItemView::UpdatePrimaryAction() {
  397. // If the associated `item()` has been deleted then `this` is in the process
  398. // of being destroyed and no action needs to be taken.
  399. if (!item())
  400. return;
  401. if (!IsMouseHovered()) {
  402. primary_action_container_->SetVisible(false);
  403. OnPrimaryActionVisibilityChanged(false);
  404. return;
  405. }
  406. // Cancel.
  407. // NOTE: Only in-progress items currently support cancellation.
  408. const bool is_item_in_progress = !item()->progress().IsComplete();
  409. primary_action_cancel_->SetVisible(
  410. is_item_in_progress && holding_space_util::SupportsInProgressCommand(
  411. item(), HoldingSpaceCommandId::kCancelItem));
  412. // Pin.
  413. const bool is_item_pinned =
  414. HoldingSpaceController::Get()->model()->ContainsItem(
  415. HoldingSpaceItem::Type::kPinnedFile, item()->file_path());
  416. primary_action_pin_->SetToggled(!is_item_pinned);
  417. primary_action_pin_->SetVisible(!is_item_in_progress);
  418. // Container.
  419. primary_action_container_->SetVisible(primary_action_cancel_->GetVisible() ||
  420. primary_action_pin_->GetVisible());
  421. OnPrimaryActionVisibilityChanged(primary_action_container_->GetVisible());
  422. }
  423. BEGIN_METADATA(HoldingSpaceItemView, views::View)
  424. END_METADATA
  425. } // namespace ash