window_cycle_view.cc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. // Copyright 2021 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/wm/window_cycle/window_cycle_view.h"
  5. #include "ash/accessibility/accessibility_controller_impl.h"
  6. #include "ash/public/cpp/metrics_util.h"
  7. #include "ash/public/cpp/style/color_provider.h"
  8. #include "ash/shell.h"
  9. #include "ash/strings/grit/ash_strings.h"
  10. #include "ash/style/ash_color_provider.h"
  11. #include "ash/wm/window_cycle/window_cycle_controller.h"
  12. #include "ash/wm/window_cycle/window_cycle_item_view.h"
  13. #include "base/bind.h"
  14. #include "base/check_op.h"
  15. #include "base/cxx17_backports.h"
  16. #include "base/metrics/histogram_macros.h"
  17. #include "base/time/time.h"
  18. #include "chromeos/constants/chromeos_features.h"
  19. #include "third_party/abseil-cpp/absl/types/optional.h"
  20. #include "third_party/skia/include/core/SkColor.h"
  21. #include "ui/aura/window.h"
  22. #include "ui/base/l10n/l10n_util.h"
  23. #include "ui/base/metadata/metadata_impl_macros.h"
  24. #include "ui/compositor/animation_throughput_reporter.h"
  25. #include "ui/compositor/layer.h"
  26. #include "ui/compositor/layer_animator.h"
  27. #include "ui/compositor/layer_type.h"
  28. #include "ui/compositor/scoped_layer_animation_settings.h"
  29. #include "ui/gfx/animation/tween.h"
  30. #include "ui/gfx/font.h"
  31. #include "ui/gfx/geometry/insets.h"
  32. #include "ui/gfx/geometry/rect_f.h"
  33. #include "ui/gfx/geometry/rounded_corners_f.h"
  34. #include "ui/gfx/geometry/size.h"
  35. #include "ui/gfx/geometry/vector2d.h"
  36. #include "ui/gfx/text_constants.h"
  37. #include "ui/views/background.h"
  38. #include "ui/views/controls/label.h"
  39. #include "ui/views/highlight_border.h"
  40. #include "ui/views/layout/box_layout.h"
  41. #include "ui/views/view.h"
  42. namespace ash {
  43. namespace {
  44. // Shield rounded corner radius.
  45. constexpr int kBackgroundCornerRadius = 16;
  46. // Shield horizontal inset.
  47. constexpr int kBackgroundHorizontalInsetDp = 8;
  48. // Vertical padding between the alt-tab bandshield and the window previews.
  49. constexpr int kInsideBorderVerticalPaddingDp = 60;
  50. // Padding between the alt-tab bandshield and the tab slider container.
  51. constexpr int kMirrorContainerVerticalPaddingDp = 24;
  52. // Padding between the window previews within the alt-tab bandshield.
  53. constexpr int kBetweenChildPaddingDp = 10;
  54. // Padding between the tab slider button and the tab slider container.
  55. constexpr int kTabSliderContainerVerticalPaddingDp = 32;
  56. // The font size of "No recent items" string when there's no window in the
  57. // window cycle list.
  58. constexpr int kNoRecentItemsLabelFontSizeDp = 14;
  59. // The UMA histogram that logs smoothness of the fade-in animation.
  60. constexpr char kShowAnimationSmoothness[] =
  61. "Ash.WindowCycleView.AnimationSmoothness.Show";
  62. // The UMA histogram that logs smoothness of the window container animation.
  63. constexpr char kContainerAnimationSmoothness[] =
  64. "Ash.WindowCycleView.AnimationSmoothness.Container";
  65. // Duration of the window cycle UI fade in animation.
  66. constexpr base::TimeDelta kFadeInDuration = base::Milliseconds(100);
  67. // Duration of the window cycle elements slide animation.
  68. constexpr base::TimeDelta kContainerSlideDuration = base::Milliseconds(120);
  69. // Duration of the window cycle scale animation when a user toggles alt-tab
  70. // modes.
  71. constexpr base::TimeDelta kToggleModeScaleDuration = base::Milliseconds(150);
  72. } // namespace
  73. WindowCycleView::WindowCycleView(aura::Window* root_window,
  74. const WindowList& windows)
  75. : root_window_(root_window) {
  76. const bool is_interactive_alt_tab_mode_allowed =
  77. Shell::Get()->window_cycle_controller()->IsInteractiveAltTabModeAllowed();
  78. DCHECK(!windows.empty() || is_interactive_alt_tab_mode_allowed);
  79. // Start the occlusion tracker pauser. It's used to increase smoothness for
  80. // the fade in but we also create windows here which may occlude other
  81. // windows.
  82. occlusion_tracker_pauser_ =
  83. std::make_unique<aura::WindowOcclusionTracker::ScopedPause>();
  84. // The layer for |this| is responsible for showing color, background blur
  85. // and fading in.
  86. SetPaintToLayer(ui::LAYER_TEXTURED);
  87. ui::Layer* layer = this->layer();
  88. layer->SetFillsBoundsOpaquely(false);
  89. layer->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
  90. layer->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
  91. layer->SetName("WindowCycleView");
  92. layer->SetMasksToBounds(true);
  93. SetBackground(views::CreateRoundedRectBackground(
  94. AshColorProvider::Get()->GetBaseLayerColor(
  95. AshColorProvider::BaseLayerType::kTransparent80),
  96. kBackgroundCornerRadius));
  97. // |mirror_container_| may be larger than |this|. In this case, it will be
  98. // shifted along the x-axis when the user tabs through. It is a container
  99. // for the previews and has no rendered content.
  100. mirror_container_ = AddChildView(std::make_unique<views::View>());
  101. mirror_container_->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
  102. mirror_container_->layer()->SetName("WindowCycleView/MirrorContainer");
  103. views::BoxLayout* layout =
  104. mirror_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
  105. views::BoxLayout::Orientation::kHorizontal,
  106. gfx::Insets::TLBR(is_interactive_alt_tab_mode_allowed
  107. ? kMirrorContainerVerticalPaddingDp
  108. : kInsideBorderVerticalPaddingDp,
  109. WindowCycleView::kInsideBorderHorizontalPaddingDp,
  110. kInsideBorderVerticalPaddingDp,
  111. WindowCycleView::kInsideBorderHorizontalPaddingDp),
  112. kBetweenChildPaddingDp));
  113. layout->set_cross_axis_alignment(
  114. views::BoxLayout::CrossAxisAlignment::kStart);
  115. if (is_interactive_alt_tab_mode_allowed) {
  116. tab_slider_container_ =
  117. AddChildView(std::make_unique<WindowCycleTabSlider>());
  118. no_recent_items_label_ = AddChildView(std::make_unique<views::Label>(
  119. l10n_util::GetStringUTF16(IDS_ASH_OVERVIEW_NO_RECENT_ITEMS)));
  120. no_recent_items_label_->SetPaintToLayer();
  121. no_recent_items_label_->layer()->SetFillsBoundsOpaquely(false);
  122. no_recent_items_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
  123. no_recent_items_label_->SetVerticalAlignment(gfx::ALIGN_MIDDLE);
  124. no_recent_items_label_->SetEnabledColor(
  125. AshColorProvider::Get()->GetContentLayerColor(
  126. AshColorProvider::ContentLayerType::kIconColorSecondary));
  127. no_recent_items_label_->SetFontList(
  128. no_recent_items_label_->font_list()
  129. .DeriveWithSizeDelta(
  130. kNoRecentItemsLabelFontSizeDp -
  131. no_recent_items_label_->font_list().GetFontSize())
  132. .DeriveWithWeight(gfx::Font::Weight::NORMAL));
  133. no_recent_items_label_->SetVisible(windows.empty());
  134. no_recent_items_label_->SetPreferredSize(
  135. gfx::Size(tab_slider_container_->GetPreferredSize().width() +
  136. 2 * WindowCycleView::kInsideBorderHorizontalPaddingDp,
  137. WindowCycleItemView::kFixedPreviewHeightDp +
  138. WindowMiniView::kHeaderHeightDp +
  139. kMirrorContainerVerticalPaddingDp +
  140. kInsideBorderVerticalPaddingDp + 8));
  141. }
  142. for (auto* window : windows) {
  143. // |mirror_container_| owns |view|. The |preview_view_| in |view| will
  144. // use trilinear filtering in InitLayerOwner().
  145. auto* view = mirror_container_->AddChildView(
  146. std::make_unique<WindowCycleItemView>(window));
  147. window_view_map_[window] = view;
  148. no_previews_set_.insert(view);
  149. }
  150. // The insets in the WindowCycleItemView are coming from its border, which
  151. // paints the focus ring around the view when it is highlighted. Exclude the
  152. // insets such that the spacing between the contents of the views rather
  153. // than the views themselves is |kBetweenChildPaddingDp|.
  154. const gfx::Insets cycle_item_insets =
  155. window_view_map_.empty() ? gfx::Insets()
  156. : window_view_map_.begin()->second->GetInsets();
  157. layout->set_between_child_spacing(kBetweenChildPaddingDp -
  158. cycle_item_insets.width());
  159. }
  160. WindowCycleView::~WindowCycleView() = default;
  161. void WindowCycleView::ScaleCycleView(const gfx::Rect& screen_bounds) {
  162. auto* layer_animator = layer()->GetAnimator();
  163. if (layer_animator->is_animating()) {
  164. // There is an existing scaling animation occurring. To accurately get the
  165. // new bounds for the next layout, we must abort the ongoing animation so
  166. // |this| will set the previous bounds of the widget and clear the clip
  167. // rect.
  168. layer_animator->AbortAllAnimations();
  169. }
  170. // |screen_bounds| is in screen coords so store it in local coordinates in
  171. // |new_bounds|.
  172. gfx::Rect old_bounds = GetLocalBounds();
  173. gfx::Rect new_bounds = gfx::Rect(screen_bounds.size());
  174. if (old_bounds == new_bounds)
  175. return;
  176. if (new_bounds.width() >= old_bounds.width()) {
  177. // In this case, the cycle view is growing. To achieve the scaling
  178. // animation we set the widget bounds immediately and scale the clipping
  179. // rect of |this|'s layer from where the |old_bounds| would be in the
  180. // new local coordinates.
  181. GetWidget()->SetBounds(screen_bounds);
  182. old_bounds +=
  183. gfx::Vector2d((new_bounds.width() - old_bounds.width()) / 2, 0);
  184. } else {
  185. // In this case, the cycle view is shrinking. To achieve the scaling
  186. // animation, we first scale the clipping rect and defer updating the
  187. // widget's bounds to when the animation is complete. If we instantly
  188. // laid out, then it wouldn't appear as though the background is
  189. // shrinking.
  190. new_bounds +=
  191. gfx::Vector2d((old_bounds.width() - new_bounds.width()) / 2, 0);
  192. defer_widget_bounds_update_ = true;
  193. }
  194. layer()->SetClipRect(old_bounds);
  195. ui::ScopedLayerAnimationSettings settings(layer_animator);
  196. settings.SetTransitionDuration(kToggleModeScaleDuration);
  197. settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN_2);
  198. settings.SetPreemptionStrategy(
  199. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  200. settings.AddObserver(this);
  201. layer()->SetClipRect(new_bounds);
  202. }
  203. gfx::Rect WindowCycleView::GetTargetBounds() const {
  204. // The widget is sized clamped to the screen bounds. Its child, the mirror
  205. // container which is parent to all the previews may be larger than the
  206. // widget as some previews will be offscreen. In Layout() of |cycle_view_|
  207. // the mirror container will be slid back and forth depending on the target
  208. // window.
  209. gfx::Rect widget_rect = root_window_->GetBoundsInScreen();
  210. widget_rect.ClampToCenteredSize(GetPreferredSize());
  211. return widget_rect;
  212. }
  213. void WindowCycleView::UpdateWindows(const WindowList& windows) {
  214. const bool no_windows = windows.empty();
  215. const bool is_interactive_alt_tab_mode_allowed =
  216. Shell::Get()->window_cycle_controller()->IsInteractiveAltTabModeAllowed();
  217. if (is_interactive_alt_tab_mode_allowed) {
  218. DCHECK(no_recent_items_label_);
  219. no_recent_items_label_->SetVisible(no_windows);
  220. }
  221. if (no_windows)
  222. return;
  223. for (auto* window : windows) {
  224. auto* view = mirror_container_->AddChildView(
  225. std::make_unique<WindowCycleItemView>(window));
  226. window_view_map_[window] = view;
  227. no_previews_set_.insert(view);
  228. }
  229. // If there was an ongoing drag session, it's now been completed so reset
  230. // |horizontal_distance_dragged_|.
  231. horizontal_distance_dragged_ = 0.f;
  232. gfx::Rect widget_rect = GetTargetBounds();
  233. if (is_interactive_alt_tab_mode_allowed)
  234. ScaleCycleView(widget_rect);
  235. else
  236. GetWidget()->SetBounds(widget_rect);
  237. SetTargetWindow(windows[0]);
  238. ScrollToWindow(windows[0]);
  239. }
  240. void WindowCycleView::FadeInLayer() {
  241. DCHECK(GetWidget());
  242. layer()->SetOpacity(0.f);
  243. ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
  244. settings.SetTransitionDuration(kFadeInDuration);
  245. settings.AddObserver(this);
  246. settings.CacheRenderSurface();
  247. ui::AnimationThroughputReporter reporter(
  248. settings.GetAnimator(),
  249. metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) {
  250. UMA_HISTOGRAM_PERCENTAGE(kShowAnimationSmoothness, smoothness);
  251. })));
  252. layer()->SetOpacity(1.f);
  253. }
  254. void WindowCycleView::ScrollToWindow(aura::Window* target) {
  255. current_window_ = target;
  256. // If there was an ongoing drag session, it's now been completed so reset
  257. // |horizontal_distance_dragged_|.
  258. horizontal_distance_dragged_ = 0.f;
  259. if (GetWidget())
  260. Layout();
  261. }
  262. void WindowCycleView::SetTargetWindow(aura::Window* target) {
  263. // Hide the focus border of the previous target window and show the focus
  264. // border of the new one.
  265. if (target_window_) {
  266. auto target_it = window_view_map_.find(target_window_);
  267. if (target_it != window_view_map_.end())
  268. target_it->second->UpdateBorderState(/*show=*/false);
  269. }
  270. target_window_ = target;
  271. auto target_it = window_view_map_.find(target_window_);
  272. if (target_it != window_view_map_.end())
  273. target_it->second->UpdateBorderState(/*show=*/true);
  274. // Focus the target window if the user is not currently switching the mode
  275. // while ChromeVox is on.
  276. // During the mode switch, we prevent ChromeVox auto-announce the window
  277. // title from the focus and send our custom string to announce both window
  278. // title and the selected mode together
  279. // (see `WindowCycleController::OnModeChanged`).
  280. auto* a11y_controller = Shell::Get()->accessibility_controller();
  281. auto* window_cycle_controller = Shell::Get()->window_cycle_controller();
  282. const bool chromevox_enabled = a11y_controller->spoken_feedback().enabled();
  283. const bool is_switching_mode = window_cycle_controller->IsSwitchingMode();
  284. if (!target_window_ || (chromevox_enabled && is_switching_mode))
  285. return;
  286. if (GetWidget()) {
  287. window_view_map_[target_window_]->RequestFocus();
  288. } else {
  289. SetInitiallyFocusedView(window_view_map_[target_window_]);
  290. // When alt-tab mode selection is available, announce via ChromeVox the
  291. // current mode and the directional cue for mode switching.
  292. if (window_cycle_controller->IsInteractiveAltTabModeAllowed()) {
  293. a11y_controller->TriggerAccessibilityAlertWithMessage(
  294. l10n_util::GetStringUTF8(
  295. window_cycle_controller->IsAltTabPerActiveDesk()
  296. ? IDS_ASH_ALT_TAB_FOCUS_CURRENT_DESK_MODE
  297. : IDS_ASH_ALT_TAB_FOCUS_ALL_DESKS_MODE));
  298. }
  299. }
  300. }
  301. void WindowCycleView::HandleWindowDestruction(aura::Window* destroying_window,
  302. aura::Window* new_target) {
  303. auto view_iter = window_view_map_.find(destroying_window);
  304. WindowCycleItemView* preview = view_iter->second;
  305. views::View* parent = preview->parent();
  306. DCHECK_EQ(mirror_container_, parent);
  307. window_view_map_.erase(view_iter);
  308. no_previews_set_.erase(preview);
  309. delete preview;
  310. // With one of its children now gone, we must re-layout |mirror_container_|.
  311. // This must happen before ScrollToWindow() to make sure our own Layout()
  312. // works correctly when it's calculating highlight bounds.
  313. parent->Layout();
  314. SetTargetWindow(new_target);
  315. ScrollToWindow(new_target);
  316. }
  317. void WindowCycleView::DestroyContents() {
  318. is_destroying_ = true;
  319. window_view_map_.clear();
  320. no_previews_set_.clear();
  321. target_window_ = nullptr;
  322. current_window_ = nullptr;
  323. defer_widget_bounds_update_ = false;
  324. RemoveAllChildViews();
  325. OnFlingEnd();
  326. }
  327. void WindowCycleView::Drag(float delta_x) {
  328. horizontal_distance_dragged_ += delta_x;
  329. Layout();
  330. }
  331. void WindowCycleView::StartFling(float velocity_x) {
  332. fling_handler_ = std::make_unique<WmFlingHandler>(
  333. gfx::Vector2dF(velocity_x, 0),
  334. GetWidget()->GetNativeWindow()->GetRootWindow(),
  335. base::BindRepeating(&WindowCycleView::OnFlingStep,
  336. base::Unretained(this)),
  337. base::BindRepeating(&WindowCycleView::OnFlingEnd,
  338. base::Unretained(this)));
  339. }
  340. bool WindowCycleView::OnFlingStep(float offset) {
  341. DCHECK(fling_handler_);
  342. horizontal_distance_dragged_ += offset;
  343. Layout();
  344. return true;
  345. }
  346. void WindowCycleView::OnFlingEnd() {
  347. fling_handler_.reset();
  348. }
  349. void WindowCycleView::SetFocusTabSlider(bool focus) {
  350. tab_slider_container_->SetFocus(focus);
  351. }
  352. bool WindowCycleView::IsTabSliderFocused() {
  353. DCHECK(tab_slider_container_);
  354. return tab_slider_container_->is_focused();
  355. }
  356. aura::Window* WindowCycleView::GetWindowAtPoint(
  357. const gfx::Point& screen_point) {
  358. for (const auto& entry : window_view_map_) {
  359. if (entry.second->GetBoundsInScreen().Contains(screen_point))
  360. return entry.first;
  361. }
  362. return nullptr;
  363. }
  364. void WindowCycleView::OnModePrefsChanged() {
  365. if (tab_slider_container_)
  366. tab_slider_container_->OnModePrefsChanged();
  367. }
  368. gfx::Size WindowCycleView::CalculatePreferredSize() const {
  369. gfx::Size size = GetContentContainerBounds().size();
  370. // |mirror_container_| can have window list that overflow out of the
  371. // screen, but the window cycle view with a bandshield, cropping the
  372. // overflow window list, should remain within the specified horizontal
  373. // insets of the screen width.
  374. const int max_width = CalculateMaxWidth();
  375. size.set_width(std::min(size.width(), max_width));
  376. if (Shell::Get()
  377. ->window_cycle_controller()
  378. ->IsInteractiveAltTabModeAllowed()) {
  379. DCHECK(tab_slider_container_);
  380. // |mirror_container_| can have window list with width smaller the tab
  381. // slider's width. The padding should be 64px from the tab slider.
  382. const int min_width = tab_slider_container_->GetPreferredSize().width() +
  383. 2 * WindowCycleView::kInsideBorderHorizontalPaddingDp;
  384. size.set_width(std::max(size.width(), min_width));
  385. size.Enlarge(0, tab_slider_container_->GetPreferredSize().height() +
  386. kTabSliderContainerVerticalPaddingDp);
  387. }
  388. return size;
  389. }
  390. void WindowCycleView::Layout() {
  391. if (is_destroying_)
  392. return;
  393. const bool is_interactive_alt_tab_mode_allowed =
  394. Shell::Get()->window_cycle_controller()->IsInteractiveAltTabModeAllowed();
  395. if (bounds().IsEmpty() || (!is_interactive_alt_tab_mode_allowed &&
  396. (!target_window_ || !current_window_))) {
  397. return;
  398. }
  399. const bool first_layout = mirror_container_->bounds().IsEmpty();
  400. // If |mirror_container_| has not yet been laid out, we must lay it and
  401. // its descendants out so that the calculations based on |target_view|
  402. // work properly.
  403. if (first_layout) {
  404. mirror_container_->SizeToPreferredSize();
  405. layer()->SetRoundedCornerRadius(
  406. gfx::RoundedCornersF{kBackgroundCornerRadius});
  407. }
  408. gfx::RectF target_bounds;
  409. if (current_window_ || !is_interactive_alt_tab_mode_allowed) {
  410. views::View* target_view = window_view_map_[current_window_];
  411. target_bounds = gfx::RectF(target_view->GetLocalBounds());
  412. views::View::ConvertRectToTarget(target_view, mirror_container_,
  413. &target_bounds);
  414. } else {
  415. DCHECK(no_recent_items_label_);
  416. target_bounds = gfx::RectF(no_recent_items_label_->bounds());
  417. }
  418. // Content container represents the mirror container with >=1 windows or
  419. // no-recent-items label when there is no window to be shown.
  420. gfx::Rect content_container_bounds = GetContentContainerBounds();
  421. // Case one: the container is narrower than the screen. Center the
  422. // container.
  423. int x_offset = (width() - content_container_bounds.width()) / 2;
  424. if (x_offset < 0) {
  425. // Case two: the container is wider than the screen. Center the target
  426. // view by moving the list just enough to ensure the target view is in
  427. // the center. Additionally, offset by however much the user has dragged.
  428. x_offset = width() / 2 - mirror_container_->GetMirroredXInView(
  429. target_bounds.CenterPoint().x());
  430. // However, the container must span the screen, i.e. the maximum x is 0
  431. // and the minimum for its right boundary is the width of the screen.
  432. int minimum_x = width() - content_container_bounds.width();
  433. x_offset = base::clamp(x_offset, minimum_x, 0);
  434. // If the user has dragged, offset the container based on how much they
  435. // have dragged. Cap |horizontal_distance_dragged_| based on the available
  436. // distance from the container to the left and right boundaries.
  437. float clamped_horizontal_distance_dragged = base::clamp(
  438. horizontal_distance_dragged_, static_cast<float>(minimum_x - x_offset),
  439. static_cast<float>(-x_offset));
  440. if (horizontal_distance_dragged_ != clamped_horizontal_distance_dragged)
  441. OnFlingEnd();
  442. horizontal_distance_dragged_ = clamped_horizontal_distance_dragged;
  443. x_offset += horizontal_distance_dragged_;
  444. }
  445. content_container_bounds.set_x(x_offset);
  446. // Layout a tab slider if there is more than one desk.
  447. if (is_interactive_alt_tab_mode_allowed) {
  448. // TODO(crbug.com/1216238): Change these back to DCHECKs once the bug is
  449. // resolved.
  450. CHECK(tab_slider_container_);
  451. CHECK(no_recent_items_label_);
  452. // Layout the tab slider.
  453. const gfx::Size tab_slider_size = tab_slider_container_->GetPreferredSize();
  454. const gfx::Rect tab_slider_mirror_container_bounds(
  455. (width() - tab_slider_size.width()) / 2,
  456. kTabSliderContainerVerticalPaddingDp, tab_slider_size.width(),
  457. tab_slider_size.height());
  458. tab_slider_container_->SetBoundsRect(tab_slider_mirror_container_bounds);
  459. // Move window cycle container down.
  460. content_container_bounds.set_y(tab_slider_container_->y() +
  461. tab_slider_container_->height());
  462. // Unlike the bounds of scrollable mirror container, the bounds of label
  463. // should not overflow out of the screen.
  464. const gfx::Rect no_recent_item_bounds_(
  465. std::max(0, content_container_bounds.x()), content_container_bounds.y(),
  466. std::min(width(), content_container_bounds.width()),
  467. content_container_bounds.height());
  468. no_recent_items_label_->SetBoundsRect(no_recent_item_bounds_);
  469. }
  470. // Enable animations only after the first Layout() pass. If |this| is
  471. // animating or |defer_widget_bounds_update_|, don't animate as well since
  472. // the cycle view is already being animated or just finished animating for
  473. // mode switch.
  474. std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
  475. absl::optional<ui::AnimationThroughputReporter> reporter;
  476. if (!first_layout && !this->layer()->GetAnimator()->is_animating() &&
  477. !defer_widget_bounds_update_ &&
  478. mirror_container_->bounds() != content_container_bounds) {
  479. settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
  480. mirror_container_->layer()->GetAnimator());
  481. settings->SetTransitionDuration(kContainerSlideDuration);
  482. reporter.emplace(
  483. settings->GetAnimator(),
  484. metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) {
  485. // Reports animation metrics when the mirror container, which holds
  486. // all the preview views slides along the x-axis. This can happen
  487. // while tabbing through windows, if the window cycle ui spans the
  488. // length of the display.
  489. UMA_HISTOGRAM_PERCENTAGE(kContainerAnimationSmoothness, smoothness);
  490. })));
  491. }
  492. mirror_container_->SetBoundsRect(content_container_bounds);
  493. // If an element in |no_previews_set_| is no onscreen (its bounds in |this|
  494. // coordinates intersects |this|), create the rest of its elements and
  495. // remove it from the set.
  496. const gfx::RectF local_bounds(GetLocalBounds());
  497. for (auto it = no_previews_set_.begin(); it != no_previews_set_.end();) {
  498. WindowCycleItemView* view = *it;
  499. gfx::RectF bounds(view->GetLocalBounds());
  500. views::View::ConvertRectToTarget(view, this, &bounds);
  501. if (bounds.Intersects(local_bounds)) {
  502. view->ShowPreview();
  503. it = no_previews_set_.erase(it);
  504. } else {
  505. ++it;
  506. }
  507. }
  508. }
  509. void WindowCycleView::OnImplicitAnimationsCompleted() {
  510. occlusion_tracker_pauser_.reset();
  511. layer()->SetClipRect(gfx::Rect());
  512. if (defer_widget_bounds_update_) {
  513. // This triggers a Layout() so reset |defer_widget_bounds_update_| after
  514. // calling SetBounds() to prevent the mirror container from animating.
  515. GetWidget()->SetBounds(GetTargetBounds());
  516. defer_widget_bounds_update_ = false;
  517. }
  518. }
  519. void WindowCycleView::OnThemeChanged() {
  520. views::View::OnThemeChanged();
  521. background()->SetNativeControlColor(
  522. AshColorProvider::Get()->GetBaseLayerColor(
  523. AshColorProvider::BaseLayerType::kTransparent80));
  524. if (chromeos::features::IsDarkLightModeEnabled()) {
  525. SetBorder(std::make_unique<views::HighlightBorder>(
  526. kBackgroundCornerRadius,
  527. views::HighlightBorder::Type::kHighlightBorder1,
  528. /*use_light_colors=*/false));
  529. }
  530. }
  531. bool WindowCycleView::IsEventInTabSliderContainer(
  532. const gfx::Point& screen_point) {
  533. return tab_slider_container_ &&
  534. tab_slider_container_->GetBoundsInScreen().Contains(screen_point);
  535. }
  536. int WindowCycleView::CalculateMaxWidth() const {
  537. return root_window_->GetBoundsInScreen().size().width() -
  538. 2 * kBackgroundHorizontalInsetDp;
  539. }
  540. gfx::Rect WindowCycleView::GetContentContainerBounds() const {
  541. const bool empty_mirror_container = mirror_container_->children().empty();
  542. if (empty_mirror_container && no_recent_items_label_)
  543. return gfx::Rect(no_recent_items_label_->GetPreferredSize());
  544. return gfx::Rect(mirror_container_->GetPreferredSize());
  545. }
  546. BEGIN_METADATA(WindowCycleView, views::WidgetDelegateView)
  547. END_METADATA
  548. } // namespace ash