app_list_item_view.cc 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314
  1. // Copyright (c) 2012 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/app_list/views/app_list_item_view.h"
  5. #include <algorithm>
  6. #include <string>
  7. #include <utility>
  8. #include <vector>
  9. #include "ash/app_list/app_list_metrics.h"
  10. #include "ash/app_list/app_list_util.h"
  11. #include "ash/app_list/app_list_view_delegate.h"
  12. #include "ash/app_list/model/app_list_folder_item.h"
  13. #include "ash/app_list/model/app_list_item.h"
  14. #include "ash/app_list/views/app_list_menu_model_adapter.h"
  15. #include "ash/app_list/views/apps_grid_context_menu.h"
  16. #include "ash/constants/ash_features.h"
  17. #include "ash/public/cpp/app_list/app_list_color_provider.h"
  18. #include "ash/public/cpp/app_list/app_list_config.h"
  19. #include "ash/public/cpp/app_list/app_list_types.h"
  20. #include "ash/public/cpp/style/color_provider.h"
  21. #include "ash/strings/grit/ash_strings.h"
  22. #include "ash/style/ash_color_provider.h"
  23. #include "ash/style/dot_indicator.h"
  24. #include "base/auto_reset.h"
  25. #include "base/bind.h"
  26. #include "base/check.h"
  27. #include "base/strings/utf_string_conversions.h"
  28. #include "cc/paint/paint_flags.h"
  29. #include "ui/accessibility/ax_node_data.h"
  30. #include "ui/base/l10n/l10n_util.h"
  31. #include "ui/base/metadata/metadata_impl_macros.h"
  32. #include "ui/base/resource/resource_bundle.h"
  33. #include "ui/compositor/layer.h"
  34. #include "ui/compositor/scoped_layer_animation_settings.h"
  35. #include "ui/gfx/canvas.h"
  36. #include "ui/gfx/color_palette.h"
  37. #include "ui/gfx/font_list.h"
  38. #include "ui/gfx/geometry/point.h"
  39. #include "ui/gfx/geometry/rounded_corners_f.h"
  40. #include "ui/gfx/geometry/transform_util.h"
  41. #include "ui/gfx/image/canvas_image_source.h"
  42. #include "ui/gfx/image/image_skia_operations.h"
  43. #include "ui/gfx/shadow_value.h"
  44. #include "ui/views/accessibility/accessibility_paint_checks.h"
  45. #include "ui/views/accessibility/view_accessibility.h"
  46. #include "ui/views/controls/image_view.h"
  47. #include "ui/views/controls/label.h"
  48. #include "ui/views/controls/menu/menu_runner.h"
  49. #include "ui/views/focus/focus_manager.h"
  50. #include "ui/views/widget/widget.h"
  51. namespace ash {
  52. namespace {
  53. // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
  54. constexpr int kMouseDragUIDelayInMs = 200;
  55. // Delay in milliseconds of when the dragging UI should be shown for touch drag.
  56. // Note: For better user experience, this is made shorter than
  57. // ET_GESTURE_LONG_PRESS delay, which is too long for this case, e.g., about
  58. // 650ms.
  59. constexpr int kTouchLongpressDelayInMs = 300;
  60. // The drag and drop app icon should get scaled by this factor.
  61. constexpr float kDragDropAppIconScale = 1.2f;
  62. // The drag and drop icon scaling up or down animation transition duration.
  63. constexpr int kDragDropAppIconScaleTransitionInMs = 200;
  64. // The width of the focus ring within a folder.
  65. constexpr int kFocusRingWidth = 2;
  66. // The size of the notification indicator circle over the size of the icon.
  67. constexpr float kNotificationIndicatorWidthRatio = 14.0f / 64.0f;
  68. // The size of the notification indicator circle padding over the size of the
  69. // icon.
  70. constexpr float kNotificationIndicatorPaddingRatio = 4.0f / 64.0f;
  71. // Size of the "new install" blue dot that appears to the left of the title.
  72. constexpr int kNewInstallDotSize = 8;
  73. // Distance between the "new install" blue dot and the title.
  74. constexpr int kNewInstallDotPadding = 4;
  75. // The class clips the provided folder icon image.
  76. class ClippedFolderIconImageSource : public gfx::CanvasImageSource {
  77. public:
  78. ClippedFolderIconImageSource(const gfx::Size& size,
  79. const gfx::ImageSkia& image)
  80. : gfx::CanvasImageSource(size), image_(image) {}
  81. ClippedFolderIconImageSource(const ClippedFolderIconImageSource&) = delete;
  82. ClippedFolderIconImageSource& operator=(const ClippedFolderIconImageSource&) =
  83. delete;
  84. ~ClippedFolderIconImageSource() override = default;
  85. void Draw(gfx::Canvas* canvas) override {
  86. // Draw the unclipped icon on the center of the canvas with a circular mask.
  87. SkPath circular_mask;
  88. circular_mask.addCircle(SkFloatToScalar(size_.width() / 2),
  89. SkFloatToScalar(size_.height() / 2),
  90. SkIntToScalar(size_.width() / 2));
  91. cc::PaintFlags flags;
  92. flags.setStyle(cc::PaintFlags::kFill_Style);
  93. flags.setAntiAlias(true);
  94. canvas->DrawImageInPath(image_, (size_.width() - image_.size().width()) / 2,
  95. (size_.height() - image_.size().height()) / 2,
  96. circular_mask, flags);
  97. }
  98. private:
  99. const gfx::ImageSkia image_;
  100. };
  101. // Draws a dot with no shadow.
  102. class DotView : public views::View {
  103. public:
  104. DotView() {
  105. // The dot is not clickable.
  106. SetCanProcessEventsWithinSubtree(false);
  107. }
  108. DotView(const DotView&) = delete;
  109. DotView& operator=(const DotView&) = delete;
  110. ~DotView() override = default;
  111. // views::View:
  112. void OnPaint(gfx::Canvas* canvas) override {
  113. DCHECK_EQ(width(), height());
  114. const float radius = width() / 2.0f;
  115. const float scale = canvas->UndoDeviceScaleFactor();
  116. gfx::PointF center = gfx::RectF(GetLocalBounds()).CenterPoint();
  117. center.Scale(scale);
  118. cc::PaintFlags flags;
  119. flags.setColor(AshColorProvider::Get()->GetContentLayerColor(
  120. AshColorProvider::ContentLayerType::kIconColorProminent));
  121. flags.setAntiAlias(true);
  122. canvas->DrawCircle(center, scale * radius, flags);
  123. }
  124. void OnThemeChanged() override {
  125. views::View::OnThemeChanged();
  126. SchedulePaint();
  127. }
  128. };
  129. // Returns whether the `index` is considered on the left edge of a grid with
  130. // `cols` columns.
  131. bool IsIndexOnLeftEdge(GridIndex index, int cols) {
  132. return (index.slot % cols) == 0;
  133. }
  134. // Returns whether the `index` is considered on the right edge of a grid with
  135. // `cols` columns.
  136. bool IsIndexOnRightEdge(GridIndex index, int cols) {
  137. return ((index.slot + 1) % cols) == 0;
  138. }
  139. } // namespace
  140. // ImageView for the item icon.
  141. class AppListItemView::IconImageView : public views::ImageView {
  142. public:
  143. IconImageView() {
  144. SetCanProcessEventsWithinSubtree(false);
  145. SetVerticalAlignment(views::ImageView::Alignment::kLeading);
  146. }
  147. IconImageView(const IconImageView&) = delete;
  148. IconImageView& operator=(const IconImageView&) = delete;
  149. ~IconImageView() override = default;
  150. // views::View:
  151. const char* GetClassName() const override {
  152. return "AppListItemView::IconImageView";
  153. }
  154. void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
  155. views::ImageView::OnBoundsChanged(previous_bounds);
  156. if (size() != previous_bounds.size() && !insets_.IsEmpty())
  157. SetRoundedCornerAndInsets(corner_radius_, insets_);
  158. }
  159. // ui::LayerOwner:
  160. std::unique_ptr<ui::Layer> RecreateLayer() override {
  161. std::unique_ptr<ui::Layer> old_layer = views::View::RecreateLayer();
  162. // ui::Layer::Clone() does not copy the clip rect, so set it explicitly
  163. // here.
  164. if (corner_radius_ != 0 || !insets_.IsEmpty())
  165. SetRoundedCornerAndInsets(corner_radius_, insets_);
  166. return old_layer;
  167. }
  168. // Update the rounded corner and insets with animation. |extended| is true
  169. // when the target rounded corner radius and insets are for showing the
  170. // indicator circle.
  171. void SetExtendedState(const AppListConfig* config,
  172. bool extended,
  173. bool animate) {
  174. absl::optional<ui::ScopedLayerAnimationSettings> settings;
  175. if (animate) {
  176. settings.emplace(layer()->GetAnimator());
  177. settings->SetTweenType(gfx::Tween::EASE_IN);
  178. settings->SetTransitionDuration(base::Milliseconds(125));
  179. }
  180. extended_ = extended;
  181. SetRoundedCornerAndInsets(
  182. extended ? config->folder_unclipped_icon_dimension() / 2
  183. : config->folder_icon_dimension() / 2,
  184. extended ? gfx::Insets() : gfx::Insets(config->folder_icon_insets()));
  185. }
  186. // Ensure that the view has a layer.
  187. void EnsureLayer() {
  188. if (!layer()) {
  189. SetPaintToLayer();
  190. layer()->SetFillsBoundsOpaquely(false);
  191. layer()->SetName(GetClassName());
  192. }
  193. }
  194. bool extended() const { return extended_; }
  195. private:
  196. // Sets the rounded corner and the clip insets.
  197. void SetRoundedCornerAndInsets(int corner_radius, const gfx::Insets& insets) {
  198. EnsureLayer();
  199. layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(corner_radius));
  200. if (insets.IsEmpty()) {
  201. layer()->SetClipRect(GetLocalBounds());
  202. } else {
  203. gfx::Rect bounds = GetLocalBounds();
  204. bounds.Inset(insets);
  205. layer()->SetClipRect(bounds);
  206. }
  207. // Save the attributes in case the layer is recreated.
  208. corner_radius_ = corner_radius;
  209. insets_ = insets;
  210. }
  211. // Whether corner radius and insets are set for showing the drop target
  212. // indicator circle.
  213. bool extended_ = false;
  214. // The rounded corner radius.
  215. int corner_radius_ = 0;
  216. // The insets to be clipped.
  217. gfx::Insets insets_;
  218. };
  219. AppListItemView::AppListItemView(const AppListConfig* app_list_config,
  220. GridDelegate* grid_delegate,
  221. AppListItem* item,
  222. AppListViewDelegate* view_delegate,
  223. Context context)
  224. : views::Button(
  225. base::BindRepeating(&GridDelegate::OnAppListItemViewActivated,
  226. base::Unretained(grid_delegate),
  227. base::Unretained(this))),
  228. app_list_config_(app_list_config),
  229. is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
  230. item_weak_(item),
  231. grid_delegate_(grid_delegate),
  232. view_delegate_(view_delegate),
  233. context_(context) {
  234. DCHECK(app_list_config_);
  235. // TODO(crbug.com/1218186): Remove this, this is in place temporarily to be
  236. // able to submit accessibility checks. This crashes if fetching a11y node
  237. // data during paint because message_view_ is null.
  238. SetProperty(views::kSkipAccessibilityPaintChecks, true);
  239. DCHECK(grid_delegate_);
  240. DCHECK(view_delegate_);
  241. SetFocusBehavior(FocusBehavior::ALWAYS);
  242. set_suppress_default_focus_handling();
  243. GetViewAccessibility().OverrideIsLeaf(true);
  244. auto title = std::make_unique<views::Label>();
  245. title->SetBackgroundColor(SK_ColorTRANSPARENT);
  246. title->SetHandlesTooltips(false);
  247. title->SetFontList(app_list_config_->app_title_font());
  248. title->SetHorizontalAlignment(gfx::ALIGN_CENTER);
  249. title->SetEnabledColor(AppListColorProvider::Get()->GetAppListItemTextColor(
  250. grid_delegate_->IsInFolder()));
  251. icon_ = AddChildView(std::make_unique<IconImageView>());
  252. if (is_folder_) {
  253. // Set background blur for folder icon and use mask layer to clip it into
  254. // circle. Note that blur is only enabled in tablet mode to improve dragging
  255. // smoothness.
  256. if (view_delegate_->IsInTabletMode())
  257. SetBackgroundBlurEnabled(true);
  258. icon_->SetExtendedState(app_list_config_, icon_->extended(),
  259. false /*animate*/);
  260. }
  261. notification_indicator_ = AddChildView(
  262. std::make_unique<DotIndicator>(item->GetNotificationBadgeColor()));
  263. notification_indicator_->SetVisible(item->has_notification_badge());
  264. title_ = AddChildView(std::move(title));
  265. if (features::IsProductivityLauncherEnabled()) {
  266. new_install_dot_ = AddChildView(std::make_unique<DotView>());
  267. new_install_dot_->SetVisible(item_weak_->is_new_install());
  268. }
  269. SetIcon(item_weak_->GetIcon(app_list_config_->type()));
  270. SetItemName(base::UTF8ToUTF16(item->GetDisplayName()),
  271. base::UTF8ToUTF16(item->name()));
  272. item->AddObserver(this);
  273. if (is_folder_) {
  274. context_menu_for_folder_ = std::make_unique<AppsGridContextMenu>();
  275. set_context_menu_controller(context_menu_for_folder_.get());
  276. } else {
  277. set_context_menu_controller(this);
  278. }
  279. SetAnimationDuration(base::TimeDelta());
  280. preview_circle_radius_ = 0;
  281. }
  282. void AppListItemView::InitializeIconLoader() {
  283. DCHECK(item_weak_);
  284. // Creates app icon load helper. base::Unretained is safe because `this` owns
  285. // `icon_load_helper_` and `view_delegate_` outlives `this`.
  286. if (is_folder_) {
  287. AppListFolderItem* folder_item =
  288. static_cast<AppListFolderItem*>(item_weak_);
  289. icon_load_helper_.emplace(
  290. folder_item->item_list(),
  291. base::BindRepeating(&AppListViewDelegate::LoadIcon,
  292. base::Unretained(view_delegate_)));
  293. } else {
  294. icon_load_helper_.emplace(
  295. item_weak_, base::BindRepeating(&AppListViewDelegate::LoadIcon,
  296. base::Unretained(view_delegate_)));
  297. }
  298. }
  299. AppListItemView::~AppListItemView() {
  300. if (item_weak_)
  301. item_weak_->RemoveObserver(this);
  302. StopObservingImplicitAnimations();
  303. }
  304. void AppListItemView::SetIcon(const gfx::ImageSkia& icon) {
  305. // Clear icon and bail out if item icon is empty.
  306. if (icon.isNull()) {
  307. icon_->SetImage(nullptr);
  308. icon_image_ = gfx::ImageSkia();
  309. return;
  310. }
  311. icon_image_ = icon;
  312. gfx::Size icon_bounds = is_folder_
  313. ? app_list_config_->folder_unclipped_icon_size()
  314. : app_list_config_->grid_icon_size();
  315. icon_bounds = gfx::ScaleToRoundedSize(icon_bounds, icon_scale_);
  316. gfx::ImageSkia resized = gfx::ImageSkiaOperations::CreateResizedImage(
  317. icon, skia::ImageOperations::RESIZE_BEST, icon_bounds);
  318. icon_->SetImage(resized);
  319. Layout();
  320. }
  321. void AppListItemView::UpdateAppListConfig(
  322. const AppListConfig* app_list_config) {
  323. app_list_config_ = app_list_config;
  324. DCHECK(app_list_config_);
  325. if (!item_weak_) {
  326. SetIcon(gfx::ImageSkia());
  327. return;
  328. }
  329. title()->SetFontList(app_list_config_->app_title_font());
  330. SetIcon(item_weak_->GetIcon(app_list_config_->type()));
  331. if (is_folder_) {
  332. icon_->SetExtendedState(app_list_config_, icon_->extended(),
  333. false /*animate*/);
  334. }
  335. SchedulePaint();
  336. }
  337. void AppListItemView::ScaleIconImmediatly(float scale_factor) {
  338. if (icon_scale_ == scale_factor)
  339. return;
  340. icon_scale_ = scale_factor;
  341. SetIcon(icon_image_);
  342. layer()->SetTransform(gfx::Transform());
  343. }
  344. void AppListItemView::SetUIState(UIState ui_state) {
  345. if (ui_state_ == ui_state)
  346. return;
  347. switch (ui_state) {
  348. case UI_STATE_NORMAL:
  349. title_->SetVisible(true);
  350. if (ui_state_ == UI_STATE_DRAGGING) {
  351. GetWidget()->SetCursor(ui::mojom::CursorType::kNull);
  352. ScaleAppIcon(false);
  353. }
  354. break;
  355. case UI_STATE_DRAGGING:
  356. title_->SetVisible(false);
  357. if (ui_state_ == UI_STATE_NORMAL && !in_cardified_grid_) {
  358. GetWidget()->SetCursor(ui::mojom::CursorType::kGrabbing);
  359. ScaleAppIcon(true);
  360. }
  361. break;
  362. case UI_STATE_DROPPING_IN_FOLDER:
  363. break;
  364. }
  365. ui_state_ = ui_state;
  366. SchedulePaint();
  367. }
  368. void AppListItemView::ScaleAppIcon(bool scale_up) {
  369. if (!layer())
  370. return;
  371. if (!is_folder_) {
  372. if (scale_up) {
  373. icon_scale_ = kDragDropAppIconScale;
  374. SetIcon(icon_image_);
  375. layer()->SetTransform(gfx::GetScaleTransform(
  376. GetContentsBounds().CenterPoint(), 1 / kDragDropAppIconScale));
  377. } else if (drag_state_ != DragState::kNone) {
  378. // If a drag view has been created for this icon, the item transition to
  379. // target bounds is handled by the apps grid view bounds animator. At the
  380. // end of that animation, the layer will be destroyed, causing the
  381. // animation observer to get canceled. For this case, we need to scale
  382. // down the icon immediately, with no animation.
  383. ScaleIconImmediatly(1.0f);
  384. }
  385. }
  386. ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
  387. settings.SetTransitionDuration(
  388. base::Milliseconds((kDragDropAppIconScaleTransitionInMs)));
  389. settings.SetTweenType(gfx::Tween::EASE_OUT_2);
  390. if (scale_up) {
  391. if (is_folder_) {
  392. const gfx::Rect bounds(layer()->bounds().size());
  393. gfx::Transform transform =
  394. gfx::GetScaleTransform(bounds.CenterPoint(), kDragDropAppIconScale);
  395. layer()->SetTransform(transform);
  396. } else {
  397. layer()->SetTransform(gfx::Transform());
  398. }
  399. } else {
  400. if (is_folder_) {
  401. layer()->SetTransform(gfx::Transform());
  402. } else if (drag_state_ == DragState::kNone) {
  403. // To avoid poor quality icons, update icon image with the correct scale
  404. // after the transform animation is completed.
  405. settings.AddObserver(this);
  406. layer()->SetTransform(gfx::GetScaleTransform(
  407. GetContentsBounds().CenterPoint(), 1 / kDragDropAppIconScale));
  408. }
  409. }
  410. }
  411. void AppListItemView::OnImplicitAnimationsCompleted() {
  412. ScaleIconImmediatly(1.0f);
  413. }
  414. void AppListItemView::SetTouchDragging(bool touch_dragging) {
  415. if (touch_dragging_ == touch_dragging)
  416. return;
  417. touch_dragging_ = touch_dragging;
  418. if (context_menu_for_folder_)
  419. context_menu_for_folder_->set_owner_touch_dragging(touch_dragging_);
  420. SetState(STATE_NORMAL);
  421. SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
  422. // EndDrag may delete |this|.
  423. if (!touch_dragging)
  424. grid_delegate_->EndDrag(/*cancel=*/false);
  425. }
  426. void AppListItemView::SetMouseDragging(bool mouse_dragging) {
  427. mouse_dragging_ = mouse_dragging;
  428. SetState(STATE_NORMAL);
  429. SetUIState(mouse_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
  430. }
  431. void AppListItemView::OnMouseDragTimer() {
  432. // Show scaled up app icon to indicate draggable state.
  433. SetMouseDragging(true);
  434. }
  435. void AppListItemView::OnTouchDragTimer(
  436. const gfx::Point& tap_down_location,
  437. const gfx::Point& tap_down_root_location) {
  438. // Show scaled up app icon to indicate draggable state.
  439. if (!InitiateDrag(tap_down_location, tap_down_root_location))
  440. return;
  441. SetTouchDragging(true);
  442. }
  443. bool AppListItemView::InitiateDrag(const gfx::Point& location,
  444. const gfx::Point& root_location) {
  445. if (!grid_delegate_->InitiateDrag(
  446. this, location, root_location,
  447. base::BindOnce(&AppListItemView::OnDragStarted,
  448. weak_ptr_factory_.GetWeakPtr()),
  449. base::BindOnce(&AppListItemView::OnDragEnded,
  450. weak_ptr_factory_.GetWeakPtr()))) {
  451. return false;
  452. }
  453. drag_state_ = DragState::kInitialized;
  454. return true;
  455. }
  456. void AppListItemView::OnDragStarted() {
  457. DCHECK_EQ(DragState::kInitialized, drag_state_);
  458. mouse_drag_timer_.Stop();
  459. touch_drag_timer_.Stop();
  460. drag_state_ = DragState::kStarted;
  461. SetUIState(UI_STATE_DRAGGING);
  462. CancelContextMenu();
  463. }
  464. void AppListItemView::OnDragEnded() {
  465. DCHECK_NE(drag_state_, DragState::kNone);
  466. mouse_dragging_ = false;
  467. mouse_drag_timer_.Stop();
  468. touch_dragging_ = false;
  469. touch_drag_timer_.Stop();
  470. if (context_menu_for_folder_)
  471. context_menu_for_folder_->set_owner_touch_dragging(false);
  472. SetUIState(UI_STATE_NORMAL);
  473. drag_state_ = DragState::kNone;
  474. }
  475. void AppListItemView::CancelContextMenu() {
  476. if (item_menu_model_adapter_) {
  477. menu_close_initiated_from_drag_ = true;
  478. item_menu_model_adapter_->Cancel();
  479. }
  480. if (context_menu_for_folder_)
  481. context_menu_for_folder_->Cancel();
  482. }
  483. gfx::Point AppListItemView::GetDragImageOffset() {
  484. gfx::Point image = icon_->GetImageBounds().origin();
  485. return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
  486. }
  487. void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
  488. if (is_target_folder)
  489. SetUIState(UI_STATE_DROPPING_IN_FOLDER);
  490. else
  491. SetUIState(UI_STATE_NORMAL);
  492. }
  493. void AppListItemView::SilentlyRequestFocus() {
  494. DCHECK(!focus_silently_);
  495. base::AutoReset<bool> auto_reset(&focus_silently_, true);
  496. RequestFocus();
  497. }
  498. void AppListItemView::EnsureSelected() {
  499. grid_delegate_->SetSelectedView(/*view=*/this);
  500. }
  501. void AppListItemView::SetItemName(const std::u16string& display_name,
  502. const std::u16string& full_name) {
  503. const std::u16string folder_name_placeholder =
  504. ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
  505. IDS_APP_LIST_FOLDER_NAME_PLACEHOLDER);
  506. if (is_folder_ && display_name.empty()) {
  507. title_->SetText(folder_name_placeholder);
  508. } else {
  509. title_->SetText(display_name);
  510. }
  511. tooltip_text_ = display_name == full_name ? std::u16string() : full_name;
  512. // Use full name for accessibility.
  513. SetAccessibleName(
  514. is_folder_ ? l10n_util::GetStringFUTF16(
  515. IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME,
  516. full_name.empty() ? folder_name_placeholder : full_name)
  517. : full_name);
  518. Layout();
  519. }
  520. void AppListItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  521. if (!item_weak_)
  522. return;
  523. DCHECK(node_data);
  524. Button::GetAccessibleNodeData(node_data);
  525. auto app_status = item_weak_->app_status();
  526. switch (app_status) {
  527. case AppStatus::kBlocked:
  528. node_data->SetDescription(
  529. ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
  530. IDS_APP_LIST_BLOCKED_APP));
  531. break;
  532. case AppStatus::kPaused:
  533. node_data->SetDescription(
  534. ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
  535. IDS_APP_LIST_PAUSED_APP));
  536. break;
  537. default:
  538. if (item_weak_->is_new_install()) {
  539. node_data->SetDescription(
  540. ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
  541. IDS_APP_LIST_NEW_INSTALL_ACCESSIBILE_DESCRIPTION));
  542. }
  543. break;
  544. }
  545. }
  546. void AppListItemView::OnContextMenuModelReceived(
  547. const gfx::Point& point,
  548. ui::MenuSourceType source_type,
  549. std::unique_ptr<ui::SimpleMenuModel> menu_model) {
  550. waiting_for_context_menu_options_ = false;
  551. if (!menu_model || IsShowingAppMenu())
  552. return;
  553. // GetContextMenuModel is asynchronous and takes a nontrivial amount of time
  554. // to complete. If a menu is shown after the icon has moved, |grid_delegate_|
  555. // gets put in a bad state because the context menu begins to receive drag
  556. // events, interrupting the app icon drag.
  557. if (drag_state_ == DragState::kStarted)
  558. return;
  559. menu_show_initiated_from_key_ = source_type == ui::MENU_SOURCE_KEYBOARD;
  560. // Clear the existing focus in other elements to prevent having a focus
  561. // indicator on other non-selected views.
  562. if (GetFocusManager()->GetFocusedView()) {
  563. GetFocusManager()->ClearFocus();
  564. focus_removed_by_context_menu_ = true;
  565. }
  566. if (!grid_delegate_->IsSelectedView(this))
  567. grid_delegate_->ClearSelectedView();
  568. int run_types = views::MenuRunner::HAS_MNEMONICS |
  569. views::MenuRunner::USE_ASH_SYS_UI_LAYOUT |
  570. views::MenuRunner::FIXED_ANCHOR |
  571. views::MenuRunner::CONTEXT_MENU;
  572. if (source_type == ui::MENU_SOURCE_TOUCH && touch_dragging_)
  573. run_types |= views::MenuRunner::SEND_GESTURE_EVENTS_TO_OWNER;
  574. // Screen bounds don't need RTL flipping.
  575. gfx::Rect anchor_rect = GetBoundsInScreen();
  576. // Anchor the menu to the same rect that is used for selection highlight.
  577. AdaptBoundsForSelectionHighlight(&anchor_rect);
  578. // Assign the correct app type to `item_menu_model_adapter_` according to the
  579. // parent view of the app list item view.
  580. AppListMenuModelAdapter::AppListViewAppType app_type;
  581. AppLaunchedMetricParams metric_params;
  582. switch (context_) {
  583. case Context::kAppsGridView:
  584. app_type = features::IsProductivityLauncherEnabled()
  585. ? AppListMenuModelAdapter::PRODUCTIVITY_LAUNCHER_APP_GRID
  586. : AppListMenuModelAdapter::FULLSCREEN_APP_GRID;
  587. metric_params.launched_from = AppListLaunchedFrom::kLaunchedFromGrid;
  588. metric_params.launch_type = AppListLaunchType::kApp;
  589. break;
  590. case Context::kRecentAppsView:
  591. app_type = AppListMenuModelAdapter::PRODUCTIVITY_LAUNCHER_RECENT_APP;
  592. metric_params.launched_from =
  593. AppListLaunchedFrom::kLaunchedFromRecentApps;
  594. metric_params.launch_type = AppListLaunchType::kAppSearchResult;
  595. break;
  596. }
  597. view_delegate_->GetAppLaunchedMetricParams(&metric_params);
  598. item_menu_model_adapter_ = std::make_unique<AppListMenuModelAdapter>(
  599. item_weak_->GetMetadata()->id, std::move(menu_model), GetWidget(),
  600. source_type, metric_params, app_type,
  601. base::BindOnce(&AppListItemView::OnMenuClosed,
  602. weak_ptr_factory_.GetWeakPtr()),
  603. view_delegate_->IsInTabletMode());
  604. item_menu_model_adapter_->Run(
  605. anchor_rect, views::MenuAnchorPosition::kBubbleRight, run_types);
  606. if (!context_menu_shown_callback_.is_null()) {
  607. context_menu_shown_callback_.Run();
  608. }
  609. grid_delegate_->SetSelectedView(this);
  610. }
  611. void AppListItemView::ShowContextMenuForViewImpl(
  612. views::View* source,
  613. const gfx::Point& point,
  614. ui::MenuSourceType source_type) {
  615. if (IsShowingAppMenu())
  616. return;
  617. // Prevent multiple requests for context menus before the current request
  618. // completes. If a second request is sent before the first one can respond,
  619. // the Chrome side delegate will become unresponsive
  620. // (https://crbug.com/881886).
  621. if (waiting_for_context_menu_options_)
  622. return;
  623. waiting_for_context_menu_options_ = true;
  624. // When the context menu comes from the apps grid it has sorting options. When
  625. // it comes from recent apps it has an option to hide the continue section.
  626. AppListItemContext item_context = context_ == Context::kAppsGridView
  627. ? AppListItemContext::kAppsGrid
  628. : AppListItemContext::kRecentApps;
  629. view_delegate_->GetContextMenuModel(
  630. item_weak_->id(), item_context,
  631. base::BindOnce(&AppListItemView::OnContextMenuModelReceived,
  632. weak_ptr_factory_.GetWeakPtr(), point, source_type));
  633. }
  634. bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
  635. // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
  636. // background does not show up during scroll.
  637. if (event.type() == ui::ET_GESTURE_TAP_DOWN)
  638. return false;
  639. return views::Button::ShouldEnterPushedState(event);
  640. }
  641. void AppListItemView::PaintButtonContents(gfx::Canvas* canvas) {
  642. if (drag_state_ != DragState::kNone)
  643. return;
  644. if ((grid_delegate_->IsSelectedView(this) || HasFocus()) &&
  645. (view_delegate_->KeyboardTraversalEngaged() ||
  646. waiting_for_context_menu_options_ || IsShowingAppMenu())) {
  647. cc::PaintFlags flags;
  648. flags.setAntiAlias(true);
  649. // Clamshell ProductivityLauncher always has keyboard traversal engaged, so
  650. // explicitly check HasFocus() before drawing focus ring. This allows
  651. // right-click "selected" apps to avoid drawing the focus ring.
  652. const bool draw_focus_ring =
  653. features::IsProductivityLauncherEnabled() &&
  654. !view_delegate_->IsInTabletMode()
  655. ? HasFocus()
  656. : view_delegate_->KeyboardTraversalEngaged();
  657. if (draw_focus_ring) {
  658. flags.setColor(AppListColorProvider::Get()->GetFocusRingColor());
  659. flags.setStyle(cc::PaintFlags::kStroke_Style);
  660. flags.setStrokeWidth(kFocusRingWidth);
  661. } else {
  662. // Draw a background highlight ("selected" in the UI spec).
  663. const AppListColorProvider* color_provider = AppListColorProvider::Get();
  664. const SkColor bg_color = grid_delegate_->IsInFolder()
  665. ? color_provider->GetFolderBackgroundColor()
  666. : gfx::kPlaceholderColor;
  667. flags.setColor(
  668. SkColorSetA(color_provider->GetInkDropBaseColor(bg_color),
  669. color_provider->GetInkDropOpacity(bg_color) * 255));
  670. flags.setStyle(cc::PaintFlags::kFill_Style);
  671. }
  672. gfx::Rect selection_highlight_bounds = GetContentsBounds();
  673. AdaptBoundsForSelectionHighlight(&selection_highlight_bounds);
  674. canvas->DrawRoundRect(gfx::RectF(selection_highlight_bounds),
  675. app_list_config_->grid_focus_corner_radius(), flags);
  676. }
  677. const int preview_circle_radius = GetPreviewCircleRadius();
  678. if (!preview_circle_radius)
  679. return;
  680. // Draw folder dropping preview circle.
  681. gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
  682. icon_->y() + icon_->size().height() / 2);
  683. cc::PaintFlags flags;
  684. flags.setStyle(cc::PaintFlags::kFill_Style);
  685. flags.setAntiAlias(true);
  686. flags.setColor(AppListColorProvider::Get()->GetFolderBubbleColor());
  687. canvas->DrawCircle(center, preview_circle_radius, flags);
  688. }
  689. bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
  690. Button::OnMousePressed(event);
  691. if (!ShouldEnterPushedState(event))
  692. return true;
  693. if (!InitiateDrag(event.location(), event.root_location()))
  694. return true;
  695. mouse_drag_timer_.Start(FROM_HERE, base::Milliseconds(kMouseDragUIDelayInMs),
  696. this, &AppListItemView::OnMouseDragTimer);
  697. return true;
  698. }
  699. void AppListItemView::Layout() {
  700. gfx::Rect rect(GetContentsBounds());
  701. if (rect.IsEmpty())
  702. return;
  703. const gfx::Rect icon_bounds = GetIconBoundsForTargetViewBounds(
  704. app_list_config_, rect, icon_->GetImageBounds().size(), icon_scale_);
  705. icon_->SetBoundsRect(icon_bounds);
  706. gfx::Rect title_bounds = GetTitleBoundsForTargetViewBounds(
  707. app_list_config_, rect, title_->GetPreferredSize(), icon_scale_);
  708. if (new_install_dot_ && new_install_dot_->GetVisible()) {
  709. // If the new install dot is showing, and the dot would extend outside the
  710. // left edge of the tile, inset the title bounds to make space for the dot.
  711. int dot_x = title_bounds.x() - kNewInstallDotSize - kNewInstallDotPadding;
  712. if (dot_x < 0)
  713. title_bounds.Inset(gfx::Insets::TLBR(0, kNewInstallDotSize, 0, 0));
  714. }
  715. title_->SetBoundsRect(title_bounds);
  716. if (new_install_dot_) {
  717. new_install_dot_->SetBounds(
  718. title_bounds.x() - kNewInstallDotSize - kNewInstallDotPadding,
  719. title_bounds.y() + title_bounds.height() / 2 - kNewInstallDotSize / 2,
  720. kNewInstallDotSize, kNewInstallDotSize);
  721. }
  722. const float indicator_size =
  723. icon_bounds.width() * kNotificationIndicatorWidthRatio;
  724. const float indicator_padding =
  725. icon_bounds.width() * kNotificationIndicatorPaddingRatio;
  726. const float indicator_x =
  727. icon_bounds.right() - indicator_size - indicator_padding;
  728. const float indicator_y = icon_bounds.y() + indicator_padding;
  729. const gfx::Rect indicator_bounds = gfx::ToRoundedRect(
  730. gfx::RectF(indicator_x, indicator_y, indicator_size, indicator_size));
  731. notification_indicator_->SetIndicatorBounds(indicator_bounds);
  732. }
  733. gfx::Size AppListItemView::CalculatePreferredSize() const {
  734. return gfx::Size(app_list_config_->grid_tile_width(),
  735. app_list_config_->grid_tile_height());
  736. }
  737. bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
  738. // Disable space key to press the button. The keyboard events received
  739. // by this view are forwarded from a Textfield (SearchBoxView) and key
  740. // released events are not forwarded. This leaves the button in pressed
  741. // state.
  742. if (event.key_code() == ui::VKEY_SPACE)
  743. return false;
  744. return Button::OnKeyPressed(event);
  745. }
  746. void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
  747. auto weak_this = weak_ptr_factory_.GetWeakPtr();
  748. // Triggers the button's click handler callback, which might delete `this`.
  749. Button::OnMouseReleased(event);
  750. if (!weak_this)
  751. return;
  752. SetMouseDragging(false);
  753. // EndDrag may delete |this|.
  754. grid_delegate_->EndDrag(/*cancel=*/false);
  755. }
  756. void AppListItemView::OnMouseCaptureLost() {
  757. Button::OnMouseCaptureLost();
  758. SetMouseDragging(false);
  759. // EndDrag may delete |this|.
  760. grid_delegate_->EndDrag(/*cancel=*/true);
  761. }
  762. bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
  763. Button::OnMouseDragged(event);
  764. if (drag_state_ != DragState::kNone && mouse_dragging_) {
  765. // Update the drag location of the drag proxy if it has been created.
  766. // If the drag is no longer happening, it could be because this item
  767. // got removed, in which case this item has been destroyed. So, bail out
  768. // now as there will be nothing else to do anyway as
  769. // grid_delegate_->IsDragging() will be false.
  770. if (!grid_delegate_->UpdateDragFromItem(/*is_touch=*/false, event))
  771. return true;
  772. }
  773. if (!grid_delegate_->IsSelectedView(this))
  774. grid_delegate_->ClearSelectedView();
  775. return true;
  776. }
  777. bool AppListItemView::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
  778. // Ensure accelerators take priority in the app list. This ensures, e.g., that
  779. // Ctrl+Space will switch input methods rather than activate the button.
  780. return false;
  781. }
  782. void AppListItemView::OnFocus() {
  783. if (focus_silently_)
  784. return;
  785. grid_delegate_->SetSelectedView(this);
  786. }
  787. void AppListItemView::OnBlur() {
  788. SchedulePaint();
  789. if (grid_delegate_->IsSelectedView(this))
  790. grid_delegate_->ClearSelectedView();
  791. }
  792. void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
  793. switch (event->type()) {
  794. case ui::ET_GESTURE_SCROLL_BEGIN:
  795. if (touch_dragging_) {
  796. grid_delegate_->StartDragAndDropHostDragAfterLongPress();
  797. event->SetHandled();
  798. } else {
  799. touch_drag_timer_.Stop();
  800. }
  801. break;
  802. case ui::ET_GESTURE_SCROLL_UPDATE:
  803. if (touch_dragging_ && drag_state_ != DragState::kNone) {
  804. grid_delegate_->UpdateDragFromItem(/*is_touch=*/true, *event);
  805. event->SetHandled();
  806. }
  807. break;
  808. case ui::ET_GESTURE_SCROLL_END:
  809. case ui::ET_SCROLL_FLING_START:
  810. if (touch_dragging_) {
  811. SetTouchDragging(false);
  812. event->SetHandled();
  813. }
  814. break;
  815. case ui::ET_GESTURE_TAP_DOWN:
  816. if (GetState() != STATE_DISABLED) {
  817. SetState(STATE_PRESSED);
  818. touch_drag_timer_.Start(
  819. FROM_HERE, base::Milliseconds(kTouchLongpressDelayInMs),
  820. base::BindOnce(&AppListItemView::OnTouchDragTimer,
  821. base::Unretained(this), event->location(),
  822. event->root_location()));
  823. event->SetHandled();
  824. }
  825. break;
  826. case ui::ET_GESTURE_TAP:
  827. case ui::ET_GESTURE_TAP_CANCEL:
  828. if (GetState() != STATE_DISABLED) {
  829. touch_drag_timer_.Stop();
  830. SetState(STATE_NORMAL);
  831. }
  832. break;
  833. case ui::ET_GESTURE_LONG_TAP:
  834. case ui::ET_GESTURE_END:
  835. touch_drag_timer_.Stop();
  836. SetTouchDragging(false);
  837. if (IsShowingAppMenu())
  838. grid_delegate_->SetSelectedView(this);
  839. break;
  840. case ui::ET_GESTURE_TWO_FINGER_TAP:
  841. if (touch_dragging_) {
  842. SetTouchDragging(false);
  843. } else {
  844. touch_drag_timer_.Stop();
  845. }
  846. break;
  847. default:
  848. break;
  849. }
  850. if (!event->handled())
  851. Button::OnGestureEvent(event);
  852. }
  853. void AppListItemView::OnThemeChanged() {
  854. views::Button::OnThemeChanged();
  855. if (item_weak_) {
  856. item_weak_->RequestFolderIconUpdate();
  857. notification_indicator_->SetColor(item_weak_->GetNotificationBadgeColor());
  858. }
  859. title_->SetEnabledColor(AppListColorProvider::Get()->GetAppListItemTextColor(
  860. grid_delegate_->IsInFolder()));
  861. SchedulePaint();
  862. }
  863. std::u16string AppListItemView::GetTooltipText(const gfx::Point& p) const {
  864. // Use the label to generate a tooltip, so that it will consider its text
  865. // truncation in making the tooltip. We do not want the label itself to have a
  866. // tooltip, so we only temporarily enable it to get the tooltip text from the
  867. // label, then disable it again.
  868. title_->SetHandlesTooltips(true);
  869. title_->SetTooltipText(tooltip_text_);
  870. std::u16string tooltip = title_->GetTooltipText(p);
  871. title_->SetHandlesTooltips(false);
  872. if (new_install_dot_ && new_install_dot_->GetVisible() && !is_folder_) {
  873. // Tooltip becomes two lines: "App Name" + "New install".
  874. tooltip = l10n_util::GetStringFUTF16(IDS_APP_LIST_NEW_INSTALL, tooltip);
  875. }
  876. return tooltip;
  877. }
  878. void AppListItemView::OnDraggedViewEnter() {
  879. if (is_folder_) {
  880. icon_->SetExtendedState(app_list_config_, true /*extended*/,
  881. true /*animate*/);
  882. return;
  883. }
  884. CreateDraggedViewHoverAnimation();
  885. dragged_view_hover_animation_->Show();
  886. }
  887. void AppListItemView::OnDraggedViewExit() {
  888. if (is_folder_) {
  889. icon_->SetExtendedState(app_list_config_, false /*extended*/,
  890. true /*animate*/);
  891. return;
  892. }
  893. CreateDraggedViewHoverAnimation();
  894. dragged_view_hover_animation_->Hide();
  895. }
  896. void AppListItemView::SetBackgroundBlurEnabled(bool enabled) {
  897. DCHECK(is_folder_);
  898. if (!enabled) {
  899. if (icon_->layer())
  900. icon_->layer()->SetBackgroundBlur(0);
  901. return;
  902. }
  903. icon_->EnsureLayer();
  904. icon_->layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
  905. icon_->layer()->SetBackdropFilterQuality(
  906. ColorProvider::kBackgroundBlurQuality);
  907. }
  908. void AppListItemView::EnsureLayer() {
  909. if (layer())
  910. return;
  911. SetPaintToLayer();
  912. layer()->SetFillsBoundsOpaquely(false);
  913. }
  914. bool AppListItemView::HasNotificationBadge() {
  915. return item_weak_->has_notification_badge();
  916. }
  917. void AppListItemView::FireMouseDragTimerForTest() {
  918. mouse_drag_timer_.FireNow();
  919. }
  920. bool AppListItemView::FireTouchDragTimerForTest() {
  921. if (!touch_drag_timer_.IsRunning())
  922. return false;
  923. touch_drag_timer_.FireNow();
  924. return true;
  925. }
  926. bool AppListItemView::IsShowingAppMenu() const {
  927. return item_menu_model_adapter_ && item_menu_model_adapter_->IsShowingMenu();
  928. }
  929. bool AppListItemView::IsNotificationIndicatorShownForTest() const {
  930. return notification_indicator_->GetVisible();
  931. }
  932. void AppListItemView::SetContextMenuShownCallbackForTest(
  933. base::RepeatingClosure closure) {
  934. context_menu_shown_callback_ = std::move(closure);
  935. }
  936. gfx::Rect AppListItemView::GetDefaultTitleBoundsForTest() {
  937. return GetTitleBoundsForTargetViewBounds(
  938. app_list_config_, GetContentsBounds(), title_->GetPreferredSize(),
  939. icon_scale_);
  940. }
  941. void AppListItemView::SetMostRecentGridIndex(GridIndex new_grid_index,
  942. int columns) {
  943. if (new_grid_index == most_recent_grid_index_) {
  944. has_pending_row_change_ = false;
  945. return;
  946. }
  947. if (most_recent_grid_index_.IsValid()) {
  948. // Pending row changes are only flagged when the item index changes from one
  949. // edge of the grid to the other.
  950. if ((IsIndexOnLeftEdge(new_grid_index, columns) &&
  951. IsIndexOnRightEdge(most_recent_grid_index_, columns)) ||
  952. (IsIndexOnLeftEdge(most_recent_grid_index_, columns) &&
  953. IsIndexOnRightEdge(new_grid_index, columns))) {
  954. has_pending_row_change_ = true;
  955. } else {
  956. has_pending_row_change_ = false;
  957. }
  958. }
  959. most_recent_grid_index_ = new_grid_index;
  960. }
  961. void AppListItemView::AnimationProgressed(const gfx::Animation* animation) {
  962. DCHECK(!is_folder_);
  963. preview_circle_radius_ = gfx::Tween::IntValueBetween(
  964. animation->GetCurrentValue(), 0,
  965. app_list_config_->folder_dropping_circle_radius() * icon_scale_);
  966. SchedulePaint();
  967. }
  968. void AppListItemView::OnMenuClosed() {
  969. // Release menu since its menu model delegate (AppContextMenu) could be
  970. // released as a result of menu command execution.
  971. item_menu_model_adapter_.reset();
  972. if (!menu_close_initiated_from_drag_) {
  973. // If the menu was not closed due to a drag sequence(e.g. multi touch) reset
  974. // the drag state.
  975. SetState(STATE_NORMAL);
  976. SetTouchDragging(false);
  977. }
  978. menu_close_initiated_from_drag_ = false;
  979. // Keep the item focused if the menu was shown via keyboard.
  980. if (!menu_show_initiated_from_key_)
  981. OnBlur();
  982. if (focus_removed_by_context_menu_) {
  983. // Restore the last focused view when exiting the menu.
  984. GetFocusManager()->RestoreFocusedView();
  985. focus_removed_by_context_menu_ = false;
  986. }
  987. }
  988. void AppListItemView::OnSyncDragEnd() {
  989. SetUIState(UI_STATE_NORMAL);
  990. }
  991. gfx::Rect AppListItemView::GetIconBounds() const {
  992. if (is_folder_) {
  993. // The folder icon is in unclipped size, so clip it before return.
  994. gfx::Rect folder_icon_bounds = icon_->bounds();
  995. folder_icon_bounds.ClampToCenteredSize(
  996. app_list_config_->folder_icon_size());
  997. return folder_icon_bounds;
  998. }
  999. return icon_->bounds();
  1000. }
  1001. gfx::Rect AppListItemView::GetIconBoundsInScreen() const {
  1002. gfx::Rect icon_bounds = GetIconBounds();
  1003. ConvertRectToScreen(this, &icon_bounds);
  1004. return icon_bounds;
  1005. }
  1006. gfx::ImageSkia AppListItemView::GetIconImage() const {
  1007. if (!is_folder_)
  1008. return icon_->GetImage();
  1009. return gfx::CanvasImageSource::MakeImageSkia<ClippedFolderIconImageSource>(
  1010. app_list_config_->folder_icon_size(), icon_->GetImage());
  1011. }
  1012. void AppListItemView::SetIconVisible(bool visible) {
  1013. icon_->SetVisible(visible);
  1014. }
  1015. void AppListItemView::EnterCardifyState() {
  1016. in_cardified_grid_ = true;
  1017. gfx::FontList font_size = app_list_config_->app_title_font();
  1018. const float cardified_scale = GetAppsGridCardifiedScale();
  1019. const int size_delta = font_size.GetFontSize() * (1 - cardified_scale);
  1020. title_->SetFontList(font_size.DeriveWithSizeDelta(-size_delta));
  1021. ScaleIconImmediatly(cardified_scale);
  1022. }
  1023. void AppListItemView::ExitCardifyState() {
  1024. title_->SetFontList(app_list_config_->app_title_font());
  1025. ScaleIconImmediatly(1.0f);
  1026. in_cardified_grid_ = false;
  1027. }
  1028. // static
  1029. gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
  1030. const AppListConfig* config,
  1031. const gfx::Rect& target_bounds,
  1032. const gfx::Size& icon_size,
  1033. const float icon_scale) {
  1034. gfx::Rect rect(target_bounds);
  1035. rect.Inset(gfx::Insets::TLBR(
  1036. 0, 0, config->grid_icon_bottom_padding() * icon_scale, 0));
  1037. rect.ClampToCenteredSize(icon_size);
  1038. return rect;
  1039. }
  1040. // static
  1041. gfx::Rect AppListItemView::GetTitleBoundsForTargetViewBounds(
  1042. const AppListConfig* config,
  1043. const gfx::Rect& target_bounds,
  1044. const gfx::Size& title_size,
  1045. float icon_scale) {
  1046. gfx::Rect rect(target_bounds);
  1047. rect.Inset(
  1048. gfx::Insets::TLBR(config->grid_title_top_padding() * icon_scale,
  1049. config->grid_title_horizontal_padding() * icon_scale,
  1050. config->grid_title_bottom_padding() * icon_scale,
  1051. config->grid_title_horizontal_padding() * icon_scale));
  1052. rect.ClampToCenteredSize(title_size);
  1053. // Respect the title preferred height, to ensure the text does not get clipped
  1054. // due to padding if the item view gets too small.
  1055. if (rect.height() < title_size.height()) {
  1056. rect.set_y(rect.y() - (title_size.height() - rect.height()) / 2);
  1057. rect.set_height(title_size.height());
  1058. }
  1059. return rect;
  1060. }
  1061. void AppListItemView::ItemIconChanged(AppListConfigType config_type) {
  1062. if (config_type != app_list_config_->type())
  1063. return;
  1064. DCHECK(item_weak_);
  1065. SetIcon(item_weak_->GetIcon(app_list_config_->type()));
  1066. }
  1067. void AppListItemView::ItemNameChanged() {
  1068. SetItemName(base::UTF8ToUTF16(item_weak_->GetDisplayName()),
  1069. base::UTF8ToUTF16(item_weak_->name()));
  1070. }
  1071. void AppListItemView::ItemBadgeVisibilityChanged() {
  1072. if (icon_)
  1073. notification_indicator_->SetVisible(item_weak_->has_notification_badge());
  1074. }
  1075. void AppListItemView::ItemBadgeColorChanged() {
  1076. notification_indicator_->SetColor(item_weak_->GetNotificationBadgeColor());
  1077. }
  1078. void AppListItemView::ItemIsNewInstallChanged() {
  1079. DCHECK(item_weak_);
  1080. if (new_install_dot_) {
  1081. new_install_dot_->SetVisible(item_weak_->is_new_install());
  1082. Layout();
  1083. }
  1084. }
  1085. void AppListItemView::ItemBeingDestroyed() {
  1086. DCHECK(item_weak_);
  1087. item_weak_->RemoveObserver(this);
  1088. item_weak_ = nullptr;
  1089. // `EndDrag()` may delete this.
  1090. if (drag_state_ != DragState::kNone)
  1091. grid_delegate_->EndDrag(/*cancel=*/true);
  1092. }
  1093. int AppListItemView::GetPreviewCircleRadius() const {
  1094. return is_folder_ ? 0 : preview_circle_radius_;
  1095. }
  1096. void AppListItemView::CreateDraggedViewHoverAnimation() {
  1097. DCHECK(!is_folder_);
  1098. if (dragged_view_hover_animation_)
  1099. return;
  1100. dragged_view_hover_animation_ = std::make_unique<gfx::SlideAnimation>(this);
  1101. dragged_view_hover_animation_->SetTweenType(gfx::Tween::EASE_IN);
  1102. dragged_view_hover_animation_->SetSlideDuration(base::Milliseconds(250));
  1103. }
  1104. void AppListItemView::AdaptBoundsForSelectionHighlight(gfx::Rect* bounds) {
  1105. // ProductivityLauncher draws the focus highlight around the whole tile.
  1106. if (!features::IsProductivityLauncherEnabled()) {
  1107. bounds->Inset(gfx::Insets::TLBR(
  1108. 0, 0, app_list_config_->grid_icon_bottom_padding(), 0));
  1109. bounds->ClampToCenteredSize(app_list_config_->grid_focus_size());
  1110. }
  1111. // Update the bounds to account for the focus ring width - by default, the
  1112. // focus ring is painted so the highlight bounds are centered within the
  1113. // focus ring stroke - this should be overridden so the outer stroke bounds
  1114. // match the grid focus size set in the app list config.
  1115. bounds->Inset(gfx::Insets(kFocusRingWidth / 2));
  1116. }
  1117. BEGIN_METADATA(AppListItemView, views::Button)
  1118. END_METADATA
  1119. } // namespace ash