diff options
-rw-r--r-- | ash/accelerators/accelerator_controller.cc | 19 | ||||
-rw-r--r-- | ash/ash.gyp | 6 | ||||
-rw-r--r-- | ash/ash_switches.cc | 3 | ||||
-rw-r--r-- | ash/ash_switches.h | 1 | ||||
-rw-r--r-- | ash/shell.cc | 2 | ||||
-rw-r--r-- | ash/shell.h | 5 | ||||
-rw-r--r-- | ash/shell_delegate.h | 1 | ||||
-rw-r--r-- | ash/wm/window_selector.cc | 216 | ||||
-rw-r--r-- | ash/wm/window_selector.h | 79 | ||||
-rw-r--r-- | ash/wm/window_selector_controller.cc | 61 | ||||
-rw-r--r-- | ash/wm/window_selector_controller.h | 57 | ||||
-rw-r--r-- | ash/wm/window_selector_delegate.h | 31 | ||||
-rw-r--r-- | ash/wm/window_selector_unittest.cc | 202 | ||||
-rw-r--r-- | chrome/app/generated_resources.grd | 6 | ||||
-rw-r--r-- | chrome/browser/about_flags.cc | 7 | ||||
-rw-r--r-- | chrome/browser/ui/ash/chrome_shell_delegate.cc | 3 |
16 files changed, 698 insertions, 1 deletions
diff --git a/ash/accelerators/accelerator_controller.cc b/ash/accelerators/accelerator_controller.cc index 8b9f9d4..b443691 100644 --- a/ash/accelerators/accelerator_controller.cc +++ b/ash/accelerators/accelerator_controller.cc @@ -45,6 +45,7 @@ #include "ash/wm/power_button_controller.h" #include "ash/wm/property_util.h" #include "ash/wm/window_cycle_controller.h" +#include "ash/wm/window_selector_controller.h" #include "ash/wm/window_util.h" #include "ash/wm/workspace/snap_sizer.h" #include "base/bind.h" @@ -95,7 +96,11 @@ bool HandleCycleWindowMRU(WindowCycleController::Direction direction, void HandleCycleWindowLinear(CycleDirection direction) { Shell::GetInstance()-> - window_cycle_controller()->HandleLinearCycleWindow(); + window_cycle_controller()->HandleLinearCycleWindow(); +} + +void ToggleOverviewMode() { + Shell::GetInstance()->window_selector_controller()->ToggleOverview(); } bool HandleAccessibleFocusCycle(bool reverse) { @@ -521,11 +526,23 @@ bool AcceleratorController::PerformAction(int action, return HandleCycleWindowMRU(WindowCycleController::FORWARD, accelerator.IsAltDown()); case CYCLE_BACKWARD_LINEAR: + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAshEnableOverviewMode)) { + shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_OVERVIEW_F5); + ToggleOverviewMode(); + return true; + } if (key_code == ui::VKEY_MEDIA_LAUNCH_APP1) shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_PREVWINDOW_F5); HandleCycleWindowLinear(CYCLE_BACKWARD); return true; case CYCLE_FORWARD_LINEAR: + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAshEnableOverviewMode)) { + shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_OVERVIEW_F5); + ToggleOverviewMode(); + return true; + } if (key_code == ui::VKEY_MEDIA_LAUNCH_APP1) shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_NEXTWINDOW_F5); HandleCycleWindowLinear(CYCLE_FORWARD); diff --git a/ash/ash.gyp b/ash/ash.gyp index dc8ee39..ed00c8d 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -484,6 +484,11 @@ 'wm/window_properties.h', 'wm/window_resizer.cc', 'wm/window_resizer.h', + 'wm/window_selector.cc', + 'wm/window_selector.h', + 'wm/window_selector_controller.cc', + 'wm/window_selector_controller.h', + 'wm/window_selector_delegate.h', 'wm/window_util.cc', 'wm/window_util.h', 'wm/workspace_controller.cc', @@ -745,6 +750,7 @@ 'wm/window_cycle_controller_unittest.cc', 'wm/window_manager_unittest.cc', 'wm/window_modality_controller_unittest.cc', + 'wm/window_selector_unittest.cc', 'wm/window_util_unittest.cc', 'wm/workspace_controller_test_helper.cc', 'wm/workspace_controller_test_helper.h', diff --git a/ash/ash_switches.cc b/ash/ash_switches.cc index 0c1ee11..b47ba50 100644 --- a/ash/ash_switches.cc +++ b/ash/ash_switches.cc @@ -114,6 +114,9 @@ const char kAshEnableMemoryMonitor[] = "ash-enable-memory-monitor"; // Enables the Oak tree viewer. const char kAshEnableOak[] = "ash-enable-oak"; +// Enables overview mode for window switching. +const char kAshEnableOverviewMode[] = "ash-enable-overview-mode"; + // Enables "sticky" edges instead of "snap-to-edge" const char kAshEnableStickyEdges[] = "ash-enable-sticky-edges"; diff --git a/ash/ash_switches.h b/ash/ash_switches.h index 49158e6..7e5d471 100644 --- a/ash/ash_switches.h +++ b/ash/ash_switches.h @@ -51,6 +51,7 @@ ASH_EXPORT extern const char kAshEnableMemoryMonitor[]; #endif ASH_EXPORT extern const char kAshEnableImmersiveFullscreen[]; ASH_EXPORT extern const char kAshEnableOak[]; +ASH_EXPORT extern const char kAshEnableOverviewMode[]; ASH_EXPORT extern const char kAshEnableStickyEdges[]; ASH_EXPORT extern const char kAshEnableTrayDragging[]; ASH_EXPORT extern const char kAshForceMirrorMode[]; diff --git a/ash/shell.cc b/ash/shell.cc index 818b309..c7b4b1f 100644 --- a/ash/shell.cc +++ b/ash/shell.cc @@ -67,6 +67,7 @@ #include "ash/wm/window_animations.h" #include "ash/wm/window_cycle_controller.h" #include "ash/wm/window_properties.h" +#include "ash/wm/window_selector_controller.h" #include "ash/wm/window_util.h" #include "ash/wm/workspace_controller.h" #include "base/bind.h" @@ -577,6 +578,7 @@ void Shell::Init() { high_contrast_controller_.reset(new HighContrastController); video_detector_.reset(new VideoDetector); window_cycle_controller_.reset(new WindowCycleController()); + window_selector_controller_.reset(new WindowSelectorController()); tooltip_controller_.reset(new views::corewm::TooltipController( gfx::SCREEN_TYPE_ALTERNATE)); diff --git a/ash/shell.h b/ash/shell.h index b6c669c..237a1a8 100644 --- a/ash/shell.h +++ b/ash/shell.h @@ -98,6 +98,7 @@ class UserWallpaperDelegate; class VideoDetector; class WebNotificationTray; class WindowCycleController; +class WindowSelectorController; namespace internal { class AcceleratorFilter; @@ -326,6 +327,9 @@ class ASH_EXPORT Shell WindowCycleController* window_cycle_controller() { return window_cycle_controller_.get(); } + WindowSelectorController* window_selector_controller() { + return window_selector_controller_.get(); + } internal::FocusCycler* focus_cycler() { return focus_cycler_.get(); } @@ -560,6 +564,7 @@ class ASH_EXPORT Shell scoped_ptr<UserActivityDetector> user_activity_detector_; scoped_ptr<VideoDetector> video_detector_; scoped_ptr<WindowCycleController> window_cycle_controller_; + scoped_ptr<WindowSelectorController> window_selector_controller_; scoped_ptr<internal::FocusCycler> focus_cycler_; scoped_ptr<DisplayController> display_controller_; scoped_ptr<HighContrastController> high_contrast_controller_; diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h index 0e7fcb4..2bf08c1 100644 --- a/ash/shell_delegate.h +++ b/ash/shell_delegate.h @@ -60,6 +60,7 @@ enum UserMetricsAction { UMA_ACCEL_NEWTAB_T, UMA_ACCEL_NEXTWINDOW_F5, UMA_ACCEL_NEXTWINDOW_TAB, + UMA_ACCEL_OVERVIEW_F5, UMA_ACCEL_PREVWINDOW_F5, UMA_ACCEL_PREVWINDOW_TAB, UMA_ACCEL_EXIT_FIRST_Q, diff --git a/ash/wm/window_selector.cc b/ash/wm/window_selector.cc new file mode 100644 index 0000000..f9de84c --- /dev/null +++ b/ash/wm/window_selector.cc @@ -0,0 +1,216 @@ +// Copyright 2013 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_selector.h" + +#include <algorithm> + +#include "ash/screen_ash.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/window_selector_delegate.h" +#include "ash/wm/window_util.h" +#include "base/memory/scoped_ptr.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/base/events/event.h" +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/gfx/interpolated_transform.h" +#include "ui/gfx/transform_util.h" +#include "ui/views/corewm/window_animations.h" + +namespace ash { + +namespace { + +const float kCardAspectRatio = 4.0f / 3.0f; +const int kWindowMargin = 20; +const int kMinCardsMajor = 3; +const int kOverviewTransitionMilliseconds = 100; + +// Applies a transform to |window| to fit within |target_bounds| while +// maintaining its aspect ratio. +void TransformWindowToFitBounds(aura::Window* window, + const gfx::Rect& target_bounds) { + const gfx::Rect bounds = window->bounds(); + float scale = std::min(1.0f, + std::min(static_cast<float>(target_bounds.width()) / bounds.width(), + static_cast<float>(target_bounds.height()) / bounds.height())); + gfx::Transform transform; + gfx::Vector2d offset( + 0.5 * (target_bounds.width() - scale * bounds.width()), + 0.5 * (target_bounds.height() - scale * bounds.height())); + transform.Translate(target_bounds.x() - bounds.x() + offset.x(), + target_bounds.y() - bounds.y() + offset.y()); + transform.Scale(scale, scale); + // TODO(flackr): The window bounds or transform could change during overview + // mode. WindowSelector should create a copy of the window so that the + // displayed windows are not affected by changes happening in the background. + // This will be necessary for alt-tab cycling as well, as some windows will + // be coming from other displays: http://crbug.com/263481. + window->SetTransform(transform); +} + +} // namespace + +WindowSelector::WindowSelector(const WindowList& windows, + WindowSelectorDelegate* delegate) + : delegate_(delegate) { + DCHECK(delegate_); + for (size_t i = 0; i < windows.size(); ++i) { + windows[i]->AddObserver(this); + WindowDetails details; + details.window = windows[i]; + details.minimized = windows[i]->GetProperty(aura::client::kShowStateKey) == + ui::SHOW_STATE_MINIMIZED; + details.original_transform = windows[i]->layer()->transform(); + if (details.minimized) { + windows[i]->Show(); + } + windows_.push_back(details); + } + PositionWindows(); + ash::Shell::GetInstance()->AddPreTargetHandler(this); +} + +WindowSelector::~WindowSelector() { + for (size_t i = 0; i < windows_.size(); i++) { + ui::ScopedLayerAnimationSettings animation_settings( + windows_[i].window->layer()->GetAnimator()); + animation_settings.SetPreemptionStrategy( + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); + animation_settings.SetTransitionDuration( + base::TimeDelta::FromMilliseconds(kOverviewTransitionMilliseconds)); + windows_[i].window->RemoveObserver(this); + gfx::Transform transform; + windows_[i].window->SetTransform(windows_[i].original_transform); + if (windows_[i].minimized) { + // Setting opacity 0 and visible false ensures that the property change + // to SHOW_STATE_MINIMIZED will not animate the window from its original + // bounds to the minimized position. + windows_[i].window->layer()->SetOpacity(0); + windows_[i].window->layer()->SetVisible(false); + windows_[i].window->SetProperty(aura::client::kShowStateKey, + ui::SHOW_STATE_MINIMIZED); + } + } + ash::Shell::GetInstance()->RemovePreTargetHandler(this); +} + +void WindowSelector::OnEvent(ui::Event* event) { + // TODO(flackr): This will prevent anything else from working while overview + // mode is active. This should only stop events from being sent to the windows + // in the overview but still allow interaction with the launcher / tray and + // hotkeys http://crbug.com/264289. + EventHandler::OnEvent(event); + event->StopPropagation(); +} + +void WindowSelector::OnMouseEvent(ui::MouseEvent* event) { + if (event->type() != ui::ET_MOUSE_RELEASED) + return; + aura::Window* target = static_cast<aura::Window*>(event->target()); + if (!target->HitTest(event->location())) + return; + + HandleSelectionEvent(event); +} + +void WindowSelector::OnGestureEvent(ui::GestureEvent* event) { + if (event->type() != ui::ET_GESTURE_TAP) + return; + HandleSelectionEvent(event); +} + +void WindowSelector::OnWindowDestroyed(aura::Window* window) { + std::vector<WindowDetails>::iterator iter = + std::find(windows_.begin(), windows_.end(), window); + DCHECK(iter != windows_.end()); + windows_.erase(iter); + if (windows_.empty()) { + delegate_->OnSelectionCanceled(); + return; + } + + PositionWindows(); +} + +void WindowSelector::HandleSelectionEvent(ui::Event* event) { + aura::Window* target = static_cast<aura::Window*>(event->target()); + + for (size_t i = 0; i < windows_.size(); i++) { + if (windows_[i].window->Contains(target)) { + // The selected window should not be minimized when window selection is + // ended. + windows_[i].minimized = false; + windows_[i].original_transform = gfx::Transform(); + + // The delegate may delete the WindowSelector, assume the object may no + // longer valid after calling this. + delegate_->OnWindowSelected(windows_[i].window); + return; + } + } +} + +void WindowSelector::PositionWindows() { + Shell::RootWindowList root_window_list = Shell::GetAllRootWindows(); + for (size_t i = 0; i < root_window_list.size(); ++i) { + PositionWindowsOnRoot(root_window_list[i]); + } +} + +void WindowSelector::PositionWindowsOnRoot(aura::RootWindow* root_window) { + gfx::Size window_size; + gfx::Rect total_bounds = ScreenAsh::GetDisplayWorkAreaBoundsInParent( + Shell::GetContainer(root_window, + internal::kShellWindowId_DefaultContainer)); + + std::vector<WindowDetails> windows; + for (size_t i = 0; i < windows_.size(); ++i) { + if (windows_[i].window->GetRootWindow() == root_window) + windows.push_back(windows_[i]); + } + if (windows.empty()) + return; + + // Find the minimum number of windows per row that will fit all of the + // windows on screen. + size_t columns = std::max( + total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1, + static_cast<int>(ceil(sqrt(total_bounds.width() * windows.size() / + (kCardAspectRatio * total_bounds.height()))))); + size_t rows = ((windows.size() + columns - 1) / columns); + window_size.set_width(std::min( + static_cast<int>(total_bounds.width() / columns), + static_cast<int>(total_bounds.height() * kCardAspectRatio / rows))); + window_size.set_height(window_size.width() / kCardAspectRatio); + + // Calculate the X and Y offsets necessary to center the grid. + int x_offset = total_bounds.x() + ((windows.size() >= columns ? 0 : + (columns - windows.size()) * window_size.width()) + + (total_bounds.width() - columns * window_size.width())) / 2; + int y_offset = total_bounds.y() + (total_bounds.height() - + rows * window_size.height()) / 2; + for (size_t i = 0; i < windows.size(); ++i) { + ui::ScopedLayerAnimationSettings animation_settings( + windows[i].window->layer()->GetAnimator()); + animation_settings.SetPreemptionStrategy( + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); + animation_settings.SetTransitionDuration( + base::TimeDelta::FromMilliseconds(kOverviewTransitionMilliseconds)); + gfx::Transform transform; + int column = i % columns; + int row = i / columns; + gfx::Rect target_bounds(window_size.width() * column + x_offset, + window_size.height() * row + y_offset, + window_size.width(), + window_size.height()); + target_bounds.Inset(kWindowMargin, kWindowMargin); + TransformWindowToFitBounds(windows[i].window, target_bounds); + } +} + +} // namespace ash diff --git a/ash/wm/window_selector.h b/ash/wm/window_selector.h new file mode 100644 index 0000000..900787d --- /dev/null +++ b/ash/wm/window_selector.h @@ -0,0 +1,79 @@ +// Copyright 2013 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_WM_WINDOW_SELECTOR_H_ +#define ASH_WM_WINDOW_SELECTOR_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "ui/aura/window_observer.h" +#include "ui/base/events/event_handler.h" +#include "ui/gfx/transform.h" + +namespace aura { +class RootWindow; +} + +namespace ash { + +class WindowSelectorDelegate; + +// The WindowSelector shows a grid of all of your windows and allows selecting +// a window by clicking or tapping on it. +class WindowSelector : public ui::EventHandler, + public aura::WindowObserver { + public: + typedef std::vector<aura::Window*> WindowList; + + WindowSelector(const WindowList& windows, + WindowSelectorDelegate* delegate); + virtual ~WindowSelector(); + + // ui::EventHandler: + virtual void OnEvent(ui::Event* event) OVERRIDE; + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE; + + // aura::WindowObserver: + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + + private: + struct WindowDetails { + WindowDetails() : window(NULL), minimized(false) {} + + bool operator==(const aura::Window* other_window) const { + return window == other_window; + } + + // A weak pointer to the window. + aura::Window* window; + + // If true, the window was minimized and this should be restored if the + // window was not selected. + bool minimized; + + // The original transform of the window before entering overview mode. + gfx::Transform original_transform; + }; + + void HandleSelectionEvent(ui::Event* event); + void PositionWindows(); + void PositionWindowsOnRoot(aura::RootWindow* root_window); + + void SelectWindow(aura::Window*); + + // List of weak pointers of windows to select from. + std::vector<WindowDetails> windows_; + + // Weak pointer to the selector delegate which will be called when a + // selection is made. + WindowSelectorDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(WindowSelector); +}; + +} // namespace ash + +#endif // ASH_WM_WINDOW_SELECTOR_H_ diff --git a/ash/wm/window_selector_controller.cc b/ash/wm/window_selector_controller.cc new file mode 100644 index 0000000..c7fe696 --- /dev/null +++ b/ash/wm/window_selector_controller.cc @@ -0,0 +1,61 @@ +// Copyright 2013 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_selector_controller.h" + +#include "ash/session_state_delegate.h" +#include "ash/shell.h" +#include "ash/wm/mru_window_tracker.h" +#include "ash/wm/window_selector.h" +#include "ash/wm/window_util.h" + +namespace ash { + +WindowSelectorController::WindowSelectorController() { +} + +WindowSelectorController::~WindowSelectorController() { +} + +// static +bool WindowSelectorController::CanSelect() { + // Don't allow a window overview if the screen is locked or a modal dialog is + // open. + return !Shell::GetInstance()->session_state_delegate()->IsScreenLocked() && + !Shell::GetInstance()->IsSystemModalWindowOpen(); +} + +void WindowSelectorController::ToggleOverview() { + if (window_selector_.get()) { + window_selector_.reset(); + } else { + std::vector<aura::Window*> windows = ash::Shell::GetInstance()-> + mru_window_tracker()->BuildMruWindowList(); + // Don't enter overview mode with no windows. + if (windows.empty()) + return; + + // Deactivating the window will hide popup windows like the omnibar or + // open menus. + aura::Window* active_window = wm::GetActiveWindow(); + if (active_window) + wm::DeactivateWindow(active_window); + window_selector_.reset(new WindowSelector(windows, this)); + } +} + +bool WindowSelectorController::IsSelecting() { + return window_selector_.get() != NULL; +} + +void WindowSelectorController::OnWindowSelected(aura::Window* window) { + window_selector_.reset(); + wm::ActivateWindow(window); +} + +void WindowSelectorController::OnSelectionCanceled() { + window_selector_.reset(); +} + +} // namespace ash diff --git a/ash/wm/window_selector_controller.h b/ash/wm/window_selector_controller.h new file mode 100644 index 0000000..8b11005 --- /dev/null +++ b/ash/wm/window_selector_controller.h @@ -0,0 +1,57 @@ +// Copyright 2013 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_WM_WINDOW_SELECTOR_CONTROLLER_H_ +#define ASH_WM_WINDOW_SELECTOR_CONTROLLER_H_ + +#include <list> +#include <vector> + +#include "ash/ash_export.h" +#include "ash/wm/window_selector_delegate.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "ui/aura/window_observer.h" + +namespace aura { +class Window; +} + +namespace ash { + +class WindowSelector; + +// Manages a window selector which displays an overview of all windows and +// allows selecting a window to activate it. +class ASH_EXPORT WindowSelectorController + : public WindowSelectorDelegate { + public: + WindowSelectorController(); + virtual ~WindowSelectorController(); + + // Returns true if selecting windows in an overview is enabled. This is false + // at certain times, such as when the lock screen is visible. + static bool CanSelect(); + + // Enters overview mode. This is essentially the window cycling mode however + // not released on releasing the alt key and allows selecting with the mouse + // or touch rather than keypresses. + void ToggleOverview(); + + // Returns true if window selection mode is active. + bool IsSelecting(); + + // WindowSelectorDelegate: + virtual void OnWindowSelected(aura::Window* window) OVERRIDE; + virtual void OnSelectionCanceled() OVERRIDE; + + private: + scoped_ptr<WindowSelector> window_selector_; + + DISALLOW_COPY_AND_ASSIGN(WindowSelectorController); +}; + +} // namespace ash + +#endif // ASH_WM_WINDOW_SELECTOR_CONTROLLER_H_ diff --git a/ash/wm/window_selector_delegate.h b/ash/wm/window_selector_delegate.h new file mode 100644 index 0000000..d6d0490 --- /dev/null +++ b/ash/wm/window_selector_delegate.h @@ -0,0 +1,31 @@ +// Copyright 2013 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_WM_WINDOW_SELECTOR_DELEGATE_H_ +#define ASH_WM_WINDOW_SELECTOR_DELEGATE_H_ + +#include "base/compiler_specific.h" + +namespace aura { +class Window; +} + +namespace ash { + +// Implement this class to handle the selection event from WindowSelector. +class WindowSelectorDelegate { + public: + // Invoked when a window is selected. + virtual void OnWindowSelected(aura::Window* window) = 0; + + // Invoked if selection is canceled. + virtual void OnSelectionCanceled() = 0; + + protected: + virtual ~WindowSelectorDelegate() {} +}; + +} // namespace ash + +#endif // ASH_WM_WINDOW_SELECTOR_DELEGATE_H_ diff --git a/ash/wm/window_selector_unittest.cc b/ash/wm/window_selector_unittest.cc new file mode 100644 index 0000000..01a96bf --- /dev/null +++ b/ash/wm/window_selector_unittest.cc @@ -0,0 +1,202 @@ +// Copyright 2013 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/root_window_controller.h" +#include "ash/screen_ash.h" +#include "ash/shell.h" +#include "ash/test/ash_test_base.h" +#include "ash/test/shell_test_api.h" +#include "ash/wm/mru_window_tracker.h" +#include "ash/wm/window_selector_controller.h" +#include "ash/wm/window_util.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_vector.h" +#include "base/run_loop.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/compositor/layer_animator.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/transform.h" + +namespace ash { +namespace internal { + +namespace { + +class LayerAnimationObserver : public ui::LayerAnimationObserver { + public: + LayerAnimationObserver(ui::Layer* layer) + : layer_(layer), animating_(false), message_loop_running_(false) { + layer_->GetAnimator()->AddObserver(this); + } + + virtual ~LayerAnimationObserver() { + layer_->GetAnimator()->RemoveObserver(this); + } + + virtual void OnLayerAnimationEnded( + ui::LayerAnimationSequence* sequence) OVERRIDE { + AnimationDone(); + } + + virtual void OnLayerAnimationScheduled( + ui::LayerAnimationSequence* sequence) OVERRIDE { + animating_ = true; + } + + virtual void OnLayerAnimationAborted( + ui::LayerAnimationSequence* sequence) OVERRIDE { + AnimationDone(); + } + + void WaitUntilDone() { + while (animating_) { + message_loop_running_ = true; + base::MessageLoop::current()->Run(); + message_loop_running_ = false; + } + } + + private: + void AnimationDone() { + animating_ = false; + if (message_loop_running_) + base::MessageLoop::current()->Quit(); + } + + ui::Layer* layer_; + bool animating_; + bool message_loop_running_; + + DISALLOW_COPY_AND_ASSIGN(LayerAnimationObserver); +}; + +} // namespace + +class WindowSelectorTest : public test::AshTestBase { + public: + WindowSelectorTest() {} + virtual ~WindowSelectorTest() {} + + aura::Window* CreateWindow(const gfx::Rect& bounds) { + return CreateTestWindowInShellWithDelegate(&wd, -1, bounds); + } + + bool WindowsOverlapping(aura::Window* window1, aura::Window* window2) { + gfx::RectF window1_bounds = GetTransformedTargetBounds(window1); + gfx::RectF window2_bounds = GetTransformedTargetBounds(window2); + return window1_bounds.Intersects(window2_bounds); + } + + void ToggleOverview() { + std::vector<aura::Window*> windows = ash::Shell::GetInstance()-> + mru_window_tracker()->BuildMruWindowList(); + ScopedVector<LayerAnimationObserver> animations; + for (size_t i = 0; i < windows.size(); ++i) { + animations.push_back(new LayerAnimationObserver(windows[i]->layer())); + } + ash::Shell::GetInstance()->window_selector_controller()->ToggleOverview(); + for (size_t i = 0; i < animations.size(); ++i) { + animations[i]->WaitUntilDone(); + } + } + + gfx::RectF GetTransformedBounds(aura::Window* window) { + gfx::RectF bounds(window->layer()->bounds()); + window->layer()->transform().TransformRect(&bounds); + return bounds; + } + + gfx::RectF GetTransformedTargetBounds(aura::Window* window) { + gfx::RectF bounds(window->layer()->GetTargetBounds()); + window->layer()->GetTargetTransform().TransformRect(&bounds); + return bounds; + } + + void ClickWindow(aura::Window* window) { + aura::test::EventGenerator event_generator(window->GetRootWindow(), window); + gfx::RectF target = GetTransformedBounds(window); + event_generator.ClickLeftButton(); + } + + private: + aura::test::TestWindowDelegate wd; + + DISALLOW_COPY_AND_ASSIGN(WindowSelectorTest); +}; + +// Tests entering overview mode with two windows and selecting one. +TEST_F(WindowSelectorTest, Basic) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get())); + wm::ActivateWindow(window2.get()); + EXPECT_FALSE(wm::IsActiveWindow(window1.get())); + EXPECT_TRUE(wm::IsActiveWindow(window2.get())); + + // In overview mode the windows should no longer overlap. + ToggleOverview(); + EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get())); + + // Clicking window 1 should activate it. + ClickWindow(window1.get()); + EXPECT_TRUE(wm::IsActiveWindow(window1.get())); + EXPECT_FALSE(wm::IsActiveWindow(window2.get())); +} + +// Tests that overview mode is exited if the last remaining window is destroyed. +TEST_F(WindowSelectorTest, LastWindowDestroyed) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + ToggleOverview(); + + window1.reset(); + window2.reset(); + EXPECT_FALSE(ash::Shell::GetInstance()->window_selector_controller()-> + IsSelecting()); +} + +// Tests that windows remain on the display they are currently on in overview +// mode. +TEST_F(WindowSelectorTest, MultipleDisplays) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("400x400,400x400"); + Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + + scoped_ptr<aura::Window> window1(CreateWindow(gfx::Rect(0, 0, 100, 100))); + scoped_ptr<aura::Window> window2(CreateWindow(gfx::Rect(0, 0, 100, 100))); + scoped_ptr<aura::Window> window3(CreateWindow(gfx::Rect(450, 0, 100, 100))); + scoped_ptr<aura::Window> window4(CreateWindow(gfx::Rect(450, 0, 100, 100))); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[0], window2->GetRootWindow()); + EXPECT_EQ(root_windows[1], window3->GetRootWindow()); + EXPECT_EQ(root_windows[1], window4->GetRootWindow()); + + // In overview mode, each window remains in the same root window. + ToggleOverview(); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[0], window2->GetRootWindow()); + EXPECT_EQ(root_windows[1], window3->GetRootWindow()); + EXPECT_EQ(root_windows[1], window4->GetRootWindow()); + root_windows[0]->bounds().Contains( + ToEnclosingRect(GetTransformedBounds(window1.get()))); + root_windows[0]->bounds().Contains( + ToEnclosingRect(GetTransformedBounds(window2.get()))); + root_windows[1]->bounds().Contains( + ToEnclosingRect(GetTransformedBounds(window3.get()))); + root_windows[1]->bounds().Contains( + ToEnclosingRect(GetTransformedBounds(window4.get()))); +} + +} // namespace internal +} // namespace ash diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index cc3ad40..604684d 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -6733,6 +6733,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_OVERSCROLL_HISTORY_NAVIGATION_DESCRIPTION" desc="Description for the flag to disable history navigation from horizontal overscroll."> Experimental history navigation in response to horizontal overscroll. </message> + <message name="IDS_FLAGS_OVERVIEW_MODE_NAME" desc="Title for the flag to enable window overview mode."> + Enable overview mode. + </message> + <message name="IDS_FLAGS_OVERVIEW_MODE_DESCRIPTION" desc="Description for the flag to enable window overview mode."> + Enable overview mode, activated by pushing the switch window button. + </message> <message name="IDS_FLAGS_SCROLL_END_EFFECT_NAME" desc="Title for the flag for scroll end effect from vertical overscroll."> Scroll end effect </message> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index f02a8a9..dff32ea 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -1042,6 +1042,13 @@ const Experiment kExperiments[] = { SINGLE_VALUE_TYPE(switches::kDisableMinimizeOnSecondLauncherItemClick) }, { + "enable-overview-mode", + IDS_FLAGS_OVERVIEW_MODE_NAME, + IDS_FLAGS_OVERVIEW_MODE_DESCRIPTION, + kOsCrOS, + SINGLE_VALUE_TYPE(ash::switches::kAshEnableOverviewMode) + }, + { "show-touch-hud", IDS_FLAGS_SHOW_TOUCH_HUD_NAME, IDS_FLAGS_SHOW_TOUCH_HUD_DESCRIPTION, diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc index c2d6888..1cb4879 100644 --- a/chrome/browser/ui/ash/chrome_shell_delegate.cc +++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc @@ -314,6 +314,9 @@ void ChromeShellDelegate::RecordUserMetricsAction( case ash::UMA_ACCEL_NEXTWINDOW_TAB: content::RecordAction(content::UserMetricsAction("Accel_NextWindow_Tab")); break; + case ash::UMA_ACCEL_OVERVIEW_F5: + content::RecordAction(content::UserMetricsAction("Accel_Overview_F5")); + break; case ash::UMA_ACCEL_PREVWINDOW_F5: content::RecordAction(content::UserMetricsAction("Accel_PrevWindow_F5")); break; |