#include "ash/controls/scroll_view_gradient_helper.h"
#include <memory>
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/animation/animation_builder.h"
namespace ash {
const base::TimeDelta kAnimationDuration = base::Milliseconds(50);
ScrollViewGradientHelper::ScrollViewGradientHelper(
views::ScrollView* scroll_view,
int gradient_height)
: scroll_view_(scroll_view), gradient_height_(gradient_height) {
DCHECK(scroll_view_);
DCHECK(scroll_view_->layer());
on_contents_scrolled_subscription_ =
scroll_view_->AddContentsScrolledCallback(
base::BindRepeating(&ScrollViewGradientHelper::UpdateGradientMask,
base::Unretained(this)));
on_contents_scroll_ended_subscription_ =
scroll_view_->AddContentsScrollEndedCallback(
base::BindRepeating(&ScrollViewGradientHelper::UpdateGradientMask,
base::Unretained(this)));
scroll_view_->SetPreferredViewportMargins(
gfx::Insets::VH(gradient_height_, 0));
}
ScrollViewGradientHelper::~ScrollViewGradientHelper() {
RemoveMaskLayer();
scroll_view_->SetPreferredViewportMargins(gfx::Insets());
}
void ScrollViewGradientHelper::UpdateGradientMask() {
DCHECK(scroll_view_->contents());
const gfx::Rect visible_rect = scroll_view_->GetVisibleRect();
const bool show_top_gradient = visible_rect.y() > 0;
const bool show_bottom_gradient =
visible_rect.bottom() < scroll_view_->contents()->bounds().bottom();
if (scroll_view_->contents()->bounds().IsEmpty()) {
RemoveMaskLayer();
return;
}
if (!show_top_gradient && !show_bottom_gradient) {
RemoveMaskLayer();
return;
}
gfx::LinearGradient gradient_mask(-90);
const float fade_position = std::min(
static_cast<float>(gradient_height_) / scroll_view_->bounds().height(),
0.49f);
if (show_top_gradient) {
gradient_mask.AddStep(0, 0);
gradient_mask.AddStep(fade_position, 255);
}
if (show_bottom_gradient) {
gradient_mask.AddStep((1 - fade_position), 255);
gradient_mask.AddStep(1, 0);
}
if (scroll_view_->layer()->gradient_mask() != gradient_mask) {
DVLOG(1) << "Adding gradient mask";
if (first_time_update_) {
scroll_view_->layer()->SetGradientMask(gradient_mask);
} else {
AnimateMaskLayer(gradient_mask);
first_time_update_ = true;
}
}
}
void ScrollViewGradientHelper::AnimateMaskLayer(
const gfx::LinearGradient& target_gradient) {
gfx::LinearGradient start_gradient(target_gradient);
for (auto& step : start_gradient.steps()) {
if (step.alpha < 255)
step.alpha = 255;
}
scroll_view_->layer()->SetGradientMask(start_gradient);
views::AnimationBuilder()
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.Once()
.SetDuration(kAnimationDuration)
.SetGradientMask(scroll_view_, target_gradient);
}
void ScrollViewGradientHelper::RemoveMaskLayer() {
if (!scroll_view_->layer()->HasGradientMask())
return;
DVLOG(1) << "Removing gradient mask";
scroll_view_->layer()->SetGradientMask(gfx::LinearGradient::GetEmpty());
}
}