Index: chrome/browser/ui/views/frame/scroll_end_effect_controller_ash.cc |
diff --git a/chrome/browser/ui/views/frame/scroll_end_effect_controller_ash.cc b/chrome/browser/ui/views/frame/scroll_end_effect_controller_ash.cc |
index aa0642821e61bc5cfe4d5932bf978aca44f60377..ae51c4f555b98540332e317f0a500afabf781b54 100644 |
--- a/chrome/browser/ui/views/frame/scroll_end_effect_controller_ash.cc |
+++ b/chrome/browser/ui/views/frame/scroll_end_effect_controller_ash.cc |
@@ -4,16 +4,339 @@ |
#include "chrome/browser/ui/views/frame/scroll_end_effect_controller_ash.h" |
-ScrollEndEffectController* ScrollEndEffectController::Create() { |
- return new ScrollEndEffectControllerAsh(); |
+#include "chrome/browser/ui/views/download/download_shelf_view.h" |
+#include "chrome/browser/ui/views/frame/browser_frame.h" |
+#include "chrome/browser/ui/views/frame/browser_view.h" |
+#include "content/public/browser/web_contents.h" |
+#include "content/public/browser/web_contents_view.h" |
+#include "ui/aura/window.h" |
+#include "ui/base/animation/tween.h" |
+#include "ui/compositor/layer_type.h" |
+#include "ui/gfx/safe_integer_conversions.h" |
+#include "ui/views/controls/single_split_view.h" |
+#include "ui/views/controls/webview/webview.h" |
+ |
+namespace { |
+// This factor is used to control how much translation is applied relative to |
+// the size of the window. Specifically translation is limited to a max of |
+// window_size * factor. |
+const float kScrollEndEffectFactor = 0.05; |
+} |
+ |
+// To achieve the desired effect to indicate vertical overscroll is occuring the |
+// layer tree needs to be manipulated. Specifically two clipping layers are |
+// inserted and the parenting of the web contents and non-client layers are |
+// modified. These modifications are removed when the effect is deactivated. |
+// |
+// The |frame_clipping_layer_| is used to make sure that the bits of the layers |
+// below do not peak out since the |browser_frame_layer| is going to be scaled |
+// down in size, but some of the sub-layers are going to be counter scaled. This |
+// clipping cannot be done in the |browser_frame_layer| since the shadows on the |
+// window are outside the clipping bounds, but also children of that layer. |
+// |
+// The |web_clipping_layer_| is used to trim the web contents so that is does |
+// not occlude the non-client elements. This is done to give the visual effect |
+// of the contents slide under the top elements without having to break these |
+// elements out into their own layer. |
+// |
+// Additionally the layer associated with the dev tools is promoted, so that it |
+// isn't occuluded by the web contents layer. |
+// |
+// Layout of layer tree when effect is not active: |
+// +----------------------------------------------------------+ |
+// | browser_frame_layer | |
+// +-+--------------+---------------+-----------------------+-+ |
+// | | | | |
+// | v v v |
+// v +-------------------+ +---------------------+ +-----------------+ |
+// ... | non_client_layer_ | | web_contents_layer_ | | devtools_layer_ | |
+// +-------------------+ +---------------------+ +-----------------+ |
+// |
+// |
+// Layout of the layer tree when effect is active: |
+// +---------------------+ |
+// | browser_frame_layer | |
+// +-+---------------+---+ |
+// | | |
+// | v |
+// v +-------------------------------------------------+ |
+// ... | frame_clipping_layer_ | |
+// +-----+--------------+---------------------+------+ |
+// | | | |
+// v v V |
+// +-------------------+ +---------------------+ +-----------------+ |
+// | non_client_layer_ | | web_clipping_layer_ | | devtools_layer_ | |
+// +-------------------+ +---------+-----------+ +-----------------+ |
+// | |
+// v |
+// +---------------------+ |
+// | web_contents_layer_ | |
+// +---------------------+ |
+ |
+ScrollEndEffectController* ScrollEndEffectController::Create( |
+ ScrollEndEffectControllerDelegate* delegate) { |
+ return new ScrollEndEffectControllerAsh(delegate); |
} |
-ScrollEndEffectControllerAsh::ScrollEndEffectControllerAsh() { |
+ScrollEndEffectControllerAsh::ScrollEndEffectControllerAsh( |
+ ScrollEndEffectControllerDelegate* delegate) |
+ : delegate_(delegate), |
+ is_effect_active_(false), |
+ non_client_layer_(NULL), |
+ web_contents_layer_(NULL), |
+ devtools_layer_(NULL), |
+ web_contents_parent_(NULL), |
+ devtools_parent_(NULL), |
+ non_client_view_(NULL), |
+ start_delta_y_(0), |
+ end_delta_y_(0) { |
+ animation_.reset(new ui::SlideAnimation(this)); |
+ animation_->SetSlideDuration(250); |
+ animation_->SetTweenType(ui::Tween::EASE_OUT); |
} |
ScrollEndEffectControllerAsh::~ScrollEndEffectControllerAsh() { |
} |
void ScrollEndEffectControllerAsh::OverscrollUpdate(int delta_y) { |
- // TODO(rharrison): Implement initial version of scroll end effect |
+ if (delegate_ == NULL) |
+ return; |
+ |
+ if (!is_effect_active_ && delta_y == 0) |
+ return; |
+ |
+ ui::Layer* browser_frame_layer = delegate_->GetBrowserFrameLayer(); |
+ if (!browser_frame_layer) |
+ return; |
+ |
+ if (!is_effect_active_) |
+ ActivateEffect(); |
+ |
+ int capped_delta_y = delta_y; |
+ gfx::Rect bounds = browser_frame_layer->bounds(); |
+ // Limiting the delta size being a proportion of the frame bounds size. |
+ if (capped_delta_y > 0) { |
+ capped_delta_y = std::min(gfx::ToRoundedInt((bounds.height() * |
+ kScrollEndEffectFactor)), |
+ capped_delta_y); |
+ } else if (capped_delta_y < 0) { |
+ capped_delta_y = -std::min(gfx::ToRoundedInt((bounds.height() * |
+ kScrollEndEffectFactor)), |
+ -capped_delta_y); |
+ } |
+ |
+ ApplyDelta(end_delta_y_); |
sadrul
2013/09/05 16:09:02
This seems a bit odd? I would've thought we would
rharrison
2013/09/05 20:18:34
Just applying the capped value would cause the eff
|
+ start_delta_y_ = end_delta_y_; |
+ end_delta_y_ = capped_delta_y; |
+ animation_->Reset(); |
+ animation_->Show(); |
+} |
+ |
+void ScrollEndEffectControllerAsh::AnimationEnded( |
+ const ui::Animation* animation) { |
+ if (end_delta_y_ == 0) |
+ DeactivateEffect(); |
+} |
+ |
+void ScrollEndEffectControllerAsh::AnimationProgressed( |
+ const ui::Animation* animation) { |
+ int current_delta = ui::Tween::ValueBetween(animation_->GetCurrentValue(), |
+ start_delta_y_, |
+ end_delta_y_); |
+ ApplyDelta(current_delta); |
+} |
+ |
+void ScrollEndEffectControllerAsh::ActivateEffect() { |
+ ui::Layer* browser_frame_layer = delegate_->GetBrowserFrameLayer(); |
+ if (!browser_frame_layer) |
+ return; |
+ |
+ is_effect_active_ = true; |
+ |
+ // Get layers for all of the parts to be manipulated |
+ non_client_view_ = delegate_->GetNonClientView(); |
+ non_client_layer_ = CreateViewLayer(non_client_view_); |
+ web_contents_layer_ = delegate_->GetWebContentsLayer(); |
+ devtools_layer_ = delegate_->GetDevToolsLayer(); |
+ download_view_ = delegate_->GetDownloadView(); |
+ download_layer_ = CreateViewLayer(download_view_); |
+ |
+ CHECK(non_client_layer_); |
+ CHECK(web_contents_layer_); |
+ CHECK(devtools_layer_); |
+ CHECK(download_layer_); |
+ |
+ frame_clipping_layer_.reset(CreateClippingLayer( |
+ "OverscrollFrameClippingLayer")); |
+ web_clipping_layer_.reset(CreateClippingLayer("OverscrollWebClippingLayer")); |
+ |
+ // Save bounds for restoring later |
+ devtools_bounds_ = devtools_layer_->bounds(); |
+ web_contents_bounds_ = web_contents_layer_->bounds(); |
+ |
+ // Adjust the toplogy of the layer tree to add clipping layers |
+ devtools_parent_ = devtools_layer_->parent(); |
+ web_contents_parent_ = web_contents_layer_->parent(); |
+ browser_frame_layer->Add(frame_clipping_layer_.get()); |
+ frame_clipping_layer_->Add(non_client_layer_); |
+ frame_clipping_layer_->Add(web_clipping_layer_.get()); |
+ frame_clipping_layer_->Add(devtools_layer_); |
+ frame_clipping_layer_->Add(download_layer_); |
+ web_clipping_layer_->Add(web_contents_layer_); |
+} |
+ |
+ui::Layer* ScrollEndEffectControllerAsh::CreateClippingLayer(std::string name) { |
+ ui::Layer* layer = new ui::Layer(ui::LAYER_NOT_DRAWN); |
+ layer->set_name(name); |
+ layer->SetMasksToBounds(true); |
+ layer->SetFillsBoundsOpaquely(false); |
+ return layer; |
+} |
+ |
+ui::Layer* ScrollEndEffectControllerAsh::CreateViewLayer(views::View* view) { |
sadrul
2013/09/05 16:09:02
CreateViewLayer and CreateClippingLayer could be s
rharrison
2013/09/05 20:18:34
Done.
|
+ view->SetPaintToLayer(true); |
+ ui::Layer* layer = view->layer(); |
+ layer->SetFillsBoundsOpaquely(false); |
+ view->parent()->SchedulePaint(); |
+ return layer; |
+} |
+ |
+void ScrollEndEffectControllerAsh::DeactivateEffect() { |
+ ui::Layer* browser_frame_layer = delegate_->GetBrowserFrameLayer(); |
+ if (!browser_frame_layer) |
+ return; |
+ |
+ is_effect_active_ = false; |
+ |
+ devtools_layer_->SetBounds(devtools_bounds_); |
+ devtools_parent_->Add(devtools_layer_); |
+ devtools_parent_ = NULL; |
+ devtools_layer_ = NULL; |
+ |
+ web_contents_layer_->SetBounds(web_contents_bounds_); |
+ web_contents_parent_->Add(web_contents_layer_); |
+ web_contents_parent_ = NULL; |
+ web_contents_layer_ = NULL; |
+ |
+ frame_clipping_layer_->Remove(download_layer_); |
+ download_view_->SetPaintToLayer(false); |
+ download_layer_ = NULL; |
+ download_view_ = NULL; |
+ |
+ frame_clipping_layer_->Remove(non_client_layer_); |
+ non_client_view_->SetPaintToLayer(false); |
+ non_client_layer_ = NULL; |
+ non_client_view_ = NULL; |
+ |
+ frame_clipping_layer_->Remove(web_clipping_layer_.get()); |
+ web_clipping_layer_.reset(); |
+ browser_frame_layer->Remove(frame_clipping_layer_.get()); |
+ frame_clipping_layer_.reset(); |
+ browser_frame_layer = NULL; |
+} |
+ |
+void ScrollEndEffectControllerAsh::ApplyDelta(int delta_y) { |
+ SetBoundsForEffect(); |
sadrul
2013/09/05 16:09:02
Hm. I assume you call this for each ApplyDelta() c
rharrison
2013/09/05 20:18:34
yup
|
+ |
+ ui::Layer* frame = delegate_->GetBrowserFrameLayer(); |
sadrul
2013/09/05 16:09:02
Is there a reason GetBrowserFrameLayer() isn't cac
rharrison
2013/09/05 20:18:34
It is an artifact from how I refactored the code.
|
+ gfx::Rect bounds = frame->bounds(); |
+ |
+ float scale_factor = std::abs(delta_y); |
+ scale_factor /= bounds.height(); |
+ scale_factor = 1 - scale_factor; |
+ |
+ gfx::Transform frame_transform; |
+ if (delta_y > 0) { |
sadrul
2013/09/05 16:09:02
Can you set a 'bool scrolling_down = delta_y > 0;'
rharrison
2013/09/05 20:18:34
Done.
|
+ frame_transform.Translate(0, delta_y); |
+ frame_transform.Scale(1, scale_factor); |
+ } else { |
+ frame_transform.Scale(1, scale_factor); |
+ } |
+ CHECK(frame_transform.GetInverse(&counter_transform_)); |
+ frame->SetTransform(frame_transform); |
+ |
+ gfx::Transform download_transform = |
+ CounterTransfromAboutPoint(0, |
+ download_layer_->bounds().origin().y()); |
+ if (delta_y < 0) |
+ download_transform.Translate(0, delta_y); |
+ download_layer_->SetTransform(download_transform); |
+ |
+ gfx::Transform devtools_transform = |
+ CounterTransfromAboutPoint(0, |
+ devtools_layer_->bounds().origin().y()); |
+ if (delta_y < 0) |
+ devtools_transform.Translate(0, delta_y); |
+ devtools_layer_->SetTransform(devtools_transform); |
+ |
+ gfx::Transform web_clipping_transform = |
+ CounterTransfromAboutPoint(0, |
+ web_clipping_layer_->bounds().origin().y()); |
+ if (delta_y >= 0) |
+ web_clipping_transform.Translate(0, delta_y); |
+ web_clipping_layer_->SetTransform(web_clipping_transform); |
+ |
+ if (delta_y <= 0) { |
+ gfx::Transform web_contents_transform; |
+ web_contents_transform.Translate(0, delta_y); |
+ web_contents_layer_->SetTransform(web_contents_transform); |
+ } |
+ |
+ gfx::Transform non_client_transform = counter_transform_; |
+ if (delta_y >= 0) |
+ non_client_transform.Translate(0, delta_y); |
+ non_client_layer_->SetTransform(non_client_transform); |
+} |
+ |
+void ScrollEndEffectControllerAsh::SetBoundsForEffect() { |
+ ui::Layer* browser_frame_layer = delegate_->GetBrowserFrameLayer(); |
+ if (!browser_frame_layer) |
+ return; |
+ |
+ gfx::Rect frame_clipping_bounds = browser_frame_layer->bounds(); |
+ frame_clipping_bounds.set_origin(gfx::Point()); |
+ frame_clipping_layer_->SetBounds(frame_clipping_bounds); |
+ |
+ gfx::Rect web_clipping_bounds = browser_frame_layer->bounds(); |
+ int download_height = download_view_->visible() ? |
+ download_view_->bounds().height() : 0; |
+ int non_client_top_height = browser_frame_layer->bounds().height() - |
+ web_contents_layer_->bounds().height() - |
+ delegate_->GetDevToolsHeight() - |
+ delegate_->GetDividerHeight() - |
+ download_height; |
+ int web_clipping_height = browser_frame_layer->bounds().height() - |
+ non_client_top_height - |
+ delegate_->GetDevToolsHeight() - |
+ delegate_->GetDividerHeight() - |
+ download_height; |
+ |
+ web_clipping_bounds.set_height(web_clipping_height); |
+ web_clipping_bounds.set_origin(gfx::Point(0, non_client_top_height)); |
+ web_clipping_layer_->SetBounds(web_clipping_bounds); |
+ |
+ gfx::Rect devtools_bounds = browser_frame_layer->bounds(); |
+ devtools_bounds.set_origin(gfx::Point(0, |
+ browser_frame_layer->bounds().height() - |
+ delegate_->GetDevToolsHeight() - |
+ download_height)); |
+ devtools_bounds.set_height(delegate_->GetDevToolsHeight()); |
+ devtools_layer_->SetBounds(devtools_bounds); |
+ |
+ // Move the web contents since bounds are relative to the parent layer |
+ gfx::Rect web_contents_bounds = web_clipping_bounds; |
+ web_contents_bounds.set_origin(gfx::Point(0, 0)); |
sadrul
2013/09/05 16:09:02
gfx::Point() like above
rharrison
2013/09/05 20:18:34
Done.
|
+ web_contents_layer_->SetBounds(web_contents_bounds); |
+} |
+ |
+gfx::Transform ScrollEndEffectControllerAsh::CounterTransfromAboutPoint(int x, |
+ int y) { |
+ gfx::Transform to_frame_transform; |
+ to_frame_transform.Translate(-x, -y); |
+ gfx::Transform from_frame_transform; |
+ from_frame_transform.Translate(x, y); |
+ gfx::Transform transform = counter_transform_; |
+ transform.ConcatTransform(to_frame_transform); |
+ transform.PreconcatTransform(from_frame_transform); |
+ return transform; |
} |