// 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 "ui/android/overscroll_glow.h" #include #include "cc/layers/layer.h" #include "ui/android/edge_effect_base.h" #include "ui/android/window_android_compositor.h" using std::max; using std::min; namespace ui { namespace { const float kEpsilon = 1e-3f; bool IsApproxZero(float value) { return std::abs(value) < kEpsilon; } gfx::Vector2dF ZeroSmallComponents(gfx::Vector2dF vector) { if (IsApproxZero(vector.x())) vector.set_x(0); if (IsApproxZero(vector.y())) vector.set_y(0); return vector; } } // namespace OverscrollGlow::OverscrollGlow(OverscrollGlowClient* client) : client_(client), edge_offsets_(), initialized_(false), allow_horizontal_overscroll_(true), allow_vertical_overscroll_(true) { DCHECK(client); } OverscrollGlow::~OverscrollGlow() { Detach(); } void OverscrollGlow::Reset() { if (!initialized_) return; Detach(); for (size_t i = 0; i < EDGE_COUNT; ++i) edge_effects_[i]->Finish(); } bool OverscrollGlow::IsActive() const { if (!initialized_) return false; for (size_t i = 0; i < EDGE_COUNT; ++i) { if (!edge_effects_[i]->IsFinished()) return true; } return false; } float OverscrollGlow::GetVisibleAlpha() const { float max_alpha = 0; for (size_t i = 0; i < EDGE_COUNT; ++i) { if (!edge_effects_[i]->IsFinished()) max_alpha = std::max(max_alpha, edge_effects_[i]->GetAlpha()); } return std::min(max_alpha, 1.f); } bool OverscrollGlow::OnOverscrolled(base::TimeTicks current_time, const gfx::Vector2dF& accumulated_overscroll, gfx::Vector2dF overscroll_delta, gfx::Vector2dF velocity, const gfx::Vector2dF& overscroll_location) { // The size of the glow determines the relative effect of the inputs; an // empty-sized effect is effectively disabled. if (viewport_size_.IsEmpty()) return false; if (!allow_horizontal_overscroll_) { overscroll_delta.set_x(0); velocity.set_x(0); } if (!allow_vertical_overscroll_) { overscroll_delta.set_y(0); velocity.set_y(0); } // Ignore sufficiently small values that won't meaningfuly affect animation. overscroll_delta = ZeroSmallComponents(overscroll_delta); if (overscroll_delta.IsZero()) { if (initialized_) Release(current_time); return CheckNeedsAnimate(); } if (!InitializeIfNecessary()) return false; gfx::Vector2dF old_overscroll = accumulated_overscroll - overscroll_delta; bool x_overscroll_started = !IsApproxZero(overscroll_delta.x()) && IsApproxZero(old_overscroll.x()); bool y_overscroll_started = !IsApproxZero(overscroll_delta.y()) && IsApproxZero(old_overscroll.y()); velocity = ZeroSmallComponents(velocity); if (!velocity.IsZero()) Absorb(current_time, velocity, x_overscroll_started, y_overscroll_started); else Pull(current_time, overscroll_delta, overscroll_location); return CheckNeedsAnimate(); } bool OverscrollGlow::Animate(base::TimeTicks current_time, cc::Layer* parent_layer) { DCHECK(parent_layer); if (!CheckNeedsAnimate()) return false; UpdateLayerAttachment(parent_layer); for (size_t i = 0; i < EDGE_COUNT; ++i) { if (edge_effects_[i]->Update(current_time)) { EdgeEffectBase::Edge edge = static_cast(i); edge_effects_[i]->ApplyToLayers(edge, viewport_size_, edge_offsets_[i]); } } return CheckNeedsAnimate(); } void OverscrollGlow::OnFrameUpdated( const gfx::SizeF& viewport_size, const gfx::SizeF& content_size, const gfx::Vector2dF& content_scroll_offset) { viewport_size_ = viewport_size; edge_offsets_[EdgeEffectBase::EDGE_TOP] = -content_scroll_offset.y(); edge_offsets_[EdgeEffectBase::EDGE_LEFT] = -content_scroll_offset.x(); edge_offsets_[EdgeEffectBase::EDGE_BOTTOM] = content_size.height() - content_scroll_offset.y() - viewport_size.height(); edge_offsets_[EdgeEffectBase::EDGE_RIGHT] = content_size.width() - content_scroll_offset.x() - viewport_size.width(); // Only allow overscroll on scrollable axes, matching platform behavior. allow_horizontal_overscroll_ = std::ceil(viewport_size_.width()) < std::floor(content_size.width()); allow_vertical_overscroll_ = std::ceil(viewport_size_.height()) < std::floor(content_size.height()); } bool OverscrollGlow::CheckNeedsAnimate() { if (!initialized_) { Detach(); return false; } if (IsActive()) return true; Detach(); return false; } void OverscrollGlow::UpdateLayerAttachment(cc::Layer* parent) { DCHECK(parent); if (!root_layer_.get()) return; if (!CheckNeedsAnimate()) return; if (root_layer_->parent() != parent) parent->AddChild(root_layer_); for (size_t i = 0; i < EDGE_COUNT; ++i) edge_effects_[i]->SetParent(root_layer_.get()); } void OverscrollGlow::Detach() { if (root_layer_.get()) root_layer_->RemoveFromParent(); } bool OverscrollGlow::InitializeIfNecessary() { if (initialized_) return true; DCHECK(!root_layer_.get()); root_layer_ = cc::Layer::Create(); for (size_t i = 0; i < EDGE_COUNT; ++i) { edge_effects_[i] = client_->CreateEdgeEffect(); DCHECK(edge_effects_[i]); } initialized_ = true; return true; } void OverscrollGlow::Pull(base::TimeTicks current_time, const gfx::Vector2dF& overscroll_delta, const gfx::Vector2dF& overscroll_location) { DCHECK(initialized_); DCHECK(!overscroll_delta.IsZero()); DCHECK(!viewport_size_.IsEmpty()); const float inv_width = 1.f / viewport_size_.width(); const float inv_height = 1.f / viewport_size_.height(); gfx::Vector2dF overscroll_pull = gfx::ScaleVector2d(overscroll_delta, inv_width, inv_height); const float edge_pull[EDGE_COUNT] = { min(overscroll_pull.y(), 0.f), // Top min(overscroll_pull.x(), 0.f), // Left max(overscroll_pull.y(), 0.f), // Bottom max(overscroll_pull.x(), 0.f) // Right }; gfx::Vector2dF displacement = gfx::ScaleVector2d(overscroll_location, inv_width, inv_height); displacement.set_x(max(0.f, min(1.f, displacement.x()))); displacement.set_y(max(0.f, min(1.f, displacement.y()))); const float edge_displacement[EDGE_COUNT] = { 1.f - displacement.x(), // Top displacement.y(), // Left displacement.x(), // Bottom 1.f - displacement.y() // Right }; for (size_t i = 0; i < EDGE_COUNT; ++i) { if (!edge_pull[i]) continue; edge_effects_[i]->Pull( current_time, std::abs(edge_pull[i]), edge_displacement[i]); GetOppositeEdge(i)->Release(current_time); } } void OverscrollGlow::Absorb(base::TimeTicks current_time, const gfx::Vector2dF& velocity, bool x_overscroll_started, bool y_overscroll_started) { DCHECK(initialized_); DCHECK(!velocity.IsZero()); // Only trigger on initial overscroll at a non-zero velocity const float overscroll_velocities[EDGE_COUNT] = { y_overscroll_started ? min(velocity.y(), 0.f) : 0, // Top x_overscroll_started ? min(velocity.x(), 0.f) : 0, // Left y_overscroll_started ? max(velocity.y(), 0.f) : 0, // Bottom x_overscroll_started ? max(velocity.x(), 0.f) : 0 // Right }; for (size_t i = 0; i < EDGE_COUNT; ++i) { if (!overscroll_velocities[i]) continue; edge_effects_[i]->Absorb(current_time, std::abs(overscroll_velocities[i])); GetOppositeEdge(i)->Release(current_time); } } void OverscrollGlow::Release(base::TimeTicks current_time) { DCHECK(initialized_); for (size_t i = 0; i < EDGE_COUNT; ++i) edge_effects_[i]->Release(current_time); } EdgeEffectBase* OverscrollGlow::GetOppositeEdge(int edge_index) { DCHECK(initialized_); return edge_effects_[(edge_index + 2) % EDGE_COUNT].get(); } } // namespace ui