Home | History | Annotate | Download | only in aura
      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 
     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 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 
    117 GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents)
    118     : web_contents_(web_contents),
    119       completion_threshold_(0.f) {}
    120 
    121 GestureNavSimple::~GestureNavSimple() {}
    122 
    123 void 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 
    135 void 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 
    143 void 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 
    151 bool 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 
    166 gfx::Rect GestureNavSimple::GetVisibleBounds() const {
    167   return web_contents_->GetNativeView()->bounds();
    168 }
    169 
    170 bool GestureNavSimple::OnOverscrollUpdate(float delta_x, float delta_y) {
    171   return ApplyEffectsForDelta(std::abs(delta_x) + 50.f);
    172 }
    173 
    174 void 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 
    184 void 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