blob: 993898c25979a7795d9816700ed88e7aa5830a24 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/window_cycle_list.h"
#include <map>
#include <string>
#include <utility>
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/metrics_util.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/style/ash_color_provider.h"
#include "ash/wm/window_mini_view.h"
#include "ash/wm/window_preview_view.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/ranges.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/aura/scoped_window_targeter.h"
#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/display.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/animation/bounds_animator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_animations.h"
namespace ash {
namespace {
bool g_disable_initial_delay = false;
// Shield rounded corner radius
constexpr gfx::RoundedCornersF kBackgroundCornerRadius{4.f};
// Shield background blur sigma.
constexpr float kBackgroundBlurSigma =
static_cast<float>(AshColorProvider::LayerBlurSigma::kBlurDefault);
// Quality of the shield background blur.
constexpr float kBackgroundBlurQuality = 0.33f;
// All previews are the same height (this is achieved via a combination of
// scaling and padding).
constexpr int kFixedPreviewHeightDp = 256;
// The min and max width for preview size are in relation to the fixed height.
constexpr int kMinPreviewWidthDp = kFixedPreviewHeightDp / 2;
constexpr int kMaxPreviewWidthDp = kFixedPreviewHeightDp * 2;
// Padding between the alt-tab bandshield and the window previews.
constexpr int kInsideBorderHorizontalPaddingDp = 64;
constexpr int kInsideBorderVerticalPaddingDp = 60;
// Padding between the window previews within the alt-tab bandshield.
constexpr int kBetweenChildPaddingDp = 10;
// The UMA histogram that logs smoothness of the fade-in animation.
constexpr char kShowAnimationSmoothness[] =
"Ash.WindowCycleView.AnimationSmoothness.Show";
// The UMA histogram that logs smoothness of the window container animation.
constexpr char kContainerAnimationSmoothness[] =
"Ash.WindowCycleView.AnimationSmoothness.Container";
// Delay before the UI fade in animation starts. This is so users can switch
// quickly between windows without bringing up the UI.
constexpr base::TimeDelta kShowDelayDuration =
base::TimeDelta::FromMilliseconds(150);
// Duration of the window cycle UI fade in animation.
constexpr base::TimeDelta kFadeInDuration =
base::TimeDelta::FromMilliseconds(100);
// Duration of the window cycle elements slide animation.
constexpr base::TimeDelta kContainerSlideDuration =
base::TimeDelta::FromMilliseconds(120);
// The alt-tab cycler widget is not activatable (except when ChromeVox is on),
// so we use WindowTargeter to send input events to the widget.
class CustomWindowTargeter : public aura::WindowTargeter {
public:
explicit CustomWindowTargeter(aura::Window* tab_cycler)
: tab_cycler_(tab_cycler) {}
CustomWindowTargeter(const CustomWindowTargeter&) = delete;
CustomWindowTargeter& operator=(const CustomWindowTargeter&) = delete;
~CustomWindowTargeter() override = default;
// aura::WindowTargeter:
ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
ui::Event* event) override {
if (event->IsLocatedEvent())
return aura::WindowTargeter::FindTargetForEvent(root, event);
return tab_cycler_;
}
private:
aura::Window* tab_cycler_;
};
} // namespace
// This view represents a single aura::Window by displaying a title and a
// thumbnail of the window's contents.
class WindowCycleItemView : public WindowMiniView {
public:
explicit WindowCycleItemView(aura::Window* window) : WindowMiniView(window) {
SetFocusBehavior(FocusBehavior::ALWAYS);
SetNotifyEnterExitOnChild(true);
}
WindowCycleItemView(const WindowCycleItemView&) = delete;
WindowCycleItemView& operator=(const WindowCycleItemView&) = delete;
~WindowCycleItemView() override = default;
// Shows the preview and icon. For performance reasons, these are not created
// on construction. This should be called at most one time during the lifetime
// of |this|.
void ShowPreview() {
DCHECK(!preview_view());
UpdateIconView();
SetShowPreview(/*show=*/true);
UpdatePreviewRoundedCorners(/*show=*/true);
}
// views::View:
void OnMouseEntered(const ui::MouseEvent& event) override {
Shell::Get()->window_cycle_controller()->SetFocusedWindow(source_window());
}
bool OnMousePressed(const ui::MouseEvent& event) override {
Shell::Get()->window_cycle_controller()->SetFocusedWindow(source_window());
Shell::Get()->window_cycle_controller()->CompleteCycling();
return true;
}
void OnGestureEvent(ui::GestureEvent* event) override {
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN:
case ui::ET_GESTURE_SHOW_PRESS:
case ui::ET_GESTURE_LONG_TAP:
case ui::ET_GESTURE_TAP:
Shell::Get()->window_cycle_controller()->SetFocusedWindow(
source_window());
event->SetHandled();
break;
case ui::ET_GESTURE_END:
Shell::Get()->window_cycle_controller()->CompleteCycling();
event->SetHandled();
break;
default:
break;
}
}
private:
// WindowMiniView:
gfx::Size GetPreviewViewSize() const override {
// When the preview is not shown, do an estimate of the expected size.
// |this| will not be visible anyways, and will get corrected once
// ShowPreview() is called.
if (!preview_view()) {
gfx::SizeF source_size(source_window()->bounds().size());
// Windows may have no size in tests.
if (source_size.IsEmpty())
return gfx::Size();
const float aspect_ratio = source_size.width() / source_size.height();
return gfx::Size(kFixedPreviewHeightDp * aspect_ratio,
kFixedPreviewHeightDp);
}
// Returns the size for the preview view, scaled to fit within the max
// bounds. Scaling is always 1:1 and we only scale down, never up.
gfx::Size preview_pref_size = preview_view()->GetPreferredSize();
if (preview_pref_size.width() > kMaxPreviewWidthDp ||
preview_pref_size.height() > kFixedPreviewHeightDp) {
const float scale =
std::min(kMaxPreviewWidthDp / float{preview_pref_size.width()},
kFixedPreviewHeightDp / float{preview_pref_size.height()});
preview_pref_size =
gfx::ScaleToFlooredSize(preview_pref_size, scale, scale);
}
return preview_pref_size;
}
// views::View:
void Layout() override {
WindowMiniView::Layout();
if (!preview_view())
return;
// Show the backdrop if the preview view does not take up all the bounds
// allocated for it.
gfx::Rect preview_max_bounds = GetContentsBounds();
preview_max_bounds.Subtract(GetHeaderBounds());
const gfx::Rect preview_area_bounds = preview_view()->bounds();
SetBackdropVisibility(preview_max_bounds.size() !=
preview_area_bounds.size());
}
gfx::Size CalculatePreferredSize() const override {
// Previews can range in width from half to double of
// |kFixedPreviewHeightDp|. Padding will be added to the sides to achieve
// this if the preview is too narrow.
gfx::Size preview_size = GetPreviewViewSize();
// All previews are the same height (this may add padding on top and
// bottom).
preview_size.set_height(kFixedPreviewHeightDp);
// Previews should never be narrower than half or wider than double their
// fixed height.
preview_size.set_width(base::ClampToRange(
preview_size.width(), kMinPreviewWidthDp, kMaxPreviewWidthDp));
const int margin = GetInsets().width();
preview_size.Enlarge(margin, margin + WindowMiniView::kHeaderHeightDp);
return preview_size;
}
};
// A view that shows a collection of windows the user can tab through.
class WindowCycleView : public views::WidgetDelegateView,
public ui::ImplicitAnimationObserver {
public:
explicit WindowCycleView(const WindowCycleList::WindowList& windows) {
DCHECK(!windows.empty());
// Start the occlusion tracker pauser. It's used to increase smoothness for
// the fade in but we also create windows here which may occlude other
// windows.
occlusion_tracker_pauser_ =
std::make_unique<aura::WindowOcclusionTracker::ScopedPause>();
// The layer for |this| is responsible for showing color, background blur
// and fading in.
SetPaintToLayer(ui::LAYER_SOLID_COLOR);
ui::Layer* layer = this->layer();
SkColor background_color = AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kTransparent80);
layer->SetColor(background_color);
layer->SetBackgroundBlur(kBackgroundBlurSigma);
layer->SetBackdropFilterQuality(kBackgroundBlurQuality);
layer->SetName("WindowCycleView");
// |mirror_container_| may be larger than |this|. In this case, it will be
// shifted along the x-axis when the user tabs through. It is a container
// for the previews and has no rendered content.
mirror_container_ = AddChildView(std::make_unique<views::View>());
mirror_container_->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
mirror_container_->layer()->SetName("WindowCycleView/MirrorContainer");
views::BoxLayout* layout =
mirror_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(kInsideBorderVerticalPaddingDp,
kInsideBorderHorizontalPaddingDp),
kBetweenChildPaddingDp));
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
for (auto* window : windows) {
// |mirror_container_| owns |view|. The |preview_view_| in |view| will
// use trilinear filtering in InitLayerOwner().
auto* view = mirror_container_->AddChildView(
std::make_unique<WindowCycleItemView>(window));
window_view_map_[window] = view;
no_previews_set_.insert(view);
}
// The insets in the WindowCycleItemView are coming from its border, which
// paints the focus ring around the view when it is highlighted. Exclude the
// insets such that the spacing between the contents of the views rather
// than the views themselves is |kBetweenChildPaddingDp|.
const gfx::Insets cycle_item_insets =
window_view_map_.begin()->second->GetInsets();
layout->set_between_child_spacing(kBetweenChildPaddingDp -
cycle_item_insets.width());
}
WindowCycleView(const WindowCycleView&) = delete;
WindowCycleView& operator=(const WindowCycleView&) = delete;
~WindowCycleView() override = default;
void UpdateWindows(const WindowCycleList::WindowList& windows) {
for (auto* window : windows) {
auto* view = mirror_container_->AddChildView(
std::make_unique<WindowCycleItemView>(window));
window_view_map_[window] = view;
no_previews_set_.insert(view);
}
// Resize the widget.
aura::Window* root_window = Shell::GetRootWindowForNewWindows();
gfx::Rect widget_rect = root_window->GetBoundsInScreen();
widget_rect.ClampToCenteredSize(GetPreferredSize());
GetWidget()->SetBounds(widget_rect);
SetTargetWindow(windows[0]);
ScrollToWindow(windows[0]);
}
void FadeInLayer() {
DCHECK(GetWidget());
layer()->SetOpacity(0.f);
ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
settings.SetTransitionDuration(kFadeInDuration);
settings.AddObserver(this);
settings.CacheRenderSurface();
ui::AnimationThroughputReporter reporter(
settings.GetAnimator(),
metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) {
UMA_HISTOGRAM_PERCENTAGE(kShowAnimationSmoothness, smoothness);
})));
layer()->SetOpacity(1.f);
}
void ScrollToWindow(aura::Window* target) {
current_window_ = target;
if (GetWidget()) {
Layout();
}
}
void SetTargetWindow(aura::Window* target) {
// Hide the focus border of the previous target window and show the focus
// border of the new one.
if (target_window_) {
auto target_it = window_view_map_.find(target_window_);
if (target_it != window_view_map_.end())
target_it->second->UpdateBorderState(/*show=*/false);
}
target_window_ = target;
auto target_it = window_view_map_.find(target_window_);
if (target_it != window_view_map_.end())
target_it->second->UpdateBorderState(/*show=*/true);
if (target_window_) {
if (GetWidget())
window_view_map_[target_window_]->RequestFocus();
else
SetInitiallyFocusedView(window_view_map_[target_window_]);
}
}
void HandleWindowDestruction(aura::Window* destroying_window,
aura::Window* new_target) {
auto view_iter = window_view_map_.find(destroying_window);
WindowCycleItemView* preview = view_iter->second;
views::View* parent = preview->parent();
DCHECK_EQ(mirror_container_, parent);
window_view_map_.erase(view_iter);
no_previews_set_.erase(preview);
delete preview;
// With one of its children now gone, we must re-layout
// |mirror_container_|. This must happen before ScrollToWindow() to make
// sure our own Layout() works correctly when it's calculating highlight
// bounds.
parent->Layout();
SetTargetWindow(new_target);
ScrollToWindow(new_target);
}
void DestroyContents() {
window_view_map_.clear();
no_previews_set_.clear();
target_window_ = nullptr;
current_window_ = nullptr;
RemoveAllChildViews(true);
}
// views::WidgetDelegateView:
gfx::Size CalculatePreferredSize() const override {
return mirror_container_->GetPreferredSize();
}
void Layout() override {
if (!target_window_ || !current_window_ || bounds().IsEmpty())
return;
const bool first_layout = mirror_container_->bounds().IsEmpty();
// If |mirror_container_| has not yet been laid out, we must lay it and
// its descendants out so that the calculations based on |target_view|
// work properly.
if (first_layout) {
mirror_container_->SizeToPreferredSize();
// Give rounded corners to our layer if it takes up less space than the
// width of the screen (our width will match |mirror_container_|'s if the
// widget's width is less than that of the screen).
if (mirror_container_->GetPreferredSize().width() <= width())
layer()->SetRoundedCornerRadius(kBackgroundCornerRadius);
}
views::View* target_view = window_view_map_[current_window_];
gfx::RectF target_bounds(target_view->GetLocalBounds());
views::View::ConvertRectToTarget(target_view, mirror_container_,
&target_bounds);
gfx::Rect container_bounds(mirror_container_->GetPreferredSize());
// Case one: the container is narrower than the screen. Center the
// container.
int x_offset = (width() - container_bounds.width()) / 2;
if (x_offset < 0) {
// Case two: the container is wider than the screen. Center the target
// view by moving the list just enough to ensure the target view is in
// the center.
x_offset = width() / 2 - mirror_container_->GetMirroredXInView(
target_bounds.CenterPoint().x());
// However, the container must span the screen, i.e. the maximum x is 0
// and the minimum for its right boundary is the width of the screen.
x_offset = std::min(x_offset, 0);
x_offset = std::max(x_offset, width() - container_bounds.width());
}
container_bounds.set_x(x_offset);
// Enable animations only after the first Layout() pass.
std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
base::Optional<ui::AnimationThroughputReporter> reporter;
if (!first_layout) {
settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
mirror_container_->layer()->GetAnimator());
settings->SetTransitionDuration(kContainerSlideDuration);
reporter.emplace(
settings->GetAnimator(),
metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) {
// Reports animation metrics when the mirror container, which holds
// all the preview views slides along the x-axis. This can happen
// while tabbing through windows, if the window cycle ui spans the
// length of the display.
UMA_HISTOGRAM_PERCENTAGE(kContainerAnimationSmoothness, smoothness);
})));
}
mirror_container_->SetBoundsRect(container_bounds);
// If an element in |no_previews_set_| is no onscreen (its bounds in |this|
// coordinates intersects |this|), create the rest of its elements and
// remove it from the set.
const gfx::RectF local_bounds(GetLocalBounds());
for (auto it = no_previews_set_.begin(); it != no_previews_set_.end();) {
WindowCycleItemView* view = *it;
gfx::RectF bounds(view->GetLocalBounds());
views::View::ConvertRectToTarget(view, this, &bounds);
if (bounds.Intersects(local_bounds)) {
view->ShowPreview();
it = no_previews_set_.erase(it);
} else {
++it;
}
}
}
aura::Window* GetTargetWindow() { return target_window_; }
const views::View::Views& GetPreviewViewsForTesting() const {
return mirror_container_->children();
}
const aura::Window* GetTargetWindowForTesting() const {
return target_window_;
}
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override {
occlusion_tracker_pauser_.reset();
}
private:
std::map<aura::Window*, WindowCycleItemView*> window_view_map_;
views::View* mirror_container_ = nullptr;
// The |target_window_| is the window that has the focus ring. When the user
// completes cycling the |target_window_| is activated.
aura::Window* target_window_ = nullptr;
// The |current_window_| is the window that the window cycle list uses to
// determine the layout and positioning of the list's items. If this window's
// preview can equally divide the list it is centered, otherwise it is
// off-center.
aura::Window* current_window_ = nullptr;
// Set which contains items which have been created but have some of their
// performance heavy elements not created yet. These elements will be created
// once onscreen to improve fade in performance, then removed from this set.
base::flat_set<WindowCycleItemView*> no_previews_set_;
// Used for preventng occlusion state computations for the duration of the
// fade in animation.
std::unique_ptr<aura::WindowOcclusionTracker::ScopedPause>
occlusion_tracker_pauser_;
};
WindowCycleList::WindowCycleList(const WindowList& windows)
: windows_(windows) {
if (!ShouldShowUi())
Shell::Get()->mru_window_tracker()->SetIgnoreActivations(true);
for (auto* window : windows_)
window->AddObserver(this);
if (ShouldShowUi()) {
// Disable the tab scrubber so three finger scrolling doesn't scrub tabs as
// well.
Shell::Get()->shell_delegate()->SetTabScrubberEnabled(false);
if (g_disable_initial_delay) {
InitWindowCycleView();
} else {
show_ui_timer_.Start(FROM_HERE, kShowDelayDuration, this,
&WindowCycleList::InitWindowCycleView);
}
}
}
WindowCycleList::~WindowCycleList() {
if (!ShouldShowUi())
Shell::Get()->mru_window_tracker()->SetIgnoreActivations(false);
Shell::Get()->shell_delegate()->SetTabScrubberEnabled(true);
for (auto* window : windows_)
window->RemoveObserver(this);
if (cycle_ui_widget_)
cycle_ui_widget_->Close();
// Store the target window before |cycle_view_| is destroyed.
aura::Window* target_window = nullptr;
// |this| is responsible for notifying |cycle_view_| when windows are
// destroyed. Since |this| is going away, clobber |cycle_view_|. Otherwise
// there will be a race where a window closes after now but before the
// Widget::Close() call above actually destroys |cycle_view_|. See
// crbug.com/681207
if (cycle_view_) {
target_window = cycle_view_->GetTargetWindow();
cycle_view_->DestroyContents();
}
// While the cycler widget is shown, the windows listed in the cycler is
// marked as force-visible and don't contribute to occlusion. In order to
// work occlusion calculation properly, we need to activate a window after
// the widget has been destroyed. See b/138914552.
if (!windows_.empty() && user_did_accept_) {
if (!target_window)
target_window = windows_[current_index_];
SelectWindow(target_window);
}
Shell::Get()->frame_throttling_controller()->EndThrottling();
}
void WindowCycleList::ReplaceWindows(const WindowList& windows) {
if (windows.empty())
return;
RemoveAllWindows();
windows_ = windows;
for (auto* new_window : windows_)
new_window->AddObserver(this);
if (ShouldShowUi() && cycle_view_)
cycle_view_->UpdateWindows(windows_);
}
void WindowCycleList::Step(WindowCycleController::Direction direction) {
if (windows_.empty())
return;
// If the position of the window cycle list is out-of-sync with the currently
// selected item, scroll to the selected item and then step.
if (cycle_view_) {
aura::Window* selected_window = cycle_view_->GetTargetWindow();
Scroll(GetIndexOfWindow(selected_window) - current_index_);
}
const int offset = direction == WindowCycleController::FORWARD ? 1 : -1;
SetFocusedWindow(windows_[GetOffsettedWindowIndex(offset)]);
Scroll(offset);
}
void WindowCycleList::ScrollInDirection(
WindowCycleController::Direction direction) {
if (windows_.empty())
return;
const int offset = direction == WindowCycleController::FORWARD ? 1 : -1;
Scroll(offset);
}
void WindowCycleList::SetFocusedWindow(aura::Window* window) {
if (windows_.empty())
return;
if (ShouldShowUi() && cycle_view_)
cycle_view_->SetTargetWindow(windows_[GetIndexOfWindow(window)]);
}
bool WindowCycleList::IsEventInCycleView(ui::LocatedEvent* event) {
if (!cycle_view_)
return false;
aura::Window* target = static_cast<aura::Window*>(event->target());
aura::Window* event_root = target->GetRootWindow();
gfx::Point event_screen_point = event->root_location();
wm::ConvertPointToScreen(event_root, &event_screen_point);
return cycle_view_->GetBoundsInScreen().Contains(event_screen_point);
}
bool WindowCycleList::ShouldShowUi() {
return windows_.size() > 1u;
}
// static
void WindowCycleList::DisableInitialDelayForTesting() {
g_disable_initial_delay = true;
}
void WindowCycleList::OnWindowDestroying(aura::Window* window) {
window->RemoveObserver(this);
WindowList::iterator i = std::find(windows_.begin(), windows_.end(), window);
// TODO(oshima): Change this back to DCHECK once crbug.com/483491 is fixed.
CHECK(i != windows_.end());
int removed_index = static_cast<int>(i - windows_.begin());
windows_.erase(i);
if (current_index_ > removed_index ||
current_index_ == static_cast<int>(windows_.size())) {
current_index_--;
}
if (cycle_view_) {
auto* new_target_window =
windows_.empty() ? nullptr : windows_[current_index_];
cycle_view_->HandleWindowDestruction(window, new_target_window);
if (windows_.empty()) {
// This deletes us.
Shell::Get()->window_cycle_controller()->CancelCycling();
return;
}
}
}
void WindowCycleList::OnDisplayMetricsChanged(const display::Display& display,
uint32_t changed_metrics) {
if (cycle_ui_widget_ &&
display.id() ==
display::Screen::GetScreen()
->GetDisplayNearestWindow(cycle_ui_widget_->GetNativeWindow())
.id() &&
(changed_metrics & (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION))) {
Shell::Get()->window_cycle_controller()->CancelCycling();
// |this| is deleted.
return;
}
}
void WindowCycleList::RemoveAllWindows() {
for (auto* window : windows_) {
window->RemoveObserver(this);
if (cycle_view_)
cycle_view_->HandleWindowDestruction(window, nullptr);
}
windows_.clear();
current_index_ = 0;
window_selected_ = false;
}
void WindowCycleList::InitWindowCycleView() {
if (cycle_view_)
return;
cycle_view_ = new WindowCycleView(windows_);
cycle_view_->SetTargetWindow(windows_[current_index_]);
cycle_view_->ScrollToWindow(windows_[current_index_]);
// We need to activate the widget if ChromeVox is enabled as ChromeVox
// relies on activation.
const bool spoken_feedback_enabled =
Shell::Get()->accessibility_controller()->spoken_feedback().enabled();
views::Widget* widget = new views::Widget();
views::Widget::InitParams params;
params.delegate = cycle_view_;
params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.layer_type = ui::LAYER_NOT_DRAWN;
// Don't let the alt-tab cycler be activatable. This lets the currently
// activated window continue to be in the foreground. This may affect
// things such as video automatically pausing/playing.
if (!spoken_feedback_enabled)
params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
params.accept_events = true;
params.name = "WindowCycleList (Alt+Tab)";
// TODO(estade): make sure nothing untoward happens when the lock screen
// or a system modal dialog is shown.
aura::Window* root_window = Shell::GetRootWindowForNewWindows();
params.parent = root_window->GetChildById(kShellWindowId_OverlayContainer);
// The widget is sized clamped to the screen bounds. Its child, the mirror
// container which is parent to all the previews may be larger than the widget
// as some previews will be offscreen. In Layout() of |cyclev_view_| the
// mirror container will be slid back and forth depending on the target
// window.
gfx::Rect widget_rect = root_window->GetBoundsInScreen();
widget_rect.ClampToCenteredSize(cycle_view_->GetPreferredSize());
params.bounds = widget_rect;
screen_observer_.Add(display::Screen::GetScreen());
widget->Init(std::move(params));
widget->Show();
cycle_view_->FadeInLayer();
cycle_ui_widget_ = widget;
// Since this window is not activated, grab events.
if (!spoken_feedback_enabled) {
window_targeter_ = std::make_unique<aura::ScopedWindowTargeter>(
widget->GetNativeWindow()->GetRootWindow(),
std::make_unique<CustomWindowTargeter>(widget->GetNativeWindow()));
}
// Close the app list, if it's open in clamshell mode.
if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
Shell::Get()->app_list_controller()->DismissAppList();
Shell::Get()->frame_throttling_controller()->StartThrottling(windows_);
}
void WindowCycleList::SelectWindow(aura::Window* window) {
// If the list has only one window, the window can be selected twice (in
// Scroll() and the destructor). This causes ARC PIP windows to be restored
// twice, which leads to a wrong window state.
if (window_selected_)
return;
if (window->GetProperty(kPipOriginalWindowKey)) {
window_util::ExpandArcPipWindow();
} else {
window->Show();
WindowState::Get(window)->Activate();
}
window_selected_ = true;
}
void WindowCycleList::Scroll(int offset) {
if (windows_.empty())
return;
// When there is only one window, we should give feedback to the user. If
// the window is minimized, we should also show it.
if (windows_.size() == 1) {
::wm::AnimateWindow(windows_[0], ::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
SelectWindow(windows_[0]);
return;
}
DCHECK(static_cast<size_t>(current_index_) < windows_.size());
if (!cycle_view_ && current_index_ == 0) {
// Special case the situation where we're cycling forward but the MRU
// window is not active. This occurs when all windows are minimized. The
// starting window should be the first one rather than the second.
if (offset == 1 && !wm::IsActiveWindow(windows_[0]))
current_index_ = -1;
}
current_index_ = GetOffsettedWindowIndex(offset);
if (ShouldShowUi()) {
if (current_index_ > 1)
InitWindowCycleView();
if (cycle_view_)
cycle_view_->ScrollToWindow(windows_[current_index_]);
}
}
int WindowCycleList::GetIndexOfWindow(aura::Window* window) const {
auto target_window = std::find(windows_.begin(), windows_.end(), window);
DCHECK(target_window != windows_.end());
return std::distance(windows_.begin(), target_window);
}
int WindowCycleList::GetOffsettedWindowIndex(int offset) const {
DCHECK(!windows_.empty());
const int offsetted_index =
(current_index_ + offset + windows_.size()) % windows_.size();
DCHECK(windows_[offsetted_index]);
return offsetted_index;
}
const views::View::Views& WindowCycleList::GetWindowCycleItemViewsForTesting()
const {
return cycle_view_->GetPreviewViewsForTesting();
}
const aura::Window* WindowCycleList::GetTargetWindowForTesting() const {
return cycle_view_->GetTargetWindowForTesting();
}
} // namespace ash