Commit 6d7ebab8 authored by estade's avatar estade Committed by Commit bot

[CrOS] Initial rough cut of alt-tab window cycling UI.

A bit ugly, but mostly functional. I think most changes going forward
should be aesthetic, so this seems like a decent place for an initial
commit.

Enabled with --ash-enable-window-cycle-ui flag.

BUG=626111

Review-Url: https://codereview.chromium.org/2129773002
Cr-Commit-Position: refs/heads/master@{#406120}
parent cbb31e64
......@@ -340,6 +340,8 @@
'common/wm/drag_details.h',
'common/wm/focus_rules.cc',
'common/wm/focus_rules.h',
'common/wm/forwarding_layer_delegate.cc',
'common/wm/forwarding_layer_delegate.h',
'common/wm/fullscreen_window_finder.cc',
'common/wm/fullscreen_window_finder.h',
'common/wm/maximize_mode/maximize_mode_controller.cc',
......
......@@ -75,6 +75,9 @@ const char kAshEnableSoftwareMirroring[] = "ash-enable-software-mirroring";
// flag is removed.
const char kAshEnableTouchViewTesting[] = "ash-enable-touch-view-testing";
// Enables the window cycling UI (more visual feedback for alt-tab).
const char kAshEnableWindowCycleUi[] = "ash-enable-window-cycle-ui";
// Hides notifications that are irrelevant to Chrome OS device factory testing,
// such as battery level updates.
const char kAshHideNotificationsForFactory[] =
......
......@@ -37,6 +37,7 @@ ASH_EXPORT extern const char kAshDisableStableOverviewOrder[];
ASH_EXPORT extern const char kAshEnableStableOverviewOrder[];
ASH_EXPORT extern const char kAshEnableSoftwareMirroring[];
ASH_EXPORT extern const char kAshEnableTouchViewTesting[];
ASH_EXPORT extern const char kAshEnableWindowCycleUi[];
ASH_EXPORT extern const char kAshHideNotificationsForFactory[];
ASH_EXPORT extern const char kAshHostWindowBounds[];
ASH_EXPORT extern const char kAshMaterialDesign[];
......
// Copyright 2016 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/common/wm/forwarding_layer_delegate.h"
#include "ash/common/wm_window.h"
#include "base/callback.h"
#include "ui/compositor/layer.h"
namespace ash {
namespace wm {
ForwardingLayerDelegate::ForwardingLayerDelegate(WmWindow* original_window,
ui::LayerDelegate* delegate)
: original_window_(original_window), original_delegate_(delegate) {}
ForwardingLayerDelegate::~ForwardingLayerDelegate() {}
void ForwardingLayerDelegate::OnPaintLayer(const ui::PaintContext& context) {
if (!original_delegate_)
return;
// |original_delegate_| may have already been deleted or
// disconnected by this time. Check if |original_delegate_| is still
// used by the original_window tree, or skip otherwise.
if (IsDelegateValid(original_window_->GetLayer()))
original_delegate_->OnPaintLayer(context);
else
original_delegate_ = nullptr;
}
void ForwardingLayerDelegate::OnDelegatedFrameDamage(
const gfx::Rect& damage_rect_in_dip) {}
void ForwardingLayerDelegate::OnDeviceScaleFactorChanged(
float device_scale_factor) {
// Don't tell the original delegate about device scale factor change
// on cloned layer because the original layer is still on the same display.
}
base::Closure ForwardingLayerDelegate::PrepareForLayerBoundsChange() {
return base::Closure();
}
bool ForwardingLayerDelegate::IsDelegateValid(ui::Layer* layer) const {
if (layer->delegate() == original_delegate_)
return true;
for (auto* child : layer->children()) {
if (IsDelegateValid(child))
return true;
}
return false;
}
} // namespace wm
} // namespace ash
// Copyright 2016 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.
#ifndef ASH_COMMON_WM_FORWARDING_LAYER_DELEGATE_H_
#define ASH_COMMON_WM_FORWARDING_LAYER_DELEGATE_H_
#include "base/macros.h"
#include "ui/compositor/layer_delegate.h"
namespace ui {
class Layer;
}
namespace ash {
class WmWindow;
namespace wm {
// A layer delegate to paint the content of a recreated layer by delegating
// the paint request to the original delegate. It checks if the original
// delegate is still valid by traversing the original layers.
class ForwardingLayerDelegate : public ui::LayerDelegate {
public:
ForwardingLayerDelegate(WmWindow* original_window,
ui::LayerDelegate* delegate);
~ForwardingLayerDelegate() override;
private:
bool IsDelegateValid(ui::Layer* layer) const;
// ui:LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override;
void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override;
void OnDeviceScaleFactorChanged(float device_scale_factor) override;
base::Closure PrepareForLayerBoundsChange() override;
WmWindow* original_window_;
ui::LayerDelegate* original_delegate_;
DISALLOW_COPY_AND_ASSIGN(ForwardingLayerDelegate);
};
} // namespace wm
} // namespace ash
#endif // ASH_COMMON_WM_FORWARDING_LAYER_DELEGATE_H_
......@@ -6,7 +6,9 @@
#include <algorithm>
#include "ash/aura/wm_window_aura.h"
#include "ash/common/shell_window_ids.h"
#include "ash/common/wm/forwarding_layer_delegate.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
......@@ -28,61 +30,9 @@
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
// A layer delegate to paint the content of the recreaetd layers
// by delegating the paint request to the original delegate.
// It checks if the orignal delegate is still valid by traversing
// the original layers.
class DragWindowLayerDelegate : public ui::LayerDelegate {
public:
DragWindowLayerDelegate(aura::Window* original_window,
ui::LayerDelegate* delegate)
: original_window_(original_window), original_delegate_(delegate) {}
~DragWindowLayerDelegate() override {}
private:
// ui:LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override {
if (!original_delegate_)
return;
// |original_delegate_| may have already been deleted or
// disconnected by this time. Check if |original_delegate_| is still
// used by the original_window tree, or skip otherwise.
if (IsDelegateValid(original_window_->layer()))
original_delegate_->OnPaintLayer(context);
else
original_delegate_ = nullptr;
}
void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
void OnDeviceScaleFactorChanged(float device_scale_factor) override {
// Don't tell the original delegate about device scale factor change
// on cloned layer because the original layer is still on the same display.
}
base::Closure PrepareForLayerBoundsChange() override {
return base::Closure();
}
bool IsDelegateValid(ui::Layer* layer) {
if (layer->delegate() == original_delegate_)
return true;
for (auto* child : layer->children()) {
if (IsDelegateValid(child))
return true;
}
return false;
}
aura::Window* original_window_;
ui::LayerDelegate* original_delegate_;
DISALLOW_COPY_AND_ASSIGN(DragWindowLayerDelegate);
};
} // namespace
// This keeps tack of the drag window's state. It creates/destory/updates bounds
// and opacity based on the current bounds.
// This keeps track of the drag window's state. It creates/destroys/updates
// bounds and opacity based on the current bounds.
class DragWindowController::DragWindowDetails
: public aura::WindowDelegate,
public ::wm::LayerDelegateFactory {
......@@ -186,8 +136,8 @@ class DragWindowController::DragWindowDetails
ui::LayerDelegate* CreateDelegate(ui::LayerDelegate* delegate) override {
if (!delegate)
return nullptr;
DragWindowLayerDelegate* new_delegate =
new DragWindowLayerDelegate(original_window_, delegate);
wm::ForwardingLayerDelegate* new_delegate = new wm::ForwardingLayerDelegate(
WmWindowAura::Get(original_window_), delegate);
delegates_.push_back(base::WrapUnique(new_delegate));
return new_delegate;
}
......@@ -227,7 +177,7 @@ class DragWindowController::DragWindowDetails
aura::Window* original_window_ = nullptr;
std::vector<std::unique_ptr<DragWindowLayerDelegate>> delegates_;
std::vector<std::unique_ptr<wm::ForwardingLayerDelegate>> delegates_;
// The copy of window_->layer() and its descendants.
std::unique_ptr<ui::LayerTreeOwner> layer_owner_;
......
......@@ -4,16 +4,43 @@
#include "ash/wm/window_cycle_list.h"
#include <list>
#include <map>
#include "ash/common/ash_switches.h"
#include "ash/common/shell_window_ids.h"
#include "ash/common/wm/forwarding_layer_delegate.h"
#include "ash/common/wm/mru_window_tracker.h"
#include "ash/common/wm/window_state.h"
#include "ash/common/wm_root_window_controller.h"
#include "ash/common/wm_shell.h"
#include "ash/common/wm_window.h"
#include "ash/shell.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/window_util.h"
#include "base/command_line.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/views/background.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/visibility_controller.h"
#include "ui/wm/core/window_util.h"
namespace ash {
void EnsureAllChildrenAreVisible(ui::Layer* layer) {
std::list<ui::Layer*> layers;
layers.push_back(layer);
while (!layers.empty()) {
for (auto child : layers.front()->children())
layers.push_back(child);
layers.front()->SetVisible(true);
layers.pop_front();
}
}
// Returns the window immediately below |window| in the current container.
WmWindow* GetWindowBelow(WmWindow* window) {
WmWindow* parent = window->GetParent();
......@@ -55,6 +82,176 @@ class ScopedShowWindow : public WmWindowObserver {
DISALLOW_COPY_AND_ASSIGN(ScopedShowWindow);
};
// A view that mirrors a single window. Layers are lifted from the underlying
// window (which gets new ones in their place). New paint calls, if any, are
// forwarded to the underlying window.
class WindowMirrorView : public views::View, public ::wm::LayerDelegateFactory {
public:
explicit WindowMirrorView(WmWindow* window) : target_(window) {
DCHECK(window);
}
~WindowMirrorView() override {}
void Init() {
SetPaintToLayer(true);
layer_owner_ = ::wm::RecreateLayers(
target_->GetInternalWidget()->GetNativeView(), this);
GetMirrorLayer()->parent()->Remove(GetMirrorLayer());
layer()->Add(GetMirrorLayer());
// Some extra work is needed when the target window is minimized.
if (target_->GetWindowState()->IsMinimized()) {
GetMirrorLayer()->SetVisible(true);
GetMirrorLayer()->SetOpacity(1);
EnsureAllChildrenAreVisible(GetMirrorLayer());
}
}
// views::View:
gfx::Size GetPreferredSize() const override {
const int kMaxWidth = 800;
const int kMaxHeight = 600;
gfx::Size target_size = target_->GetBounds().size();
if (target_size.width() <= kMaxWidth &&
target_size.height() <= kMaxHeight) {
return target_size;
}
float scale =
std::min(kMaxWidth / static_cast<float>(target_size.width()),
kMaxHeight / static_cast<float>(target_size.height()));
return gfx::ScaleToCeiledSize(target_size, scale, scale);
}
void Layout() override {
// Position at 0, 0.
GetMirrorLayer()->SetBounds(gfx::Rect(GetMirrorLayer()->bounds().size()));
// Scale down if necessary.
gfx::Transform mirror_transform;
if (size() != target_->GetBounds().size()) {
const float scale =
width() / static_cast<float>(target_->GetBounds().width());
mirror_transform.Scale(scale, scale);
}
GetMirrorLayer()->SetTransform(mirror_transform);
}
// ::wm::LayerDelegateFactory:
ui::LayerDelegate* CreateDelegate(ui::LayerDelegate* delegate) override {
if (!delegate)
return nullptr;
delegates_.push_back(
base::WrapUnique(new wm::ForwardingLayerDelegate(target_, delegate)));
return delegates_.back().get();
}
private:
// Gets the root of the layer tree that was lifted from |target_| (and is now
// a child of |this->layer()|).
ui::Layer* GetMirrorLayer() { return layer_owner_->root(); }
// The original window that is being represented by |this|.
WmWindow* target_;
// Retains ownership of the mirror layer tree.
std::unique_ptr<ui::LayerTreeOwner> layer_owner_;
std::vector<std::unique_ptr<wm::ForwardingLayerDelegate>> delegates_;
DISALLOW_COPY_AND_ASSIGN(WindowMirrorView);
};
// A view that shows a collection of windows the user can tab through.
class WindowCycleView : public views::View {
public:
explicit WindowCycleView(const WindowCycleList::WindowList& windows)
: mirror_container_(new views::View()),
selector_view_(new views::View()),
target_window_(nullptr) {
DCHECK(!windows.empty());
SetPaintToLayer(true);
layer()->SetFillsBoundsOpaquely(false);
// TODO(estade): adjust constants in this function (colors, spacing, corner
// radius) as per mocks.
const float kCornerRadius = 5;
set_background(views::Background::CreateBackgroundPainter(
true, views::Painter::CreateSolidRoundRectPainter(
SkColorSetA(SK_ColorBLACK, 0xA5), kCornerRadius)));
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kHorizontal, 25, 25, 20);
layout->set_cross_axis_alignment(
views::BoxLayout::CROSS_AXIS_ALIGNMENT_START);
mirror_container_->SetLayoutManager(layout);
for (WmWindow* window : windows) {
WindowMirrorView* view = new WindowMirrorView(window);
view->Init();
window_view_map_[window] = view;
mirror_container_->AddChildView(view);
}
selector_view_->set_background(views::Background::CreateBackgroundPainter(
true, views::Painter::CreateSolidRoundRectPainter(SK_ColorBLUE,
kCornerRadius)));
AddChildView(selector_view_);
AddChildView(mirror_container_);
SetTargetWindow(windows.front());
}
~WindowCycleView() override {}
void SetTargetWindow(WmWindow* target) {
target_window_ = target;
if (GetWidget())
Layout();
}
void HandleWindowDestruction(WmWindow* destroying_window,
WmWindow* new_target) {
auto view_iter = window_view_map_.find(destroying_window);
view_iter->second->parent()->RemoveChildView(view_iter->second);
window_view_map_.erase(view_iter);
SetTargetWindow(new_target);
}
// views::View overrides:
gfx::Size GetPreferredSize() const override {
return mirror_container_->GetPreferredSize();
}
void Layout() override {
// Possible if the last window is deleted.
if (!target_window_)
return;
views::View* target_view = window_view_map_[target_window_];
gfx::RectF target_bounds(target_view->GetLocalBounds());
views::View::ConvertRectToTarget(target_view, this, &target_bounds);
target_bounds.Inset(gfx::InsetsF(-15));
selector_view_->SetBoundsRect(gfx::ToEnclosingRect(target_bounds));
mirror_container_->SetBoundsRect(GetLocalBounds());
}
WmWindow* target_window() { return target_window_; }
private:
std::map<WmWindow*, WindowMirrorView*> window_view_map_;
views::View* mirror_container_;
views::View* selector_view_;
WmWindow* target_window_;
DISALLOW_COPY_AND_ASSIGN(WindowCycleView);
};
ScopedShowWindow::ScopedShowWindow()
: window_(nullptr), stack_window_above_(nullptr), minimized_(false) {}
......@@ -107,22 +304,55 @@ void ScopedShowWindow::OnWindowTreeChanging(WmWindow* window,
}
WindowCycleList::WindowCycleList(const WindowList& windows)
: windows_(windows), current_index_(0) {
: windows_(windows), current_index_(0), cycle_view_(nullptr) {
WmShell::Get()->mru_window_tracker()->SetIgnoreActivations(true);
for (WmWindow* window : windows_)
window->AddObserver(this);
if (ShouldShowUi()) {
WmWindow* root_window = WmShell::Get()->GetRootWindowForNewWindows();
views::Widget* widget = new views::Widget;
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.accept_events = true;
// TODO(estade): make sure nothing untoward happens when the lock screen
// or a system modal dialog is shown.
root_window->GetRootWindowController()
->ConfigureWidgetInitParamsForContainer(
widget, kShellWindowId_OverlayContainer, &params);
widget->Init(params);
cycle_view_ = new WindowCycleView(windows_);
widget->SetContentsView(cycle_view_);
// TODO(estade): right now this just extends past the edge of the screen if
// there are too many windows. Handle this more gracefully. Also, if
// the display metrics change, cancel the UI.
gfx::Rect widget_rect = widget->GetWorkAreaBoundsInScreen();
gfx::Size widget_size = cycle_view_->GetPreferredSize();
widget_rect.ClampToCenteredSize(widget_size);
widget_rect.set_width(widget_size.width());
widget->SetBounds(widget_rect);
widget->Show();
cycle_ui_widget_.reset(widget);
}
}
WindowCycleList::~WindowCycleList() {
WmShell::Get()->mru_window_tracker()->SetIgnoreActivations(false);
for (WmWindow* window : windows_) {
// TODO(oshima): Remove this once crbug.com/483491 is fixed.
CHECK(window);
for (WmWindow* window : windows_)
window->RemoveObserver(this);
}
if (showing_window_)
showing_window_->CancelRestore();
if (cycle_view_ && cycle_view_->target_window()) {
cycle_view_->target_window()->Show();
cycle_view_->target_window()->GetWindowState()->Activate();
}
}
void WindowCycleList::Step(WindowCycleController::Direction direction) {
......@@ -147,6 +377,11 @@ void WindowCycleList::Step(WindowCycleController::Direction direction) {
current_index_ = (current_index_ + windows_.size()) % windows_.size();
DCHECK(windows_[current_index_]);
if (cycle_view_) {
cycle_view_->SetTargetWindow(windows_[current_index_]);
return;
}
// Make sure the next window is visible.
showing_window_.reset(new ScopedShowWindow);
showing_window_->Show(windows_[current_index_]);
......@@ -164,6 +399,23 @@ void WindowCycleList::OnWindowDestroying(WmWindow* window) {
current_index_ == static_cast<int>(windows_.size())) {
current_index_--;
}
if (cycle_view_) {
WmWindow* new_target_window =
windows_.empty() ? nullptr : windows_[current_index_];
cycle_view_->HandleWindowDestruction(window, new_target_window);
if (windows_.empty()) {
// This deletes us.
Shell::GetInstance()->window_cycle_controller()->StopCycling();
return;
}
}
}
bool WindowCycleList::ShouldShowUi() {
return windows_.size() > 1 &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAshEnableWindowCycleUi);
}
} // namespace ash
......@@ -13,9 +13,15 @@
#include "ash/wm/window_cycle_controller.h"
#include "base/macros.h"
namespace views {
class Label;
class Widget;
}
namespace ash {
class ScopedShowWindow;
class WindowCycleView;
// Tracks a set of Windows that can be stepped through. This class is used by
// the WindowCycleController.
......@@ -37,12 +43,15 @@ class ASH_EXPORT WindowCycleList : public WmWindowObserver {
friend class WindowCycleControllerTest;
const WindowList& windows() const { return windows_; }
// aura::WindowObserver overrides:
// WmWindowObserver overrides:
// There is a chance a window is destroyed, for example by JS code. We need to
// take care of that even if it is not intended for the user to close a window
// while window cycling.
void OnWindowDestroying(WmWindow* window) override;
// Returns true if the window list overlay should be shown.
bool ShouldShowUi();
// List of weak pointers to windows to use while cycling with the keyboard.
// List is built when the user initiates the gesture (i.e. hits alt-tab the
// first time) and is emptied when the gesture is complete (i.e. releases the
......@@ -56,6 +65,13 @@ class ASH_EXPORT WindowCycleList : public WmWindowObserver {
// Wrapper for the window brought to the front.
std::unique_ptr<ScopedShowWindow> showing_window_;
// The top level View for the window cycle UI. May be null if the UI is not
// showing.
WindowCycleView* cycle_view_;
// The widget that hosts the window cycle UI.
std::unique_ptr<views::Widget> cycle_ui_widget_;
DISALLOW_COPY_AND_ASSIGN(WindowCycleList);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment