| #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 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; | |
| ScrollVirtualizer* m_virtualizer = nullptr; | |
| 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; | |
| // 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(); | |
| 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); | |
| void runPhysics(); | |
| void constrainChild(LayoutNodeProvider* child) override; | |
| void addLayoutChild(LayoutNodeProvider* child) override; | |
| Constraint* constraint() override { return this; } | |
| void constrainVirtualized(bool force = false); | |
| bool advanceComponent(float elapsedSeconds, | |
| AdvanceFlags flags = AdvanceFlags::Animate | | |
| AdvanceFlags::NewFrame) override; | |
| ScrollPhysics* physics() const { return m_physics; } | |
| void physics(ScrollPhysics* physics) { m_physics = physics; } | |
| void initPhysics(); | |
| 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(); | |
| 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()); } | |
| 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(); | |
| /// 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 |