// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "athena/wm/window_overview_mode.h" #include <algorithm> #include <functional> #include <vector> #include "base/macros.h" #include "ui/aura/scoped_window_targeter.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" #include "ui/aura/window_property.h" #include "ui/aura/window_targeter.h" #include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/events/event_handler.h" #include "ui/gfx/transform.h" #include "ui/wm/core/shadow.h" namespace { struct WindowOverviewState { // The transform for when the window is at the topmost position. gfx::Transform top; // The transform for when the window is at the bottom-most position. gfx::Transform bottom; // The current overview state of the window. 0.f means the window is at the // topmost position. 1.f means the window is at the bottom-most position. float progress; scoped_ptr<wm::Shadow> shadow; }; } // namespace DECLARE_WINDOW_PROPERTY_TYPE(WindowOverviewState*) DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowOverviewState, kWindowOverviewState, NULL) namespace athena { namespace { // Sets the progress-state for the window in the overview mode. void SetWindowProgress(aura::Window* window, float progress) { WindowOverviewState* state = window->GetProperty(kWindowOverviewState); gfx::Transform transform = gfx::Tween::TransformValueBetween(progress, state->top, state->bottom); window->SetTransform(transform); state->progress = progress; } // Resets the overview-related state for |window|. void RestoreWindowState(aura::Window* window) { window->ClearProperty(kWindowOverviewState); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); settings.SetPreemptionStrategy( ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250)); window->SetTransform(gfx::Transform()); } // Always returns the same target. class StaticWindowTargeter : public aura::WindowTargeter { public: explicit StaticWindowTargeter(aura::Window* target) : target_(target) {} virtual ~StaticWindowTargeter() {} private: // aura::WindowTargeter: virtual ui::EventTarget* FindTargetForEvent(ui::EventTarget* root, ui::Event* event) OVERRIDE { return target_; } virtual ui::EventTarget* FindTargetForLocatedEvent( ui::EventTarget* root, ui::LocatedEvent* event) OVERRIDE { return target_; } aura::Window* target_; DISALLOW_COPY_AND_ASSIGN(StaticWindowTargeter); }; class WindowOverviewModeImpl : public WindowOverviewMode, public ui::EventHandler { public: WindowOverviewModeImpl(aura::Window* container, WindowOverviewModeDelegate* delegate) : container_(container), delegate_(delegate), scoped_targeter_(new aura::ScopedWindowTargeter( container, scoped_ptr<ui::EventTargeter>( new StaticWindowTargeter(container)))) { container_->set_target_handler(this); // Prepare the desired transforms for all the windows, and set the initial // state on the windows. ComputeTerminalStatesForAllWindows(); SetInitialWindowStates(); } virtual ~WindowOverviewModeImpl() { container_->set_target_handler(container_->delegate()); aura::Window::Windows windows = container_->children(); if (windows.empty()) return; std::for_each(windows.begin(), windows.end(), &RestoreWindowState); } private: // Computes the transforms for all windows in both the topmost and bottom-most // positions. The transforms are set in the |kWindowOverviewState| property of // the windows. void ComputeTerminalStatesForAllWindows() { aura::Window::Windows windows = container_->children(); size_t window_count = windows.size(); size_t index = 0; const gfx::Size container_size = container_->bounds().size(); const int kGapBetweenWindowsBottom = 10; const int kGapBetweenWindowsTop = 5; const float kMinScale = 0.6f; const float kMaxScale = 0.95f; for (aura::Window::Windows::reverse_iterator iter = windows.rbegin(); iter != windows.rend(); ++iter, ++index) { aura::Window* window = (*iter); gfx::Transform top_transform; int top = (window_count - index - 1) * kGapBetweenWindowsTop; float x_translate = container_size.width() * (1 - kMinScale) / 2.; top_transform.Translate(x_translate, top); top_transform.Scale(kMinScale, kMinScale); gfx::Transform bottom_transform; int bottom = GetScrollableHeight() - (index * kGapBetweenWindowsBottom); x_translate = container_size.width() * (1 - kMaxScale) / 2.; bottom_transform.Translate(x_translate, bottom - window->bounds().y()); bottom_transform.Scale(kMaxScale, kMaxScale); WindowOverviewState* state = new WindowOverviewState; state->top = top_transform; state->bottom = bottom_transform; state->progress = 0.f; state->shadow = CreateShadowForWindow(window); window->SetProperty(kWindowOverviewState, state); } } // Sets the initial position for the windows for the overview mode. void SetInitialWindowStates() { aura::Window::Windows windows = container_->children(); size_t window_count = windows.size(); // The initial overview state of the topmost three windows. const float kInitialProgress[] = { 0.5f, 0.05f, 0.01f }; for (size_t i = 0; i < window_count; ++i) { float progress = 0.f; aura::Window* window = windows[window_count - 1 - i]; if (i < arraysize(kInitialProgress)) progress = kInitialProgress[i]; scoped_refptr<ui::LayerAnimator> animator = window->layer()->GetAnimator(); // Unset any in-progress animation. { ui::ScopedLayerAnimationSettings settings(animator); settings.SetPreemptionStrategy( ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET); window->Show(); window->SetTransform(gfx::Transform()); } // Setup the animation. { ui::ScopedLayerAnimationSettings settings(animator); settings.SetPreemptionStrategy( ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250)); SetWindowProgress(window, progress); } } } scoped_ptr<wm::Shadow> CreateShadowForWindow(aura::Window* window) { scoped_ptr<wm::Shadow> shadow(new wm::Shadow()); shadow->Init(wm::Shadow::STYLE_ACTIVE); shadow->SetContentBounds(gfx::Rect(window->bounds().size())); shadow->layer()->SetVisible(true); window->layer()->Add(shadow->layer()); return shadow.Pass(); } aura::Window* SelectWindowAt(ui::LocatedEvent* event) { CHECK_EQ(container_, event->target()); // Find the old targeter to find the target of the event. ui::EventTarget* window = container_; ui::EventTargeter* targeter = scoped_targeter_->old_targeter(); while (!targeter && window->GetParentTarget()) { window = window->GetParentTarget(); targeter = window->GetEventTargeter(); } if (!targeter) return NULL; aura::Window* target = static_cast<aura::Window*>( targeter->FindTargetForLocatedEvent(container_, event)); while (target && target->parent() != container_) target = target->parent(); return target; } // Scroll the window list by |delta_y| amount. |delta_y| is negative when // scrolling up; and positive when scrolling down. void DoScroll(float delta_y) { const float kEpsilon = 1e-3f; aura::Window::Windows windows = container_->children(); float delta_y_p = std::abs(delta_y) / GetScrollableHeight(); if (delta_y < 0) { // Scroll up. Start with the top-most (i.e. behind-most in terms of // z-index) window, and try to scroll them up. for (aura::Window::Windows::iterator iter = windows.begin(); delta_y_p > kEpsilon && iter != windows.end(); ++iter) { aura::Window* window = (*iter); WindowOverviewState* state = window->GetProperty(kWindowOverviewState); if (state->progress > kEpsilon) { // It is possible to scroll |window| up. Scroll it up, and update // |delta_y_p| for the next window. float apply = delta_y_p * state->progress; SetWindowProgress(window, std::max(0.f, state->progress - apply * 3)); delta_y_p -= apply; } } } else { // Scroll down. Start with the bottom-most (i.e. front-most in terms of // z-index) window, and try to scroll them down. for (aura::Window::Windows::reverse_iterator iter = windows.rbegin(); delta_y_p > kEpsilon && iter != windows.rend(); ++iter) { aura::Window* window = (*iter); WindowOverviewState* state = window->GetProperty(kWindowOverviewState); if (1.f - state->progress > kEpsilon) { // It is possible to scroll |window| down. Scroll it down, and update // |delta_y_p| for the next window. SetWindowProgress(window, std::min(1.f, state->progress + delta_y_p)); delta_y_p /= 2.f; } } } } int GetScrollableHeight() const { const float kScrollableFraction = 0.65f; return container_->bounds().height() * kScrollableFraction; } // ui::EventHandler: virtual void OnMouseEvent(ui::MouseEvent* mouse) OVERRIDE { if (mouse->type() == ui::ET_MOUSE_PRESSED) { aura::Window* select = SelectWindowAt(mouse); if (select) { mouse->SetHandled(); delegate_->OnSelectWindow(select); } } else if (mouse->type() == ui::ET_MOUSEWHEEL) { DoScroll(static_cast<ui::MouseWheelEvent*>(mouse)->y_offset()); } } virtual void OnScrollEvent(ui::ScrollEvent* scroll) OVERRIDE { if (scroll->type() == ui::ET_SCROLL) DoScroll(scroll->y_offset()); } virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE { if (gesture->type() == ui::ET_GESTURE_TAP) { aura::Window* select = SelectWindowAt(gesture); if (select) { gesture->SetHandled(); delegate_->OnSelectWindow(select); } } else if (gesture->type() == ui::ET_GESTURE_SCROLL_UPDATE) { DoScroll(gesture->details().scroll_y()); } } aura::Window* container_; WindowOverviewModeDelegate* delegate_; scoped_ptr<aura::ScopedWindowTargeter> scoped_targeter_; DISALLOW_COPY_AND_ASSIGN(WindowOverviewModeImpl); }; } // namespace scoped_ptr<WindowOverviewMode> WindowOverviewMode::Create( aura::Window* window, WindowOverviewModeDelegate* delegate) { return scoped_ptr<WindowOverviewMode>( new WindowOverviewModeImpl(window, delegate)); } } // namespace athena