Add SwiftUI-style animation, transitions, and content transitions#528
Open
zxss702 wants to merge 80 commits intomoreSwift:mainfrom
Open
Add SwiftUI-style animation, transitions, and content transitions#528zxss702 wants to merge 80 commits intomoreSwift:mainfrom
zxss702 wants to merge 80 commits intomoreSwift:mainfrom
Conversation
Views now update automatically when an `@Observable` model class used inside `body` changes. The same is true for `@Perceptible`. Adds a `@Bindable` property wrapper so that properties of observable models can be easily used as bindings.
… slide/push transitions
There was a problem hiding this comment.
Pull request overview
This PR introduces a graph-driven SwiftUI-style animation system to SwiftCrossUI, spanning animation primitives, transactions, transitions, content transitions, animators, and backend presentation hooks. It fits into the core rendering architecture by moving animation policy into the view graph and transaction pipeline instead of individual backends.
Changes:
- Adds core animation/transaction APIs (
Animation,Transaction, transitions, content transitions,PhaseAnimator,KeyframeAnimator, animatable protocols/types). - Wires transaction-aware scheduling and presentation updates through the view graph, state system, and multiple backends.
- Adds tests, example apps, and docs for transitions, observable models, and animation showcases.
Reviewed changes
Copilot reviewed 131 out of 132 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/SwiftCrossUITests/TransitionTests.swift | Adds regression coverage for optional-view transition wrapping. |
| Tests/SwiftCrossUITests/PublisherTests.swift | Adds coverage for default ObservableObject publisher stability. |
| Sources/WinUIInterop/include/WinUIInterop.h | Declares WinUI interop APIs for refresh rate and blur. |
| Sources/WinUIInterop/dummy.cpp | Removes previous placeholder WinUI interop source. |
| Sources/UIKitBackend/UIKitBackend.swift | Exposes preferred frame rate for animation scheduling. |
| Sources/UIKitBackend/UIKitBackend+Passive.swift | Adds UIKit text layout fragment extraction. |
| Sources/UIKitBackend/UIKitBackend+Container.swift | Adds UIKit effect setters for opacity/transform/visibility/z-order. |
| Sources/SwiftCrossUI/_App.swift | Integrates app root with graph update host and observation-driven refresh. |
| Sources/SwiftCrossUI/Views/_BuiltinPickerImplementation.swift | Reduces redundant picker updates and caches selected/options state. |
| Sources/SwiftCrossUI/Views/VStack.swift | Adds layout input key support for stack cache invalidation. |
| Sources/SwiftCrossUI/Views/TupleView.swift.gyb | Adds layout state plumbing and transition trait forwarding for tuple views. |
| Sources/SwiftCrossUI/Views/ToggleSwitch.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/ToggleButton.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/TextField.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/Spacer.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/Slider.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/SecureField.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/ScrollView.swift | Adjusts scroll view sizing/alignment logic. |
| Sources/SwiftCrossUI/Views/ProgressView.swift | Adds layout input keys for progress-related views. |
| Sources/SwiftCrossUI/Views/OptionalView.swift | Reworks optional child handling onto dynamic transition state. |
| Sources/SwiftCrossUI/Views/Modifiers/ViewModifier.swift | Introduces ViewModifier, ModifiedContent, and placeholder content views. |
| Sources/SwiftCrossUI/Views/Modifiers/TransactionModifier.swift | Adds .transaction and .animation(..., value:) view modifiers. |
| Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift | Adds layout input key support for frame modifiers. |
| Sources/SwiftCrossUI/Views/Modifiers/Effects/OpacityModifier.swift | Adds animatable opacity effect view. |
| Sources/SwiftCrossUI/Views/Menu.swift | Adds layout input key support for menu buttons. |
| Sources/SwiftCrossUI/Views/IDView.swift | Adds explicit identity wrapper with transition-aware replacement handling. |
| Sources/SwiftCrossUI/Views/HStack.swift | Adds layout input key support for horizontal stacks. |
| Sources/SwiftCrossUI/Views/Group.swift | Adds layout input key support for groups. |
| Sources/SwiftCrossUI/Views/EmptyView.swift | Adds layout input key support for empty views. |
| Sources/SwiftCrossUI/Views/EitherView.swift | Reworks conditional view switching onto dynamic transition state. |
| Sources/SwiftCrossUI/Views/DynamicTransitionIdentity.swift | Defines transition identity model. |
| Sources/SwiftCrossUI/Views/DynamicTransitionContent.swift | Defines transition content payload. |
| Sources/SwiftCrossUI/Views/Checkbox.swift | Adds layout input key support. |
| Sources/SwiftCrossUI/Views/Button.swift | Adds layout input key support for buttons. |
| Sources/SwiftCrossUI/Views/AnyView.swift | Improves child widget replacement when erased child type changes. |
| Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift | Injects graph update host into root graph environment. |
| Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift | Exposes layout-preparation and identity/generation hooks. |
| Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift | Adds type-erased layout-preparation and identity APIs. |
| Sources/SwiftCrossUI/Values/UnitPoint.swift | Adds unit-space anchor type used by effects/transitions. |
| Sources/SwiftCrossUI/Values/Path.swift | Adds non-uniform affine scaling helper. |
| Sources/SwiftCrossUI/Values/Color/Color.swift | Adds layout input key support for color views. |
| Sources/SwiftCrossUI/Values/Angle.swift | Adds SwiftUI-style angle value type. |
| Sources/SwiftCrossUI/SwiftCrossUI.docc/Examples.md | Documents the new animation example app. |
| Sources/SwiftCrossUI/State/ViewModelObserver.swift | Adds observation helper protocol for view/body recomputation. |
| Sources/SwiftCrossUI/State/StateImpl.swift | Makes state reads/writes transaction-aware and dependency-tracked. |
| Sources/SwiftCrossUI/State/State.swift | Extends State support for Observation/Perception-backed models. |
| Sources/SwiftCrossUI/State/Publisher.swift | Preserves transaction context in throttled UI updates. |
| Sources/SwiftCrossUI/State/Published.swift | Makes published bindings and sends transaction-aware. |
| Sources/SwiftCrossUI/State/ObservableObject.swift | Reworks default ObservableObject publisher storage/caching. |
| Sources/SwiftCrossUI/State/Locations/Location.swift | Adds transaction/update/render thread-local contexts. |
| Sources/SwiftCrossUI/State/Binding.swift | Adds transaction-carrying bindings and binding animation helpers. |
| Sources/SwiftCrossUI/State/Bindable.swift | Introduces @Bindable for model-backed bindings. |
| Sources/SwiftCrossUI/State/AppStorage/AppStorage.swift | Adds dependency-read tracking for app storage state. |
| Sources/SwiftCrossUI/Scenes/WindowReference.swift | Routes window scene updates through graph update host and observation. |
| Sources/SwiftCrossUI/Layout/StackLayoutCache.swift | Adds cache signature for layout-input-based invalidation. |
| Sources/SwiftCrossUI/Layout/LayoutInputKey.swift | Introduces layout input key/fingerprint infrastructure. |
| Sources/SwiftCrossUI/Environment/EnvironmentValues.swift | Adds render-frame request and graph update host environment hooks. |
| Sources/SwiftCrossUI/Backend/BaseAppBackend.swift | Requires view-effect backend feature support. |
| Sources/SwiftCrossUI/Backend/BackendFeatures/ViewEffects.swift | Defines backend effect API surface. |
| Sources/SwiftCrossUI/Backend/BackendFeatures/PassiveViews/TextViews.swift | Adds text fragment API for content transitions. |
| Sources/SwiftCrossUI/Backend/BackendFeatures/Core/Core.swift | Adds preferred FPS API for backends. |
| Sources/SwiftCrossUI/Animation/Transition/TransitionPhase.swift | Defines transition phases and properties. |
| Sources/SwiftCrossUI/Animation/Transition/Transition.swift | Adds transition protocol and composition helpers. |
| Sources/SwiftCrossUI/Animation/Transition/ContentTransition.swift | Adds content transition API including numeric text mode. |
| Sources/SwiftCrossUI/Animation/Transition/BuiltinTransitions.swift | Implements built-in transitions and transition combinators. |
| Sources/SwiftCrossUI/Animation/Transition/AnyTransition.swift | Adds type-erased transition model and transition modifier. |
| Sources/SwiftCrossUI/Animation/Transaction/TransactionEnvironment.swift | Adds branch/current transaction environment handling. |
| Sources/SwiftCrossUI/Animation/Transaction/TransactionContext.swift | Adds withTransaction/withAnimation helpers. |
| Sources/SwiftCrossUI/Animation/Transaction/Transaction.swift | Defines transaction payload and animation completion support. |
| Sources/SwiftCrossUI/Animation/Runtime/PresentationAnimation.swift | Adds presentation-value animator for frame-based interpolation. |
| Sources/SwiftCrossUI/Animation/Runtime/LayoutPresentationStore.swift | Stores animated layout positions across frames. |
| Sources/SwiftCrossUI/Animation/Runtime/AnimatableEffectChildren.swift | Adds shared child storage for animatable effect views. |
| Sources/SwiftCrossUI/Animation/Core/UnitCurve.swift | Adds timing-curve implementation. |
| Sources/SwiftCrossUI/Animation/Core/TimelineSchedule.swift | Adds timeline scheduling types. |
| Sources/SwiftCrossUI/Animation/Core/Spring.swift | Adds spring model and spring math helpers. |
| Sources/SwiftCrossUI/Animation/Core/PhaseAnimator.swift | Adds SwiftUI-style phase animator view. |
| Sources/SwiftCrossUI/Animation/Core/KeyframesBuilder.swift | Adds result builders for keyframe APIs. |
| Sources/SwiftCrossUI/Animation/Core/CustomAnimation.swift | Defines custom animation protocol. |
| Sources/SwiftCrossUI/Animation/Core/BuiltinAnimations.swift | Implements built-in curve/spring/delay/repeat animation wrappers. |
| Sources/SwiftCrossUI/Animation/Core/AnimationState.swift | Adds animation-local state/context storage. |
| Sources/SwiftCrossUI/Animation/Core/AnimationCompletionCriteria.swift | Defines animation completion criteria. |
| Sources/SwiftCrossUI/Animation/Core/Animation.swift | Adds main animation value type and factory methods. |
| Sources/SwiftCrossUI/Animation/Animatable/VectorArithmetic.swift | Adds vector arithmetic and animatable conformances. |
| Sources/SwiftCrossUI/Animation/Animatable/EmptyAnimatableData.swift | Adds empty animatable data type. |
| Sources/SwiftCrossUI/Animation/Animatable/AnimatableValues.swift | Adds single-value animatable container. |
| Sources/SwiftCrossUI/Animation/Animatable/AnimatablePair.swift | Adds animatable pair type. |
| Sources/SwiftCrossUI/Animation/Animatable/Animatable.swift | Adds animatable protocol and ignored wrapper. |
| Sources/GtkCHelpers/gtk_custom_root_widget.c | Adjusts GTK root widget natural sizing behavior. |
| Sources/Gtk3Backend/Gtk3Backend.swift | Adds GTK text layout fragment extraction. |
| Sources/DummyBackend/DummyBackend.swift | Adds stored effect state for animation tests. |
| Sources/AndroidBackend/Kotlin/AndroidBackendHelpers.kt | Adds Android helpers for FPS, main-thread dispatch, and text fragments. |
| Sources/AndroidBackend/AndroidBackendHelpers.swift | Exposes new Android helper methods to Swift. |
| Sources/AndroidBackend/AndroidBackend.swift | Adds Android FPS/effect/text-fragment support and safer sizing. |
| Package.swift | Adds swift-perception and WinUIInterop dependency wiring. |
| Package.resolved | Resolves new root package dependencies. |
| Examples/Sources/ObservableExample/ObservableModel.swift | Adds perceptible observable example model. |
| Examples/Sources/ObservableExample/ObservableApp.swift | Adds example app demonstrating @Bindable/observable models. |
| Examples/Sources/AnimationExample/TransitionShowcaseSection.swift | Adds transition demo UI. |
| Examples/Sources/AnimationExample/TransitionItem.swift | Adds transition demo item model. |
| Examples/Sources/AnimationExample/TransactionBindingSection.swift | Adds transaction/binding animation demo UI. |
| Examples/Sources/AnimationExample/TimelineShowcaseSection.swift | Adds phase/keyframe animation demo UI. |
| Examples/Sources/AnimationExample/ScaleFadeModifier.swift | Adds custom modifier used in transition demo. |
| Examples/Sources/AnimationExample/PivotTransition.swift | Adds custom transition used in demo. |
| Examples/Sources/AnimationExample/MotionPhase.swift | Adds phase data for phase animator demo. |
| Examples/Sources/AnimationExample/KeyframeProbeValue.swift | Adds animatable value used by keyframe demo. |
| Examples/Sources/AnimationExample/IdentityStateTile.swift | Adds .id() reset demo tile. |
| Examples/Sources/AnimationExample/IdentityResetProbe.swift | Adds identity reset transition demo. |
| Examples/Sources/AnimationExample/ContentTransitionSection.swift | Adds content transition demo UI. |
| Examples/Sources/AnimationExample/AnimationTrackSection.swift | Adds implicit/scoped animation demo UI. |
| Examples/Sources/AnimationExample/AnimationShowcaseView.swift | Assembles the animation showcase screen. |
| Examples/Sources/AnimationExample/AnimationPreset.swift | Defines animation presets for the example app. |
| Examples/Sources/AnimationExample/AnimationApp.swift | Adds new animation example executable entrypoint. |
| Examples/Package.swift | Registers animation and observable example targets/dependencies. |
| Examples/Package.resolved | Resolves example package dependencies. |
| Examples/Bundler.toml | Adds bundler entries for new example apps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+171
to
+193
| public func value<V: Animatable>( | ||
| fromValue: V, | ||
| toValue: V, | ||
| initialVelocity: V, | ||
| time: TimeInterval | ||
| ) -> V { | ||
| var value = fromValue | ||
| let delta = toValue.animatableData - fromValue.animatableData | ||
| value.animatableData = fromValue.animatableData + self.value(target: delta, time: time) | ||
| return value | ||
| } | ||
|
|
||
| public func velocity<V: Animatable>( | ||
| fromValue: V, | ||
| toValue: V, | ||
| initialVelocity: V, | ||
| time: TimeInterval | ||
| ) -> V { | ||
| var value = fromValue | ||
| value.animatableData = self.velocity( | ||
| target: toValue.animatableData - fromValue.animatableData, | ||
| time: time | ||
| ) |
Comment on lines
+96
to
+112
| func publisher(for object: any ObservableObject) -> Publisher { | ||
| let key = ObjectIdentifier(object) | ||
|
|
||
| lock.lock() | ||
| if let entry = entries[key], entry.owner != nil { | ||
| let publisher = entry.publisher | ||
| lock.unlock() | ||
| return publisher | ||
| } | ||
| lock.unlock() | ||
|
|
||
| let entry = makeEntry(for: object) | ||
|
|
||
| lock.lock() | ||
| entries = entries.filter { $0.value.owner != nil } | ||
| entries[key] = entry | ||
| lock.unlock() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a SwiftUI-aligned animation system to SwiftCrossUI, covering core animation types, transactions, animated view modifiers, transitions, content transitions, PhaseAnimator, KeyframeAnimator, backend rendering hooks, and an animation example app.
The implementation is based on SwiftUI’s model: state changes produce transactions, transactions carry animation intent, the view graph schedules updates and render frames, and backends only apply the current presentation value for each frame. This keeps animation behavior in SwiftCrossUI’s view graph instead of pushing animation policy into each backend.
Motivation
Before this PR, SwiftCrossUI could update layout and state, but it did not have a unified animation pipeline comparable to SwiftUI. That made common SwiftUI patterns hard to express:
.animation(..., value:)andwithAnimation.transition(...).contentTransition(...)PhaseAnimatorandKeyframeAnimatorThis PR fills that gap while preserving SwiftCrossUI’s existing layout and backend architecture.
SwiftUI Alignment
This PR intentionally follows SwiftUI naming and behavior where practical:
Animation,CustomAnimation,UnitCurve,Spring,VectorArithmetic,Animatable,AnimatablePair, andEmptyAnimatableData.opacity,offset,scaleEffect,rotationEffect,transformEffect,blur(radius:opaque:), andzIndex.Transition,AnyTransition,TransitionPhase, and built-in transitions..blurReplace,.push(from:), slide behavior, insertion/removal phase handling, and support for multiple simultaneous removal transitions.PhaseAnimatorandKeyframeAnimatorAPIs.Angle.zero, non-uniformscaleEffect(x:y:anchor:), andblur(radius:opaque:)for source compatibility with SwiftUI-style code.Some behavior is intentionally best-effort where backend capabilities differ. For example,
blur(radius:opaque:)accepts the SwiftUI-compatibleopaqueparameter, but current backend blur APIs do not expose an opaque edge-sampling mode, so that flag is preserved for API compatibility and ignored internally for now.State And Transaction Model
The state system now participates in animation transactions:
TransactionContextandStateMutationContextmake implicit and explicit animations flow through graph updates.This is important because SwiftUI animation behavior is not just a visual effect. It depends on when and why state changes happen. By moving animation intent through transactions, SwiftCrossUI can animate state-driven updates consistently.
View Graph And Render Scheduling
This PR adds a graph-owned update scheduler:
GraphUpdateHostbatches state and observation mutations.BaseAppBackend/FullAppBackend/BackendFeatures.*protocol split.This keeps animation scheduling centralized and avoids making each backend responsible for deciding graph dirtiness or animation lifecycle.
Layout Identity And Stability
Animation exposes layout identity problems, especially around conditional views,
ForEach, and transitions. This PR adds layout infrastructure to make animated updates stable:LayoutInputKeyand layout generation tracking.ForEachchild indices during animated transitions.The goal is to avoid reusing layout or graph state when the logical view identity has changed, while still allowing stable reuse when it is correct.
Transitions
The PR adds transition infrastructure for insertion and removal:
Transition,AnyTransition,TransitionPhase, and built-in transition implementations.blurReplacetransition support.Usage example:
Content Transitions
The PR adds content transition support, including text-specific transitions:
ContentTransitionAPI.nil.Usage example:
PhaseAnimator And KeyframeAnimator
This PR adds SwiftUI-style phase and keyframe animation tools:
PhaseAnimatorcycles through phase values and now correctly advances through all phases when triggered.KeyframeAnimatorsupports keyframe tracks and combines them into one root timeline.Usage example:
Backend Support
Animation rendering is wired through backend-facing APIs instead of hardcoding platform behavior in views:
BackendFeatures.ViewEffectsto the backend feature model.WinUI Interop
The previous placeholder C++ file is replaced with a real WinUI composition interop implementation:
WinUIInterop.cpp.Examples And Documentation
This PR adds an AnimationExample app and updates documentation references:
The example is intended to exercise real workflows rather than only demonstrate isolated APIs.
Tests
This PR adds and updates coverage for:
PhaseAnimatoradvancing through all phases.ForEachanimated transition stability.Validation run locally:
swift build --target SwiftCrossUIswift build --target SwiftCrossUITestsswift build --package-path Examples --target AnimationExampleswift test --skip-build --filter SwiftCrossUITestsThe animation-related tests pass. On my machine, the full filtered test run still reports the existing AppKitBackend dimension mismatch where the expected size is
92x96and the local result is102x104; this appears to be a local AppKit text/layout metric difference rather than an animation regression.Notes For Reviewers
The largest conceptual change is that animation is now graph- and transaction-driven. Backends should remain mostly presentation executors: they apply opacity, transform, blur, z-index, widget size, and position for the current frame, but they do not own animation state or decide graph update scheduling.
Review focus areas:
LayoutInputKeyAI Usage Statement
Animationdirectory—including animation curves (Spring.swift) and built-inTransitionpresets (such asMoveTransition)—were written entirely by hand.WinUIBackendwere implemented by Codex; since I am not particularly familiar with WinUI, I simply verified that the code was "functional."AnimationExamplewere written by Codex; I highly recommend adding animation support to the other examples as well.Viewcomponents were performed by Codex—for instance, the mechanical addition ofLayoutInputKeysupport.The introductory summary for this PR was also translated using Codex. I subsequently used Google Translate to translate it back into Chinese for verification, and I can confirm that the PR summary is free of errors.