1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/web_contents/aura/gesture_nav_simple.h" 6 7#include "cc/layers/layer.h" 8#include "content/browser/frame_host/navigation_controller_impl.h" 9#include "content/browser/renderer_host/overscroll_controller.h" 10#include "content/browser/web_contents/web_contents_impl.h" 11#include "content/browser/web_contents/web_contents_view.h" 12#include "content/public/browser/browser_thread.h" 13#include "content/public/browser/overscroll_configuration.h" 14#include "content/public/common/content_client.h" 15#include "ui/aura/window.h" 16#include "ui/compositor/layer.h" 17#include "ui/compositor/layer_animation_observer.h" 18#include "ui/compositor/layer_delegate.h" 19#include "ui/compositor/scoped_layer_animation_settings.h" 20#include "ui/gfx/animation/tween.h" 21#include "ui/gfx/canvas.h" 22#include "ui/gfx/image/image.h" 23#include "ui/resources/grit/ui_resources.h" 24 25namespace content { 26 27namespace { 28 29const int kArrowHeight = 280; 30const int kArrowWidth = 140; 31const float kMinOpacity = 0.25f; 32 33bool ShouldNavigateForward(const NavigationController& controller, 34 OverscrollMode mode) { 35 return mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST) && 36 controller.CanGoForward(); 37} 38 39bool ShouldNavigateBack(const NavigationController& controller, 40 OverscrollMode mode) { 41 return mode == (base::i18n::IsRTL() ? OVERSCROLL_WEST : OVERSCROLL_EAST) && 42 controller.CanGoBack(); 43} 44 45// An animation observers that deletes itself and a pointer after the end of the 46// animation. 47template <class T> 48class DeleteAfterAnimation : public ui::ImplicitAnimationObserver { 49 public: 50 explicit DeleteAfterAnimation(scoped_ptr<T> object) 51 : object_(object.Pass()) {} 52 53 private: 54 friend class base::DeleteHelper<DeleteAfterAnimation<T> >; 55 56 virtual ~DeleteAfterAnimation() {} 57 58 // ui::ImplicitAnimationObserver: 59 virtual void OnImplicitAnimationsCompleted() OVERRIDE { 60 // Deleting an observer when a ScopedLayerAnimationSettings is iterating 61 // over them can cause a crash (which can happen during tests). So instead, 62 // schedule this observer to be deleted soon. 63 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this); 64 } 65 66 scoped_ptr<T> object_; 67 DISALLOW_COPY_AND_ASSIGN(DeleteAfterAnimation); 68}; 69 70} // namespace 71 72// A layer delegate that paints the shield with the arrow in it. 73class ArrowLayerDelegate : public ui::LayerDelegate { 74 public: 75 explicit ArrowLayerDelegate(int resource_id) 76 : image_(GetContentClient()->GetNativeImageNamed(resource_id)), 77 left_arrow_(resource_id == IDR_BACK_ARROW) { 78 CHECK(!image_.IsEmpty()); 79 } 80 81 virtual ~ArrowLayerDelegate() {} 82 83 bool left() const { return left_arrow_; } 84 85 private: 86 // ui::LayerDelegate: 87 virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE { 88 SkPaint paint; 89 paint.setColor(SkColorSetARGB(0xa0, 0, 0, 0)); 90 paint.setStyle(SkPaint::kFill_Style); 91 paint.setAntiAlias(true); 92 93 canvas->DrawCircle( 94 gfx::Point(left_arrow_ ? 0 : kArrowWidth, kArrowHeight / 2), 95 kArrowWidth, 96 paint); 97 canvas->DrawImageInt(*image_.ToImageSkia(), 98 left_arrow_ ? 0 : kArrowWidth - image_.Width(), 99 (kArrowHeight - image_.Height()) / 2); 100 } 101 102 virtual void OnDelegatedFrameDamage( 103 const gfx::Rect& damage_rect_in_dip) OVERRIDE {} 104 105 virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {} 106 107 virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE { 108 return base::Closure(); 109 } 110 111 const gfx::Image& image_; 112 const bool left_arrow_; 113 114 DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate); 115}; 116 117GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents) 118 : web_contents_(web_contents), 119 completion_threshold_(0.f) {} 120 121GestureNavSimple::~GestureNavSimple() {} 122 123void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform& transform, 124 float opacity) { 125 ui::Layer* layer = arrow_.get(); 126 ui::ScopedLayerAnimationSettings settings(arrow_->GetAnimator()); 127 settings.AddObserver( 128 new DeleteAfterAnimation<ArrowLayerDelegate>(arrow_delegate_.Pass())); 129 settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(arrow_.Pass())); 130 settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(clip_layer_.Pass())); 131 layer->SetTransform(transform); 132 layer->SetOpacity(opacity); 133} 134 135void GestureNavSimple::AbortGestureAnimation() { 136 if (!arrow_) 137 return; 138 gfx::Transform transform; 139 transform.Translate(arrow_delegate_->left() ? -kArrowWidth : kArrowWidth, 0); 140 ApplyEffectsAndDestroy(transform, kMinOpacity); 141} 142 143void GestureNavSimple::CompleteGestureAnimation() { 144 if (!arrow_) 145 return; 146 // Make sure the fade-out starts from the complete state. 147 ApplyEffectsForDelta(completion_threshold_); 148 ApplyEffectsAndDestroy(arrow_->transform(), 0.f); 149} 150 151bool GestureNavSimple::ApplyEffectsForDelta(float delta_x) { 152 if (!arrow_) 153 return false; 154 CHECK_GT(completion_threshold_, 0.f); 155 CHECK_GE(delta_x, 0.f); 156 double complete = std::min(1.f, delta_x / completion_threshold_); 157 float translate_x = gfx::Tween::FloatValueBetween(complete, -kArrowWidth, 0); 158 gfx::Transform transform; 159 transform.Translate(arrow_delegate_->left() ? translate_x : -translate_x, 160 0.f); 161 arrow_->SetTransform(transform); 162 arrow_->SetOpacity(gfx::Tween::FloatValueBetween(complete, kMinOpacity, 1.f)); 163 return true; 164} 165 166gfx::Rect GestureNavSimple::GetVisibleBounds() const { 167 return web_contents_->GetNativeView()->bounds(); 168} 169 170bool GestureNavSimple::OnOverscrollUpdate(float delta_x, float delta_y) { 171 return ApplyEffectsForDelta(std::abs(delta_x) + 50.f); 172} 173 174void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode) { 175 CompleteGestureAnimation(); 176 177 NavigationControllerImpl& controller = web_contents_->GetController(); 178 if (ShouldNavigateForward(controller, overscroll_mode)) 179 controller.GoForward(); 180 else if (ShouldNavigateBack(controller, overscroll_mode)) 181 controller.GoBack(); 182} 183 184void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode, 185 OverscrollMode new_mode) { 186 NavigationControllerImpl& controller = web_contents_->GetController(); 187 if (!ShouldNavigateForward(controller, new_mode) && 188 !ShouldNavigateBack(controller, new_mode)) { 189 AbortGestureAnimation(); 190 return; 191 } 192 193 arrow_.reset(new ui::Layer(ui::LAYER_TEXTURED)); 194 // Note that RTL doesn't affect the arrow that should be displayed. 195 int resource_id = 0; 196 if (new_mode == OVERSCROLL_WEST) 197 resource_id = IDR_FORWARD_ARROW; 198 else if (new_mode == OVERSCROLL_EAST) 199 resource_id = IDR_BACK_ARROW; 200 else 201 NOTREACHED(); 202 203 arrow_delegate_.reset(new ArrowLayerDelegate(resource_id)); 204 arrow_->set_delegate(arrow_delegate_.get()); 205 arrow_->SetFillsBoundsOpaquely(false); 206 207 aura::Window* window = web_contents_->GetNativeView(); 208 const gfx::Rect& window_bounds = window->bounds(); 209 completion_threshold_ = window_bounds.width() * 210 GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE); 211 212 // Align on the left or right edge. 213 int x = (resource_id == IDR_BACK_ARROW) ? 0 : 214 (window_bounds.width() - kArrowWidth); 215 // Align in the center vertically. 216 int y = std::max(0, (window_bounds.height() - kArrowHeight) / 2); 217 arrow_->SetBounds(gfx::Rect(x, y, kArrowWidth, kArrowHeight)); 218 ApplyEffectsForDelta(0.f); 219 220 // Adding the arrow as a child of the content window is not sufficient, 221 // because it is possible for a new layer to be parented on top of the arrow 222 // layer (e.g. when the navigated-to page is displayed while the completion 223 // animation is in progress). So instead, a clip layer (that doesn't paint) is 224 // installed on top of the content window as its sibling, and the arrow layer 225 // is added to that clip layer. 226 clip_layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN)); 227 clip_layer_->SetBounds(window->layer()->bounds()); 228 clip_layer_->SetMasksToBounds(true); 229 clip_layer_->Add(arrow_.get()); 230 231 ui::Layer* parent = window->layer()->parent(); 232 parent->Add(clip_layer_.get()); 233 parent->StackAtTop(clip_layer_.get()); 234} 235 236} // namespace content 237