#include "cc/input/scroll_elasticity_helper.h"
#include "base/memory/raw_ptr.h"
#include "cc/layers/layer_impl.h"
#include "cc/trees/layer_tree_host_impl.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/scroll_node.h"
namespace cc {
namespace {
void MarkNodeNeedsUpdate(
LayerTreeHostImpl& host_impl,
const ScrollNode& scroll_node,
[[maybe_unused]] const gfx::Vector2dF& stretch_amount) {
LayerTreeImpl& active_tree = *host_impl.active_tree();
PropertyTrees& property_trees = *active_tree.property_trees();
TransformTree& transform_tree = property_trees.transform_tree_mutable();
const bool is_root =
scroll_node.scrolls_inner_viewport || scroll_node.scrolls_outer_viewport;
if (TransformNode* transform_node =
transform_tree.Node(scroll_node.transform_id)) {
#if BUILDFLAG(IS_ANDROID)
const bool has_stretch = !stretch_amount.IsZero();
transform_node->has_potential_animation = has_stretch;
#endif
transform_node->needs_local_transform_update = true;
transform_node->SetTransformChanged(DamageReason::kCompositorScroll);
transform_tree.set_needs_update(true);
}
active_tree.set_needs_update_draw_properties();
host_impl.SetNeedsRedraw(false,
false);
if (is_root) {
host_impl.SetFullViewportDamage();
}
}
}
class ScrollElasticityHelperImpl : public ScrollElasticityHelper {
public:
explicit ScrollElasticityHelperImpl(LayerTreeHostImpl* host_impl);
~ScrollElasticityHelperImpl() override;
bool IsUserScrollableHorizontal(ElementId) const override;
bool IsUserScrollableVertical(ElementId) const override;
void ResetStretchAmounts() override;
void ForceApplyStretchAmounts() override;
gfx::Vector2dF StretchAmount(ElementId) const override;
gfx::Size ScrollBounds(ElementId) const override;
void SetStretchAmount(ElementId,
const gfx::Vector2dF& stretch_amount) override;
gfx::PointF ScrollOffset(ElementId) const override;
gfx::PointF MaxScrollOffset(ElementId) const override;
void ScrollBy(ElementId, const gfx::Vector2dF& delta) override;
void RequestOneBeginFrame() override;
void AnimationFinished(ElementId) override;
bool IsRoot(ElementId) const;
const ScrollNode* GetNode(ElementId) const;
ScrollNode* GetNode(ElementId);
private:
raw_ptr<LayerTreeHostImpl> host_impl_;
};
ScrollElasticityHelperImpl::ScrollElasticityHelperImpl(
LayerTreeHostImpl* layer_tree)
: host_impl_(layer_tree) {}
ScrollElasticityHelperImpl::~ScrollElasticityHelperImpl() = default;
bool ScrollElasticityHelperImpl::IsRoot(ElementId element_id) const {
const ScrollNode* inner = host_impl_->InnerViewportScrollNode();
const ScrollNode* outer = host_impl_->OuterViewportScrollNode();
return (inner && element_id == inner->element_id) ||
(outer && element_id == outer->element_id);
}
ScrollNode* ScrollElasticityHelperImpl::GetNode(ElementId element_id) {
return host_impl_->GetScrollTree().FindNodeFromElementId(element_id);
}
const ScrollNode* ScrollElasticityHelperImpl::GetNode(
ElementId element_id) const {
return host_impl_->GetScrollTree().FindNodeFromElementId(element_id);
}
bool ScrollElasticityHelperImpl::IsUserScrollableHorizontal(
ElementId element_id) const {
const ScrollNode* scroll_node = IsRoot(element_id)
? host_impl_->OuterViewportScrollNode()
: GetNode(element_id);
if (!scroll_node) {
return false;
}
return scroll_node->user_scrollable_horizontal;
}
bool ScrollElasticityHelperImpl::IsUserScrollableVertical(
ElementId element_id) const {
const ScrollNode* scroll_node = IsRoot(element_id)
? host_impl_->OuterViewportScrollNode()
: GetNode(element_id);
if (!scroll_node) {
return false;
}
return scroll_node->user_scrollable_vertical;
}
gfx::Vector2dF ScrollElasticityHelperImpl::StretchAmount(
ElementId element_id) const {
const bool is_root = IsRoot(element_id);
const ScrollNode* scroll_node =
is_root ? host_impl_->InnerViewportScrollNode() : GetNode(element_id);
if (!scroll_node) {
return gfx::Vector2dF();
}
const gfx::Vector2dF stretch = host_impl_->active_tree()
->property_trees()
->scroll_tree()
.GetElasticOverscroll(*scroll_node);
CHECK(is_root || scroll_node->is_composited || stretch.IsZero());
return stretch;
}
gfx::Size ScrollElasticityHelperImpl::ScrollBounds(ElementId element_id) const {
const ScrollNode* scroll_node = IsRoot(element_id)
? host_impl_->OuterViewportScrollNode()
: GetNode(element_id);
return scroll_node ? scroll_node->container_bounds : gfx::Size();
}
void ScrollElasticityHelperImpl::ResetStretchAmounts() {
base::flat_map<ElementId, gfx::Vector2dF>& elastic_overscroll =
host_impl_->active_tree()
->property_trees()
->scroll_tree_mutable()
.elastic_overscroll();
const std::vector to_remove(elastic_overscroll.begin(),
elastic_overscroll.end());
for (auto& [element_id, _] : to_remove) {
SetStretchAmount(element_id, gfx::Vector2dF());
}
elastic_overscroll.clear();
}
void ScrollElasticityHelperImpl::ForceApplyStretchAmounts() {
LayerTreeImpl& active_tree = *host_impl_->active_tree();
PropertyTrees& property_trees = *active_tree.property_trees();
const ScrollTree& scroll_tree = property_trees.scroll_tree();
for (const auto& [element_id, stretch_amount] :
scroll_tree.elastic_overscroll()) {
if (stretch_amount.IsZero()) {
continue;
}
if (const ScrollNode* scroll_node =
IsRoot(element_id) ? host_impl_->InnerViewportScrollNode()
: GetNode(element_id)) {
MarkNodeNeedsUpdate(*host_impl_, *scroll_node, stretch_amount);
}
}
}
void ScrollElasticityHelperImpl::SetStretchAmount(
ElementId element_id,
const gfx::Vector2dF& stretch_amount) {
const bool is_root = IsRoot(element_id);
const ScrollNode* scroll_node =
is_root ? host_impl_->InnerViewportScrollNode() : GetNode(element_id);
if (!scroll_node) {
return;
}
const gfx::Vector2dF effective_stretch_amount =
is_root || scroll_node->is_composited ? stretch_amount : gfx::Vector2dF();
LayerTreeImpl& active_tree = *host_impl_->active_tree();
PropertyTrees& property_trees = *active_tree.property_trees();
if (property_trees.scroll_tree_mutable().SetElasticOverscroll(
*scroll_node, effective_stretch_amount)) {
MarkNodeNeedsUpdate(*host_impl_, *scroll_node, effective_stretch_amount);
}
}
gfx::PointF ScrollElasticityHelperImpl::ScrollOffset(
ElementId element_id) const {
if (IsRoot(element_id)) {
return host_impl_->active_tree()->TotalScrollOffset();
}
return host_impl_->active_tree()->TotalScrollOffset(element_id);
}
gfx::PointF ScrollElasticityHelperImpl::MaxScrollOffset(
ElementId element_id) const {
if (IsRoot(element_id)) {
return host_impl_->active_tree()->TotalMaxScrollOffset();
}
return host_impl_->active_tree()->TotalMaxScrollOffset(element_id);
}
void ScrollElasticityHelperImpl::ScrollBy(ElementId element_id,
const gfx::Vector2dF& delta) {
ScrollNode* scroll_node;
if (IsRoot(element_id)) {
scroll_node = host_impl_->OuterViewportScrollNode()
? host_impl_->OuterViewportScrollNode()
: host_impl_->InnerViewportScrollNode();
} else {
scroll_node = host_impl_->GetScrollTree().FindNodeFromElementId(element_id);
}
if (scroll_node) {
LayerTreeImpl* tree_impl = host_impl_->active_tree();
tree_impl->property_trees()->scroll_tree_mutable().ScrollBy(
*scroll_node, delta, tree_impl);
}
}
void ScrollElasticityHelperImpl::RequestOneBeginFrame() {
host_impl_->SetNeedsOneBeginImplFrame();
}
void ScrollElasticityHelperImpl::AnimationFinished(ElementId element_id) {
host_impl_->ElasticOverscrollAnimationFinished(element_id);
}
ScrollElasticityHelper* ScrollElasticityHelper::CreateForLayerTreeHostImpl(
LayerTreeHostImpl* host_impl) {
return new ScrollElasticityHelperImpl(host_impl);
}
}