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