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 "grit/ui_resources.h" 16 #include "ui/aura/window.h" 17 #include "ui/compositor/layer.h" 18 #include "ui/compositor/layer_animation_observer.h" 19 #include "ui/compositor/layer_delegate.h" 20 #include "ui/compositor/scoped_layer_animation_settings.h" 21 #include "ui/gfx/animation/tween.h" 22 #include "ui/gfx/canvas.h" 23 #include "ui/gfx/image/image.h" 24 25 namespace content { 26 27 namespace { 28 29 const int kArrowHeight = 280; 30 const int kArrowWidth = 140; 31 const float kMinOpacity = 0.25f; 32 33 bool ShouldNavigateForward(const NavigationController& controller, 34 OverscrollMode mode) { 35 return mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST) && 36 controller.CanGoForward(); 37 } 38 39 bool 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. 47 template <class T> 48 class 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. 73 class 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 OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {} 103 104 virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE { 105 return base::Closure(); 106 } 107 108 const gfx::Image& image_; 109 const bool left_arrow_; 110 111 DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate); 112 }; 113 114 GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents) 115 : web_contents_(web_contents), 116 completion_threshold_(0.f) {} 117 118 GestureNavSimple::~GestureNavSimple() {} 119 120 void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform& transform, 121 float opacity) { 122 ui::Layer* layer = arrow_.get(); 123 ui::ScopedLayerAnimationSettings settings(arrow_->GetAnimator()); 124 settings.AddObserver( 125 new DeleteAfterAnimation<ArrowLayerDelegate>(arrow_delegate_.Pass())); 126 settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(arrow_.Pass())); 127 settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(clip_layer_.Pass())); 128 layer->SetTransform(transform); 129 layer->SetOpacity(opacity); 130 } 131 132 void GestureNavSimple::AbortGestureAnimation() { 133 if (!arrow_) 134 return; 135 gfx::Transform transform; 136 transform.Translate(arrow_delegate_->left() ? -kArrowWidth : kArrowWidth, 0); 137 ApplyEffectsAndDestroy(transform, kMinOpacity); 138 } 139 140 void GestureNavSimple::CompleteGestureAnimation() { 141 if (!arrow_) 142 return; 143 // Make sure the fade-out starts from the complete state. 144 ApplyEffectsForDelta(completion_threshold_); 145 ApplyEffectsAndDestroy(arrow_->transform(), 0.f); 146 } 147 148 void GestureNavSimple::ApplyEffectsForDelta(float delta_x) { 149 if (!arrow_) 150 return; 151 CHECK_GT(completion_threshold_, 0.f); 152 CHECK_GE(delta_x, 0.f); 153 double complete = std::min(1.f, delta_x / completion_threshold_); 154 float translate_x = gfx::Tween::FloatValueBetween(complete, -kArrowWidth, 0); 155 gfx::Transform transform; 156 transform.Translate(arrow_delegate_->left() ? translate_x : -translate_x, 157 0.f); 158 arrow_->SetTransform(transform); 159 arrow_->SetOpacity(gfx::Tween::FloatValueBetween(complete, kMinOpacity, 1.f)); 160 } 161 162 gfx::Rect GestureNavSimple::GetVisibleBounds() const { 163 return web_contents_->GetNativeView()->bounds(); 164 } 165 166 void GestureNavSimple::OnOverscrollUpdate(float delta_x, float delta_y) { 167 ApplyEffectsForDelta(std::abs(delta_x) + 50.f); 168 } 169 170 void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode) { 171 CompleteGestureAnimation(); 172 173 NavigationControllerImpl& controller = web_contents_->GetController(); 174 if (ShouldNavigateForward(controller, overscroll_mode)) 175 controller.GoForward(); 176 else if (ShouldNavigateBack(controller, overscroll_mode)) 177 controller.GoBack(); 178 } 179 180 void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode, 181 OverscrollMode new_mode) { 182 NavigationControllerImpl& controller = web_contents_->GetController(); 183 if (!ShouldNavigateForward(controller, new_mode) && 184 !ShouldNavigateBack(controller, new_mode)) { 185 AbortGestureAnimation(); 186 return; 187 } 188 189 arrow_.reset(new ui::Layer(ui::LAYER_TEXTURED)); 190 // Note that RTL doesn't affect the arrow that should be displayed. 191 int resource_id = 0; 192 if (new_mode == OVERSCROLL_WEST) 193 resource_id = IDR_FORWARD_ARROW; 194 else if (new_mode == OVERSCROLL_EAST) 195 resource_id = IDR_BACK_ARROW; 196 else 197 NOTREACHED(); 198 199 arrow_delegate_.reset(new ArrowLayerDelegate(resource_id)); 200 arrow_->set_delegate(arrow_delegate_.get()); 201 arrow_->SetFillsBoundsOpaquely(false); 202 203 aura::Window* window = web_contents_->GetNativeView(); 204 const gfx::Rect& window_bounds = window->bounds(); 205 completion_threshold_ = window_bounds.width() * 206 GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE); 207 208 // Align on the left or right edge. 209 int x = (resource_id == IDR_BACK_ARROW) ? 0 : 210 (window_bounds.width() - kArrowWidth); 211 // Align in the center vertically. 212 int y = std::max(0, (window_bounds.height() - kArrowHeight) / 2); 213 arrow_->SetBounds(gfx::Rect(x, y, kArrowWidth, kArrowHeight)); 214 ApplyEffectsForDelta(0.f); 215 216 // Adding the arrow as a child of the content window is not sufficient, 217 // because it is possible for a new layer to be parented on top of the arrow 218 // layer (e.g. when the navigated-to page is displayed while the completion 219 // animation is in progress). So instead, a clip layer (that doesn't paint) is 220 // installed on top of the content window as its sibling, and the arrow layer 221 // is added to that clip layer. 222 clip_layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN)); 223 clip_layer_->SetBounds(window->layer()->bounds()); 224 clip_layer_->SetMasksToBounds(true); 225 clip_layer_->Add(arrow_.get()); 226 227 ui::Layer* parent = window->layer()->parent(); 228 parent->Add(clip_layer_.get()); 229 parent->StackAtTop(clip_layer_.get()); 230 } 231 232 } // namespace content 233