summaryrefslogtreecommitdiffstats
path: root/ash
diff options
context:
space:
mode:
authorpkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-05 22:14:46 +0000
committerpkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-05 22:14:46 +0000
commit51903e03437d794f6ca636f8be001cfe4d37a590 (patch)
tree223a7d4c6f61782925ea7d9e693671176c5182e5 /ash
parent830edfdbeac173247654fcb930ae8b5193963387 (diff)
downloadchromium_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.gyp2
-rw-r--r--ash/wm/workspace/workspace.cc11
-rw-r--r--ash/wm/workspace/workspace.h4
-rw-r--r--ash/wm/workspace/workspace_cycler.cc150
-rw-r--r--ash/wm/workspace/workspace_cycler.h83
-rw-r--r--ash/wm/workspace/workspace_cycler_animator.cc710
-rw-r--r--ash/wm/workspace/workspace_cycler_animator.h188
-rw-r--r--ash/wm/workspace/workspace_manager.cc87
-rw-r--r--ash/wm/workspace/workspace_manager.h31
-rw-r--r--ash/wm/workspace_controller.cc10
-rw-r--r--ash/wm/workspace_controller.h5
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);
};