Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// AsymmetricTransition.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete
// ID: 144244338250150A46EBD0B28C550067 (SwiftUICore)

// MARK: - AnyTransition + asymmetric

@available(OpenSwiftUI_v1_0, *)
extension AnyTransition {

/// Provides a composite transition that uses a different transition for
/// insertion versus removal.
public static func asymmetric(insertion: AnyTransition, removal: AnyTransition) -> AnyTransition {
var insertionVisitor = InsertionVisitor(removal: removal, result: nil)
insertion.visitBase(applying: &insertionVisitor)
return insertionVisitor.result!
}

private struct InsertionVisitor: TransitionVisitor {
var removal: AnyTransition
var result: AnyTransition?

mutating func visit<T>(_ transition: T) where T: Transition {
var removalVisitor = RemovalVisitor(insertion: transition, result: nil)
removal.visitBase(applying: &removalVisitor)
result = removalVisitor.result
}
}

private struct RemovalVisitor<Insertion>: TransitionVisitor where Insertion: Transition {
let insertion: Insertion
var result: AnyTransition?

mutating func visit<T>(_ transition: T) where T: Transition {
result = AnyTransition(AsymmetricTransition(insertion: insertion, removal: transition))
}
}
}

// MARK: - AsymmetricTransition

/// A composite `Transition` that uses a different transition for
/// insertion versus removal.
@available(OpenSwiftUI_v5_0, *)
public struct AsymmetricTransition<Insertion, Removal>: Transition where Insertion: Transition, Removal: Transition {
/// The `Transition` defining the insertion phase of `self`.
public var insertion: Insertion

/// The `Transition` defining the removal phase of `self`.
public var removal: Removal

/// Creates a composite `Transition` that uses a different transition for
/// insertion versus removal.
public init(insertion: Insertion, removal: Removal) {
self.insertion = insertion
self.removal = removal
}

public func body(content: Content, phase: TransitionPhase) -> some View {
removal.apply(
content: insertion.apply(
content: content,
phase: phase != .didDisappear ? phase : .identity
),
phase: phase == .didDisappear ? phase : .identity
)
}

public static var properties: TransitionProperties {
Insertion.properties.union(Removal.properties)
}

public func _makeContentTransition(transition: inout _Transition_ContentTransition) {
switch transition.operation {
case .hasContentTransition:
transition.result = .bool(insertion.hasContentTransition || removal.hasContentTransition)
case .effects(let style, let size):
var effects: [ContentTransition.Effect] = []
let insertionEffects = insertion.contentTransitionEffects(style: style, size: size)
for effect in insertionEffects {
effects.append(
ContentTransition.Effect(
type: effect.type,
begin: effect.begin,
duration: effect.duration,
events: .add,
flags: effect.flags
)
)
}
let removalEffects = removal.contentTransitionEffects(style: style, size: size)
for effect in removalEffects {
effects.append(
ContentTransition.Effect(
type: effect.type,
begin: effect.begin,
duration: effect.duration,
events: .remove,
flags: effect.flags
)
)
}
transition.result = .effects(effects)
}
}
}

@available(*, unavailable)
extension AsymmetricTransition: Sendable {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// CombiningTransition.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete
// ID: E95479797AFE5A67B59EE39088DDE631 (SwiftUICore)

// MARK: - AnyTransition + combined

@available(OpenSwiftUI_v1_0, *)
extension AnyTransition {

/// Combines this transition with another, returning a new transition that
/// is the result of both transitions being applied.
public func combined(with other: AnyTransition) -> AnyTransition {
var firstVisitor = FirstVisitor(second: other, result: nil)
visitBase(applying: &firstVisitor)
return firstVisitor.result!
}

private struct FirstVisitor: TransitionVisitor {
var second: AnyTransition
var result: AnyTransition?

mutating func visit<T>(_ transition: T) where T: Transition {
var secondVisitor = SecondVisitor(first: transition, result: nil)
second.visitBase(applying: &secondVisitor)
result = secondVisitor.result
}
}

private struct SecondVisitor<First>: TransitionVisitor where First: Transition {
let first: First
var result: AnyTransition?

mutating func visit<T>(_ transition: T) where T: Transition {
result = AnyTransition(CombiningTransition(transition1: first, transition2: transition))
}
}
}

// MARK: - Transition + combined

@available(OpenSwiftUI_v5_0, *)
extension Transition {

/// Combines this transition with another, returning a new transition that
/// is the result of both transitions being applied.
@MainActor
@preconcurrency
public func combined<T>(with other: T) -> some Transition where T: Transition {
CombiningTransition(transition1: self, transition2: other)
}
}

// MARK: - CombiningTransition

/// A transition that combines two transitions.
struct CombiningTransition<First, Second>: Transition where First: Transition, Second: Transition {
var transition1: First
var transition2: Second

init(transition1: First, transition2: Second) {
self.transition1 = transition1
self.transition2 = transition2
}

func body(content: Content, phase: TransitionPhase) -> some View {
transition2.apply(
content: transition1.apply(content: content, phase: phase),
phase: phase
)
}

static var properties: TransitionProperties {
First.properties.union(Second.properties)
}

func _makeContentTransition(transition: inout _Transition_ContentTransition) {
switch transition.operation {
case .hasContentTransition:
transition.result = .bool(transition1.hasContentTransition || transition2.hasContentTransition)
case .effects(let style, let size):
var effects: [ContentTransition.Effect] = transition1.contentTransitionEffects(style: style, size: size)
effects.append(contentsOf: transition2.contentTransitionEffects(style: style, size: size))
transition.result = .effects(effects)
}
}
}

@available(*, unavailable)
extension CombiningTransition: Sendable {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// FilteredTransition.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete
// ID: B9F0F810276E171D84377A7686E819B9 (SwiftUICore)

// MARK: - AnyTransition + animation

@available(OpenSwiftUI_v1_0, *)
extension AnyTransition {

/// Attaches an animation to this transition.
public func animation(_ animation: Animation?) -> AnyTransition {
var filterVisitor = FilterVisitor(filter: { t, _ in
t.animation = animation
}, result: nil)
visitBase(applying: &filterVisitor)
return filterVisitor.result!
}

private struct FilterVisitor: TransitionVisitor {
var filter: (inout Transaction, TransitionPhase) -> Void
var result: AnyTransition?

mutating func visit<T>(_ transition: T) where T: Transition {
result = AnyTransition(FilteredTransition(transition: transition, filter: filter))
}
}
}

// MARK: - Transition + animation

@available(OpenSwiftUI_v5_0, *)
extension Transition {

/// Attaches an animation to this transition.
@MainActor
@preconcurrency
public func animation(_ animation: Animation?) -> some Transition {
transaction { t, _ in
t.animation = animation
}
}

func transaction(_ modify: @escaping (inout Transaction, TransitionPhase) -> Void) -> FilteredTransition<Self> {
FilteredTransition(transition: self, filter: modify)
}
}

// MARK: - FilteredTransition

/// A transition that applies a transaction filter.
struct FilteredTransition<Base>: Transition where Base: Transition {
var transition: Base
var filter: (inout Transaction, TransitionPhase) -> Void

init(transition: Base, filter: @escaping (inout Transaction, TransitionPhase) -> Void) {
self.transition = transition
self.filter = filter
}

func body(content: Content, phase: TransitionPhase) -> some View {
content.modifier(
ApplyTransitionModifier(transition: transition, phase: phase)
.transaction { filter(&$0, phase) }
)
}

static var properties: TransitionProperties {
Base.properties
}

func _makeContentTransition(transition: inout _Transition_ContentTransition) {
self.transition._makeContentTransition(transition: &transition)
}
}

@available(*, unavailable)
extension FilteredTransition: Sendable {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// ModifierTransition.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete

// MARK: - AnyTransition + modifier

@available(OpenSwiftUI_v1_0, *)
extension AnyTransition {

/// Returns a transition defined between an active modifier and an identity
/// modifier.
public static func modifier<E>(active: E, identity: E) -> AnyTransition where E: ViewModifier {
.init(ModifierTransition(activeModifier: active, identityModifier: identity))
}
}

// MARK: - ModifierTransition

/// A transition defined between an active modifier and an identity modifier.
struct ModifierTransition<Modifier>: Transition where Modifier: ViewModifier {
/// The modifier applied when the view is not in the identity phase.
var activeModifier: Modifier

/// The modifier applied when the view is in the identity phase.
var identityModifier: Modifier

/// Creates a transition defined between an active modifier and an identity
/// modifier.
init(activeModifier: Modifier, identityModifier: Modifier) {
self.activeModifier = activeModifier
self.identityModifier = identityModifier
}

func body(content: Content, phase: TransitionPhase) -> some View {
content.modifier(phase.isIdentity ? identityModifier : activeModifier)
}
}

@available(*, unavailable)
extension ModifierTransition: Sendable {}

Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ extension MoveTransition: Sendable {}

extension Edge {
@inline(__always)
fileprivate func translationOffset(for size: CGSize) -> CGSize {
func translationOffset(for size: CGSize) -> CGSize {
switch self {
case .top:
return CGSize(width: 0, height: -size.height)
Expand Down
Loading
Loading