diff options
author | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-05 22:14:46 +0000 |
---|---|---|
committer | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-05 22:14:46 +0000 |
commit | 51903e03437d794f6ca636f8be001cfe4d37a590 (patch) | |
tree | 223a7d4c6f61782925ea7d9e693671176c5182e5 /ash | |
parent | 830edfdbeac173247654fcb930ae8b5193963387 (diff) | |
download | chromium_src-51903e03437d794f6ca636f8be001cfe4d37a590.zip chromium_src-51903e03437d794f6ca636f8be001cfe4d37a590.tar.gz chromium_src-51903e03437d794f6ca636f8be001cfe4d37a590.tar.bz2 |
Implement animations for workspace cycling
BUG=160905
Test=Manual
R=sadrul
Review URL: https://chromiumcodereview.appspot.com/11597003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@180794 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash')
-rw-r--r-- | ash/ash.gyp | 2 | ||||
-rw-r--r-- | ash/wm/workspace/workspace.cc | 11 | ||||
-rw-r--r-- | ash/wm/workspace/workspace.h | 4 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_cycler.cc | 150 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_cycler.h | 83 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_cycler_animator.cc | 710 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_cycler_animator.h | 188 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_manager.cc | 87 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_manager.h | 31 | ||||
-rw-r--r-- | ash/wm/workspace_controller.cc | 10 | ||||
-rw-r--r-- | ash/wm/workspace_controller.h | 5 |
11 files changed, 1187 insertions, 94 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index c5d09f8..4a60984 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -430,6 +430,8 @@ 'wm/workspace/workspace_animations.h', 'wm/workspace/workspace_cycler.cc', 'wm/workspace/workspace_cycler.h', + 'wm/workspace/workspace_cycler_animator.cc', + 'wm/workspace/workspace_cycler_animator.h', 'wm/workspace/workspace_event_handler.cc', 'wm/workspace/workspace_event_handler.h', 'wm/workspace/workspace_layout_manager.cc', diff --git a/ash/wm/workspace/workspace.cc b/ash/wm/workspace/workspace.cc index 8c4a3b9..a573cc2c 100644 --- a/ash/wm/workspace/workspace.cc +++ b/ash/wm/workspace/workspace.cc @@ -48,6 +48,17 @@ Workspace::~Workspace() { DCHECK(!window_); } +aura::Window* Workspace::GetTopmostActivatableWindow() { + for (aura::Window::Windows::const_reverse_iterator i = + window_->children().rbegin(); + i != window_->children().rend(); + ++i) { + if (wm::CanActivateWindow(*i)) + return (*i); + } + return NULL; +} + aura::Window* Workspace::ReleaseWindow() { // Remove the LayoutManager and EventFilter as they refer back to us and/or // WorkspaceManager. diff --git a/ash/wm/workspace/workspace.h b/ash/wm/workspace/workspace.h index 575a368..0ad230b 100644 --- a/ash/wm/workspace/workspace.h +++ b/ash/wm/workspace/workspace.h @@ -36,6 +36,10 @@ class ASH_EXPORT Workspace { bool is_maximized); ~Workspace(); + // Returns the topmost activatable window. This corresponds to the most + // recently activated window in the workspace. + aura::Window* GetTopmostActivatableWindow(); + // Resets state. This should be used before destroying the Workspace. aura::Window* ReleaseWindow(); diff --git a/ash/wm/workspace/workspace_cycler.cc b/ash/wm/workspace/workspace_cycler.cc index 1080bb0..b4b8b25 100644 --- a/ash/wm/workspace/workspace_cycler.cc +++ b/ash/wm/workspace/workspace_cycler.cc @@ -4,6 +4,8 @@ #include "ash/wm/workspace/workspace_cycler.h" +#include <cmath> + #include "ash/shell.h" #include "ash/wm/workspace/workspace_manager.h" #include "ui/base/events/event.h" @@ -14,12 +16,12 @@ namespace internal { namespace { -// The required vertical distance to scrub to the next workspace. -const int kWorkspaceStepSize = 10; +// The required vertical distance to initiate workspace cycling. +const float kDistanceToInitiateWorkspaceCycling = 10.0f; -// Returns true is scrubbing is enabled. -bool IsScrubbingEnabled() { - // Scrubbing is disabled if the screen is locked or a modal dialog is open. +// Returns true if cycling is allowed. +bool IsCyclingAllowed() { + // Cycling is disabled if the screen is locked or a modal dialog is open. return !Shell::GetInstance()->IsScreenLocked() && !Shell::GetInstance()->IsSystemModalWindowOpen(); } @@ -28,34 +30,102 @@ bool IsScrubbingEnabled() { WorkspaceCycler::WorkspaceCycler(WorkspaceManager* workspace_manager) : workspace_manager_(workspace_manager), - scrubbing_(false), - scroll_x_(0), - scroll_y_(0) { + animator_(NULL), + state_(NOT_CYCLING), + scroll_x_(0.0f), + scroll_y_(0.0f) { ash::Shell::GetInstance()->AddPreTargetHandler(this); } WorkspaceCycler::~WorkspaceCycler() { - scrubbing_ = false; + SetState(NOT_CYCLING); ash::Shell::GetInstance()->RemovePreTargetHandler(this); } +void WorkspaceCycler::AbortCycling() { + SetState(NOT_CYCLING); +} + +void WorkspaceCycler::SetState(State new_state) { + if (state_ == NOT_CYCLING_TRACKING_SCROLL && new_state == STOPPING_CYCLING) + new_state = NOT_CYCLING; + + if (state_ == new_state || !IsValidNextState(new_state)) + return; + + state_ = new_state; + + if (new_state == STARTING_CYCLING) { + animator_.reset(new WorkspaceCyclerAnimator(this)); + workspace_manager_->InitWorkspaceCyclerAnimatorWithCurrentState( + animator_.get()); + animator_->AnimateStartingCycler(); + } else if (new_state == STOPPING_CYCLING) { + if (animator_.get()) + animator_->AnimateStoppingCycler(); + } else if (new_state == NOT_CYCLING) { + scroll_x_ = 0.0f; + scroll_y_ = 0.0f; + if (animator_.get()) { + animator_->AbortAnimations(); + animator_.reset(); + } + } +} + +bool WorkspaceCycler::IsValidNextState(State next_state) const { + if (state_ == next_state) + return true; + + switch (next_state) { + case NOT_CYCLING: + return true; + case NOT_CYCLING_TRACKING_SCROLL: + return state_ == NOT_CYCLING; + case STARTING_CYCLING: + return state_ == NOT_CYCLING_TRACKING_SCROLL; + case CYCLING: + return state_ == STARTING_CYCLING; + case STOPPING_CYCLING: + return (state_ == STARTING_CYCLING || state_ == CYCLING); + } + + NOTREACHED(); + return false; +} + +void WorkspaceCycler::OnEvent(ui::Event* event) { + if (!IsCyclingAllowed()) + SetState(NOT_CYCLING); + + ui::EventHandler::OnEvent(event); +} + void WorkspaceCycler::OnScrollEvent(ui::ScrollEvent* event) { + // End cycling when the user taps after having cycled through workspaces. + // TODO(pkotwicz): Use ui::ET_SCROLL_FLING_START instead to end cycling once + // it works for three fingers. (http://crbug.com/170484) + if (state_ != NOT_CYCLING && event->type() == ui::ET_SCROLL_FLING_CANCEL) { + SetState(STOPPING_CYCLING); + event->StopPropagation(); + return; + } + if (event->finger_count() != 3 || event->type() != ui::ET_SCROLL) { - scrubbing_ = false; + if (state_ != NOT_CYCLING) + event->StopPropagation(); return; } - if (!IsScrubbingEnabled()) { - scrubbing_ = false; + if (!IsCyclingAllowed() || + !workspace_manager_->CanStartCyclingThroughWorkspaces()) { + DCHECK_EQ(NOT_CYCLING, state_); return; } - if (!scrubbing_) { - scrubbing_ = true; - scroll_x_ = 0; - scroll_y_ = 0; - } + if (state_ == NOT_CYCLING) + SetState(NOT_CYCLING_TRACKING_SCROLL); if (ui::IsNaturalScrollEnabled()) { scroll_x_ += event->x_offset(); @@ -65,28 +135,40 @@ void WorkspaceCycler::OnScrollEvent(ui::ScrollEvent* event) { scroll_y_ -= event->y_offset(); } - // TODO(pkotwicz): Implement scrubbing through several workspaces as the - // result of a single scroll event. - if (std::abs(scroll_y_) > kWorkspaceStepSize) { - workspace_manager_->CycleToWorkspace(scroll_y_ > 0 ? - WorkspaceManager::CYCLE_NEXT : WorkspaceManager::CYCLE_PREVIOUS); + if (state_ == NOT_CYCLING_TRACKING_SCROLL) { + if (fabs(scroll_x_) > kDistanceToInitiateWorkspaceCycling) { + // Only initiate workspace cycling if there recently was a significant + // amount of vertical movement as opposed to vertical movement + // accumulated over a long horizontal three finger scroll. + scroll_x_ = 0.0f; + scroll_y_ = 0.0f; + } + + if (fabs(scroll_y_) >= kDistanceToInitiateWorkspaceCycling) + SetState(STARTING_CYCLING); + } - scroll_x_ = 0; - scroll_y_ = 0; + if (state_ == CYCLING && event->y_offset() != 0.0f) { + DCHECK(animator_.get()); + animator_->AnimateCyclingByScrollDelta(event->y_offset()); event->SetHandled(); - return; } +} - if (std::abs(scroll_x_) > kWorkspaceStepSize) { - // Update |scroll_x_| and |scroll_y_| such that workspaces are only cycled - // through when there recently was a significant amount of vertical movement - // as opposed to vertical movement accumulated over a long horizontal three - // finger scroll. - scroll_x_ = 0; - scroll_y_ = 0; - } +void WorkspaceCycler::StartWorkspaceCyclerAnimationFinished() { + DCHECK_EQ(STARTING_CYCLING, state_); + SetState(CYCLING); +} + +void WorkspaceCycler::StopWorkspaceCyclerAnimationFinished() { + DCHECK_EQ(STOPPING_CYCLING, state_); + Workspace* workspace_to_activate = animator_->get_selected_workspace(); + animator_.reset(); + SetState(NOT_CYCLING); - // The active workspace was not changed, do not consume the event. + // Activate the workspace after updating the state so that a call to + // AbortCycling() as a result of SetActiveWorkspaceFromCycler() is a noop. + workspace_manager_->SetActiveWorkspaceFromCycler(workspace_to_activate); } } // namespace internal diff --git a/ash/wm/workspace/workspace_cycler.h b/ash/wm/workspace/workspace_cycler.h index 032f54c..6811d8d 100644 --- a/ash/wm/workspace/workspace_cycler.h +++ b/ash/wm/workspace/workspace_cycler.h @@ -6,8 +6,10 @@ #define ASH_WM_WORKSPACE_WORKSPACE_CYCLER_H_ #include "ash/ash_export.h" +#include "ash/wm/workspace/workspace_cycler_animator.h" #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" #include "ui/base/events/event_handler.h" namespace ash { @@ -15,27 +17,90 @@ namespace internal { class WorkspaceManager; -// Class to enable quick workspace switching (scrubbing) via 3 finger vertical +// Class to enable quick workspace switching (cycling) via 3 finger vertical // scroll. -class ASH_EXPORT WorkspaceCycler : public ui::EventHandler { +// During cycling, the workspaces are arranged in a card stack as shown below: +// ____________________________________________ +// | ________________________ | +// | | | | +// | _|________________________|_ | +// | | | | +// | _|____________________________|_ | +// | | | | +// | | | | +// | | workspace to activate | | +// | | | | +// | | | | +// | _|________________________________|_ | +// | | | | +// | _|____________________________________|_ | +// | | | | +// |_|________________________________________|_| +// The user selects a workspace to activate by moving the workspace they want +// to activate to the most prominent position in the card stack. + +class ASH_EXPORT WorkspaceCycler : public ui::EventHandler, + public WorkspaceCyclerAnimator::Delegate { public: explicit WorkspaceCycler(WorkspaceManager* workspace_manager); virtual ~WorkspaceCycler(); + // Abort any animations that the cycler is doing. The active workspace is set + // to the active workspace before cycling was initiated. + // Cycling should be aborted: + // - Before a workspace is added or destroyed. + // - Before a workspace is activated. + // - When the workspace bounds or the shelf bounds change. + void AbortCycling(); + private: + // The cycler state. + enum State { + NOT_CYCLING, + + // The cycler is waiting for the user to scroll far enough vertically to + // trigger cycling workspaces. + NOT_CYCLING_TRACKING_SCROLL, + + // The workspaces are animating into a card stack configuration. The cycler + // is in this state for the duration of the animation. + STARTING_CYCLING, + + // The user is moving workspaces around in the card stack. + CYCLING, + + // The workspace that the user selected to be active is being animated to + // take up the entire screen. The cycler is in this state for the duration + // of the animation. + STOPPING_CYCLING, + }; + + // Set the cycler state to |new_state|. + // The state is not changed if transitioning from the current state to + // |new_state| is not valid. + void SetState(State new_state); + + // Returns true if transitioning from |state_| to |next_state| is valid. + bool IsValidNextState(State next_state) const; + // ui::EventHandler overrides: + virtual void OnEvent(ui::Event* event) OVERRIDE; virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; + // WorkspaceCyclerAnimator::Delegate overrides: + virtual void StartWorkspaceCyclerAnimationFinished() OVERRIDE; + virtual void StopWorkspaceCyclerAnimationFinished() OVERRIDE; + WorkspaceManager* workspace_manager_; - // Whether the user is scrubbing through workspaces. - bool scrubbing_; + scoped_ptr<WorkspaceCyclerAnimator> animator_; + + // The cycler's state. + State state_; - // The amount of scrolling which has occurred since the last time the - // workspace was switched. If scrubbing has just begun, |scroll_x_| and - // |scroll_y_| are set to 0. - int scroll_x_; - int scroll_y_; + // The amount of scrolling which has occurred. + float scroll_x_; + float scroll_y_; DISALLOW_COPY_AND_ASSIGN(WorkspaceCycler); }; diff --git a/ash/wm/workspace/workspace_cycler_animator.cc b/ash/wm/workspace/workspace_cycler_animator.cc new file mode 100644 index 0000000..a10bf944 --- /dev/null +++ b/ash/wm/workspace/workspace_cycler_animator.cc @@ -0,0 +1,710 @@ +// Copyright (c) 2012 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/workspace/workspace_cycler_animator.h" + +#include <algorithm> +#include <cmath> + +#include "ash/launcher/launcher.h" +#include "ash/root_window_controller.h" +#include "ash/screen_ash.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/property_util.h" +#include "ash/wm/shelf_layout_manager.h" +#include "ash/wm/workspace/colored_window_controller.h" +#include "ash/wm/workspace/workspace.h" +#include "ui/aura/window.h" +#include "ui/base/events/event_utils.h" +#include "ui/compositor/layer_animator.h" +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/gfx/transform_util.h" +#include "ui/views/widget/widget.h" + +namespace { + +// The maximum number of visible workspaces deeper than the selected workspace. +const int kMinVisibleOffsetFromSelected = -2; + +// The maximum number of visible workspaces shallower than the selected +// workspace. +const int kMaxVisibleOffsetFromSelected = 3; + +// The scale of the selected workspace when it is completely selected. +const double kSelectedWorkspaceScale = 0.95; + +// The minimum scale for workspaces in the top stack. The scales of the +// workspaces in the top stack decrease as you go deeper into the stack. +const double kMinTopStackScale = 0.9; + +// The maximum scale for workspaces in the bottom stack. The scales of the +// workspaces in the bottom stack increase as you go up the stack. +const double kMaxBottomStackScale = 1.0; + +// The minimum workspace brightness. +const float kMinBrightness = -0.4f; + +// The required vertical scroll amount to cycle to the next / previous +// workspace. +const double kScrollAmountToCycleToNextWorkspace = 10; + +// The ratio of the duration of the animation to the amount that the user has +// scrolled. +// The duration of an animation is computed by: +// distance scrolled * |kCyclerStepAnimationDurationRatio|. +const double kCyclerStepAnimationDurationRatio = 10; + +// The duration of the animations when animating starting the cycler. +const int kStartCyclerAnimationDuration = 100; + +// The duration of the animations when animating stopping the cycler. +const int kStopCyclerAnimationDuration = 100; + +// The background opacity. +const float kBackgroundOpacity = .8f; + +} // namespace + +namespace ash { +namespace internal { + +// Class which computes the transform, brightness, and visbility of workspaces +// on behalf of the animator. +class StyleCalculator { + public: + StyleCalculator(const gfx::Rect& screen_bounds, + const gfx::Rect& maximized_bounds, + size_t num_workspaces); + ~StyleCalculator(); + + // Returns the transform, brightness, and visibility that the workspace at + // |workspace_index| should be animated to when the user has stopped cycling + // through workspaces given |selected_workspace_index|. + // Unlike GetTargetProperties(), the method does not have a |scroll_delta| + // argument as stopping the workspace cycler always results in complately + // selecting a workspace. + void GetStoppedTargetProperties(size_t selected_workspace_index, + size_t workspace_index, + gfx::Transform* transform, + float* brightness, + bool* visible) const; + + // Returns the transform, brightness, and visibility that the workspace at + // |workspace_index| should be animated to when the user is cycling through + // workspaces given |selected_workspace_index| and |scroll_delta|. + // |scroll_delta| is the amount of pixels that the user has scrolled + // vertically since completely selecting the workspace at + // |selected_workspace_index|. + void GetTargetProperties(size_t selected_workspace_index, + size_t workspace_index, + double scroll_delta, + gfx::Transform* transform, + float* brightness, + bool* visible) const; + + private: + // Returns the target animation transform of the workspace which is at + // |offset_from_selected| from the selected workspace when the user is not + // cycling through workspaces. + // This method assumes |scroll_delta| == 0. + gfx::Transform GetStoppedTargetTransformForOffset( + int offset_from_selected) const; + + // Returns the target animation transform of the workspace which is at + // |offset_from_selected| from the selected workspace. + // This method like all the methods below assumes |scroll_delta| == 0 for the + // sake of simplicity. The transform for |scroll_delta| != 0 can be obtained + // via interpolation. + gfx::DecomposedTransform GetTargetTransformForOffset( + int offset_from_selected) const; + + // Returns the target animation brightness of the workspace which is at + // |offset_from_selected| from the selected workspace. + // This method assumes |scroll_delta| == 0. + float GetTargetBrightnessForOffset(int offset_from_selected) const; + + // Returns the target animation visibility of the workspace with the given + // parameters. + // This method assumes |scroll_delta| == 0. + bool GetTargetVisibilityForOffset(int offset_from_selected, + size_t workspace_index) const; + + // The bounds of the display containing the workspaces in workspace + // coordinates, including the shelf if any. + const gfx::Rect screen_bounds_; + + // The bounds of a maximized window. This excludes the shelf if any. + const gfx::Rect maximized_bounds_; + + // The combined number of visible and hidden workspaces. + size_t num_workspaces_; + + DISALLOW_COPY_AND_ASSIGN(StyleCalculator); +}; + +StyleCalculator::StyleCalculator(const gfx::Rect& screen_bounds, + const gfx::Rect& maximized_bounds, + size_t num_workspaces) + : screen_bounds_(screen_bounds), + maximized_bounds_(maximized_bounds), + num_workspaces_(num_workspaces) { +} + +StyleCalculator::~StyleCalculator() { +} + +void StyleCalculator::GetStoppedTargetProperties( + size_t selected_workspace_index, + size_t workspace_index, + gfx::Transform* transform, + float* brightness, + bool* visible) const { + DCHECK_LT(selected_workspace_index, num_workspaces_); + DCHECK_LT(workspace_index, num_workspaces_); + int offset_from_selected = static_cast<int>( + workspace_index - selected_workspace_index); + if (transform) + *transform = GetStoppedTargetTransformForOffset(offset_from_selected); + + if (brightness) + *brightness = GetTargetBrightnessForOffset(offset_from_selected); + + // All the workspaces other than the selected workspace are either occluded by + // the selected workspace or off screen. + if (visible) + *visible = (selected_workspace_index == workspace_index); +} + +void StyleCalculator::GetTargetProperties( + size_t selected_workspace_index, + size_t workspace_index, + double scroll_delta, + gfx::Transform* transform, + float* brightness, + bool* visible) const { + DCHECK_LT(selected_workspace_index, num_workspaces_); + DCHECK_LT(workspace_index, num_workspaces_); + + int offset_from_selected = static_cast<int>( + workspace_index - selected_workspace_index); + + int first_offset_from_selected = offset_from_selected; + int second_offset_from_selected = offset_from_selected; + if (scroll_delta < 0) { + // The user is part of the way to selecting the workspace at + // |selected_workspace_index| - 1 -> |offset_from_selected| + 1. + second_offset_from_selected = offset_from_selected + 1; + } else if (scroll_delta > 0) { + // The user is part of the way to selecting the workspace at + // |selected_workspace_index| + 1 -> |offset_from_selected| - 1. + second_offset_from_selected = offset_from_selected - 1; + } + + double progress = fabs(scroll_delta / kScrollAmountToCycleToNextWorkspace); + DCHECK_GT(1.0, progress); + + if (transform) { + gfx::DecomposedTransform first_transform = GetTargetTransformForOffset( + first_offset_from_selected); + + gfx::DecomposedTransform interpolated_transform; + if (first_offset_from_selected == second_offset_from_selected) { + interpolated_transform = first_transform; + } else { + gfx::DecomposedTransform second_transform = GetTargetTransformForOffset( + second_offset_from_selected); + gfx::BlendDecomposedTransforms(&interpolated_transform, + second_transform, + first_transform, + progress); + } + *transform = gfx::ComposeTransform(interpolated_transform); + } + + if (brightness) { + float first_brightness = GetTargetBrightnessForOffset( + first_offset_from_selected); + float second_brightness = GetTargetBrightnessForOffset( + second_offset_from_selected); + *brightness = first_brightness + progress * + (second_brightness - first_brightness); + } + + if (visible) { + bool first_visible = GetTargetVisibilityForOffset( + first_offset_from_selected, workspace_index); + bool second_visible = GetTargetVisibilityForOffset( + second_offset_from_selected, workspace_index); + *visible = (first_visible || second_visible); + } +} + +gfx::Transform StyleCalculator::GetStoppedTargetTransformForOffset( + int offset_from_selected) const { + if (offset_from_selected <= 0) { + // The selected workspace takes up the entire screen. The workspaces deeper + // than the selected workspace are stacked exactly under the selected + // workspace and are completely occluded by it. + return gfx::Transform(); + } + + // The workspaces shallower than the selected workspace are stacked exactly + // on top of each other offscreen. + gfx::Transform transform; + transform.Translate(0, screen_bounds_.height()); + transform.Scale(kMaxBottomStackScale, kMaxBottomStackScale); + return transform; +} + +gfx::DecomposedTransform StyleCalculator::GetTargetTransformForOffset( + int offset_from_selected) const { + // When cycling, the workspaces are spread out from the positions computed by + // GetStoppedTargetTransformForOffset(). The transforms are computed so that + // on screen the workspaces look like this: + // ____________________________________________ + // | ________________________ | + // | | deepest workpace |_________|_ + // | _|________________________|_ | | + // | | | | | + // | _|____________________________|_ | |_ top stack. + // | | selected / shallowest workspace| | | + // | | | | | + // | | |_____|_| + // | | | | + // | | | | + // | _|________________________________|_ | + // | | deepest workspace |___|_ + // | _|____________________________________|_ | |_ bottom stack. + // | | shallowest workspace |_|_| + // |_|________________________________________|_| + // The selected workspace is the most visible workspace. It is the workspace + // which will be activated if the user does not do any more cycling. + bool in_top_stack = (offset_from_selected <= 0); + + // Give workspaces with offsets below |kMinVisibleOffsetFromSelected| the + // same transform as the workspace at |kMinVisibleOffsetFromSelected|. As + // the workspace at |kMinVisibleOffsetFromSelected| completely occludes + // these extra workspaces, they can be hidden. + // |kMaxVisibleOffsetFromSelected| is dealt with similarly. + if (offset_from_selected < kMinVisibleOffsetFromSelected) + offset_from_selected = kMinVisibleOffsetFromSelected; + else if (offset_from_selected > kMaxVisibleOffsetFromSelected) + offset_from_selected = kMaxVisibleOffsetFromSelected; + + double scale = kSelectedWorkspaceScale; + if (in_top_stack) { + scale -= static_cast<double>(offset_from_selected) / + kMinVisibleOffsetFromSelected * + (kSelectedWorkspaceScale - kMinTopStackScale); + } else { + scale += static_cast<double>(offset_from_selected) / + kMaxVisibleOffsetFromSelected * + (kMaxBottomStackScale - kSelectedWorkspaceScale); + } + + // Compute the workspace's y offset. As abs(|offset_from_selected|) increases, + // the amount of the top of the workspace which is visible from beneath the + // shallower workspaces decreases exponentially. + double y_offset = 0; + if (in_top_stack) { + const double kTopStackYOffsets[-kMinVisibleOffsetFromSelected + 1] = + { 40, 28, 20 }; + y_offset = kTopStackYOffsets[ + static_cast<size_t>(std::abs(offset_from_selected))]; + } else { + const double kBottomStackYOffsets[kMaxVisibleOffsetFromSelected] = + { -40, -32, -20 }; + y_offset = maximized_bounds_.height() + + kBottomStackYOffsets[static_cast<size_t>(offset_from_selected - 1)]; + } + + // Center the workspace horizontally. + double x_offset = maximized_bounds_.width() * (1 - scale) / 2; + + gfx::DecomposedTransform transform; + transform.translate[0] = x_offset; + transform.translate[1] = y_offset; + transform.scale[0] = scale; + transform.scale[1] = scale; + return transform; +} + +float StyleCalculator::GetTargetBrightnessForOffset( + int offset_from_selected) const { + int max_visible_distance_from_selected = std::max( + std::abs(kMinVisibleOffsetFromSelected), kMaxVisibleOffsetFromSelected); + return kMinBrightness * std::min( + 1.0, + static_cast<double>(std::abs(offset_from_selected)) / + max_visible_distance_from_selected); +} + +bool StyleCalculator::GetTargetVisibilityForOffset( + int offset_from_selected, + size_t workspace_index) const { + // The workspace at the highest possible index is the shallowest workspace + // out of both stacks and is always visible. + // The workspace at |kMaxVisibleWorkspaceFromSelected| is hidden because + // it has the same transform as the shallowest workspace and is completely + // occluded by it. + if (workspace_index == num_workspaces_ - 1) + return true; + + return offset_from_selected >= kMinVisibleOffsetFromSelected && + offset_from_selected < kMaxVisibleOffsetFromSelected; +} + +WorkspaceCyclerAnimator::WorkspaceCyclerAnimator(Delegate* delegate) + : delegate_(delegate), + initial_active_workspace_index_(0), + selected_workspace_index_(0), + scroll_delta_(0), + animation_type_(NONE), + launcher_background_controller_(NULL), + style_calculator_(NULL) { +} + +WorkspaceCyclerAnimator::~WorkspaceCyclerAnimator() { + StopObservingImplicitAnimations(); + animation_type_ = NONE; +} + +void WorkspaceCyclerAnimator::Init(const std::vector<Workspace*>& workspaces, + Workspace* initial_active_workspace) { + workspaces_ = workspaces; + + std::vector<Workspace*>::iterator it = std::find(workspaces_.begin(), + workspaces_.end(), initial_active_workspace); + DCHECK(it != workspaces_.end()); + initial_active_workspace_index_ = it - workspaces_.begin(); + selected_workspace_index_ = initial_active_workspace_index_; + + screen_bounds_ = ScreenAsh::GetDisplayBoundsInParent( + workspaces_[0]->window()); + maximized_bounds_ = ScreenAsh::GetMaximizedWindowBoundsInParent( + workspaces_[0]->window()); + style_calculator_.reset(new StyleCalculator( + screen_bounds_, maximized_bounds_, workspaces_.size())); +} + +void WorkspaceCyclerAnimator::AnimateStartingCycler() { + // Ensure that the workspaces are stacked with respect to their order + // in |workspaces_|. + aura::Window* parent = workspaces_[0]->window()->parent(); + DCHECK(parent); + for (size_t i = 0; i < workspaces_.size() - 1; ++i) { + parent->StackChildAbove(workspaces_[i + 1]->window(), + workspaces_[i]->window()); + } + + // Set the initial transform and brightness of the workspaces such that they + // animate correctly when AnimateToUpdatedState() is called after the loop. + // Start at index 1 as the desktop workspace is not animated. + for (size_t i = 1; i < workspaces_.size(); ++i) { + aura::Window* window = workspaces_[i]->window(); + ui::Layer* layer = window->layer(); + + gfx::Transform transform; + float brightness = 0.0f; + style_calculator_->GetStoppedTargetProperties(selected_workspace_index_, i, + &transform, &brightness, NULL); + layer->SetTransform(transform); + layer->SetLayerBrightness(brightness); + } + + scroll_delta_ = 0; + animation_type_ = CYCLER_START; + AnimateToUpdatedState(kStartCyclerAnimationDuration); + + aura::Window* background = GetDesktopBackground(); + if (background) { + ui::Layer* layer = background->layer(); + + if (!background->IsVisible()) { + background->Show(); + layer->SetOpacity(0.0f); + } + + { + ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); + settings.SetPreemptionStrategy( + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); + settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( + kStartCyclerAnimationDuration)); + + layer->SetOpacity(kBackgroundOpacity); + } + } + + // Create a window to simulate a fully opaque launcher. This prevents + // workspaces from showing from behind the launcher. + CreateLauncherBackground(); +} + +void WorkspaceCyclerAnimator::AnimateStoppingCycler() { + if (scroll_delta_ != 0) { + // Completely select the workspace at |selected_workspace_index_|. + int animation_duration = GetAnimationDurationForChangeInScrollDelta( + -scroll_delta_); + scroll_delta_ = 0; + animation_type_ = CYCLER_COMPLETELY_SELECT; + AnimateToUpdatedState(animation_duration); + return; + } + + animation_type_ = CYCLER_END; + AnimateToUpdatedState(kStopCyclerAnimationDuration); + + aura::Window* background = GetDesktopBackground(); + if (background) { + ui::Layer* layer = background->layer(); + ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); + settings.SetPreemptionStrategy( + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); + settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( + kStopCyclerAnimationDuration)); + + layer->SetOpacity((selected_workspace_index_ == 0) ? 1.0f : 0.0f); + } +} + +void WorkspaceCyclerAnimator::AbortAnimations() { + StopObservingImplicitAnimations(); + animation_type_ = NONE; + CyclerStopped(initial_active_workspace_index_); +} + +void WorkspaceCyclerAnimator::AnimateCyclingByScrollDelta(float scroll_delta) { + if (scroll_delta == 0.0f) + return; + + // Drop any updates received while an animation is running. + // TODO(pkotwicz): Do something better. + if (animation_type_ != NONE) + return; + + if (ui::IsNaturalScrollEnabled()) + scroll_delta *= -1; + + double old_scroll_delta = scroll_delta_; + scroll_delta_ += scroll_delta; + + double min_scroll_delta = -1 * + static_cast<double>(selected_workspace_index_) * + kScrollAmountToCycleToNextWorkspace; + double max_scroll_delta = static_cast<double>( + workspaces_.size() - 1 - selected_workspace_index_) * + kScrollAmountToCycleToNextWorkspace; + + if (scroll_delta_ < min_scroll_delta) + scroll_delta_ = min_scroll_delta; + else if (scroll_delta_ > max_scroll_delta) + scroll_delta_ = max_scroll_delta; + + if (scroll_delta_ == old_scroll_delta) + return; + + // Set the selected workspace to the workspace that the user is closest to + // selecting completely. A workspace is completely selected when the user + // has scrolled the exact amount to select the workspace with no undershoot / + // overshoot. + int workspace_change = floor(scroll_delta_ / + kScrollAmountToCycleToNextWorkspace + .5); + selected_workspace_index_ += workspace_change; + + // Set |scroll_delta_| to the amount of undershoot / overshoot. + scroll_delta_ -= workspace_change * kScrollAmountToCycleToNextWorkspace; + + int animation_duration = GetAnimationDurationForChangeInScrollDelta( + scroll_delta_ - old_scroll_delta); + + animation_type_ = CYCLER_UPDATE; + AnimateToUpdatedState(animation_duration); +} + +void WorkspaceCyclerAnimator::AnimateToUpdatedState(int animation_duration) { + DCHECK_NE(NONE, animation_type_); + + bool animator_to_wait_for_selected = false; + + // Start at index 1, as the animator does not animate the desktop workspace. + for (size_t i = 1; i < workspaces_.size(); ++i) { + gfx::Transform transform; + float brightness = 0.0f; + bool visible = false; + if (animation_type_ == CYCLER_END) { + DCHECK_EQ(0, scroll_delta_); + style_calculator_->GetStoppedTargetProperties(selected_workspace_index_, + i, &transform, &brightness, &visible); + } else { + style_calculator_->GetTargetProperties(selected_workspace_index_, i, + scroll_delta_, &transform, &brightness, &visible); + } + + aura::Window* window = workspaces_[i]->window(); + int workspace_animation_duration = animation_duration; + if (!window->IsVisible()) { + if (visible) { + // The workspace's previous state is unknown, set the state immediately. + workspace_animation_duration = 0; + } else { + // Don't bother animating workspaces which aren't visible. + continue; + } + } + // Hide the workspace after |animation_duration| in the case of + // |visible| == false as the workspace to be hidden may not be completely + // occluded till the animation has completed. + + bool wait_for_animator = false; + if (!animator_to_wait_for_selected && workspace_animation_duration != 0) { + // The completion of the animations for this workspace will be used to + // indicate that the animations for all workspaces have completed. + wait_for_animator = true; + animator_to_wait_for_selected = true; + } + + AnimateTo(i, wait_for_animator, workspace_animation_duration, + transform, brightness, visible); + } + + // All of the animations started by this method were of zero duration. + // Call the animation callback. + if (!animator_to_wait_for_selected) + OnImplicitAnimationsCompleted(); +} + +void WorkspaceCyclerAnimator::CyclerStopped(size_t visible_workspace_index) { + // Start at index 1 as the animator does not animate the desktop workspace. + for(size_t i = 1; i < workspaces_.size(); ++i) { + aura::Window* window = workspaces_[i]->window(); + ui::Layer* layer = window->layer(); + layer->SetLayerBrightness(0.0f); + layer->SetTransform(gfx::Transform()); + + if (i == visible_workspace_index) + window->Show(); + else + window->Hide(); + } + + aura::Window* background = GetDesktopBackground(); + if (background) { + background->layer()->SetOpacity(1.0); + if (visible_workspace_index != 0u) + background->Hide(); + } + + launcher_background_controller_.reset(); +} + +void WorkspaceCyclerAnimator::AnimateTo(size_t workspace_index, + bool wait_for_animation_to_complete, + int animation_duration, + const gfx::Transform& target_transform, + float target_brightness, + bool target_visibility) { + aura::Window* window = workspaces_[workspace_index]->window(); + ui::Layer* layer = window->layer(); + ui::LayerAnimator* animator = layer->GetAnimator(); + ui::ScopedLayerAnimationSettings settings(animator); + + if (wait_for_animation_to_complete) { + StopObservingImplicitAnimations(); + DCHECK_NE(animation_type_, NONE); + settings.AddObserver(this); + } + + settings.SetPreemptionStrategy( + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); + settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( + animation_duration)); + + if (target_visibility) { + window->Show(); + + // Set the opacity in case the layer has some weird initial state. + layer->SetOpacity(1.0f); + } else { + window->Hide(); + } + + layer->SetTransform(target_transform); + layer->SetLayerBrightness(target_brightness); +} + +int WorkspaceCyclerAnimator::GetAnimationDurationForChangeInScrollDelta( + double change) const { + return static_cast<int>( + fabs(change) * kCyclerStepAnimationDurationRatio); +} + +void WorkspaceCyclerAnimator::CreateLauncherBackground() { + if (screen_bounds_ == maximized_bounds_) + return; + + aura::Window* random_workspace_window = workspaces_[0]->window(); + ash::Launcher* launcher = ash::Launcher::ForWindow(random_workspace_window); + aura::Window* launcher_window = launcher->widget()->GetNativeWindow(); + + // TODO(pkotwicz): Figure out what to do when the launcher visible state is + // SHELF_AUTO_HIDE. + ShelfLayoutManager* shelf_layout_manager = + ShelfLayoutManager::ForLauncher(launcher_window); + if (!shelf_layout_manager->IsVisible()) + return; + + gfx::Rect shelf_bounds = shelf_layout_manager->GetIdealBounds(); + + launcher_background_controller_.reset(new ColoredWindowController( + launcher_window->parent(), "LauncherBackground")); + launcher_background_controller_->SetColor(SK_ColorBLACK); + aura::Window* tint_window = + launcher_background_controller_->GetWidget()->GetNativeWindow(); + tint_window->SetBounds(shelf_bounds); + launcher_window->parent()->StackChildBelow(tint_window, launcher_window); + tint_window->Show(); +} + +aura::Window* WorkspaceCyclerAnimator::GetDesktopBackground() const { + RootWindowController* root_controller = GetRootWindowController( + workspaces_[0]->window()->GetRootWindow()); + if (!root_controller) + return NULL; + + return root_controller->GetContainer( + kShellWindowId_DesktopBackgroundContainer); +} + +void WorkspaceCyclerAnimator::NotifyDelegate( + AnimationType completed_animation) { + if (completed_animation == CYCLER_START) + delegate_->StartWorkspaceCyclerAnimationFinished(); + else if (completed_animation == CYCLER_END) + delegate_->StopWorkspaceCyclerAnimationFinished(); +} + +void WorkspaceCyclerAnimator::OnImplicitAnimationsCompleted() { + AnimationType completed_animation = animation_type_; + animation_type_ = NONE; + + if (completed_animation == CYCLER_COMPLETELY_SELECT) + AnimateStoppingCycler(); + else if (completed_animation == CYCLER_END) + CyclerStopped(selected_workspace_index_); + + if (completed_animation == CYCLER_START || + completed_animation == CYCLER_END) { + // Post a task to notify the delegate of the animation completion because + // the delegate may delete |this| as a result of getting notified. + MessageLoopForUI::current()->PostTask( + FROM_HERE, + base::Bind(&WorkspaceCyclerAnimator::NotifyDelegate, + AsWeakPtr(), + completed_animation)); + } +} + +} // namespace internal +} // namespace ash diff --git a/ash/wm/workspace/workspace_cycler_animator.h b/ash/wm/workspace/workspace_cycler_animator.h new file mode 100644 index 0000000..0ed0d22 --- /dev/null +++ b/ash/wm/workspace/workspace_cycler_animator.h @@ -0,0 +1,188 @@ +// Copyright (c) 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_WORKSPACE_WORKSPACE_CYCLER_ANIMATOR_H_ +#define ASH_WM_WORKSPACE_WORKSPACE_CYCLER_ANIMATOR_H_ + +#include <vector> + +#include "ash/ash_export.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "ui/compositor/layer_animation_observer.h" +#include "ui/gfx/rect.h" + +namespace aura { +class Window; +} + +namespace gfx { +class Transform; +} + +namespace ash { +namespace internal { +class ColoredWindowController; +class StyleCalculator; +class Workspace; + +// Class which manages the animations for cycling through workspaces. +class ASH_EXPORT WorkspaceCyclerAnimator : + public base::SupportsWeakPtr<WorkspaceCyclerAnimator>, + public ui::ImplicitAnimationObserver { + public: + // Class used for notifying when the workspace cycler has finished animating + // starting and stopping the cycler. + class ASH_EXPORT Delegate { + public: + // Called when the animations initiated by AnimateStartingCycler() are + // completed. + virtual void StartWorkspaceCyclerAnimationFinished() = 0; + + // Called when the animations initiated by AnimateStoppingCycler() are + // completed. This is not called as a result of AbortAnimations(). + virtual void StopWorkspaceCyclerAnimationFinished() = 0; + + protected: + virtual ~Delegate() {} + }; + + explicit WorkspaceCyclerAnimator(Delegate* delegate); + virtual ~WorkspaceCyclerAnimator(); + + // Initializes the animator with the passed in state. The animator caches all + // of the parameters. + // AbortAnimations() should be called: + // - Before a workspace is added or destroyed. + // - Before a workspace is activated. + // - When the workspace bounds or the shelf bounds change. + void Init(const std::vector<Workspace*>& workspaces, + Workspace* initial_active_workspace); + + // Animate starting the workspace cycler. + // StartWorkspaceCyclerAnimationFinished() will be called on the delegate when + // the animations have completed. + void AnimateStartingCycler(); + + // Animate stopping the workspace cycler. + // StopWorkspaceCyclerAnimationFinished() will be called on the delegate when + // the animations have completed. + void AnimateStoppingCycler(); + + // Abort the animations started by the animator and reset any state set by the + // animator. + void AbortAnimations(); + + // Animate cycling by |scroll_delta|. + void AnimateCyclingByScrollDelta(float scroll_delta); + + // Returns the workspace which should be activated if the user does not do + // any more cycling. + Workspace* get_selected_workspace() const { + return workspaces_[selected_workspace_index_]; + } + + private: + enum AnimationType { + NONE, + CYCLER_START, + CYCLER_UPDATE, + CYCLER_COMPLETELY_SELECT, + CYCLER_END + }; + + // Start animations of |animation_duration| to the state dictated by + // |selected_workspace_index_|, |scroll_delta_| and |animation_type_|. + void AnimateToUpdatedState(int animation_duration); + + // Called after the animations, if any, have occurred for stopping / aborting + // cycling. Resets any state set by the animator. + // The workspace at |visible_workspace_index| is set as the only visible + // workspace. + void CyclerStopped(size_t visible_workspace_index); + + // Start an animation of |animation_duration| of the workspace's properties to + // the passed in target values. + // If |wait_for_animation_to_complete| is set to true, the animations for all + // of the workspaces are considered completed and the animation callback is + // called when the animation for the workspace at |workpace_index| has + // completed. |wait_for_animations_to_complete| can be set on a single + // workspace. If workspaces have animations of different durations, this + // property should be set on the workspace with the longest animation. + void AnimateTo(size_t workspace_index, + bool wait_for_animation_to_complete, + int animation_duration, + const gfx::Transform& target_transform, + float target_brightness, + bool target_visibility); + + // Returns the animation duration of cycing given |change|, the change in + // scroll delta. + int GetAnimationDurationForChangeInScrollDelta(double change) const; + + // Create a black window and place it behind the launcher. This simulates a + // fully opaque launcher background. + void CreateLauncherBackground(); + + // Returns the desktop background window. + aura::Window* GetDesktopBackground() const; + + // Notify the |delegate| about |completed_animation|. + void NotifyDelegate(AnimationType completed_animation); + + // Overridden from ui::ImplicitAnimationObserver. + virtual void OnImplicitAnimationsCompleted() OVERRIDE; + + // The delegate to be notified when the animator has finished animating + // starting or stopping the cycler. + Delegate* delegate_; + + // Cache of the unminimized workspaces. + std::vector<Workspace*> workspaces_; + + // The bounds of the display containing the workspaces in workspace + // coordinates, including the shelf if any. + gfx::Rect screen_bounds_; + + // The bounds of a maximized window. This excludes the shelf if any. + gfx::Rect maximized_bounds_; + + // The index of the active workspace when the cycler was constructed. + size_t initial_active_workspace_index_; + + // The index of the workspace which should be activated if the user does not + // do any more cycling. + size_t selected_workspace_index_; + + // The amount that the user has scrolled vertically in pixels since selecting + // the workspace at |selected_workspace_index_|. + // Values: + // 0 + // The workspace at |selected_workspace_index_| is completely selected. + // Positive + // The user is part of the way to selecting the workspace at + // |selected_workspace_index_ + 1|. + // Negative + // The user is part of the way to selecting the workspace at + // |selected_workspace_index_ - 1|. + double scroll_delta_; + + // The type of the current animation. |animation_type_| is NONE when the + // current animation is finished and a new one can be started. + AnimationType animation_type_; + + // The window controller for the fake black launcher background. + scoped_ptr<ColoredWindowController> launcher_background_controller_; + + // Used by the animator to compute the transform, brightness, and visibility + // of workspaces during animations. + scoped_ptr<StyleCalculator> style_calculator_; + + DISALLOW_COPY_AND_ASSIGN(WorkspaceCyclerAnimator); +}; + +} // namespace internal +} // namespace ash + +#endif // ASH_WM_WORKSPACE_WORKSPACE_CYCLER_ANIMATOR_H_ diff --git a/ash/wm/workspace/workspace_manager.cc b/ash/wm/workspace/workspace_manager.cc index 95c63f1..17ba261 100644 --- a/ash/wm/workspace/workspace_manager.cc +++ b/ash/wm/workspace/workspace_manager.cc @@ -7,6 +7,7 @@ #include <algorithm> #include <functional> +#include "ash/ash_switches.h" #include "ash/root_window_controller.h" #include "ash/shell.h" #include "ash/shell_window_ids.h" @@ -20,9 +21,12 @@ #include "ash/wm/workspace/auto_window_management.h" #include "ash/wm/workspace/desktop_background_fade_controller.h" #include "ash/wm/workspace/workspace_animations.h" +#include "ash/wm/workspace/workspace_cycler.h" +#include "ash/wm/workspace/workspace_cycler_animator.h" #include "ash/wm/workspace/workspace_layout_manager.h" #include "ash/wm/workspace/workspace.h" #include "base/auto_reset.h" +#include "base/command_line.h" #include "base/logging.h" #include "base/stl_util.h" #include "ui/aura/client/aura_constants.h" @@ -112,7 +116,8 @@ WorkspaceManager::WorkspaceManager(Window* contents_view) clear_unminimizing_workspace_factory_(this)), unminimizing_workspace_(NULL), app_terminating_(false), - creating_fade_(false) { + creating_fade_(false), + workspace_cycler_(NULL) { // Clobber any existing event filter. contents_view->SetEventFilter(NULL); // |contents_view| takes ownership of LayoutManagerImpl. @@ -121,6 +126,11 @@ WorkspaceManager::WorkspaceManager(Window* contents_view) workspaces_.push_back(active_workspace_); active_workspace_->window()->Show(); Shell::GetInstance()->AddShellObserver(this); + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAshEnableWorkspaceScrubbing)) { + workspace_cycler_.reset(new WorkspaceCycler(this)); + } } WorkspaceManager::~WorkspaceManager() { @@ -266,31 +276,28 @@ Window* WorkspaceManager::GetParentForNewWindow(Window* window) { return desktop_workspace()->window(); } -bool WorkspaceManager::CycleToWorkspace(CycleDirection direction) { - aura::Window* active_window = wm::GetActiveWindow(); - if (!active_workspace_->window()->Contains(active_window)) - active_window = NULL; +bool WorkspaceManager::CanStartCyclingThroughWorkspaces() const { + return workspace_cycler_.get() && workspaces_.size() > 1u; +} - Workspaces::const_iterator workspace_i(FindWorkspace(active_workspace_)); - int workspace_offset = 0; - if (direction == CYCLE_PREVIOUS) { - workspace_offset = 1; - if (workspace_i == workspaces_.end() - 1) - return false; - } else { - workspace_offset = -1; - if (workspace_i == workspaces_.begin()) - return false; - } +void WorkspaceManager::InitWorkspaceCyclerAnimatorWithCurrentState( + WorkspaceCyclerAnimator* animator) { + if (animator) + animator->Init(workspaces_, active_workspace_); +} - Workspaces::const_iterator next_workspace_i(workspace_i + workspace_offset); - SetActiveWorkspace(*next_workspace_i, SWITCH_OTHER, base::TimeDelta()); +void WorkspaceManager::SetActiveWorkspaceFromCycler(Workspace* workspace) { + if (!workspace || workspace == active_workspace_) + return; + + SetActiveWorkspace(workspace, SWITCH_WORKSPACE_CYCLER, base::TimeDelta()); - // The activation controller will pick a window from the just activated - // workspace to activate as a result of DeactivateWindow(). - if (active_window) - wm::DeactivateWindow(active_window); - return true; + // Activate the topmost window in the newly activated workspace as + // SetActiveWorkspace() does not do so. + aura::Window* topmost_activatable_window = + workspace->GetTopmostActivatableWindow(); + if (topmost_activatable_window) + wm::ActivateWindow(topmost_activatable_window); } void WorkspaceManager::DoInitialAnimation() { @@ -334,6 +341,11 @@ void WorkspaceManager::SetActiveWorkspace(Workspace* workspace, if (active_workspace_ == workspace) return; + // It is possible for a user to use accelerator keys to restore windows etc + // while the user is cycling through workspaces. + if (workspace_cycler_.get()) + workspace_cycler_->AbortCycling(); + pending_workspaces_.erase(workspace); // Adjust the z-order. No need to adjust the z-order for the desktop since @@ -410,6 +422,11 @@ void WorkspaceManager::MoveWorkspaceToPendingOrDelete( DCHECK_NE(desktop_workspace(), workspace); + // The user may have closed or minimized a window via accelerator keys while + // cycling through workspaces. + if (workspace_cycler_.get()) + workspace_cycler_->AbortCycling(); + if (workspace == active_workspace_) SelectNextWorkspace(reason); @@ -524,12 +541,26 @@ void WorkspaceManager::ShowOrHideDesktopBackground( base::TimeDelta duration, bool show) const { WorkspaceAnimationDetails details; - details.animate = true; details.direction = show ? WORKSPACE_ANIMATE_UP : WORKSPACE_ANIMATE_DOWN; - details.animate_scale = reason != SWITCH_MAXIMIZED_OR_RESTORED; - details.duration = duration; - if (reason == SWITCH_INITIAL) - details.pause_time_ms = kInitialPauseTimeMS; + + switch (reason) { + case SWITCH_WORKSPACE_CYCLER: + // The workspace cycler has already animated the desktop background's + // opacity. Do not do any further animation. + break; + case SWITCH_MAXIMIZED_OR_RESTORED: + // Do not do any animations. + break; + case SWITCH_INITIAL: + details.animate = true; + details.animate_scale = true; + details.pause_time_ms = kInitialPauseTimeMS; + break; + default: + details.animate = true; + details.animate_scale = true; + break; + } if (show) ash::internal::ShowWorkspace(window, details); else diff --git a/ash/wm/workspace/workspace_manager.h b/ash/wm/workspace/workspace_manager.h index 08a50bbe..cb827a7 100644 --- a/ash/wm/workspace/workspace_manager.h +++ b/ash/wm/workspace/workspace_manager.h @@ -41,6 +41,8 @@ namespace internal { class DesktopBackgroundFadeController; class ShelfLayoutManager; +class WorkspaceCycler; +class WorkspaceCyclerAnimator; class WorkspaceLayoutManager; class WorkspaceManagerTest2; class Workspace; @@ -53,11 +55,6 @@ class Workspace; // are maximized and restored they are reparented to the right Window. class ASH_EXPORT WorkspaceManager : public ash::ShellObserver { public: - enum CycleDirection { - CYCLE_NEXT, - CYCLE_PREVIOUS - }; - explicit WorkspaceManager(aura::Window* viewport); virtual ~WorkspaceManager(); @@ -86,10 +83,18 @@ class ASH_EXPORT WorkspaceManager : public ash::ShellObserver { // when a new Window is being added. aura::Window* GetParentForNewWindow(aura::Window* window); - // Called by the workspace cycler to activate the next workspace in - // |direction|. Returns false if there are no more workspaces to cycle - // to in |direction|. - bool CycleToWorkspace(CycleDirection direction); + // Returns true if the user can start cycling through workspaces. + bool CanStartCyclingThroughWorkspaces() const; + + // Initializes |animator| with the workspace manager's current state on + // behalf of the workspace cycler. + // This state should be cleared by the workspace cycler when + // WorkspaceCycler::AbortCycling() is called. + void InitWorkspaceCyclerAnimatorWithCurrentState( + WorkspaceCyclerAnimator* animator); + + // Called by the workspace cycler to update the active workspace. + void SetActiveWorkspaceFromCycler(Workspace* workspace); // Starts the animation that occurs on first login. void DoInitialAnimation(); @@ -119,6 +124,10 @@ class ASH_EXPORT WorkspaceManager : public ash::ShellObserver { // rather we run the animations as if a switch occurred. SWITCH_INITIAL, + // Switch as the result of the user selecting a new active workspace via the + // workspace cycler. + SWITCH_WORKSPACE_CYCLER, + // Edge case. See comment in OnWorkspaceWindowShowStateChanged(). Don't // make other types randomly use this! SWITCH_OTHER, @@ -263,6 +272,10 @@ class ASH_EXPORT WorkspaceManager : public ash::ShellObserver { // DesktopBackgroundFadeController. bool creating_fade_; + // Cycles through the workspace manager's workspaces in response to a three + // finger vertical scroll. + scoped_ptr<WorkspaceCycler> workspace_cycler_; + DISALLOW_COPY_AND_ASSIGN(WorkspaceManager); }; diff --git a/ash/wm/workspace_controller.cc b/ash/wm/workspace_controller.cc index 93099f9..c880663 100644 --- a/ash/wm/workspace_controller.cc +++ b/ash/wm/workspace_controller.cc @@ -4,11 +4,8 @@ #include "ash/wm/workspace_controller.h" -#include "ash/ash_switches.h" #include "ash/wm/window_util.h" -#include "ash/wm/workspace/workspace_cycler.h" #include "ash/wm/workspace/workspace_manager.h" -#include "base/command_line.h" #include "ui/aura/client/activation_client.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/root_window.h" @@ -18,14 +15,9 @@ namespace ash { namespace internal { WorkspaceController::WorkspaceController(aura::Window* viewport) - : viewport_(viewport), - workspace_cycler_(NULL) { + : viewport_(viewport) { aura::RootWindow* root_window = viewport->GetRootWindow(); workspace_manager_.reset(new WorkspaceManager(viewport)); - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kAshEnableWorkspaceScrubbing)) { - workspace_cycler_.reset(new WorkspaceCycler(workspace_manager_.get())); - } aura::client::GetActivationClient(root_window)->AddObserver(this); } diff --git a/ash/wm/workspace_controller.h b/ash/wm/workspace_controller.h index ba82e09..310c36e 100644 --- a/ash/wm/workspace_controller.h +++ b/ash/wm/workspace_controller.h @@ -20,7 +20,6 @@ namespace internal { class ShelfLayoutManager; class WorkspaceControllerTestHelper; -class WorkspaceCycler; class WorkspaceEventHandler; class WorkspaceManager; @@ -60,10 +59,6 @@ class ASH_EXPORT WorkspaceController scoped_ptr<WorkspaceManager> workspace_manager_; - // Cycles through the WorkspaceManager's workspaces in response to a three - // finger vertical scroll. - scoped_ptr<WorkspaceCycler> workspace_cycler_; - DISALLOW_COPY_AND_ASSIGN(WorkspaceController); }; |