Chromium Code Reviews| Index: content/browser/android/overscroll_controller_android.cc |
| diff --git a/content/browser/android/overscroll_controller_android.cc b/content/browser/android/overscroll_controller_android.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..cb18e66a64ca2a8fd7c2cfa4398ba729ba73469b |
| --- /dev/null |
| +++ b/content/browser/android/overscroll_controller_android.cc |
| @@ -0,0 +1,221 @@ |
| +// 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 "content/browser/android/overscroll_controller_android.h" |
| + |
| +#include "base/android/build_info.h" |
| +#include "cc/layers/layer.h" |
| +#include "cc/output/compositor_frame_metadata.h" |
| +#include "content/browser/android/content_view_core_impl.h" |
| +#include "content/browser/android/edge_effect.h" |
| +#include "content/browser/android/edge_effect_l.h" |
| +#include "content/common/input/did_overscroll_params.h" |
| +#include "ui/base/android/window_android.h" |
| +#include "ui/base/android/window_android_compositor.h" |
| + |
| +namespace content { |
| +namespace { |
| + |
| +// Used for conditional creation of EdgeEffect types for the overscroll glow. |
| +const int kAndroidLSDKVersion = 21; |
| + |
| +ui::SystemUIResourceManager* GetSystemUIResourceManager( |
| + ContentViewCore* content_view_core) { |
| + ui::WindowAndroid* window = content_view_core->GetWindowAndroid(); |
| + DCHECK(window); |
| + ui::WindowAndroidCompositor* compositor = window->GetCompositor(); |
| + DCHECK(compositor); |
| + return &compositor->GetSystemUIResourceManager(); |
| +} |
| + |
| +scoped_ptr<EdgeEffectBase> CreateGlowEdgeEffect( |
| + ui::SystemUIResourceManager* resource_manager, |
| + float dpi_scale) { |
| + DCHECK(resource_manager); |
| + static bool use_l_flavoured_effect = |
| + base::android::BuildInfo::GetInstance()->sdk_int() >= kAndroidLSDKVersion; |
| + if (use_l_flavoured_effect) |
| + return scoped_ptr<EdgeEffectBase>(new EdgeEffectL(resource_manager)); |
| + |
| + return scoped_ptr<EdgeEffectBase>( |
| + new EdgeEffect(resource_manager, dpi_scale)); |
| +} |
| + |
| +} // namespace |
| + |
| +OverscrollControllerAndroid::OverscrollControllerAndroid( |
| + ContentViewCoreImpl* content_view_core) |
| + : WebContentsObserver(content_view_core->GetWebContents()), |
| + content_view_core_(content_view_core), |
| + enabled_(true), |
| + glow_effect_(base::Bind(&CreateGlowEdgeEffect, |
| + GetSystemUIResourceManager(content_view_core), |
| + content_view_core->GetDpiScale())), |
| + refresh_effect_(GetSystemUIResourceManager(content_view_core), this), |
| + triggered_refresh_active_(false) { |
| +} |
| + |
| +OverscrollControllerAndroid::~OverscrollControllerAndroid() { |
| +} |
| + |
| +bool OverscrollControllerAndroid::WillHandleGestureEvent( |
| + const blink::WebGestureEvent& event) { |
| + if (!enabled_) |
| + return false; |
| + |
| + bool handled = false; |
| + bool maybe_needs_animate = false; |
| + switch (event.type) { |
| + case blink::WebInputEvent::GestureScrollBegin: |
| + refresh_effect_.OnScrollBegin(); |
| + break; |
| + |
| + case blink::WebInputEvent::GestureScrollUpdate: { |
| + gfx::Vector2dF scroll_delta(event.data.scrollUpdate.deltaX, |
| + event.data.scrollUpdate.deltaY); |
| + scroll_delta.Scale(GetDpiScale()); |
| + maybe_needs_animate = true; |
| + handled = refresh_effect_.WillHandleScrollUpdate(scroll_delta); |
| + } break; |
| + |
| + case blink::WebInputEvent::GestureScrollEnd: |
| + refresh_effect_.OnScrollEnd(gfx::Vector2dF()); |
| + maybe_needs_animate = true; |
| + break; |
| + |
| + case blink::WebInputEvent::GestureFlingStart: { |
| + gfx::Vector2dF scroll_velocity(event.data.flingStart.velocityX, |
| + event.data.flingStart.velocityY); |
| + scroll_velocity.Scale(GetDpiScale()); |
| + refresh_effect_.OnScrollEnd(scroll_velocity); |
| + if (refresh_effect_.IsActive()) { |
| + // TODO(jdduke): Figure out a cleaner way of suppressing a fling. |
|
aelias_OOO_until_Jul13
2014/10/30 03:53:21
What would happen if we just let flings always go
jdduke (slow)
2014/10/30 15:19:02
That's the behavior I had originally, and it's cer
|
| + // It's important that the any downstream code sees a scroll-ending |
| + // event (in this case GestureFlingStart) if it has seen a scroll begin. |
| + // Thus, we cannot simply consume the fling. Changing the event type to |
| + // a GestureScrollEnd might work in practice, but could lead to |
| + // unexpected results. For now, simply truncate the fling velocity, but |
| + // not to zero as downstream code may not expect a zero-velocity fling. |
| + blink::WebGestureEvent& modified_event = |
| + const_cast<blink::WebGestureEvent&>(event); |
| + modified_event.data.flingStart.velocityX = .01f; |
| + modified_event.data.flingStart.velocityY = .01f; |
| + } |
| + maybe_needs_animate = true; |
| + } break; |
| + |
| + default: |
| + break; |
| + } |
| + |
| + if (maybe_needs_animate && refresh_effect_.IsActive()) |
| + content_view_core_->SetNeedsAnimate(); |
|
Ted C
2014/10/31 23:52:05
I wonder if we should just make GetRenderWidgetHos
jdduke (slow)
2014/11/06 00:10:37
Your call, don't have a strong opinion here. It's
Ted C
2014/11/06 19:25:40
I have a slight preference for exposing RWHVA. Ma
jdduke (slow)
2014/11/07 18:33:44
I did us all a favor and removed all of the Conten
|
| + |
| + return handled; |
| +} |
| + |
| +void OverscrollControllerAndroid::OnGestureEventAck( |
| + const blink::WebGestureEvent& event, |
| + InputEventAckState ack_result) { |
| + if (!enabled_) |
| + return; |
| + |
| + if (event.type == blink::WebInputEvent::GestureScrollUpdate) { |
| + bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED; |
| + refresh_effect_.OnScrollUpdateAck(consumed); |
| + } |
| +} |
| + |
| +void OverscrollControllerAndroid::OnOverscrolled( |
| + const DidOverscrollParams& params) { |
| + if (!enabled_) |
| + return; |
| + |
| + if (refresh_effect_.IsActive() || |
| + refresh_effect_.IsPendingScrollUpdateAck()) { |
| + // An active (or potentially active) refresh effect should always pre-empt |
| + // the passive glow effect. |
| + return; |
| + } |
| + |
| + const float dpi_scale = GetDpiScale(); |
| + if (glow_effect_.OnOverscrolled( |
| + base::TimeTicks::Now(), |
| + gfx::ScaleVector2d(params.accumulated_overscroll, dpi_scale), |
| + gfx::ScaleVector2d(params.latest_overscroll_delta, dpi_scale), |
| + gfx::ScaleVector2d(params.current_fling_velocity, dpi_scale), |
| + gfx::ScaleVector2d( |
| + params.causal_event_viewport_point.OffsetFromOrigin(), |
| + dpi_scale))) { |
| + content_view_core_->SetNeedsAnimate(); |
| + } |
| +} |
| + |
| +bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time) { |
| + if (!enabled_) |
| + return false; |
| + |
| + scoped_refptr<cc::Layer> parent = content_view_core_->GetLayer(); |
| + bool needs_animate = refresh_effect_.Animate(current_time, parent.get()); |
| + needs_animate |= glow_effect_.Animate(current_time, parent.get()); |
| + return needs_animate; |
| +} |
| + |
| +void OverscrollControllerAndroid::OnFrameMetadataUpdated( |
| + const cc::CompositorFrameMetadata& frame_metadata) { |
| + const float scale_factor = |
| + frame_metadata.page_scale_factor * frame_metadata.device_scale_factor; |
| + gfx::SizeF viewport_size = |
| + gfx::ScaleSize(frame_metadata.scrollable_viewport_size, scale_factor); |
| + gfx::SizeF content_size = |
| + gfx::ScaleSize(frame_metadata.root_layer_size, scale_factor); |
| + gfx::Vector2dF content_scroll_offset = |
| + gfx::ScaleVector2d(frame_metadata.root_scroll_offset, scale_factor); |
| + |
| + refresh_effect_.UpdateDisplay(viewport_size, content_scroll_offset); |
| + glow_effect_.UpdateDisplay( |
| + viewport_size, content_size, content_scroll_offset); |
| +} |
| + |
| +void OverscrollControllerAndroid::Enable() { |
| + enabled_ = true; |
| +} |
| + |
| +void OverscrollControllerAndroid::Disable() { |
| + if (!enabled_) |
| + return; |
| + enabled_ = false; |
| + if (!enabled_) { |
| + refresh_effect_.Reset(); |
| + glow_effect_.Reset(); |
| + } |
| +} |
| + |
| +void OverscrollControllerAndroid::DidNavigateMainFrame( |
| + const LoadCommittedDetails& details, |
| + const FrameNavigateParams& params) { |
| + // Once the main frame has navigated, there's little need to further animate |
| + // the reload effect. Note that the effect will naturally time out should the |
| + // reload be interruped for any reason. |
| + triggered_refresh_active_ = false; |
| +} |
| + |
| +void OverscrollControllerAndroid::TriggerRefresh() { |
| + triggered_refresh_active_ = false; |
| + WebContents* web_contents = content_view_core_->GetWebContents(); |
| + if (web_contents) { |
| + triggered_refresh_active_ = true; |
| + web_contents->ReloadFocusedFrame(false); |
| + } |
| +} |
| + |
| +bool OverscrollControllerAndroid::IsTriggeredRefreshActive() const { |
| + return triggered_refresh_active_; |
| +} |
| + |
| +float OverscrollControllerAndroid::GetDpiScale() const { |
| + return content_view_core_->GetDpiScale(); |
| +} |
| + |
| +} // namespace content |