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 
5 #include "ash/system/holding_space/holding_space_item_view.h"
6 
7 #include "ash/public/cpp/holding_space/holding_space_client.h"
8 #include "ash/public/cpp/holding_space/holding_space_constants.h"
9 #include "ash/public/cpp/holding_space/holding_space_controller.h"
10 #include "ash/public/cpp/holding_space/holding_space_item.h"
11 #include "ash/public/cpp/holding_space/holding_space_model.h"
12 #include "ash/public/cpp/shelf_config.h"
13 #include "ash/style/ash_color_provider.h"
14 #include "ash/system/holding_space/holding_space_item_view_delegate.h"
15 #include "base/bind.h"
16 #include "ui/base/class_property.h"
17 #include "ui/base/dragdrop/drag_drop_types.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_palette.h"
20 #include "ui/gfx/paint_vector_icon.h"
21 #include "ui/views/accessibility/view_accessibility.h"
22 #include "ui/views/background.h"
23 #include "ui/views/controls/button/image_button.h"
24 #include "ui/views/controls/highlight_path_generator.h"
25 #include "ui/views/metadata/metadata_impl_macros.h"
26 #include "ui/views/painter.h"
27 #include "ui/views/style/platform_style.h"
28 #include "ui/views/vector_icons.h"
29 #include "ui/views/widget/widget.h"
30 
31 namespace ash {
32 
33 namespace {
34 
35 // A UI class property used to identify if a view is an instance of
36 // `HoldingSpaceItemView`. Class name is not an adequate identifier as it may be
37 // overridden by subclasses.
38 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsHoldingSpaceItemViewProperty, false)
39 
40 // CallbackPainter -------------------------------------------------------------
41 
42 // A painter which delegates painting to a callback.
43 class CallbackPainter : public views::Painter {
44  public:
45   using Callback = base::RepeatingCallback<void(gfx::Canvas*, gfx::Size)>;
46 
47   CallbackPainter(const CallbackPainter&) = delete;
48   CallbackPainter& operator=(const CallbackPainter&) = delete;
49   ~CallbackPainter() override = default;
50 
51   // Creates a painted layer which delegates painting to `callback`.
CreatePaintedLayer(Callback callback)52   static std::unique_ptr<ui::LayerOwner> CreatePaintedLayer(Callback callback) {
53     auto owner = views::Painter::CreatePaintedLayer(
54         base::WrapUnique(new CallbackPainter(callback)));
55     owner->layer()->SetFillsBoundsOpaquely(false);
56     return owner;
57   }
58 
59  private:
CallbackPainter(Callback callback)60   explicit CallbackPainter(Callback callback) : callback_(callback) {}
61 
62   // views::Painter:
GetMinimumSize() const63   gfx::Size GetMinimumSize() const override { return gfx::Size(); }
Paint(gfx::Canvas * canvas,const gfx::Size & size)64   void Paint(gfx::Canvas* canvas, const gfx::Size& size) override {
65     callback_.Run(canvas, size);
66   }
67 
68   Callback callback_;
69 };
70 
71 }  // namespace
72 
73 // HoldingSpaceItemView --------------------------------------------------------
74 
HoldingSpaceItemView(HoldingSpaceItemViewDelegate * delegate,const HoldingSpaceItem * item)75 HoldingSpaceItemView::HoldingSpaceItemView(
76     HoldingSpaceItemViewDelegate* delegate,
77     const HoldingSpaceItem* item)
78     : delegate_(delegate), item_(item) {
79   SetProperty(kIsHoldingSpaceItemViewProperty, true);
80 
81   set_context_menu_controller(delegate_);
82   set_drag_controller(delegate_);
83 
84   SetNotifyEnterExitOnChild(true);
85 
86   // Accessibility.
87   GetViewAccessibility().OverrideName(item->text());
88   GetViewAccessibility().OverrideRole(ax::mojom::Role::kButton);
89 
90   // Background.
91   SetBackground(views::CreateRoundedRectBackground(
92       AshColorProvider::Get()->GetControlsLayerColor(
93           AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive),
94       kHoldingSpaceCornerRadius));
95 
96   // Layer.
97   SetPaintToLayer();
98   layer()->SetFillsBoundsOpaquely(false);
99 
100   // Focus.
101   SetFocusBehavior(FocusBehavior::ALWAYS);
102   focused_layer_owner_ =
103       CallbackPainter::CreatePaintedLayer(base::BindRepeating(
104           &HoldingSpaceItemView::OnPaintFocus, base::Unretained(this)));
105   layer()->Add(focused_layer_owner_->layer());
106 
107   // Selection.
108   selected_layer_owner_ =
109       CallbackPainter::CreatePaintedLayer(base::BindRepeating(
110           &HoldingSpaceItemView::OnPaintSelect, base::Unretained(this)));
111   layer()->Add(selected_layer_owner_->layer());
112 
113   // Ink drop.
114   SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER);
115   SetInkDropVisibleOpacity(
116       AshColorProvider::Get()->GetRippleAttributes().inkdrop_opacity);
117 
118   // Ink drop layers should match the corner radius of this view.
119   views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
120                                                 kHoldingSpaceCornerRadius);
121 
122   delegate_->OnHoldingSpaceItemViewCreated(this);
123 }
124 
125 HoldingSpaceItemView::~HoldingSpaceItemView() = default;
126 
127 // static
Cast(views::View * view)128 HoldingSpaceItemView* HoldingSpaceItemView::Cast(views::View* view) {
129   DCHECK(HoldingSpaceItemView::IsInstance(view));
130   return static_cast<HoldingSpaceItemView*>(view);
131 }
132 
133 // static
IsInstance(views::View * view)134 bool HoldingSpaceItemView::IsInstance(views::View* view) {
135   return view->GetProperty(kIsHoldingSpaceItemViewProperty);
136 }
137 
GetInkDropBaseColor() const138 SkColor HoldingSpaceItemView::GetInkDropBaseColor() const {
139   return AshColorProvider::Get()->GetRippleAttributes().base_color;
140 }
141 
HandleAccessibleAction(const ui::AXActionData & action_data)142 bool HoldingSpaceItemView::HandleAccessibleAction(
143     const ui::AXActionData& action_data) {
144   return delegate_->OnHoldingSpaceItemViewAccessibleAction(this, action_data) ||
145          views::InkDropHostView::HandleAccessibleAction(action_data);
146 }
147 
OnBoundsChanged(const gfx::Rect & previous_bounds)148 void HoldingSpaceItemView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
149   gfx::Rect bounds = GetLocalBounds();
150   selected_layer_owner_->layer()->SetBounds(bounds);
151   selected_layer_owner_->layer()->SchedulePaint(
152       selected_layer_owner_->layer()->bounds());
153 
154   // The focus ring is painted just outside the bounds for this view.
155   const float kFocusInsets =
156       -2.f - (views::PlatformStyle::kFocusHaloThickness / 2.f);
157 
158   bounds.Inset(gfx::Insets(kFocusInsets));
159   focused_layer_owner_->layer()->SetBounds(bounds);
160   focused_layer_owner_->layer()->SchedulePaint(
161       focused_layer_owner_->layer()->bounds());
162 }
163 
OnFocus()164 void HoldingSpaceItemView::OnFocus() {
165   focused_layer_owner_->layer()->SchedulePaint(
166       focused_layer_owner_->layer()->bounds());
167 }
168 
OnBlur()169 void HoldingSpaceItemView::OnBlur() {
170   focused_layer_owner_->layer()->SchedulePaint(
171       focused_layer_owner_->layer()->bounds());
172 }
173 
OnGestureEvent(ui::GestureEvent * event)174 void HoldingSpaceItemView::OnGestureEvent(ui::GestureEvent* event) {
175   delegate_->OnHoldingSpaceItemViewGestureEvent(this, *event);
176 }
177 
OnKeyPressed(const ui::KeyEvent & event)178 bool HoldingSpaceItemView::OnKeyPressed(const ui::KeyEvent& event) {
179   return delegate_->OnHoldingSpaceItemViewKeyPressed(this, event);
180 }
181 
OnMouseEvent(ui::MouseEvent * event)182 void HoldingSpaceItemView::OnMouseEvent(ui::MouseEvent* event) {
183   switch (event->type()) {
184     case ui::ET_MOUSE_ENTERED:
185     case ui::ET_MOUSE_EXITED:
186       UpdatePin();
187       break;
188     default:
189       break;
190   }
191   views::InkDropHostView::OnMouseEvent(event);
192 }
193 
OnMousePressed(const ui::MouseEvent & event)194 bool HoldingSpaceItemView::OnMousePressed(const ui::MouseEvent& event) {
195   return delegate_->OnHoldingSpaceItemViewMousePressed(this, event);
196 }
197 
OnMouseReleased(const ui::MouseEvent & event)198 void HoldingSpaceItemView::OnMouseReleased(const ui::MouseEvent& event) {
199   delegate_->OnHoldingSpaceItemViewMouseReleased(this, event);
200 }
201 
StartDrag(const ui::LocatedEvent & event,ui::mojom::DragEventSource source)202 void HoldingSpaceItemView::StartDrag(const ui::LocatedEvent& event,
203                                      ui::mojom::DragEventSource source) {
204   int drag_operations = GetDragOperations(event.location());
205   if (drag_operations == ui::DragDropTypes::DRAG_NONE)
206     return;
207 
208   views::Widget* widget = GetWidget();
209   DCHECK(widget);
210 
211   if (widget->dragged_view())
212     return;
213 
214   auto data = std::make_unique<ui::OSExchangeData>();
215   WriteDragData(event.location(), data.get());
216 
217   gfx::Point widget_location(event.location());
218   views::View::ConvertPointToWidget(this, &widget_location);
219   widget->RunShellDrag(this, std::move(data), widget_location, drag_operations,
220                        source);
221 }
222 
SetSelected(bool selected)223 void HoldingSpaceItemView::SetSelected(bool selected) {
224   if (selected_ == selected)
225     return;
226 
227   selected_ = selected;
228 
229   selected_layer_owner_->layer()->SchedulePaint(
230       selected_layer_owner_->layer()->bounds());
231 }
232 
AddPin(views::View * parent)233 views::ToggleImageButton* HoldingSpaceItemView::AddPin(views::View* parent) {
234   DCHECK(!pin_);
235 
236   pin_ = parent->AddChildView(std::make_unique<views::ToggleImageButton>());
237   pin_->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
238   pin_->SetVisible(false);
239 
240   const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
241       AshColorProvider::ContentLayerType::kButtonIconColor);
242 
243   const gfx::ImageSkia unpinned_icon = gfx::CreateVectorIcon(
244       views::kUnpinIcon, kHoldingSpacePinIconSize, icon_color);
245   const gfx::ImageSkia pinned_icon = gfx::CreateVectorIcon(
246       views::kPinIcon, kHoldingSpacePinIconSize, icon_color);
247 
248   pin_->SetImage(views::Button::STATE_NORMAL, unpinned_icon);
249   pin_->SetToggledImage(views::Button::STATE_NORMAL, &pinned_icon);
250 
251   pin_->SetImageHorizontalAlignment(
252       views::ToggleImageButton::HorizontalAlignment::ALIGN_CENTER);
253   pin_->SetImageVerticalAlignment(
254       views::ToggleImageButton::VerticalAlignment::ALIGN_MIDDLE);
255 
256   pin_->SetCallback(base::BindRepeating(&HoldingSpaceItemView::OnPinPressed,
257                                         base::Unretained(this)));
258 
259   return pin_;
260 }
261 
OnPaintFocus(gfx::Canvas * canvas,gfx::Size size)262 void HoldingSpaceItemView::OnPaintFocus(gfx::Canvas* canvas, gfx::Size size) {
263   if (!HasFocus())
264     return;
265 
266   cc::PaintFlags flags;
267   flags.setAntiAlias(true);
268   flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
269       AshColorProvider::ControlsLayerType::kFocusRingColor));
270   flags.setStrokeWidth(views::PlatformStyle::kFocusHaloThickness);
271   flags.setStyle(cc::PaintFlags::kStroke_Style);
272 
273   gfx::Rect bounds = gfx::Rect(size);
274   bounds.Inset(gfx::Insets(flags.getStrokeWidth() / 2));
275   canvas->DrawRoundRect(bounds, kHoldingSpaceCornerRadius, flags);
276 }
277 
OnPaintSelect(gfx::Canvas * canvas,gfx::Size size)278 void HoldingSpaceItemView::OnPaintSelect(gfx::Canvas* canvas, gfx::Size size) {
279   if (!selected_)
280     return;
281 
282   const SkColor color = AshColorProvider::Get()->GetControlsLayerColor(
283       AshColorProvider::ControlsLayerType::kFocusRingColor);
284 
285   const SkColor overlay_color =
286       SkColorSetA(color, kHoldingSpaceSelectedOverlayOpacity * 0xFF);
287 
288   cc::PaintFlags flags;
289   flags.setAntiAlias(true);
290   flags.setColor(overlay_color);
291 
292   gfx::Rect bounds = gfx::Rect(size);
293   canvas->DrawRoundRect(bounds, kHoldingSpaceCornerRadius, flags);
294 
295   flags.setColor(color);
296   flags.setStrokeWidth(views::PlatformStyle::kFocusHaloThickness);
297   flags.setStyle(cc::PaintFlags::kStroke_Style);
298 
299   bounds.Inset(gfx::Insets(flags.getStrokeWidth() / 2));
300   canvas->DrawRoundRect(bounds, kHoldingSpaceCornerRadius, flags);
301 }
302 
OnPinPressed()303 void HoldingSpaceItemView::OnPinPressed() {
304   const bool is_item_pinned = HoldingSpaceController::Get()->model()->GetItem(
305       HoldingSpaceItem::GetFileBackedItemId(HoldingSpaceItem::Type::kPinnedFile,
306                                             item()->file_path()));
307 
308   // Unpinning `item()` may result in the destruction of this view.
309   auto weak_ptr = weak_factory_.GetWeakPtr();
310   if (is_item_pinned)
311     HoldingSpaceController::Get()->client()->UnpinItems({item()});
312   else
313     HoldingSpaceController::Get()->client()->PinItems({item()});
314 
315   if (weak_ptr)
316     UpdatePin();
317 }
318 
UpdatePin()319 void HoldingSpaceItemView::UpdatePin() {
320   if (!IsMouseHovered()) {
321     pin_->SetVisible(false);
322     return;
323   }
324 
325   const bool is_item_pinned = HoldingSpaceController::Get()->model()->GetItem(
326       HoldingSpaceItem::GetFileBackedItemId(HoldingSpaceItem::Type::kPinnedFile,
327                                             item()->file_path()));
328 
329   pin_->SetToggled(!is_item_pinned);
330   pin_->SetVisible(true);
331 }
332 
333 BEGIN_METADATA(HoldingSpaceItemView, views::InkDropHostView)
334 END_METADATA
335 
336 }  // namespace ash
337