shelf_navigation_widget.cc 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. // Copyright 2019 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_navigation_widget.h"
  5. #include "ash/focus_cycler.h"
  6. #include "ash/public/cpp/metrics_util.h"
  7. #include "ash/public/cpp/shelf_config.h"
  8. #include "ash/shelf/back_button.h"
  9. #include "ash/shelf/home_button.h"
  10. #include "ash/shelf/shelf.h"
  11. #include "ash/shelf/shelf_focus_cycler.h"
  12. #include "ash/shelf/shelf_layout_manager.h"
  13. #include "ash/shelf/shelf_layout_manager_observer.h"
  14. #include "ash/shelf/shelf_view.h"
  15. #include "ash/shelf/shelf_widget.h"
  16. #include "ash/shell.h"
  17. #include "ash/strings/grit/ash_strings.h"
  18. #include "ash/system/status_area_widget.h"
  19. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  20. #include "base/bind.h"
  21. #include "base/i18n/rtl.h"
  22. #include "base/memory/weak_ptr.h"
  23. #include "base/metrics/histogram_macros.h"
  24. #include "ui/base/l10n/l10n_util.h"
  25. #include "ui/compositor/animation_throughput_reporter.h"
  26. #include "ui/compositor/compositor.h"
  27. #include "ui/compositor/layer.h"
  28. #include "ui/compositor/layer_animation_observer.h"
  29. #include "ui/compositor/layer_delegate.h"
  30. #include "ui/compositor/paint_recorder.h"
  31. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  32. #include "ui/compositor/scoped_layer_animation_settings.h"
  33. #include "ui/compositor/throughput_tracker.h"
  34. #include "ui/gfx/canvas.h"
  35. #include "ui/gfx/geometry/rounded_corners_f.h"
  36. #include "ui/gfx/geometry/transform_util.h"
  37. #include "ui/views/animation/bounds_animator.h"
  38. #include "ui/views/background.h"
  39. #include "ui/views/highlight_border.h"
  40. #include "ui/views/view.h"
  41. #include "ui/wm/core/coordinate_conversion.h"
  42. namespace ash {
  43. namespace {
  44. // The duration of the back button opacity animation.
  45. constexpr base::TimeDelta kButtonOpacityAnimationDuration =
  46. base::Milliseconds(50);
  47. // Returns the bounds for the first button shown in this view (the back
  48. // button in tablet mode, the home button otherwise).
  49. gfx::Rect GetFirstButtonBounds(bool is_shelf_horizontal) {
  50. // ShelfNavigationWidget is larger than the buttons in order to enable child
  51. // views to capture events nearby.
  52. return gfx::Rect(
  53. ShelfConfig::Get()->control_button_edge_spacing(is_shelf_horizontal),
  54. ShelfConfig::Get()->control_button_edge_spacing(!is_shelf_horizontal),
  55. ShelfConfig::Get()->control_size(), ShelfConfig::Get()->control_size());
  56. }
  57. // Returns the bounds for the second button shown in this view (which is
  58. // always the home button and only in tablet mode, which implies a horizontal
  59. // shelf).
  60. gfx::Rect GetSecondButtonBounds() {
  61. // Second button only shows for horizontal shelf.
  62. return gfx::Rect(ShelfConfig::Get()->control_button_edge_spacing(
  63. true /* is_primary_axis_edge */) +
  64. ShelfConfig::Get()->control_size() +
  65. ShelfConfig::Get()->button_spacing(),
  66. ShelfConfig::Get()->control_button_edge_spacing(
  67. false /* is_primary_axis_edge */),
  68. ShelfConfig::Get()->control_size(),
  69. ShelfConfig::Get()->control_size());
  70. }
  71. bool IsBackButtonShown(bool horizontal_alignment) {
  72. // Back button should only be shown in horizontal shelf.
  73. // TODO(https://crbug.com/1102648): Horizontal shelf should be implied by
  74. // tablet mode, but this may not be the case during tablet mode transition as
  75. // shelf layout may get updated before the correct shelf alignment is set.
  76. // Remove this when the linked bug is resolved.
  77. if (!horizontal_alignment)
  78. return false;
  79. // TODO(https://crbug.com/1058205): Test this behavior.
  80. if (ShelfConfig::Get()->is_virtual_keyboard_shown())
  81. return true;
  82. if (!ShelfConfig::Get()->shelf_controls_shown())
  83. return false;
  84. return Shell::Get()->IsInTabletMode() && ShelfConfig::Get()->is_in_app();
  85. }
  86. bool IsHomeButtonShown() {
  87. return ShelfConfig::Get()->shelf_controls_shown();
  88. }
  89. // An implicit animation observer that hides a view once the view's opacity
  90. // animation finishes.
  91. // It deletes itself when the animation is done.
  92. class AnimationObserverToHideView : public ui::ImplicitAnimationObserver {
  93. public:
  94. explicit AnimationObserverToHideView(views::View* view) : view_(view) {}
  95. ~AnimationObserverToHideView() override = default;
  96. // ui::ImplicitAnimationObserver:
  97. void OnImplicitAnimationsCompleted() override {
  98. if (view_->layer()->GetTargetOpacity() == 0.0f)
  99. view_->SetVisible(false);
  100. delete this;
  101. }
  102. private:
  103. views::View* const view_;
  104. };
  105. // Tracks the animation smoothness of a view's bounds animation using
  106. // ui::ThroughputTracker.
  107. class BoundsAnimationReporter : public gfx::AnimationDelegate {
  108. public:
  109. BoundsAnimationReporter(views::View* view,
  110. metrics_util::ReportCallback report_callback)
  111. : tracker_(
  112. view->GetWidget()->GetCompositor()->RequestNewThroughputTracker()) {
  113. tracker_.Start(std::move(report_callback));
  114. }
  115. BoundsAnimationReporter(const BoundsAnimationReporter& other) = delete;
  116. BoundsAnimationReporter& operator=(const BoundsAnimationReporter& other) =
  117. delete;
  118. ~BoundsAnimationReporter() override = default;
  119. // gfx::AnimationDelegate:
  120. void AnimationEnded(const gfx::Animation* animation) override {
  121. tracker_.Stop();
  122. }
  123. void AnimationCanceled(const gfx::Animation* animation) override {
  124. tracker_.Cancel();
  125. }
  126. private:
  127. ui::ThroughputTracker tracker_;
  128. };
  129. class BackgroundLayerDelegate : public ui::LayerDelegate {
  130. public:
  131. BackgroundLayerDelegate(ui::Layer* layer, views::View* shelf_view)
  132. : layer_(layer), shelf_view_(shelf_view) {}
  133. BackgroundLayerDelegate(const BackgroundLayerDelegate&) = delete;
  134. BackgroundLayerDelegate& operator=(const BackgroundLayerDelegate&) = delete;
  135. ~BackgroundLayerDelegate() override {}
  136. void SetBackgroundColor(SkColor color) {
  137. background_color_ = color;
  138. layer_->SchedulePaint(layer_->PaintableRegion());
  139. }
  140. private:
  141. // views::LayerDelegate:
  142. void OnPaintLayer(const ui::PaintContext& context) override {
  143. ui::PaintRecorder recorder(context, layer_->size());
  144. gfx::Canvas* canvas = recorder.canvas();
  145. // Get the corner radius from `layer_`.
  146. gfx::RoundedCornersF corner_radii = layer_->rounded_corner_radii();
  147. // cc::PaintFlags flags for the background.
  148. cc::PaintFlags flags;
  149. flags.setColor(background_color_);
  150. flags.setAntiAlias(true);
  151. flags.setStyle(cc::PaintFlags::kFill_Style);
  152. canvas->DrawRoundRect(gfx::Rect(layer_->size()), corner_radii.upper_left(),
  153. flags);
  154. // Don't draw highlight border if the shelf widget is showing.
  155. if (!Shell::Get()->IsInTabletMode() || ShelfConfig::Get()->is_in_app())
  156. return;
  157. views::HighlightBorder::PaintBorderToCanvas(
  158. canvas, *shelf_view_, gfx::Rect(layer_->size()), corner_radii,
  159. views::HighlightBorder::Type::kHighlightBorder2, false);
  160. }
  161. void OnDeviceScaleFactorChanged(float old_device_scale_factor,
  162. float new_device_scale_factor) override {
  163. layer_->SchedulePaint(layer_->bounds());
  164. }
  165. ui::Layer* const layer_;
  166. views::View* const shelf_view_;
  167. // The background color of `layer_`. Note that this value has to be updated by
  168. // SetBackgroundColor() and the default value should never be drawn.
  169. SkColor background_color_ = SK_ColorRED;
  170. };
  171. } // namespace
  172. // An animation metrics reporter for the shelf navigation buttons.
  173. class ASH_EXPORT NavigationButtonAnimationMetricsReporter {
  174. public:
  175. // The different kinds of navigation buttons.
  176. enum class NavigationButtonType {
  177. // The Navigation Widget's back button.
  178. kBackButton,
  179. // The Navigation Widget's home button.
  180. kHomeButton
  181. };
  182. explicit NavigationButtonAnimationMetricsReporter(
  183. NavigationButtonType navigation_button_type)
  184. : navigation_button_type_(navigation_button_type) {}
  185. ~NavigationButtonAnimationMetricsReporter() = default;
  186. NavigationButtonAnimationMetricsReporter(
  187. const NavigationButtonAnimationMetricsReporter&) = delete;
  188. NavigationButtonAnimationMetricsReporter& operator=(
  189. const NavigationButtonAnimationMetricsReporter&) = delete;
  190. void ReportSmoothness(HotseatState target_hotseat_state, int smoothness) {
  191. switch (target_hotseat_state) {
  192. case HotseatState::kShownClamshell:
  193. case HotseatState::kShownHomeLauncher:
  194. switch (navigation_button_type_) {
  195. case NavigationButtonType::kBackButton:
  196. UMA_HISTOGRAM_PERCENTAGE(
  197. "Ash.NavigationWidget.BackButton.AnimationSmoothness."
  198. "TransitionToShownHotseat",
  199. smoothness);
  200. break;
  201. case NavigationButtonType::kHomeButton:
  202. UMA_HISTOGRAM_PERCENTAGE(
  203. "Ash.NavigationWidget.HomeButton.AnimationSmoothness."
  204. "TransitionToShownHotseat",
  205. smoothness);
  206. break;
  207. default:
  208. NOTREACHED();
  209. break;
  210. }
  211. break;
  212. case HotseatState::kExtended:
  213. switch (navigation_button_type_) {
  214. case NavigationButtonType::kBackButton:
  215. UMA_HISTOGRAM_PERCENTAGE(
  216. "Ash.NavigationWidget.BackButton.AnimationSmoothness."
  217. "TransitionToExtendedHotseat",
  218. smoothness);
  219. break;
  220. case NavigationButtonType::kHomeButton:
  221. UMA_HISTOGRAM_PERCENTAGE(
  222. "Ash.NavigationWidget.HomeButton.AnimationSmoothness."
  223. "TransitionToExtendedHotseat",
  224. smoothness);
  225. break;
  226. default:
  227. NOTREACHED();
  228. break;
  229. }
  230. break;
  231. case HotseatState::kHidden:
  232. switch (navigation_button_type_) {
  233. case NavigationButtonType::kBackButton:
  234. UMA_HISTOGRAM_PERCENTAGE(
  235. "Ash.NavigationWidget.BackButton.AnimationSmoothness."
  236. "TransitionToHiddenHotseat",
  237. smoothness);
  238. break;
  239. case NavigationButtonType::kHomeButton:
  240. UMA_HISTOGRAM_PERCENTAGE(
  241. "Ash.NavigationWidget.HomeButton.AnimationSmoothness."
  242. "TransitionToHiddenHotseat",
  243. smoothness);
  244. break;
  245. default:
  246. NOTREACHED();
  247. break;
  248. }
  249. break;
  250. default:
  251. NOTREACHED();
  252. break;
  253. }
  254. }
  255. metrics_util::ReportCallback GetReportCallback(
  256. HotseatState target_hotseat_state) {
  257. DCHECK_NE(target_hotseat_state, HotseatState::kNone);
  258. return metrics_util::ForSmoothness(base::BindRepeating(
  259. &NavigationButtonAnimationMetricsReporter::ReportSmoothness,
  260. weak_ptr_factory_.GetWeakPtr(), target_hotseat_state));
  261. }
  262. private:
  263. // The type of navigation button that is animated.
  264. const NavigationButtonType navigation_button_type_;
  265. base::WeakPtrFactory<NavigationButtonAnimationMetricsReporter>
  266. weak_ptr_factory_{this};
  267. };
  268. class ShelfNavigationWidget::Delegate : public views::AccessiblePaneView,
  269. public views::WidgetDelegate {
  270. public:
  271. Delegate(Shelf* shelf, ShelfView* shelf_view);
  272. Delegate(const Delegate&) = delete;
  273. Delegate& operator=(const Delegate&) = delete;
  274. ~Delegate() override;
  275. // Initializes the view.
  276. void Init(ui::Layer* parent_layer);
  277. // Updates the layout and visibility of `opaque_background_`.
  278. void UpdateOpaqueBackground();
  279. // Updates the color of `opaque_background_`.
  280. void UpdateBackgroundColor();
  281. // views::View:
  282. FocusTraversable* GetPaneFocusTraversable() override;
  283. void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
  284. void ReorderChildLayers(ui::Layer* parent_layer) override;
  285. void OnBoundsChanged(const gfx::Rect& old_bounds) override;
  286. void OnThemeChanged() override;
  287. // views::AccessiblePaneView:
  288. View* GetDefaultFocusableChild() override;
  289. // views::WidgetDelegate:
  290. bool CanActivate() const override;
  291. views::Widget* GetWidget() override { return View::GetWidget(); }
  292. const views::Widget* GetWidget() const override { return View::GetWidget(); }
  293. BackButton* back_button() const { return back_button_; }
  294. HomeButton* home_button() const { return home_button_; }
  295. void set_default_last_focusable_child(bool default_last_focusable_child) {
  296. default_last_focusable_child_ = default_last_focusable_child;
  297. }
  298. private:
  299. void SetParentLayer(ui::Layer* layer);
  300. void RefreshAccessibilityWidgetNextPreviousFocus(ShelfWidget* shelf);
  301. BackButton* back_button_ = nullptr;
  302. HomeButton* home_button_ = nullptr;
  303. // When true, the default focus of the navigation widget is the last
  304. // focusable child.
  305. bool default_last_focusable_child_ = false;
  306. // A background layer that may be visible depending on shelf state.
  307. ui::Layer opaque_background_;
  308. // The layer delegate that helps drawing the highlight border on
  309. // `opaque_background_`.
  310. std::unique_ptr<BackgroundLayerDelegate> background_delegate_;
  311. Shelf* shelf_ = nullptr;
  312. };
  313. ShelfNavigationWidget::Delegate::Delegate(Shelf* shelf, ShelfView* shelf_view)
  314. : opaque_background_(features::IsDarkLightModeEnabled()
  315. ? ui::LAYER_TEXTURED
  316. : ui::LAYER_SOLID_COLOR),
  317. shelf_(shelf) {
  318. SetOwnedByWidget(true);
  319. if (features::IsDarkLightModeEnabled()) {
  320. background_delegate_ = std::make_unique<BackgroundLayerDelegate>(
  321. &opaque_background_, shelf_view);
  322. opaque_background_.set_delegate(background_delegate_.get());
  323. opaque_background_.SetFillsBoundsOpaquely(false);
  324. }
  325. set_allow_deactivate_on_esc(true);
  326. const int control_size = ShelfConfig::Get()->control_size();
  327. std::unique_ptr<BackButton> back_button_ptr =
  328. std::make_unique<BackButton>(shelf);
  329. back_button_ = AddChildView(std::move(back_button_ptr));
  330. back_button_->SetSize(gfx::Size(control_size, control_size));
  331. std::unique_ptr<HomeButton> home_button_ptr =
  332. std::make_unique<HomeButton>(shelf);
  333. home_button_ = AddChildView(std::move(home_button_ptr));
  334. home_button_->set_context_menu_controller(shelf_view);
  335. home_button_->SetSize(gfx::Size(control_size, control_size));
  336. // Ensure widgets are represented in accessibility.
  337. if (shelf->hotseat_widget()) {
  338. shelf->hotseat_widget()->GetRootView()->NotifyAccessibilityEvent(
  339. ax::mojom::Event::kChildrenChanged, true);
  340. }
  341. if (shelf->GetStatusAreaWidget()) {
  342. shelf->GetStatusAreaWidget()->GetRootView()->NotifyAccessibilityEvent(
  343. ax::mojom::Event::kChildrenChanged, true);
  344. }
  345. RefreshAccessibilityWidgetNextPreviousFocus(shelf->shelf_widget());
  346. opaque_background_.SetName("shelfNavigation/Background");
  347. }
  348. ShelfNavigationWidget::Delegate::~Delegate() = default;
  349. void ShelfNavigationWidget::Delegate::Init(ui::Layer* parent_layer) {
  350. SetParentLayer(parent_layer);
  351. UpdateOpaqueBackground();
  352. }
  353. void ShelfNavigationWidget::Delegate::UpdateOpaqueBackground() {
  354. UpdateBackgroundColor();
  355. // Hide background if no buttons should be shown.
  356. if (!IsHomeButtonShown() &&
  357. !IsBackButtonShown(shelf_->IsHorizontalAlignment())) {
  358. opaque_background_.SetVisible(false);
  359. return;
  360. }
  361. if (Shell::Get()->IsInTabletMode() && ShelfConfig::Get()->is_in_app()) {
  362. opaque_background_.SetVisible(false);
  363. return;
  364. }
  365. opaque_background_.SetVisible(true);
  366. float radius = ShelfConfig::Get()->control_border_radius();
  367. gfx::RoundedCornersF rounded_corners = {radius, radius, radius, radius};
  368. if (opaque_background_.rounded_corner_radii() != rounded_corners)
  369. opaque_background_.SetRoundedCornerRadius(rounded_corners);
  370. // The opaque background does not show up when there are two buttons.
  371. gfx::Rect opaque_background_bounds =
  372. GetMirroredRect(GetFirstButtonBounds(shelf_->IsHorizontalAlignment()));
  373. opaque_background_.SetBounds(opaque_background_bounds);
  374. opaque_background_.SetBackgroundBlur(
  375. ShelfConfig::Get()->GetShelfControlButtonBlurRadius());
  376. }
  377. void ShelfNavigationWidget::Delegate::UpdateBackgroundColor() {
  378. SkColor background_color = ShelfConfig::Get()->GetShelfControlButtonColor();
  379. if (background_delegate_)
  380. background_delegate_->SetBackgroundColor(background_color);
  381. else
  382. opaque_background_.SetColor(background_color);
  383. }
  384. bool ShelfNavigationWidget::Delegate::CanActivate() const {
  385. // We don't want mouse clicks to activate us, but we need to allow
  386. // activation when the user is using the keyboard (FocusCycler).
  387. return Shell::Get()->focus_cycler()->widget_activating() == GetWidget();
  388. }
  389. views::FocusTraversable*
  390. ShelfNavigationWidget::Delegate::GetPaneFocusTraversable() {
  391. return this;
  392. }
  393. void ShelfNavigationWidget::Delegate::GetAccessibleNodeData(
  394. ui::AXNodeData* node_data) {
  395. node_data->role = ax::mojom::Role::kToolbar;
  396. node_data->SetName(l10n_util::GetStringUTF8(IDS_ASH_SHELF_ACCESSIBLE_NAME));
  397. RefreshAccessibilityWidgetNextPreviousFocus(
  398. Shelf::ForWindow(GetWidget()->GetNativeWindow())->shelf_widget());
  399. }
  400. void ShelfNavigationWidget::Delegate::ReorderChildLayers(
  401. ui::Layer* parent_layer) {
  402. views::View::ReorderChildLayers(parent_layer);
  403. parent_layer->StackAtBottom(&opaque_background_);
  404. }
  405. void ShelfNavigationWidget::Delegate::OnBoundsChanged(
  406. const gfx::Rect& old_bounds) {
  407. UpdateOpaqueBackground();
  408. }
  409. void ShelfNavigationWidget::Delegate::OnThemeChanged() {
  410. views::AccessiblePaneView::OnThemeChanged();
  411. UpdateBackgroundColor();
  412. }
  413. views::View* ShelfNavigationWidget::Delegate::GetDefaultFocusableChild() {
  414. return default_last_focusable_child_ ? GetLastFocusableChild()
  415. : GetFirstFocusableChild();
  416. }
  417. void ShelfNavigationWidget::Delegate::SetParentLayer(ui::Layer* layer) {
  418. layer->Add(&opaque_background_);
  419. ReorderLayers();
  420. }
  421. void ShelfNavigationWidget::Delegate::
  422. RefreshAccessibilityWidgetNextPreviousFocus(ShelfWidget* shelf) {
  423. GetViewAccessibility().OverrideNextFocus(shelf->hotseat_widget());
  424. GetViewAccessibility().OverridePreviousFocus(shelf->status_area_widget());
  425. }
  426. ShelfNavigationWidget::TestApi::TestApi(ShelfNavigationWidget* widget)
  427. : navigation_widget_(widget) {}
  428. ShelfNavigationWidget::TestApi::~TestApi() = default;
  429. bool ShelfNavigationWidget::TestApi::IsHomeButtonVisible() const {
  430. const HomeButton* button = navigation_widget_->delegate_->home_button();
  431. const float opacity = button->layer()->GetTargetOpacity();
  432. DCHECK(opacity == 0.0f || opacity == 1.0f)
  433. << "Unexpected opacity " << opacity;
  434. return opacity > 0.0f && button->GetVisible();
  435. }
  436. bool ShelfNavigationWidget::TestApi::IsBackButtonVisible() const {
  437. const BackButton* button = navigation_widget_->delegate_->back_button();
  438. const float opacity = button->layer()->GetTargetOpacity();
  439. DCHECK(opacity == 0.0f || opacity == 1.0f)
  440. << "Unexpected opacity " << opacity;
  441. return opacity > 0.0f && button->GetVisible();
  442. }
  443. views::BoundsAnimator* ShelfNavigationWidget::TestApi::GetBoundsAnimator() {
  444. return navigation_widget_->bounds_animator_.get();
  445. }
  446. ShelfNavigationWidget::ShelfNavigationWidget(Shelf* shelf,
  447. ShelfView* shelf_view)
  448. : shelf_(shelf),
  449. delegate_(new ShelfNavigationWidget::Delegate(shelf, shelf_view)),
  450. bounds_animator_(
  451. std::make_unique<views::BoundsAnimator>(delegate_,
  452. /*use_transforms=*/true)),
  453. back_button_metrics_reporter_(
  454. std::make_unique<NavigationButtonAnimationMetricsReporter>(
  455. NavigationButtonAnimationMetricsReporter::NavigationButtonType::
  456. kBackButton)),
  457. home_button_metrics_reporter_(
  458. std::make_unique<NavigationButtonAnimationMetricsReporter>(
  459. NavigationButtonAnimationMetricsReporter::NavigationButtonType::
  460. kHomeButton)) {
  461. DCHECK(shelf_);
  462. }
  463. ShelfNavigationWidget::~ShelfNavigationWidget() {
  464. // Cancel animations now so the BoundsAnimator doesn't outlive the metrics
  465. // reporter associated to it.
  466. bounds_animator_->Cancel();
  467. }
  468. void ShelfNavigationWidget::Initialize(aura::Window* container) {
  469. DCHECK(container);
  470. views::Widget::InitParams params(
  471. views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  472. params.name = "ShelfNavigationWidget";
  473. params.delegate = delegate_;
  474. params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  475. params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
  476. params.parent = container;
  477. Init(std::move(params));
  478. delegate_->Init(GetLayer());
  479. set_focus_on_creation(false);
  480. delegate_->SetEnableArrowKeyTraversal(true);
  481. SetContentsView(delegate_);
  482. SetSize(CalculateIdealSize(/*only_visible_area=*/false));
  483. UpdateLayout(/*animate=*/false);
  484. }
  485. void ShelfNavigationWidget::OnMouseEvent(ui::MouseEvent* event) {
  486. if (event->IsMouseWheelEvent()) {
  487. ui::MouseWheelEvent* mouse_wheel_event = event->AsMouseWheelEvent();
  488. shelf_->ProcessMouseWheelEvent(mouse_wheel_event);
  489. return;
  490. }
  491. views::Widget::OnMouseEvent(event);
  492. }
  493. void ShelfNavigationWidget::OnScrollEvent(ui::ScrollEvent* event) {
  494. shelf_->ProcessScrollEvent(event);
  495. if (!event->handled())
  496. views::Widget::OnScrollEvent(event);
  497. }
  498. bool ShelfNavigationWidget::OnNativeWidgetActivationChanged(bool active) {
  499. if (!Widget::OnNativeWidgetActivationChanged(active))
  500. return false;
  501. if (active)
  502. delegate_->SetPaneFocusAndFocusDefault();
  503. return true;
  504. }
  505. void ShelfNavigationWidget::OnGestureEvent(ui::GestureEvent* event) {
  506. // Shelf::ProcessGestureEvent expects an event whose location is in screen
  507. // coordinates - create a copy of the event with the location in screen
  508. // coordinate system.
  509. ui::GestureEvent copy_event(*event);
  510. gfx::Point location_in_screen(copy_event.location());
  511. wm::ConvertPointToScreen(GetNativeWindow(), &location_in_screen);
  512. copy_event.set_location(location_in_screen);
  513. if (shelf_->ProcessGestureEvent(copy_event)) {
  514. event->StopPropagation();
  515. return;
  516. }
  517. views::Widget::OnGestureEvent(event);
  518. }
  519. BackButton* ShelfNavigationWidget::GetBackButton() const {
  520. return IsBackButtonShown(shelf_->IsHorizontalAlignment())
  521. ? delegate_->back_button()
  522. : nullptr;
  523. }
  524. HomeButton* ShelfNavigationWidget::GetHomeButton() const {
  525. return IsHomeButtonShown() ? delegate_->home_button() : nullptr;
  526. }
  527. void ShelfNavigationWidget::SetDefaultLastFocusableChild(
  528. bool default_last_focusable_child) {
  529. delegate_->set_default_last_focusable_child(default_last_focusable_child);
  530. }
  531. void ShelfNavigationWidget::CalculateTargetBounds() {
  532. const gfx::Point shelf_origin =
  533. shelf_->shelf_widget()->GetTargetBounds().origin();
  534. gfx::Point nav_origin = gfx::Point(shelf_origin.x(), shelf_origin.y());
  535. gfx::Size nav_size = CalculateIdealSize(/*only_visible_area=*/false);
  536. if (shelf_->IsHorizontalAlignment() && base::i18n::IsRTL()) {
  537. nav_origin.set_x(shelf_->shelf_widget()->GetTargetBounds().size().width() -
  538. nav_size.width());
  539. }
  540. target_bounds_ = gfx::Rect(nav_origin, nav_size);
  541. clip_rect_after_rtl_ = CalculateClipRectAfterRTL();
  542. }
  543. gfx::Rect ShelfNavigationWidget::GetTargetBounds() const {
  544. return target_bounds_;
  545. }
  546. void ShelfNavigationWidget::UpdateLayout(bool animate) {
  547. const bool back_button_shown =
  548. IsBackButtonShown(shelf_->IsHorizontalAlignment());
  549. const bool home_button_shown = IsHomeButtonShown();
  550. const ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager();
  551. // Having a window which is visible but does not have an opacity is an
  552. // illegal state. Also, never show this widget outside of an active session.
  553. if (layout_manager->GetOpacity() &&
  554. layout_manager->is_active_session_state()) {
  555. ShowInactive();
  556. } else {
  557. Hide();
  558. }
  559. // If the widget is currently active, and all the buttons will be hidden,
  560. // focus out to the status area (the widget's focus manager does not properly
  561. // handle the case where the widget does not have another view to focus - it
  562. // would clear the focus, and hit a DCHECK trying to cycle focus within the
  563. // widget).
  564. if (IsActive() && !back_button_shown && !home_button_shown) {
  565. Shelf::ForWindow(GetNativeWindow())
  566. ->shelf_focus_cycler()
  567. ->FocusOut(true /*reverse*/, SourceView::kShelfNavigationView);
  568. }
  569. // Use the same duration for all parts of the upcoming animation.
  570. const base::TimeDelta animation_duration =
  571. animate ? ShelfConfig::Get()->shelf_animation_duration()
  572. : base::Milliseconds(0);
  573. const HotseatState target_hotseat_state =
  574. layout_manager->CalculateHotseatState(layout_manager->visibility_state(),
  575. layout_manager->auto_hide_state());
  576. const bool update_opacity = !animate || GetLayer()->GetTargetOpacity() !=
  577. layout_manager->GetOpacity();
  578. const bool update_bounds =
  579. !animate || GetLayer()->GetTargetBounds() != target_bounds_;
  580. if (update_opacity || update_bounds) {
  581. ui::ScopedLayerAnimationSettings nav_animation_setter(
  582. GetNativeView()->layer()->GetAnimator());
  583. nav_animation_setter.SetTransitionDuration(animation_duration);
  584. nav_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
  585. nav_animation_setter.SetPreemptionStrategy(
  586. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  587. absl::optional<ui::AnimationThroughputReporter> reporter;
  588. if (animate) {
  589. reporter.emplace(nav_animation_setter.GetAnimator(),
  590. shelf_->GetNavigationWidgetAnimationReportCallback(
  591. target_hotseat_state));
  592. }
  593. if (update_opacity)
  594. GetLayer()->SetOpacity(layout_manager->GetOpacity());
  595. if (update_bounds)
  596. SetBounds(target_bounds_);
  597. }
  598. if (update_bounds)
  599. GetLayer()->SetClipRect(clip_rect_after_rtl_);
  600. views::View* const back_button = delegate_->back_button();
  601. UpdateButtonVisibility(back_button, back_button_shown, animate,
  602. back_button_metrics_reporter_.get(),
  603. target_hotseat_state);
  604. views::View* const home_button = delegate_->home_button();
  605. UpdateButtonVisibility(home_button, home_button_shown, animate,
  606. home_button_metrics_reporter_.get(),
  607. target_hotseat_state);
  608. if (back_button_shown) {
  609. // TODO(https://crbug.com/1058205): Test this behavior.
  610. gfx::Transform rotation;
  611. // If the IME virtual keyboard is visible, rotate the back button downwards,
  612. // this indicates it can be used to close the keyboard.
  613. if (ShelfConfig::Get()->is_virtual_keyboard_shown())
  614. rotation.Rotate(270.0);
  615. delegate_->back_button()->layer()->SetTransform(TransformAboutPivot(
  616. delegate_->back_button()->GetCenterPoint(), rotation));
  617. }
  618. gfx::Rect home_button_bounds =
  619. back_button_shown ? GetSecondButtonBounds()
  620. : GetFirstButtonBounds(shelf_->IsHorizontalAlignment());
  621. if (animate) {
  622. if (bounds_animator_->GetTargetBounds(home_button) != home_button_bounds) {
  623. bounds_animator_->SetAnimationDuration(
  624. ui::ScopedAnimationDurationScaleMode::duration_multiplier() *
  625. animation_duration);
  626. bounds_animator_->AnimateViewTo(
  627. home_button, home_button_bounds,
  628. std::make_unique<BoundsAnimationReporter>(
  629. home_button, home_button_metrics_reporter_->GetReportCallback(
  630. target_hotseat_state)));
  631. }
  632. } else {
  633. bounds_animator_->Cancel();
  634. home_button->SetBoundsRect(home_button_bounds);
  635. }
  636. back_button->SetBoundsRect(
  637. GetFirstButtonBounds(shelf_->IsHorizontalAlignment()));
  638. delegate_->UpdateOpaqueBackground();
  639. }
  640. void ShelfNavigationWidget::UpdateTargetBoundsForGesture(int shelf_position) {
  641. if (shelf_->IsHorizontalAlignment())
  642. target_bounds_.set_y(shelf_position);
  643. else
  644. target_bounds_.set_x(shelf_position);
  645. }
  646. gfx::Rect ShelfNavigationWidget::GetVisibleBounds() const {
  647. return gfx::Rect(target_bounds_.origin(), clip_rect_after_rtl_.size());
  648. }
  649. void ShelfNavigationWidget::PrepareForGettingFocus(bool last_element) {
  650. SetDefaultLastFocusableChild(last_element);
  651. // The native view of the navigation widget is not activatable when its target
  652. // visibility is false. So show the widget before setting focus.
  653. // Layer opacity should be set first. Because it is not allowed that a window
  654. // is visible but the layers alpha is fully transparent.
  655. ui::Layer* layer = GetLayer();
  656. if (layer->GetTargetOpacity() != 1.f)
  657. GetLayer()->SetOpacity(1.f);
  658. if (!IsVisible())
  659. ShowInactive();
  660. }
  661. void ShelfNavigationWidget::HandleLocaleChange() {
  662. delegate_->home_button()->HandleLocaleChange();
  663. delegate_->back_button()->HandleLocaleChange();
  664. }
  665. void ShelfNavigationWidget::UpdateButtonVisibility(
  666. views::View* button,
  667. bool visible,
  668. bool animate,
  669. NavigationButtonAnimationMetricsReporter* metrics_reporter,
  670. HotseatState target_hotseat_state) {
  671. if (animate && button->layer()->GetTargetOpacity() == visible)
  672. return;
  673. // Update visibility immediately only if making the button visible. When
  674. // hiding the button, the visibility will be updated when the animations
  675. // complete (by AnimationObserverToHideView).
  676. if (visible)
  677. button->SetVisible(true);
  678. button->SetFocusBehavior(visible ? views::View::FocusBehavior::ALWAYS
  679. : views::View::FocusBehavior::NEVER);
  680. ui::ScopedLayerAnimationSettings opacity_settings(
  681. button->layer()->GetAnimator());
  682. opacity_settings.SetTransitionDuration(
  683. animate ? kButtonOpacityAnimationDuration : base::TimeDelta());
  684. opacity_settings.SetPreemptionStrategy(
  685. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  686. absl::optional<ui::AnimationThroughputReporter> reporter;
  687. if (animate) {
  688. reporter.emplace(opacity_settings.GetAnimator(),
  689. metrics_reporter->GetReportCallback(target_hotseat_state));
  690. }
  691. if (!visible)
  692. opacity_settings.AddObserver(new AnimationObserverToHideView(button));
  693. button->layer()->SetOpacity(visible ? 1.0f : 0.0f);
  694. }
  695. gfx::Rect ShelfNavigationWidget::CalculateClipRectAfterRTL() const {
  696. gfx::Rect clip_bounds;
  697. if (Shell::Get()->IsInTabletMode()) {
  698. clip_bounds = gfx::Rect(CalculateIdealSize(/*only_visible_area=*/true));
  699. } else {
  700. clip_bounds = gfx::Rect(target_bounds_.size());
  701. }
  702. // Bounds will be used to set a layer clip rect, and thus need to be modified
  703. // for RTL - avoid using `GetMirroredRect()` method, as it would use the
  704. // current widget/root view bounds instead of target bounds.
  705. if (base::i18n::IsRTL()) {
  706. clip_bounds.set_x(target_bounds_.width() - clip_bounds.right());
  707. }
  708. return clip_bounds;
  709. }
  710. gfx::Size ShelfNavigationWidget::CalculateIdealSize(
  711. bool only_visible_area) const {
  712. const bool home_button_shown = IsHomeButtonShown();
  713. const bool back_button_shown =
  714. IsBackButtonShown(shelf_->IsHorizontalAlignment());
  715. if (!home_button_shown && !back_button_shown)
  716. return gfx::Size();
  717. int controls_space = 0;
  718. const int control_size = ShelfConfig::Get()->control_size();
  719. if (Shell::Get()->IsInTabletMode() && !only_visible_area) {
  720. // There are home button and back button. So the maximum is 2.
  721. controls_space = control_size * 2 + ShelfConfig::Get()->button_spacing();
  722. } else {
  723. // Use CalculatePreferredSize here to take the launcher nudge label into
  724. // consider.
  725. controls_space += home_button_shown
  726. ? GetHomeButton()->CalculatePreferredSize().width()
  727. : 0;
  728. controls_space += back_button_shown ? control_size : 0;
  729. controls_space +=
  730. (CalculateButtonCount() - 1) * ShelfConfig::Get()->button_spacing();
  731. }
  732. const int major_axis_spacing =
  733. 2 * ShelfConfig::Get()->control_button_edge_spacing(
  734. shelf_->IsHorizontalAlignment());
  735. const int major_axis_length = controls_space + major_axis_spacing;
  736. // Calculate |minor_axis_spacing|.
  737. int minor_axis_spacing;
  738. if (only_visible_area) {
  739. minor_axis_spacing = 2 * ShelfConfig::Get()->control_button_edge_spacing(
  740. !shelf_->IsHorizontalAlignment());
  741. } else {
  742. // When calculating the minor axis length of the navigation widget, use
  743. // the larger edge spacing between the home launcher state and the in-app
  744. // state. It ensures that the widget' size is constant during the transition
  745. // between these two states.
  746. DCHECK_GT(ShelfConfig::Get()->system_shelf_size(),
  747. ShelfConfig::Get()->in_app_shelf_size());
  748. minor_axis_spacing = ShelfConfig::Get()->system_shelf_size() - control_size;
  749. }
  750. const int minor_axis_length = control_size + minor_axis_spacing;
  751. return shelf_->IsHorizontalAlignment()
  752. ? gfx::Size(major_axis_length, minor_axis_length)
  753. : gfx::Size(minor_axis_length, major_axis_length);
  754. }
  755. int ShelfNavigationWidget::CalculateButtonCount() const {
  756. return (IsBackButtonShown(shelf_->IsHorizontalAlignment()) ? 1 : 0) +
  757. (IsHomeButtonShown() ? 1 : 0);
  758. }
  759. } // namespace ash