blob: c9c3797b54c6031bb5d46999f92de31966cb6680 [file] [log] [blame]
// 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.
#include "content/browser/web_contents/aura/window_slider.h"
#include <algorithm>
#include "base/bind.h"
#include "base/callback.h"
#include "content/browser/web_contents/aura/shadow_layer_delegate.h"
#include "content/public/browser/overscroll_configuration.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/events/event.h"
namespace content {
namespace {
void DeleteLayerAndShadow(ui::Layer* layer,
ShadowLayerDelegate* shadow) {
delete shadow;
delete layer;
}
// An animation observer that runs a callback at the end of the animation, and
// destroys itself.
class CallbackAnimationObserver : public ui::ImplicitAnimationObserver {
public:
CallbackAnimationObserver(const base::Closure& closure)
: closure_(closure) {
}
virtual ~CallbackAnimationObserver() {}
private:
// Overridden from ui::ImplicitAnimationObserver:
virtual void OnImplicitAnimationsCompleted() OVERRIDE {
if (!closure_.is_null())
closure_.Run();
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
const base::Closure closure_;
DISALLOW_COPY_AND_ASSIGN(CallbackAnimationObserver);
};
} // namespace
WindowSlider::WindowSlider(Delegate* delegate,
aura::Window* event_window,
aura::Window* owner)
: delegate_(delegate),
event_window_(event_window),
owner_(owner),
delta_x_(0.f),
weak_factory_(this),
active_start_threshold_(0.f),
start_threshold_touchscreen_(content::GetOverscrollConfig(
content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN)),
start_threshold_touchpad_(content::GetOverscrollConfig(
content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHPAD)),
complete_threshold_(content::GetOverscrollConfig(
content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE)) {
event_window_->AddPreTargetHandler(this);
event_window_->AddObserver(this);
owner_->AddObserver(this);
}
WindowSlider::~WindowSlider() {
if (event_window_) {
event_window_->RemovePreTargetHandler(this);
event_window_->RemoveObserver(this);
}
if (owner_)
owner_->RemoveObserver(this);
delegate_->OnWindowSliderDestroyed();
}
void WindowSlider::ChangeOwner(aura::Window* new_owner) {
if (owner_)
owner_->RemoveObserver(this);
owner_ = new_owner;
if (owner_) {
owner_->AddObserver(this);
UpdateForScroll(0.f, 0.f);
}
}
bool WindowSlider::IsSlideInProgress() const {
return fabs(delta_x_) >= active_start_threshold_ || slider_.get() ||
weak_factory_.HasWeakPtrs();
}
void WindowSlider::SetupSliderLayer() {
ui::Layer* parent = owner_->layer()->parent();
parent->Add(slider_.get());
if (delta_x_ < 0)
parent->StackAbove(slider_.get(), owner_->layer());
else
parent->StackBelow(slider_.get(), owner_->layer());
slider_->SetBounds(owner_->layer()->bounds());
slider_->SetVisible(true);
}
void WindowSlider::UpdateForScroll(float x_offset, float y_offset) {
float old_delta = delta_x_;
delta_x_ += x_offset;
if (fabs(delta_x_) < active_start_threshold_ && !slider_.get())
return;
if ((old_delta < 0 && delta_x_ > 0) ||
(old_delta > 0 && delta_x_ < 0)) {
slider_.reset();
shadow_.reset();
}
float translate = 0.f;
ui::Layer* translate_layer = NULL;
if (!slider_.get()) {
slider_.reset(delta_x_ < 0 ? delegate_->CreateFrontLayer() :
delegate_->CreateBackLayer());
if (!slider_.get())
return;
SetupSliderLayer();
}
if (delta_x_ <= -active_start_threshold_) {
translate = owner_->bounds().width() +
std::max(delta_x_ + active_start_threshold_,
static_cast<float>(-owner_->bounds().width()));
translate_layer = slider_.get();
} else if (delta_x_ >= active_start_threshold_) {
translate = std::min(delta_x_ - active_start_threshold_,
static_cast<float>(owner_->bounds().width()));
translate_layer = owner_->layer();
} else {
return;
}
if (!shadow_.get())
shadow_.reset(new ShadowLayerDelegate(translate_layer));
gfx::Transform transform;
transform.Translate(translate, 0);
translate_layer->SetTransform(transform);
}
void WindowSlider::UpdateForFling(float x_velocity, float y_velocity) {
if (!slider_.get())
return;
int width = owner_->bounds().width();
float ratio = (fabs(delta_x_) - active_start_threshold_) / width;
if (ratio < complete_threshold_) {
ResetScroll();
return;
}
ui::Layer* sliding = delta_x_ < 0 ? slider_.get() : owner_->layer();
ui::ScopedLayerAnimationSettings settings(sliding->GetAnimator());
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTweenType(gfx::Tween::EASE_OUT);
settings.AddObserver(new CallbackAnimationObserver(
base::Bind(&WindowSlider::CompleteWindowSlideAfterAnimation,
weak_factory_.GetWeakPtr())));
gfx::Transform transform;
transform.Translate(delta_x_ < 0 ? 0 : width, 0);
sliding->SetTransform(transform);
}
void WindowSlider::ResetScroll() {
if (!slider_.get())
return;
// Do not trigger any callbacks if this animation replaces any in-progress
// animation.
weak_factory_.InvalidateWeakPtrs();
// Reset the state of the sliding layer.
if (slider_.get()) {
ui::Layer* layer = slider_.release();
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTweenType(gfx::Tween::EASE_OUT);
// Delete the layer and the shadow at the end of the animation.
settings.AddObserver(new CallbackAnimationObserver(
base::Bind(&DeleteLayerAndShadow,
base::Unretained(layer),
base::Unretained(shadow_.release()))));
gfx::Transform transform;
transform.Translate(delta_x_ < 0 ? layer->bounds().width() : 0, 0);
layer->SetTransform(transform);
}
// Reset the state of the main layer.
{
ui::ScopedLayerAnimationSettings settings(owner_->layer()->GetAnimator());
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTweenType(gfx::Tween::EASE_OUT);
settings.AddObserver(new CallbackAnimationObserver(
base::Bind(&WindowSlider::AbortWindowSlideAfterAnimation,
weak_factory_.GetWeakPtr())));
owner_->layer()->SetTransform(gfx::Transform());
owner_->layer()->SetLayerBrightness(0.f);
}
delta_x_ = 0.f;
}
void WindowSlider::CancelScroll() {
ResetScroll();
}
void WindowSlider::CompleteWindowSlideAfterAnimation() {
weak_factory_.InvalidateWeakPtrs();
shadow_.reset();
slider_.reset();
delta_x_ = 0.f;
delegate_->OnWindowSlideComplete();
}
void WindowSlider::AbortWindowSlideAfterAnimation() {
weak_factory_.InvalidateWeakPtrs();
delegate_->OnWindowSlideAborted();
}
void WindowSlider::OnKeyEvent(ui::KeyEvent* event) {
CancelScroll();
}
void WindowSlider::OnMouseEvent(ui::MouseEvent* event) {
if (!(event->flags() & ui::EF_IS_SYNTHESIZED))
CancelScroll();
}
void WindowSlider::OnScrollEvent(ui::ScrollEvent* event) {
active_start_threshold_ = start_threshold_touchpad_;
if (event->type() == ui::ET_SCROLL)
UpdateForScroll(event->x_offset_ordinal(), event->y_offset_ordinal());
else if (event->type() == ui::ET_SCROLL_FLING_START)
UpdateForFling(event->x_offset_ordinal(), event->y_offset_ordinal());
else
CancelScroll();
event->SetHandled();
}
void WindowSlider::OnGestureEvent(ui::GestureEvent* event) {
active_start_threshold_ = start_threshold_touchscreen_;
const ui::GestureEventDetails& details = event->details();
switch (event->type()) {
case ui::ET_GESTURE_SCROLL_BEGIN:
ResetScroll();
break;
case ui::ET_GESTURE_SCROLL_UPDATE:
UpdateForScroll(details.scroll_x(), details.scroll_y());
break;
case ui::ET_GESTURE_SCROLL_END:
UpdateForFling(0.f, 0.f);
break;
case ui::ET_SCROLL_FLING_START:
UpdateForFling(details.velocity_x(), details.velocity_y());
break;
case ui::ET_GESTURE_PINCH_BEGIN:
case ui::ET_GESTURE_PINCH_UPDATE:
case ui::ET_GESTURE_PINCH_END:
CancelScroll();
break;
default:
break;
}
event->SetHandled();
}
void WindowSlider::OnWindowRemovingFromRootWindow(aura::Window* window) {
if (window == event_window_) {
window->RemoveObserver(this);
window->RemovePreTargetHandler(this);
event_window_ = NULL;
} else if (window == owner_) {
window->RemoveObserver(this);
owner_ = NULL;
delete this;
} else {
NOTREACHED();
}
}
} // namespace content