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