Index: content/browser/web_contents/aura/window_slider.cc |
diff --git a/content/browser/web_contents/aura/window_slider.cc b/content/browser/web_contents/aura/window_slider.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f7ffa7e38392c0607513efa7219946bd47d34ba5 |
--- /dev/null |
+++ b/content/browser/web_contents/aura/window_slider.cc |
@@ -0,0 +1,309 @@ |
+// 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 { |
+ |
+// 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) { |
+ } |
+ |
+ ~CallbackAnimationObserver() override {} |
+ |
+ private: |
+ // Overridden from ui::ImplicitAnimationObserver: |
+ 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), |
+ active_animator_(NULL), |
+ delta_x_(0.f), |
+ 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)), |
+ weak_factory_(this) { |
+ 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 { |
+ // if active_start_threshold_ is 0, it means that sliding hasn't been started |
+ return active_start_threshold_ != 0 && (slider_.get() || active_animator_); |
+} |
+ |
+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) { |
+ if (active_animator_) { |
+ // If there is an active animation, complete it before processing the scroll |
+ // so that the callbacks that are invoked on the Delegate are consistent. |
+ // Completing the animation may destroy WindowSlider through the animation |
+ // callback, so we can't continue processing the scroll event here. |
+ delta_x_ += x_offset; |
+ CompleteActiveAnimations(); |
+ return; |
+ } |
+ |
+ 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::CompleteOrResetSlide() { |
+ if (!slider_.get()) |
+ return; |
+ |
+ int width = owner_->bounds().width(); |
+ float ratio = (fabs(delta_x_) - active_start_threshold_) / width; |
+ if (ratio < complete_threshold_) { |
+ ResetSlide(); |
+ return; |
+ } |
+ |
+ ui::Layer* sliding = delta_x_ < 0 ? slider_.get() : owner_->layer(); |
+ active_animator_ = sliding->GetAnimator(); |
+ |
+ ui::ScopedLayerAnimationSettings settings(active_animator_); |
+ settings.SetPreemptionStrategy( |
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
+ settings.SetTweenType(gfx::Tween::EASE_OUT); |
+ settings.AddObserver(new CallbackAnimationObserver( |
+ base::Bind(&WindowSlider::SlideAnimationCompleted, |
+ weak_factory_.GetWeakPtr(), |
+ base::Passed(&slider_), |
+ base::Passed(&shadow_)))); |
+ |
+ gfx::Transform transform; |
+ transform.Translate(delta_x_ < 0 ? 0 : width, 0); |
+ delta_x_ = 0; |
+ delegate_->OnWindowSlideCompleting(); |
+ sliding->SetTransform(transform); |
+} |
+ |
+void WindowSlider::CompleteActiveAnimations() { |
+ if (active_animator_) |
+ active_animator_->StopAnimating(); |
+} |
+ |
+void WindowSlider::ResetSlide() { |
+ if (!slider_.get()) |
+ return; |
+ |
+ // Reset the state of the sliding layer. |
+ if (slider_.get()) { |
+ ui::Layer* translate_layer; |
+ gfx::Transform transform; |
+ if (delta_x_ < 0) { |
+ translate_layer = slider_.get(); |
+ transform.Translate(translate_layer->bounds().width(), 0); |
+ } else { |
+ translate_layer = owner_->layer(); |
+ } |
+ |
+ active_animator_ = translate_layer->GetAnimator(); |
+ ui::ScopedLayerAnimationSettings settings(active_animator_); |
+ settings.SetPreemptionStrategy( |
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
+ settings.SetTweenType(gfx::Tween::EASE_OUT); |
+ settings.AddObserver(new CallbackAnimationObserver( |
+ base::Bind(&WindowSlider::ResetSlideAnimationCompleted, |
+ weak_factory_.GetWeakPtr(), |
+ base::Passed(&slider_), |
+ base::Passed(&shadow_)))); |
+ translate_layer->SetTransform(transform); |
+ } |
+ |
+ delta_x_ = 0.f; |
+} |
+ |
+void WindowSlider::SlideAnimationCompleted( |
+ scoped_ptr<ui::Layer> layer, scoped_ptr<ShadowLayerDelegate> shadow) { |
+ active_animator_ = NULL; |
+ shadow.reset(); |
+ delegate_->OnWindowSlideCompleted(layer.Pass()); |
+} |
+ |
+void WindowSlider::ResetSlideAnimationCompleted( |
+ scoped_ptr<ui::Layer> layer, scoped_ptr<ShadowLayerDelegate> shadow) { |
+ active_animator_ = NULL; |
+ shadow.reset(); |
+ layer.reset(); |
+ delegate_->OnWindowSlideAborted(); |
+} |
+ |
+void WindowSlider::OnKeyEvent(ui::KeyEvent* event) { |
+ ResetSlide(); |
+} |
+ |
+void WindowSlider::OnMouseEvent(ui::MouseEvent* event) { |
+ if (!(event->flags() & ui::EF_IS_SYNTHESIZED)) |
+ ResetSlide(); |
+} |
+ |
+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) |
+ CompleteOrResetSlide(); |
+ else |
+ ResetSlide(); |
+ 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: |
+ CompleteActiveAnimations(); |
+ break; |
+ |
+ case ui::ET_GESTURE_SCROLL_UPDATE: |
+ UpdateForScroll(details.scroll_x(), details.scroll_y()); |
+ break; |
+ |
+ case ui::ET_GESTURE_SCROLL_END: |
+ CompleteOrResetSlide(); |
+ break; |
+ |
+ case ui::ET_SCROLL_FLING_START: |
+ CompleteOrResetSlide(); |
+ break; |
+ |
+ case ui::ET_GESTURE_PINCH_BEGIN: |
+ case ui::ET_GESTURE_PINCH_UPDATE: |
+ case ui::ET_GESTURE_PINCH_END: |
+ ResetSlide(); |
+ break; |
+ |
+ default: |
+ break; |
+ } |
+ |
+ event->SetHandled(); |
+} |
+ |
+void WindowSlider::OnWindowRemovingFromRootWindow(aura::Window* window, |
+ aura::Window* new_root) { |
+ 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 |