| #ifndef _RIVE_SCROLL_CONSTRAINT_HPP_ | |
| #define _RIVE_SCROLL_CONSTRAINT_HPP_ | |
| #include "rive/generated/constraints/scrolling/scroll_constraint_base.hpp" | |
| #include "rive/advance_flags.hpp" | |
| #include "rive/advancing_component.hpp" | |
| #include "rive/constraints/layout_constraint.hpp" | |
| #include "rive/constraints/scrolling/scroll_physics.hpp" | |
| #include "rive/layout_component.hpp" | |
| #include "rive/math/math_types.hpp" | |
| #include "rive/math/transform_components.hpp" | |
| #include <stdio.h> | |
| namespace rive | |
| { | |
| class LayoutNodeProvider; | |
| class ScrollVirtualizer; | |
| class VirtualLayout; | |
| class ScrollConstraint : public ScrollConstraintBase, | |
| public AdvancingComponent, | |
| public LayoutConstraint | |
| { | |
| private: | |
| // Coordinate space a scroll write was expressed in. | |
| enum class ScrollSpace : uint8_t | |
| { | |
| none, | |
| percent, | |
| index, | |
| }; | |
| // A percent/index write awaiting conversion to an offset. | |
| struct ScrollAxisIntent | |
| { | |
| ScrollSpace space = ScrollSpace::none; | |
| float value = 0; | |
| }; | |
| ScrollPhysics* m_physics = nullptr; | |
| ScrollVirtualizer* m_virtualizer = nullptr; | |
| // Geometry of virtualized content; only allocated when virtualizing. | |
| VirtualLayout* m_virtualLayout = nullptr; | |
| // What the virtual layout was built from: every input but item sizes, and | |
| // each scroll child's version of those. It rebuilds only when they change. | |
| std::vector<float> m_virtualInputs; | |
| std::vector<uint32_t> m_virtualVersions; | |
| std::vector<float> m_pendingInputs; | |
| std::vector<uint32_t> m_pendingVersions; | |
| std::vector<float> m_columnLines; | |
| std::vector<float> m_rowLines; | |
| // A rebuild layout hasn't been given the new grid contents for yet. | |
| bool m_contributionsStale = false; | |
| std::vector<LayoutNodeProvider*> m_layoutChildren; | |
| TransformComponents m_componentsA; | |
| TransformComponents m_componentsB; | |
| Mat2D m_scrollTransform; | |
| float m_offsetX = 0; | |
| float m_offsetY = 0; | |
| float m_lastFrameOffsetX = 0; | |
| float m_lastFrameOffsetY = 0; | |
| int m_childConstraintAppliedCount = 0; | |
| bool m_isDragging = false; | |
| bool m_isScrollBarDragging = false; | |
| bool m_hasListChildren = false; | |
| // A wheel or trackpad gesture owns this view. Distinct from m_isDragging: | |
| // no pointer is down, and a phaseless wheel ends by going idle. | |
| bool m_isScrolling = false; | |
| float m_scrollIdleSeconds = 0; | |
| // Unresolved writes per axis; held until layout can convert. | |
| ScrollAxisIntent m_intentX; | |
| ScrollAxisIntent m_intentY; | |
| AABB boundsForFlatIndex(size_t index); | |
| /// Returns false when layout can't resolve the index yet; out-of-range | |
| /// indices clamp to the ends. | |
| bool positionAtIndex(float index, Vec2D& outPosition); | |
| float indexAtPosition(Vec2D pos); | |
| float maxOffsetXForPercent(); | |
| float maxOffsetYForPercent(); | |
| bool isBoundsCollapsed(AABB bounds); | |
| std::vector<Vec2D> collectSnapPoints(); | |
| // Returns whether the layout was rebuilt. | |
| bool refreshVirtualLayout(); | |
| // The virtual layout, built if it's stale; null unless virtualizing. | |
| const VirtualLayout* ensureVirtualLayout(); | |
| // Gives layout the row and column contents of every grid item, realized | |
| // or not. | |
| void syncVirtualGridContributions(); | |
| // Keeps the first item on screen in place when content before it resized. | |
| void anchorScroll(float moved, bool isColumn); | |
| float virtualContentExtent(float padding); | |
| bool scrollLayoutResolvable(bool isX); | |
| float clampResolvedOffset(float value, bool isX); | |
| bool resolveIntent( | |
| const ScrollAxisIntent& intent, | |
| bool isX, | |
| float& outOffset); | |
| void setIntentX(ScrollAxisIntent intent); | |
| void setIntentY(ScrollAxisIntent intent); | |
| void resolveScrollIntents(); | |
| void clearScrollIntents() | |
| { | |
| m_intentX.space = ScrollSpace::none; | |
| m_intentY.space = ScrollSpace::none; | |
| } | |
| public: | |
| ~ScrollConstraint(); | |
| void constrain(TransformComponent* component) override; | |
| std::vector<DraggableProxy*> draggables() override; | |
| void buildDependencies() override; | |
| StatusCode import(ImportStack& importStack) override; | |
| StatusCode onAddedDirty(CoreContext* context) override; | |
| Core* clone() const override; | |
| void dragView(Vec2D delta, float timeStamp, bool trackVelocity = true); | |
| /// Whether wheel and trackpad input may drive this view. interactive | |
| /// gates every kind of input; wheelInteractive gates the indirect kind. | |
| bool wheelEnabled() { return interactive() && wheelInteractive(); } | |
| /// Whether the delta would actually move this view; an edge-stuck one | |
| /// declines so the gesture chains outward. | |
| bool canConsume(Vec2D delta); | |
| /// Whether a precise gesture can stretch this view at its edge. | |
| bool canStretch(Vec2D delta); | |
| /// Whether the offset currently sits outside the scrollable range. | |
| bool isOverscrolled(); | |
| /// The delta as it will actually be applied, after dragMultiplier. | |
| Vec2D scaledDelta(Vec2D delta); | |
| /// The wheel path: clamped, and no velocity accumulation, since a detent | |
| /// never flings and must not feed the velocity passthroughs. | |
| void scrollBy(Vec2D delta); | |
| /// True while a wheel or trackpad gesture owns this view. | |
| bool isScrolling() const { return m_isScrolling; } | |
| /// True while a pointer drag owns this view. | |
| bool isDragging() const { return m_isDragging; } | |
| /// True when this opened a new gesture rather than continuing one. | |
| bool beginScrollGesture(); | |
| void endScrollGesture(); | |
| /// Resets the idle timer that closes a phaseless gesture. | |
| void markScrollActivity() { m_scrollIdleSeconds = 0; } | |
| void runPhysics(); | |
| void constrainChild(LayoutNodeProvider* child) override; | |
| void addLayoutChild(LayoutNodeProvider* child) override; | |
| Constraint* constraint() override { return this; } | |
| void constrainVirtualized(bool force = false); | |
| // Null unless virtualizing. Rebuilt by constrainVirtualized, which every | |
| // item size change reaches through the list's computeLayoutBounds. | |
| const VirtualLayout* virtualLayout() const { return m_virtualLayout; } | |
| // Start of a scroll child's item in virtualized content space. | |
| Vec2D virtualItemPosition(LayoutNodeProvider* child, int index); | |
| bool advanceComponent( | |
| float elapsedSeconds, | |
| AdvanceFlags flags = AdvanceFlags::Animate | | |
| AdvanceFlags::NewFrame) override; | |
| ScrollPhysics* physics() const { return m_physics; } | |
| void physics(ScrollPhysics* physics) { m_physics = physics; } | |
| /// Pointer drag: primes physics and marks the drag. | |
| void initPhysics(); | |
| /// Primes the simulation and its velocity clock without claiming a | |
| /// pointer drag; the scroll path uses this so a wheel gesture ending | |
| /// cannot end a pointer drag that overlaps it. | |
| void primePhysics(); | |
| /// Runs the simulation without touching the drag flag. | |
| void startPhysics(); | |
| /// Re-primes physics when a settle has torn it down mid-gesture. True when | |
| /// it primed, meaning the velocity clock has no earlier sample. | |
| bool ensurePhysicsPrimed(); | |
| void stopPhysics(); | |
| void clearVelocity(); | |
| ScrollPhysicsType physicsType() const | |
| { | |
| return ScrollPhysicsType(physicsTypeValue()); | |
| } | |
| bool hasLayoutParent() | |
| { | |
| return parent() != nullptr && parent()->is<LayoutComponent>(); | |
| } | |
| LayoutComponent* content() { return parent()->as<LayoutComponent>(); } | |
| LayoutComponent* viewport() | |
| { | |
| return parent()->parent()->as<LayoutComponent>(); | |
| } | |
| float contentWidth(); | |
| float contentHeight(); | |
| // Passthrough properties: binds read the live extents directly. | |
| float computedContentWidth() override | |
| { | |
| return hasLayoutParent() ? contentWidth() : 0.0f; | |
| } | |
| float computedContentHeight() override | |
| { | |
| return hasLayoutParent() ? contentHeight() : 0.0f; | |
| } | |
| void setComputedContentWidth(float value) override {} | |
| void setComputedContentHeight(float value) override {} | |
| float viewportWidth(); | |
| float viewportHeight(); | |
| float visibleWidthRatio(); | |
| float visibleHeightRatio(); | |
| float minOffsetX(); | |
| float minOffsetY(); | |
| float maxOffsetX(); | |
| float maxOffsetY(); | |
| float clampedOffsetX(); | |
| float clampedOffsetY(); | |
| #ifdef TESTING | |
| // Children constrained since the content last was: one per child per | |
| // update once they have all run, which is when virtualization kicks in. | |
| int childConstraintAppliedCount() const | |
| { | |
| return m_childConstraintAppliedCount; | |
| } | |
| #endif | |
| float offsetX() { return m_offsetX; } | |
| float offsetY() { return m_offsetY; } | |
| void offsetX(float value); | |
| void offsetY(float value); | |
| void scrollOffsetXChanged() override { offsetX(scrollOffsetX()); } | |
| void scrollOffsetYChanged() override { offsetY(scrollOffsetY()); } | |
| // A bound or keyed buffer must re-run the window, as in Dart. | |
| void virtualizeBufferChanged() override { markConstraintDirty(); } | |
| void virtualizeChanged() override; | |
| float scrollPercentX() override; | |
| float scrollPercentY() override; | |
| float scrollIndex() override; | |
| void setScrollPercentX(float value) override; | |
| void setScrollPercentY(float value) override; | |
| void setScrollIndex(float value) override; | |
| float velocityX() override; | |
| float velocityY() override; | |
| void setVelocityX(float value) override {} | |
| void setVelocityY(float value) override {} | |
| bool scrollActive() override; | |
| void setScrollActive(bool value) override {} | |
| bool isScrollBarDragging() const { return m_isScrollBarDragging; } | |
| void isScrollBarDragging(bool value) | |
| { | |
| if (!m_isScrollBarDragging && value) | |
| { | |
| // User interaction supersedes held intents. | |
| clearScrollIntents(); | |
| // Prime the snapshot so the first advance comparison is | |
| // meaningful (otherwise a stale value falsely flags motion). | |
| m_lastFrameOffsetX = scrollOffsetX(); | |
| m_lastFrameOffsetY = scrollOffsetY(); | |
| } | |
| m_isScrollBarDragging = value; | |
| } | |
| size_t scrollItemCount(); | |
| std::vector<LayoutNodeProvider*>& scrollChildren() | |
| { | |
| return m_layoutChildren; | |
| } | |
| Vec2D gap(); | |
| bool mainAxisIsColumn(); | |
| // The axis virtualized lines stack along; the cross axis when wrapping. | |
| bool virtualAxisIsColumn(); | |
| // Virtualizing grid rows: a vertical (or two-way) scroll over a grid of | |
| // lists. | |
| bool virtualizesGrid(); | |
| // Every scroll child is a virtualizing list. | |
| bool scrollChildrenAreLists(); | |
| // A virtualized grid windows its columns as well as its rows. | |
| bool virtualizesGridColumns(); | |
| // A grid scrolled both ways: scrollIndex reads a cell, not a row. | |
| bool indexesGridCells(); | |
| // The axis scrollIndex counts along. | |
| bool indexesHorizontally(); | |
| // Whether a carousel cycles each axis. | |
| bool loopsX(); | |
| bool loopsY(); | |
| /// Animate scroll to the target position using the physics settling | |
| /// animation. | |
| void scrollToPosition(float targetX, float targetY); | |
| /// Given a target offset reached by scrolling from current, return the | |
| /// nearest snap offset in the direction of travel, or target unchanged | |
| /// if snap() is false, no snap points exist, or none lie in the scroll | |
| /// direction. The returned snap is at least as far as target — so an | |
| /// element brought into view by target stays in view. | |
| Vec2D nearestSnapOffsetInDirection(Vec2D current, Vec2D target); | |
| /// Get the effective scroll offset X (target if animating, current | |
| /// otherwise). Use this to check element visibility during rapid focus | |
| /// changes. | |
| float effectiveScrollOffsetX(); | |
| /// Get the effective scroll offset Y (target if animating, current | |
| /// otherwise). Use this to check element visibility during rapid focus | |
| /// changes. | |
| float effectiveScrollOffsetY(); | |
| }; | |
| } // namespace rive | |
| #endif |