blob: bca729cdff23c7ef63e75ccbf29a76a01f0e0483 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CC_ANIMATION_SCROLL_TIMELINE_H_
#define CC_ANIMATION_SCROLL_TIMELINE_H_
#include <optional>
#include <vector>
#include "base/time/time.h"
#include "cc/animation/animation_export.h"
#include "cc/animation/animation_timeline.h"
#include "cc/animation/keyframe_model.h"
#include "cc/paint/element_id.h"
namespace cc {
class ScrollTree;
// A ScrollTimeline is an animation timeline that bases its current time on the
// progress of scrolling in some scroll container.
//
// This is the compositor-side representation of the web concept expressed in
// https://wicg.github.io/scroll-animations/#scrolltimeline-interface.
class CC_ANIMATION_EXPORT ScrollTimeline : public AnimationTimeline {
public:
// cc does not know about writing modes. The ScrollDirection below is
// converted using blink::scroll_timeline_util::ConvertOrientation which takes
// the spec-compliant ScrollDirection enumeration.
// https://drafts.csswg.org/scroll-animations/#scrolldirection-enumeration
enum ScrollDirection {
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
};
struct ScrollOffsets {
ScrollOffsets() = default;
ScrollOffsets(double start_offset, double end_offset) {
start = start_offset;
end = end_offset;
}
bool operator==(const ScrollOffsets& other) const {
return start == other.start && end == other.end;
}
bool operator!=(const ScrollOffsets& other) const {
return !(*this == other);
}
double start = 0;
double end = 0;
};
// Fixed time scale converting from pixels to microseconds.
// The value is derived from error analysis of the quantization of pixels in
// LayoutUnits. The quantization is 1/64 of a pixel, and maximum possible
// error in current time calculations is 4 times that amount as shown below.
//
// progress = (scroll - start) / (end - start)
// Positions are subject to imprecision based on quantization.
// For worst case analysis, we compute the difference between the maximum
// and minimum progress based on the allowable error:
// progress = ((current offset +/- delta) - (start +/- delta) /
// ((end +/- delta) - (start +/- delta))
// where delta = kLengthPrecision = 1 / kFixedPointDenominator = 1 / 64
//
// To minimum, we take the smallest possible numerator and largest possible
// denominator, which means minimizing current offset and maximizing cover
// end time. The cover start time appears in both the numerator and
// denominator, but has a large impact on the numerator. Thus,
//
// min = ((current offset - delta) - (start + delta)) /
// ((end + delta) - (start + delta))
// = (current offset - start - 2 * delta) / range
// max = ((current offset + delta) - (start + delta)) /
// ((end - delta) - (start + delta))
// = (current offset - start + 2 * delta) / range;
// max error = max - min = 4 * delta / range
// duration = 1 [microsecond] / error
// = range / (4 / 64)
// = 16 range
static constexpr double kScrollTimelineMicrosecondsPerPixel = 16;
ScrollTimeline(std::optional<ElementId> scroller_id,
ScrollDirection direction,
std::optional<ScrollOffsets> scroll_offsets,
int animation_timeline_id);
static scoped_refptr<ScrollTimeline> Create(
std::optional<ElementId> scroller_id,
ScrollDirection direction,
std::optional<ScrollOffsets> scroll_offsets);
// Create a copy of this ScrollTimeline intended for the impl thread in the
// compositor.
scoped_refptr<AnimationTimeline> CreateImplInstance() const override;
// ScrollTimeline is active if the scroll node exists in active or pending
// scroll tree.
virtual bool IsActive(const ScrollTree& scroll_tree,
bool is_active_tree) const;
// Calculate the current time of the ScrollTimeline. This is either a
// base::TimeTicks value or std::nullopt if the current time is unresolved.
// The internal calculations are performed using doubles and the result is
// converted to base::TimeTicks. This limits the precision to 1us.
virtual std::optional<base::TimeTicks> CurrentTime(
const ScrollTree& scroll_tree,
bool is_active_tree) const;
virtual std::optional<base::TimeTicks> Duration(const ScrollTree& scroll_tree,
bool is_active_tree) const;
void UpdateScrollerIdAndScrollOffsets(
std::optional<ElementId> scroller_id,
std::optional<ScrollOffsets> scroll_offsets);
void PushPropertiesTo(AnimationTimeline* impl_timeline) override;
void ActivateTimeline() override;
bool TickScrollLinkedAnimations(
const std::vector<scoped_refptr<Animation>>& ticking_animations,
const ScrollTree& scroll_tree,
bool is_active_tree) override;
std::optional<ElementId> GetActiveIdForTest() const { return active_id(); }
std::optional<ElementId> GetPendingIdForTest() const { return pending_id(); }
ScrollDirection GetDirectionForTest() const { return direction(); }
std::optional<double> GetStartScrollOffsetForTest() const {
std::optional<ScrollOffsets> offsets = pending_offsets();
if (offsets) {
return offsets->start;
}
return std::nullopt;
}
std::optional<double> GetEndScrollOffsetForTest() const {
std::optional<ScrollOffsets> offsets = pending_offsets();
if (offsets) {
return offsets->end;
}
return std::nullopt;
}
bool IsScrollTimeline() const override;
bool IsLinkedToScroller(ElementId scroller) const override;
protected:
~ScrollTimeline() override;
private:
const std::optional<ElementId>& active_id() const {
return active_id_.Read(*this);
}
const std::optional<ElementId>& pending_id() const {
return pending_id_.Read(*this);
}
const ScrollDirection& direction() const { return direction_.Read(*this); }
const std::optional<ScrollOffsets>& active_offsets() const {
return active_offsets_.Read(*this);
}
const std::optional<ScrollOffsets>& pending_offsets() const {
return pending_offsets_.Read(*this);
}
// The scroller which this ScrollTimeline is based on. The same underlying
// scroll source may have different ids in the pending and active tree (see
// http://crbug.com/847588).
// Only the impl thread can set active properties.
ProtectedSequenceForbidden<std::optional<ElementId>> active_id_;
ProtectedSequenceWritable<std::optional<ElementId>> pending_id_;
// The direction of the ScrollTimeline indicates which axis of the scroller
// it should base its current time on, and where the origin point is.
ProtectedSequenceReadable<ScrollDirection> direction_;
ProtectedSequenceForbidden<std::optional<ScrollOffsets>> active_offsets_;
ProtectedSequenceWritable<std::optional<ScrollOffsets>> pending_offsets_;
};
inline ScrollTimeline* ToScrollTimeline(AnimationTimeline* timeline) {
DCHECK(timeline->IsScrollTimeline());
return static_cast<ScrollTimeline*>(timeline);
}
inline const ScrollTimeline* ToScrollTimeline(
const AnimationTimeline* timeline) {
DCHECK(timeline->IsScrollTimeline());
return static_cast<const ScrollTimeline*>(timeline);
}
} // namespace cc
#endif // CC_ANIMATION_SCROLL_TIMELINE_H_