shelf_app_button.cc 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. // Copyright 2013 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/shelf/shelf_app_button.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include "ash/public/cpp/shelf_config.h"
  8. #include "ash/public/cpp/shelf_model.h"
  9. #include "ash/shelf/scrollable_shelf_view.h"
  10. #include "ash/shelf/shelf.h"
  11. #include "ash/shelf/shelf_button_delegate.h"
  12. #include "ash/shelf/shelf_view.h"
  13. #include "ash/strings/grit/ash_strings.h"
  14. #include "ash/style/default_color_constants.h"
  15. #include "ash/style/default_colors.h"
  16. #include "ash/style/dot_indicator.h"
  17. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  18. #include "base/bind.h"
  19. #include "base/i18n/rtl.h"
  20. #include "base/metrics/histogram_macros.h"
  21. #include "base/time/time.h"
  22. #include "skia/ext/image_operations.h"
  23. #include "ui/accessibility/ax_action_data.h"
  24. #include "ui/accessibility/ax_node_data.h"
  25. #include "ui/base/metadata/metadata_header_macros.h"
  26. #include "ui/base/metadata/metadata_impl_macros.h"
  27. #include "ui/base/resource/resource_bundle.h"
  28. #include "ui/color/color_id.h"
  29. #include "ui/compositor/layer.h"
  30. #include "ui/compositor/scoped_layer_animation_settings.h"
  31. #include "ui/gfx/animation/animation_delegate.h"
  32. #include "ui/gfx/animation/throb_animation.h"
  33. #include "ui/gfx/canvas.h"
  34. #include "ui/gfx/color_analysis.h"
  35. #include "ui/gfx/geometry/transform_util.h"
  36. #include "ui/gfx/geometry/vector2d.h"
  37. #include "ui/gfx/image/image_skia_operations.h"
  38. #include "ui/gfx/scoped_canvas.h"
  39. #include "ui/gfx/skbitmap_operations.h"
  40. #include "ui/views/accessibility/accessibility_paint_checks.h"
  41. #include "ui/views/animation/ink_drop.h"
  42. #include "ui/views/animation/ink_drop_impl.h"
  43. #include "ui/views/animation/square_ink_drop_ripple.h"
  44. #include "ui/views/controls/focus_ring.h"
  45. #include "ui/views/controls/highlight_path_generator.h"
  46. #include "ui/views/controls/image_view.h"
  47. #include "ui/views/painter.h"
  48. namespace {
  49. constexpr int kStatusIndicatorRadiusDip = 2;
  50. constexpr int kStatusIndicatorMaxSize = 10;
  51. constexpr int kStatusIndicatorActiveSize = 8;
  52. constexpr int kStatusIndicatorRunningSize = 4;
  53. constexpr int kStatusIndicatorThickness = 2;
  54. // The size of the notification indicator circle over the size of the icon.
  55. constexpr float kNotificationIndicatorWidthRatio = 14.0f / 64.0f;
  56. // The size of the notification indicator circle padding over the size of the
  57. // icon.
  58. constexpr float kNotificationIndicatorPaddingRatio = 5.0f / 64.0f;
  59. constexpr SkColor kDefaultIndicatorColor = SK_ColorWHITE;
  60. constexpr SkAlpha kInactiveIndicatorOpacity = 0x80;
  61. // The time threshold before an item can be dragged.
  62. constexpr int kDragTimeThresholdMs = 300;
  63. // The time threshold before the ink drop should activate on a long press.
  64. constexpr int kInkDropRippleActivationTimeMs = 650;
  65. // The drag and drop app icon should get scaled by this factor.
  66. constexpr float kAppIconScale = 1.2f;
  67. // The drag and drop app icon scaling up or down animation transition duration.
  68. constexpr int kDragDropAppIconScaleTransitionMs = 200;
  69. // Simple AnimationDelegate that owns a single ThrobAnimation instance to
  70. // keep all Draw Attention animations in sync.
  71. class ShelfAppButtonAnimation : public gfx::AnimationDelegate {
  72. public:
  73. class Observer {
  74. public:
  75. virtual void AnimationProgressed() = 0;
  76. protected:
  77. virtual ~Observer() = default;
  78. };
  79. ShelfAppButtonAnimation(const ShelfAppButtonAnimation&) = delete;
  80. ShelfAppButtonAnimation& operator=(const ShelfAppButtonAnimation&) = delete;
  81. static ShelfAppButtonAnimation* GetInstance() {
  82. static ShelfAppButtonAnimation* s_instance = new ShelfAppButtonAnimation();
  83. return s_instance;
  84. }
  85. void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
  86. void RemoveObserver(Observer* observer) {
  87. observers_.RemoveObserver(observer);
  88. if (observers_.empty())
  89. animation_.Stop();
  90. }
  91. bool HasObserver(Observer* observer) const {
  92. return observers_.HasObserver(observer);
  93. }
  94. SkAlpha GetAlpha() {
  95. return GetThrobAnimation().CurrentValueBetween(SK_AlphaTRANSPARENT,
  96. SK_AlphaOPAQUE);
  97. }
  98. double GetAnimation() { return GetThrobAnimation().GetCurrentValue(); }
  99. private:
  100. ShelfAppButtonAnimation() : animation_(this) {
  101. animation_.SetThrobDuration(base::Milliseconds(800));
  102. animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT);
  103. }
  104. ~ShelfAppButtonAnimation() override = default;
  105. gfx::ThrobAnimation& GetThrobAnimation() {
  106. if (!animation_.is_animating()) {
  107. animation_.Reset();
  108. animation_.StartThrobbing(-1 /*throb indefinitely*/);
  109. }
  110. return animation_;
  111. }
  112. // gfx::AnimationDelegate
  113. void AnimationProgressed(const gfx::Animation* animation) override {
  114. if (animation != &animation_)
  115. return;
  116. if (!animation_.is_animating())
  117. return;
  118. for (auto& observer : observers_)
  119. observer.AnimationProgressed();
  120. }
  121. gfx::ThrobAnimation animation_;
  122. base::ObserverList<Observer>::Unchecked observers_;
  123. };
  124. } // namespace
  125. namespace ash {
  126. ////////////////////////////////////////////////////////////////////////////////
  127. // ShelfAppButton::AppStatusIndicatorView
  128. class ShelfAppButton::AppStatusIndicatorView
  129. : public gfx::AnimationDelegate,
  130. public views::View,
  131. public ShelfAppButtonAnimation::Observer {
  132. public:
  133. METADATA_HEADER(AppStatusIndicatorView);
  134. AppStatusIndicatorView() {
  135. // Make sure the events reach the parent view for handling.
  136. SetCanProcessEventsWithinSubtree(false);
  137. status_change_animation_ = std::make_unique<gfx::SlideAnimation>(this);
  138. status_change_animation_->SetSlideDuration(base::Milliseconds(250));
  139. status_change_animation_->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
  140. }
  141. AppStatusIndicatorView(const AppStatusIndicatorView&) = delete;
  142. AppStatusIndicatorView& operator=(const AppStatusIndicatorView&) = delete;
  143. ~AppStatusIndicatorView() override {
  144. ShelfAppButtonAnimation::GetInstance()->RemoveObserver(this);
  145. }
  146. // views::View:
  147. void OnThemeChanged() override {
  148. views::View::OnThemeChanged();
  149. SchedulePaint();
  150. }
  151. void OnPaint(gfx::Canvas* canvas) override {
  152. gfx::ScopedCanvas scoped(canvas);
  153. canvas->SaveLayerAlpha(GetAlpha());
  154. const float dsf = canvas->UndoDeviceScaleFactor();
  155. gfx::PointF center = gfx::RectF(GetLocalBounds()).CenterPoint();
  156. cc::PaintFlags flags;
  157. // Active and running indicators look a little different in the new UI.
  158. flags.setColor(AshColorProvider::Get()->GetContentLayerColor(
  159. AshColorProvider::ContentLayerType::kAppStateIndicatorColor));
  160. flags.setAntiAlias(true);
  161. flags.setStrokeCap(cc::PaintFlags::Cap::kRound_Cap);
  162. flags.setStrokeJoin(cc::PaintFlags::Join::kRound_Join);
  163. flags.setStrokeWidth(kStatusIndicatorThickness);
  164. flags.setStyle(cc::PaintFlags::kStroke_Style);
  165. float stroke_length = GetStrokeLength();
  166. gfx::PointF start;
  167. gfx::PointF end;
  168. if (horizontal_shelf_) {
  169. start = gfx::PointF(center.x() - stroke_length / 2, center.y());
  170. end = start;
  171. end.Offset(stroke_length, 0);
  172. } else {
  173. start = gfx::PointF(center.x(), center.y() - stroke_length / 2);
  174. end = start;
  175. end.Offset(0, stroke_length);
  176. }
  177. SkPath path;
  178. path.moveTo(start.x() * dsf, start.y() * dsf);
  179. path.lineTo(end.x() * dsf, end.y() * dsf);
  180. canvas->DrawPath(path, flags);
  181. }
  182. float GetStrokeLength() {
  183. if (status_change_animation_->is_animating()) {
  184. return status_change_animation_->CurrentValueBetween(
  185. kStatusIndicatorRunningSize, kStatusIndicatorActiveSize);
  186. }
  187. return active_ ? kStatusIndicatorActiveSize : kStatusIndicatorRunningSize;
  188. }
  189. SkAlpha GetAlpha() {
  190. if (show_attention_) {
  191. return ShelfAppButtonAnimation::GetInstance()->HasObserver(this)
  192. ? ShelfAppButtonAnimation::GetInstance()->GetAlpha()
  193. : SK_AlphaOPAQUE;
  194. }
  195. if (status_change_animation_->is_animating()) {
  196. return status_change_animation_->CurrentValueBetween(
  197. kInactiveIndicatorOpacity, SK_AlphaOPAQUE);
  198. }
  199. return active_ ? SK_AlphaOPAQUE : kInactiveIndicatorOpacity;
  200. }
  201. // ShelfAppButtonAnimation::Observer
  202. void AnimationProgressed() override {
  203. UpdateAnimating();
  204. SchedulePaint();
  205. }
  206. void ShowAttention(bool show) {
  207. if (show_attention_ == show)
  208. return;
  209. show_attention_ = show;
  210. if (status_change_animation_->is_animating())
  211. status_change_animation_->End();
  212. if (show_attention_) {
  213. animation_end_time_ = base::TimeTicks::Now() + base::Seconds(10);
  214. ShelfAppButtonAnimation::GetInstance()->AddObserver(this);
  215. } else {
  216. ShelfAppButtonAnimation::GetInstance()->RemoveObserver(this);
  217. }
  218. }
  219. // gfx::AnimationDelegate
  220. void AnimationProgressed(const gfx::Animation* animation) override {
  221. if (animation != status_change_animation_.get())
  222. return;
  223. SchedulePaint();
  224. }
  225. void ShowActiveStatus(bool active) {
  226. if (active_ == active)
  227. return;
  228. active_ = active;
  229. if (active_)
  230. status_change_animation_->Show();
  231. else
  232. status_change_animation_->Hide();
  233. }
  234. void SetHorizontalShelf(bool horizontal_shelf) {
  235. if (horizontal_shelf_ == horizontal_shelf)
  236. return;
  237. horizontal_shelf_ = horizontal_shelf;
  238. SchedulePaint();
  239. }
  240. private:
  241. void UpdateAnimating() {
  242. if (base::TimeTicks::Now() > animation_end_time_)
  243. ShelfAppButtonAnimation::GetInstance()->RemoveObserver(this);
  244. }
  245. bool show_attention_ = false;
  246. bool active_ = false;
  247. bool horizontal_shelf_ = true;
  248. std::unique_ptr<gfx::SlideAnimation> status_change_animation_;
  249. base::TimeTicks animation_end_time_; // For attention throbbing underline.
  250. };
  251. BEGIN_METADATA(ShelfAppButton, AppStatusIndicatorView, views::View)
  252. END_METADATA
  253. ////////////////////////////////////////////////////////////////////////////////
  254. // ShelfAppButton
  255. // static
  256. const char ShelfAppButton::kViewClassName[] = "ash/ShelfAppButton";
  257. // static
  258. bool ShelfAppButton::ShouldHandleEventFromContextMenu(
  259. const ui::GestureEvent* event) {
  260. switch (event->type()) {
  261. case ui::ET_GESTURE_END:
  262. case ui::ET_GESTURE_TAP_CANCEL:
  263. case ui::ET_GESTURE_SCROLL_BEGIN:
  264. case ui::ET_GESTURE_SCROLL_UPDATE:
  265. case ui::ET_GESTURE_SCROLL_END:
  266. case ui::ET_SCROLL_FLING_START:
  267. return true;
  268. default:
  269. return false;
  270. }
  271. }
  272. ShelfAppButton::ShelfAppButton(ShelfView* shelf_view,
  273. ShelfButtonDelegate* shelf_button_delegate)
  274. : ShelfButton(shelf_view->shelf(), shelf_button_delegate),
  275. icon_view_(new views::ImageView()),
  276. shelf_view_(shelf_view),
  277. indicator_(new AppStatusIndicatorView()) {
  278. const gfx::ShadowValue kShadows[] = {
  279. gfx::ShadowValue(gfx::Vector2d(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
  280. gfx::ShadowValue(gfx::Vector2d(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
  281. gfx::ShadowValue(gfx::Vector2d(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)),
  282. };
  283. icon_shadows_.assign(kShadows, kShadows + std::size(kShadows));
  284. // TODO(crbug.com/1218186): Remove this, this is in place temporarily to be
  285. // able to submit accessibility checks. This crashes if fetching a11y node
  286. // data during paint because message_view_ is null.
  287. SetProperty(views::kSkipAccessibilityPaintChecks, true);
  288. views::InkDrop::Get(this)->SetCreateRippleCallback(base::BindRepeating(
  289. [](ShelfAppButton* host) -> std::unique_ptr<views::InkDropRipple> {
  290. const gfx::Rect small_ripple_area = host->CalculateSmallRippleArea();
  291. const int ripple_size = host->shelf_view_->GetShelfItemRippleSize();
  292. return std::make_unique<views::SquareInkDropRipple>(
  293. gfx::Size(ripple_size, ripple_size),
  294. views::InkDrop::Get(host)->GetLargeCornerRadius(),
  295. small_ripple_area.size(),
  296. views::InkDrop::Get(host)->GetSmallCornerRadius(),
  297. small_ripple_area.CenterPoint(),
  298. views::InkDrop::Get(host)->GetBaseColor(),
  299. views::InkDrop::Get(host)->GetVisibleOpacity());
  300. },
  301. this));
  302. // TODO: refactor the layers so each button doesn't require 3.
  303. // |icon_view_| needs its own layer so it can be scaled up independently of
  304. // the ink drop ripple.
  305. icon_view_->SetPaintToLayer();
  306. icon_view_->layer()->SetFillsBoundsOpaquely(false);
  307. icon_view_->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
  308. icon_view_->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
  309. // Do not make this interactive, so that events are sent to ShelfView.
  310. icon_view_->SetCanProcessEventsWithinSubtree(false);
  311. indicator_->SetPaintToLayer();
  312. indicator_->layer()->SetFillsBoundsOpaquely(false);
  313. AddChildView(indicator_);
  314. AddChildView(icon_view_);
  315. notification_indicator_ =
  316. AddChildView(std::make_unique<DotIndicator>(kDefaultIndicatorColor));
  317. views::InkDrop::Get(this)->GetInkDrop()->AddObserver(this);
  318. // Do not set a clip, allow the ink drop to burst out.
  319. views::InstallEmptyHighlightPathGenerator(this);
  320. SetFocusBehavior(FocusBehavior::ALWAYS);
  321. SetInstallFocusRingOnFocus(true);
  322. views::FocusRing::Get(this)->SetColorId(ui::kColorAshFocusRing);
  323. // The focus ring should have an inset of half the focus border thickness, so
  324. // the parent view won't clip it.
  325. views::FocusRing::Get(this)->SetPathGenerator(
  326. std::make_unique<views::RoundRectHighlightPathGenerator>(
  327. gfx::Insets::VH(views::FocusRing::kDefaultHaloThickness / 2, 0), 0));
  328. }
  329. ShelfAppButton::~ShelfAppButton() {
  330. views::InkDrop::Get(this)->GetInkDrop()->RemoveObserver(this);
  331. }
  332. void ShelfAppButton::SetShadowedImage(const gfx::ImageSkia& image) {
  333. icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
  334. image, icon_shadows_));
  335. }
  336. void ShelfAppButton::SetImage(const gfx::ImageSkia& image) {
  337. if (image.isNull()) {
  338. // TODO: need an empty image.
  339. icon_view_->SetImage(image);
  340. icon_image_ = gfx::ImageSkia();
  341. return;
  342. }
  343. icon_image_ = image;
  344. gfx::Size preferred_size = GetPreferredIconSize();
  345. if (image.size() == preferred_size) {
  346. SetShadowedImage(image);
  347. return;
  348. }
  349. SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(
  350. image, skia::ImageOperations::RESIZE_BEST, preferred_size));
  351. }
  352. gfx::ImageSkia ShelfAppButton::GetImage() const {
  353. return icon_view_->GetImage();
  354. }
  355. gfx::ImageSkia ShelfAppButton::GetIconImage() const {
  356. const gfx::Size preferred_size = GetPreferredSize();
  357. if (icon_image_.size() == preferred_size)
  358. return icon_image_;
  359. return gfx::ImageSkiaOperations::CreateResizedImage(
  360. icon_image_, skia::ImageOperations::RESIZE_BEST, GetPreferredIconSize());
  361. }
  362. void ShelfAppButton::AddState(State state) {
  363. if (!(state_ & state)) {
  364. state_ |= state;
  365. InvalidateLayout();
  366. if (state & STATE_ATTENTION)
  367. indicator_->ShowAttention(true);
  368. if (state & STATE_ACTIVE)
  369. indicator_->ShowActiveStatus(true);
  370. if (state & STATE_NOTIFICATION)
  371. notification_indicator_->SetVisible(true);
  372. if (state & STATE_DRAGGING)
  373. ScaleAppIcon(true);
  374. }
  375. }
  376. void ShelfAppButton::ClearState(State state) {
  377. if (state_ & state) {
  378. state_ &= ~state;
  379. Layout();
  380. if (state & STATE_ATTENTION)
  381. indicator_->ShowAttention(false);
  382. if (state & STATE_ACTIVE)
  383. indicator_->ShowActiveStatus(false);
  384. if (state & STATE_NOTIFICATION)
  385. notification_indicator_->SetVisible(false);
  386. if (state & STATE_DRAGGING)
  387. ScaleAppIcon(false);
  388. }
  389. }
  390. void ShelfAppButton::ClearDragStateOnGestureEnd() {
  391. drag_timer_.Stop();
  392. ClearState(STATE_HOVERED);
  393. ClearState(STATE_DRAGGING);
  394. }
  395. gfx::Rect ShelfAppButton::GetIconBounds() const {
  396. return icon_view_->bounds();
  397. }
  398. gfx::Rect ShelfAppButton::GetIdealIconBounds(const gfx::Size& button_size,
  399. float icon_scale) const {
  400. return GetIconViewBounds(gfx::Rect(button_size), icon_scale);
  401. }
  402. views::InkDrop* ShelfAppButton::GetInkDropForTesting() {
  403. return views::InkDrop::Get(this)->GetInkDrop();
  404. }
  405. void ShelfAppButton::OnDragStarted(const ui::LocatedEvent* event) {
  406. views::InkDrop::Get(this)->AnimateToState(views::InkDropState::HIDDEN, event);
  407. }
  408. void ShelfAppButton::OnMenuClosed() {
  409. DCHECK_EQ(views::InkDropState::ACTIVATED,
  410. views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState());
  411. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  412. views::InkDropState::DEACTIVATED);
  413. context_menu_target_visibility_ = false;
  414. }
  415. void ShelfAppButton::ShowContextMenu(const gfx::Point& p,
  416. ui::MenuSourceType source_type) {
  417. // Return early if:
  418. // 1. the context menu controller is not set; or
  419. // 2. `context_menu_target_visibility_` is already true.
  420. if (!context_menu_controller() || context_menu_target_visibility_)
  421. return;
  422. context_menu_target_visibility_ = true;
  423. auto weak_this = weak_factory_.GetWeakPtr();
  424. ShelfButton::ShowContextMenu(p, source_type);
  425. // This object may have been destroyed by ShowContextMenu.
  426. if (weak_this) {
  427. // The menu will not propagate mouse events while it's shown. To address,
  428. // the hover state gets cleared once the menu was shown (and this was not
  429. // destroyed). In case context menu is shown target view does not receive
  430. // OnMouseReleased events and we need to cancel capture manually.
  431. if (shelf_view_->IsDraggedView(this))
  432. OnMouseCaptureLost();
  433. else
  434. ClearState(STATE_HOVERED);
  435. }
  436. }
  437. void ShelfAppButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  438. ShelfButton::GetAccessibleNodeData(node_data);
  439. const std::u16string title = shelf_view_->GetTitleForView(this);
  440. node_data->SetName(title.empty() ? GetAccessibleName() : title);
  441. switch (app_status_) {
  442. case AppStatus::kBlocked:
  443. node_data->SetDescription(
  444. ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
  445. IDS_SHELF_ITEM_BLOCKED_APP));
  446. break;
  447. case AppStatus::kPaused:
  448. node_data->SetDescription(
  449. ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
  450. IDS_SHELF_ITEM_PAUSED_APP));
  451. break;
  452. default:
  453. break;
  454. }
  455. }
  456. bool ShelfAppButton::ShouldEnterPushedState(const ui::Event& event) {
  457. if (!shelf_view_->ShouldEventActivateButton(this, event))
  458. return false;
  459. return Button::ShouldEnterPushedState(event);
  460. }
  461. void ShelfAppButton::ReflectItemStatus(const ShelfItem& item) {
  462. if (item.has_notification)
  463. AddState(ShelfAppButton::STATE_NOTIFICATION);
  464. else
  465. ClearState(ShelfAppButton::STATE_NOTIFICATION);
  466. app_status_ = item.app_status;
  467. const ShelfID active_id = shelf_view_->model()->active_shelf_id();
  468. if (!active_id.IsNull() && item.id == active_id) {
  469. // The active status trumps all other statuses.
  470. AddState(ShelfAppButton::STATE_ACTIVE);
  471. ClearState(ShelfAppButton::STATE_RUNNING);
  472. ClearState(ShelfAppButton::STATE_ATTENTION);
  473. // Notify the parent scrollable shelf view to show the current active app.
  474. shelf_button_delegate()->OnAppButtonActivated(this);
  475. return;
  476. }
  477. ClearState(ShelfAppButton::STATE_ACTIVE);
  478. switch (item.status) {
  479. case STATUS_CLOSED:
  480. ClearState(ShelfAppButton::STATE_RUNNING);
  481. ClearState(ShelfAppButton::STATE_ATTENTION);
  482. break;
  483. case STATUS_RUNNING:
  484. AddState(ShelfAppButton::STATE_RUNNING);
  485. ClearState(ShelfAppButton::STATE_ATTENTION);
  486. break;
  487. case STATUS_ATTENTION:
  488. ClearState(ShelfAppButton::STATE_RUNNING);
  489. AddState(ShelfAppButton::STATE_ATTENTION);
  490. break;
  491. }
  492. }
  493. bool ShelfAppButton::IsIconSizeCurrent() {
  494. gfx::Insets insets_shadows = gfx::ShadowValue::GetMargin(icon_shadows_);
  495. int icon_width =
  496. GetIconBounds().width() + insets_shadows.left() + insets_shadows.right();
  497. return icon_width == shelf_view_->GetButtonIconSize();
  498. }
  499. void ShelfAppButton::OnContextMenuModelRequestCanceled() {
  500. // The request for the context menu model gets canceled so reset the context
  501. // menu target visibility.
  502. context_menu_target_visibility_ = false;
  503. }
  504. bool ShelfAppButton::FireDragTimerForTest() {
  505. if (!drag_timer_.IsRunning())
  506. return false;
  507. drag_timer_.FireNow();
  508. return true;
  509. }
  510. void ShelfAppButton::FireRippleActivationTimerForTest() {
  511. ripple_activation_timer_.FireNow();
  512. }
  513. gfx::Rect ShelfAppButton::CalculateSmallRippleArea() const {
  514. int ink_drop_small_size = shelf_view_->GetButtonSize();
  515. gfx::Point center_point = GetLocalBounds().CenterPoint();
  516. const int padding = ShelfConfig::Get()->GetAppIconEndPadding();
  517. // Add padding to the ink drop for the left-most and right-most app buttons in
  518. // the shelf when there is a non-zero padding between the app icon and the
  519. // end of scrollable shelf.
  520. if (TabletModeController::Get()->InTabletMode() && padding > 0) {
  521. const size_t current_index =
  522. shelf_view_->view_model()->GetIndexOfView(this).value();
  523. int left_padding =
  524. (shelf_view_->visible_views_indices().front() == current_index)
  525. ? padding
  526. : 0;
  527. int right_padding =
  528. (shelf_view_->visible_views_indices().back() == current_index) ? padding
  529. : 0;
  530. if (base::i18n::IsRTL())
  531. std::swap(left_padding, right_padding);
  532. ink_drop_small_size += left_padding + right_padding;
  533. const int x_offset = (-left_padding / 2) + (right_padding / 2);
  534. center_point.Offset(x_offset, 0);
  535. }
  536. gfx::Rect small_ripple_area(
  537. gfx::Size(ink_drop_small_size, ink_drop_small_size));
  538. small_ripple_area.Offset(center_point.x() - ink_drop_small_size / 2,
  539. center_point.y() - ink_drop_small_size / 2);
  540. return small_ripple_area;
  541. }
  542. const char* ShelfAppButton::GetClassName() const {
  543. return kViewClassName;
  544. }
  545. bool ShelfAppButton::OnMousePressed(const ui::MouseEvent& event) {
  546. // TODO: This call should probably live somewhere else (such as inside
  547. // |ShelfView.PointerPressedOnButton|.
  548. // No need to scale up the app for mouse right click since the app can't be
  549. // dragged through right button.
  550. if (!(event.flags() & ui::EF_LEFT_MOUSE_BUTTON)) {
  551. Button::OnMousePressed(event);
  552. return true;
  553. }
  554. ShelfButton::OnMousePressed(event);
  555. shelf_view_->PointerPressedOnButton(this, ShelfView::MOUSE, event);
  556. if (shelf_view_->IsDraggedView(this)) {
  557. drag_timer_.Start(FROM_HERE, base::Milliseconds(kDragTimeThresholdMs),
  558. base::BindOnce(&ShelfAppButton::OnTouchDragTimer,
  559. base::Unretained(this)));
  560. }
  561. return true;
  562. }
  563. void ShelfAppButton::OnMouseReleased(const ui::MouseEvent& event) {
  564. drag_timer_.Stop();
  565. ClearState(STATE_DRAGGING);
  566. ShelfButton::OnMouseReleased(event);
  567. // PointerReleasedOnButton deletes the ShelfAppButton when user drags a pinned
  568. // running app from shelf.
  569. shelf_view_->PointerReleasedOnButton(this, ShelfView::MOUSE, false);
  570. // WARNING: we may have been deleted.
  571. }
  572. void ShelfAppButton::OnMouseCaptureLost() {
  573. ClearState(STATE_HOVERED);
  574. shelf_view_->PointerReleasedOnButton(this, ShelfView::MOUSE, true);
  575. ShelfButton::OnMouseCaptureLost();
  576. }
  577. bool ShelfAppButton::OnMouseDragged(const ui::MouseEvent& event) {
  578. ShelfButton::OnMouseDragged(event);
  579. shelf_view_->PointerDraggedOnButton(this, ShelfView::MOUSE, event);
  580. return true;
  581. }
  582. gfx::Rect ShelfAppButton::GetIconViewBounds(const gfx::Rect& button_bounds,
  583. float icon_scale) const {
  584. const float icon_size = shelf_view_->GetButtonIconSize() * icon_scale;
  585. const float icon_padding = (shelf_view_->GetButtonSize() - icon_size) / 2;
  586. const Shelf* shelf = shelf_view_->shelf();
  587. const bool is_horizontal_shelf = shelf->IsHorizontalAlignment();
  588. float x_offset = is_horizontal_shelf ? 0 : icon_padding;
  589. float y_offset = is_horizontal_shelf ? icon_padding : 0;
  590. const float icon_width =
  591. std::min(icon_size, button_bounds.width() - x_offset);
  592. const float icon_height =
  593. std::min(icon_size, button_bounds.height() - y_offset);
  594. // If on the left or top 'invert' the inset so the constant gap is on
  595. // the interior (towards the center of display) edge of the shelf.
  596. if (ShelfAlignment::kLeft == shelf->alignment())
  597. x_offset = button_bounds.width() - (icon_size + icon_padding);
  598. // Expand bounds to include shadows.
  599. gfx::Insets insets_shadows = gfx::ShadowValue::GetMargin(icon_shadows_);
  600. // Center icon with respect to the secondary axis.
  601. if (is_horizontal_shelf)
  602. x_offset = std::max(0.0f, button_bounds.width() - icon_width + 1) / 2;
  603. else
  604. y_offset = std::max(0.0f, button_bounds.height() - icon_height) / 2;
  605. gfx::RectF icon_view_bounds =
  606. gfx::RectF(button_bounds.x() + x_offset, button_bounds.y() + y_offset,
  607. icon_width, icon_height);
  608. icon_view_bounds.Inset(gfx::InsetsF(insets_shadows));
  609. // Icon size has been incorrect when running
  610. // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see
  611. // http://crbug.com/234854.
  612. DCHECK_LE(icon_width, icon_size);
  613. DCHECK_LE(icon_height, icon_size);
  614. return gfx::ToRoundedRect(icon_view_bounds);
  615. }
  616. gfx::Rect ShelfAppButton::GetNotificationIndicatorBounds(float icon_scale) {
  617. gfx::Rect scaled_icon_view_bounds =
  618. GetIconViewBounds(GetContentsBounds(), icon_scale);
  619. float diameter =
  620. kNotificationIndicatorWidthRatio * scaled_icon_view_bounds.width();
  621. float padding =
  622. kNotificationIndicatorPaddingRatio * scaled_icon_view_bounds.width();
  623. return gfx::ToRoundedRect(
  624. gfx::RectF(scaled_icon_view_bounds.right() - diameter - padding,
  625. scaled_icon_view_bounds.y() + padding, diameter, diameter));
  626. }
  627. void ShelfAppButton::Layout() {
  628. Shelf* shelf = shelf_view_->shelf();
  629. gfx::Rect icon_view_bounds =
  630. GetIconViewBounds(GetContentsBounds(), icon_scale_);
  631. const gfx::Rect button_bounds(GetContentsBounds());
  632. const int status_indicator_offet_from_shelf_edge =
  633. ShelfConfig::Get()->status_indicator_offset_from_shelf_edge();
  634. icon_view_->SetBoundsRect(icon_view_bounds);
  635. notification_indicator_->SetIndicatorBounds(
  636. GetNotificationIndicatorBounds(icon_scale_));
  637. // The indicators should be aligned with the icon, not the icon + shadow.
  638. // Use 1.0 as icon scale for |indicator_midpoint|, otherwise integer rounding
  639. // can incorrectly move the midpoint.
  640. gfx::Point indicator_midpoint =
  641. GetIconViewBounds(GetContentsBounds(), 1.0).CenterPoint();
  642. switch (shelf->alignment()) {
  643. case ShelfAlignment::kBottom:
  644. case ShelfAlignment::kBottomLocked:
  645. indicator_midpoint.set_y(button_bounds.bottom() -
  646. kStatusIndicatorRadiusDip -
  647. status_indicator_offet_from_shelf_edge);
  648. break;
  649. case ShelfAlignment::kLeft:
  650. indicator_midpoint.set_x(button_bounds.x() + kStatusIndicatorRadiusDip +
  651. status_indicator_offet_from_shelf_edge);
  652. break;
  653. case ShelfAlignment::kRight:
  654. indicator_midpoint.set_x(button_bounds.right() -
  655. kStatusIndicatorRadiusDip -
  656. status_indicator_offet_from_shelf_edge);
  657. break;
  658. }
  659. gfx::Rect indicator_bounds(indicator_midpoint, gfx::Size());
  660. indicator_bounds.Inset(gfx::Insets(-kStatusIndicatorMaxSize));
  661. indicator_->SetBoundsRect(indicator_bounds);
  662. UpdateState();
  663. views::FocusRing::Get(this)->Layout();
  664. }
  665. void ShelfAppButton::ChildPreferredSizeChanged(views::View* child) {
  666. Layout();
  667. }
  668. void ShelfAppButton::OnGestureEvent(ui::GestureEvent* event) {
  669. switch (event->type()) {
  670. case ui::ET_GESTURE_TAP_DOWN:
  671. if (shelf_view_->shelf()->IsVisible()) {
  672. AddState(STATE_HOVERED);
  673. drag_timer_.Start(FROM_HERE, base::Milliseconds(kDragTimeThresholdMs),
  674. base::BindOnce(&ShelfAppButton::OnTouchDragTimer,
  675. base::Unretained(this)));
  676. ripple_activation_timer_.Start(
  677. FROM_HERE, base::Milliseconds(kInkDropRippleActivationTimeMs),
  678. base::BindOnce(&ShelfAppButton::OnRippleTimer,
  679. base::Unretained(this)));
  680. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  681. views::InkDropState::ACTION_PENDING);
  682. event->SetHandled();
  683. }
  684. break;
  685. case ui::ET_GESTURE_TAP:
  686. [[fallthrough]]; // Ensure tapped items are not enlarged for drag.
  687. case ui::ET_GESTURE_END:
  688. // If the button is being dragged, or there is an active context menu,
  689. // for this ShelfAppButton, don't deactivate the ink drop.
  690. if (!(state_ & STATE_DRAGGING) &&
  691. !shelf_view_->IsShowingMenuForView(this) &&
  692. (views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState() ==
  693. views::InkDropState::ACTIVATED)) {
  694. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  695. views::InkDropState::DEACTIVATED);
  696. } else if (event->type() == ui::ET_GESTURE_END) {
  697. // When the gesture ends, we may need to deactivate the button's
  698. // inkdrop. For example, when a mouse event interputs the gesture press
  699. // on a shelf app button, the button's inkdrop could be in the pending
  700. // state while the button's context menu is hidden. In this case, we
  701. // have to hide the inkdrop explicitly.
  702. MaybeHideInkDropWhenGestureEnds();
  703. }
  704. ClearDragStateOnGestureEnd();
  705. break;
  706. case ui::ET_GESTURE_SCROLL_BEGIN:
  707. if (state_ & STATE_DRAGGING) {
  708. shelf_view_->PointerPressedOnButton(this, ShelfView::TOUCH, *event);
  709. event->SetHandled();
  710. } else {
  711. // The drag went to the bezel and is about to be passed to
  712. // ShelfLayoutManager.
  713. drag_timer_.Stop();
  714. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  715. views::InkDropState::HIDDEN);
  716. }
  717. break;
  718. case ui::ET_GESTURE_SCROLL_UPDATE:
  719. if ((state_ & STATE_DRAGGING) && shelf_view_->IsDraggedView(this)) {
  720. shelf_view_->PointerDraggedOnButton(this, ShelfView::TOUCH, *event);
  721. event->SetHandled();
  722. }
  723. break;
  724. case ui::ET_GESTURE_SCROLL_END:
  725. case ui::ET_SCROLL_FLING_START:
  726. if (state_ & STATE_DRAGGING) {
  727. ClearState(STATE_DRAGGING);
  728. shelf_view_->PointerReleasedOnButton(this, ShelfView::TOUCH, false);
  729. event->SetHandled();
  730. }
  731. break;
  732. case ui::ET_GESTURE_LONG_TAP:
  733. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  734. views::InkDropState::ACTIVATED);
  735. // The context menu may not show (for example, a mouse click which occurs
  736. // before the end of gesture could close the context menu). In this case,
  737. // let the overridden function handles the event to show the context menu
  738. // (see https://crbug.com/1126491).
  739. if (shelf_view_->IsShowingMenu()) {
  740. // Handle LONG_TAP to avoid opening the context menu twice.
  741. event->SetHandled();
  742. }
  743. break;
  744. case ui::ET_GESTURE_TWO_FINGER_TAP:
  745. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  746. views::InkDropState::ACTIVATED);
  747. break;
  748. default:
  749. break;
  750. }
  751. if (!event->handled())
  752. return Button::OnGestureEvent(event);
  753. }
  754. bool ShelfAppButton::HandleAccessibleAction(
  755. const ui::AXActionData& action_data) {
  756. if (notification_indicator_ && notification_indicator_->GetVisible())
  757. shelf_view_->AnnounceShelfItemNotificationBadge(this);
  758. if (action_data.action == ax::mojom::Action::kScrollToMakeVisible)
  759. shelf_button_delegate()->HandleAccessibleActionScrollToMakeVisible(this);
  760. return views::View::HandleAccessibleAction(action_data);
  761. }
  762. void ShelfAppButton::InkDropAnimationStarted() {
  763. SetInkDropAnimationStarted(/*started=*/true);
  764. }
  765. void ShelfAppButton::InkDropRippleAnimationEnded(views::InkDropState state) {
  766. // Notify the host view of the ink drop to be hidden at the end of ink drop
  767. // animation.
  768. if (state == views::InkDropState::HIDDEN)
  769. SetInkDropAnimationStarted(/*started=*/false);
  770. }
  771. void ShelfAppButton::UpdateState() {
  772. indicator_->SetVisible(!(state_ & STATE_HIDDEN) &&
  773. (state_ & STATE_ATTENTION || state_ & STATE_RUNNING ||
  774. state_ & STATE_ACTIVE));
  775. const bool is_horizontal_shelf =
  776. shelf_view_->shelf()->IsHorizontalAlignment();
  777. indicator_->SetHorizontalShelf(is_horizontal_shelf);
  778. icon_view_->SetHorizontalAlignment(
  779. is_horizontal_shelf ? views::ImageView::Alignment::kCenter
  780. : views::ImageView::Alignment::kLeading);
  781. icon_view_->SetVerticalAlignment(is_horizontal_shelf
  782. ? views::ImageView::Alignment::kLeading
  783. : views::ImageView::Alignment::kCenter);
  784. SchedulePaint();
  785. }
  786. void ShelfAppButton::OnTouchDragTimer() {
  787. AddState(STATE_DRAGGING);
  788. }
  789. void ShelfAppButton::OnRippleTimer() {
  790. if (views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState() !=
  791. views::InkDropState::ACTION_PENDING) {
  792. return;
  793. }
  794. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  795. views::InkDropState::ACTIVATED);
  796. }
  797. gfx::Transform ShelfAppButton::GetScaleTransform(float icon_scale) {
  798. gfx::RectF pre_scaling_bounds(
  799. GetMirroredRect(GetIconViewBounds(GetContentsBounds(), 1.0f)));
  800. gfx::RectF target_bounds(
  801. GetMirroredRect(GetIconViewBounds(GetContentsBounds(), icon_scale)));
  802. return gfx::TransformBetweenRects(target_bounds, pre_scaling_bounds);
  803. }
  804. gfx::Size ShelfAppButton::GetPreferredIconSize() const {
  805. const int icon_size = shelf_view_->GetButtonIconSize() * icon_scale_;
  806. // Resize the image maintaining our aspect ratio.
  807. float aspect_ratio = static_cast<float>(icon_image_.width()) /
  808. static_cast<float>(icon_image_.height());
  809. int height = icon_size;
  810. int width = static_cast<int>(aspect_ratio * height);
  811. if (width > icon_size) {
  812. width = icon_size;
  813. height = static_cast<int>(width / aspect_ratio);
  814. }
  815. return gfx::Size(width, height);
  816. }
  817. void ShelfAppButton::ScaleAppIcon(bool scale_up) {
  818. StopObservingImplicitAnimations();
  819. if (scale_up) {
  820. icon_scale_ = kAppIconScale;
  821. SetImage(icon_image_);
  822. icon_view_->layer()->SetTransform(GetScaleTransform(kAppIconScale));
  823. }
  824. ui::ScopedLayerAnimationSettings settings(icon_view_->layer()->GetAnimator());
  825. settings.SetTransitionDuration(
  826. base::Milliseconds(kDragDropAppIconScaleTransitionMs));
  827. if (scale_up) {
  828. icon_view_->layer()->SetTransform(gfx::Transform());
  829. } else {
  830. // To avoid poor quality icons, update icon image with the correct scale
  831. // after the transform animation is completed.
  832. settings.AddObserver(this);
  833. icon_view_->layer()->SetTransform(GetScaleTransform(kAppIconScale));
  834. }
  835. // Animate the notification indicator alongside the |icon_view_|.
  836. if (notification_indicator_) {
  837. gfx::RectF pre_scale(GetMirroredRect(GetNotificationIndicatorBounds(1.0)));
  838. gfx::RectF post_scale(
  839. GetMirroredRect(GetNotificationIndicatorBounds(kAppIconScale)));
  840. gfx::Transform scale_transform =
  841. gfx::TransformBetweenRects(post_scale, pre_scale);
  842. if (scale_up)
  843. notification_indicator_->layer()->SetTransform(scale_transform);
  844. ui::ScopedLayerAnimationSettings notification_settings(
  845. notification_indicator_->layer()->GetAnimator());
  846. notification_settings.SetTransitionDuration(
  847. base::Milliseconds(kDragDropAppIconScaleTransitionMs));
  848. notification_indicator_->layer()->SetTransform(scale_up ? gfx::Transform()
  849. : scale_transform);
  850. }
  851. }
  852. void ShelfAppButton::OnImplicitAnimationsCompleted() {
  853. icon_scale_ = 1.0f;
  854. SetImage(icon_image_);
  855. icon_view_->layer()->SetTransform(gfx::Transform());
  856. if (notification_indicator_)
  857. notification_indicator_->layer()->SetTransform(gfx::Transform());
  858. }
  859. void ShelfAppButton::SetInkDropAnimationStarted(bool started) {
  860. if (ink_drop_animation_started_ == started)
  861. return;
  862. ink_drop_animation_started_ = started;
  863. if (started) {
  864. ink_drop_count_ = shelf_button_delegate()->CreateScopedActiveInkDropCount(
  865. /*sender=*/this);
  866. } else {
  867. ink_drop_count_.reset(nullptr);
  868. }
  869. }
  870. void ShelfAppButton::SetNotificationBadgeColor(SkColor color) {
  871. if (notification_indicator_)
  872. notification_indicator_->SetColor(color);
  873. }
  874. void ShelfAppButton::MaybeHideInkDropWhenGestureEnds() {
  875. if (context_menu_target_visibility_ ||
  876. views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState() ==
  877. views::InkDropState::HIDDEN) {
  878. // Return early if the shelf app button's context menu should show or
  879. // the button's inkdrop has already been hidden.
  880. return;
  881. }
  882. views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
  883. views::InkDropState::HIDDEN);
  884. }
  885. } // namespace ash