mirror of
https://github.com/EmergeTools/Pow.git
synced 2026-03-26 09:05:50 +00:00
Compare commits
13 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b4b1dda28 | ||
|
|
f650bd26c7 | ||
|
|
8381662164 | ||
|
|
a504eb6d14 | ||
|
|
4f47e338c9 | ||
|
|
f2e23ad418 | ||
|
|
f0d0f3e72d | ||
|
|
5ac7140413 | ||
|
|
ebf05a2239 | ||
|
|
392ad0d0b7 | ||
|
|
897afcde74 | ||
|
|
76ced9f8e3 | ||
|
|
fdc39e509c |
32 changed files with 401 additions and 120 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -8,12 +8,12 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Select Xcode version
|
||||
run: sudo xcode-select -s '/Applications/Xcode_15.0.app/Contents/Developer'
|
||||
run: sudo xcode-select -s '/Applications/Xcode_16.1.0.app/Contents/Developer'
|
||||
- name: Run fastlane
|
||||
env:
|
||||
EMERGE_API_TOKEN: ${{ secrets.EMERGE_API_TOKEN }}
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1,3 @@
|
|||
*.DS_Store
|
||||
.build
|
||||
.swiftpm
|
||||
|
|
@ -103,6 +103,7 @@
|
|||
54F15E5429D59D250054690B /* PulseExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F15E5329D59D250054690B /* PulseExample.swift */; };
|
||||
54F15E5629D5A0D70054690B /* GlowExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F15E5529D5A0D70054690B /* GlowExample.swift */; };
|
||||
B7A5DA8D2B0E853A0035FC0A /* Pow in Frameworks */ = {isa = PBXBuildFile; productRef = B7A5DA8C2B0E853A0035FC0A /* Pow */; };
|
||||
FA13B8ED2B3BAF5600F58836 /* SnapshotPreferences in Frameworks */ = {isa = PBXBuildFile; productRef = FA13B8EC2B3BAF5600F58836 /* SnapshotPreferences */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
|
@ -212,6 +213,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B7A5DA8D2B0E853A0035FC0A /* Pow in Frameworks */,
|
||||
FA13B8ED2B3BAF5600F58836 /* SnapshotPreferences in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -420,6 +422,7 @@
|
|||
name = "Pow Example";
|
||||
packageProductDependencies = (
|
||||
B7A5DA8C2B0E853A0035FC0A /* Pow */,
|
||||
FA13B8EC2B3BAF5600F58836 /* SnapshotPreferences */,
|
||||
);
|
||||
productName = "Pow Example";
|
||||
productReference = 546A2986292A31BB00A80DE2 /* Pow Example.app */;
|
||||
|
|
@ -450,6 +453,7 @@
|
|||
);
|
||||
mainGroup = 546A297D292A31BB00A80DE2;
|
||||
packageReferences = (
|
||||
FA13B8EB2B3BAF5600F58836 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */,
|
||||
);
|
||||
productRefGroup = 546A2987292A31BB00A80DE2 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -717,7 +721,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "io.movingparts.Pow-Example.Debug";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
|
|
@ -749,7 +753,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "io.movingparts.Pow-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
|
@ -777,11 +781,27 @@
|
|||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
FA13B8EB2B3BAF5600F58836 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/EmergeTools/SnapshotPreviews-iOS";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 0.10.21;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
B7A5DA8C2B0E853A0035FC0A /* Pow */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Pow;
|
||||
};
|
||||
FA13B8EC2B3BAF5600F58836 /* SnapshotPreferences */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = FA13B8EB2B3BAF5600F58836 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */;
|
||||
productName = SnapshotPreferences;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 546A297E292A31BB00A80DE2 /* Project object */;
|
||||
|
|
|
|||
|
|
@ -109,21 +109,21 @@ struct ExampleList: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct PresentInfoAction {
|
||||
var action: (any Example.Type) -> ()
|
||||
struct PresentInfoAction: Sendable {
|
||||
var action: @MainActor (any Example.Type) -> ()
|
||||
|
||||
init(action: @escaping (any Example.Type) -> Void) {
|
||||
init(action: @escaping @MainActor (any Example.Type) -> Void) {
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func callAsFunction<T: Example>(_ type: T.Type) {
|
||||
@MainActor func callAsFunction<T: Example>(_ type: T.Type) {
|
||||
action(type)
|
||||
}
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
struct PresentInfoActionKey: EnvironmentKey {
|
||||
static var defaultValue: PresentInfoAction? = nil
|
||||
struct PresentInfoActionKey: EnvironmentKey {
|
||||
static let defaultValue: PresentInfoAction? = nil
|
||||
}
|
||||
|
||||
var presentInfoAction: PresentInfoAction? {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Pow
|
||||
import SwiftUI
|
||||
import SnapshotPreferences
|
||||
|
||||
struct CheckoutExample: View, Example {
|
||||
enum PaymentError: Error {
|
||||
|
|
@ -271,5 +272,6 @@ struct CheckoutExample_Previews: PreviewProvider {
|
|||
CheckoutExample()
|
||||
.toolbar(.visible, for: .navigationBar)
|
||||
}
|
||||
.emergeSnapshotPrecision(0.99)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ fastlane_require 'git'
|
|||
default_platform(:ios)
|
||||
|
||||
def build_for_config(config)
|
||||
|
||||
package_file = "../Package.swift"
|
||||
`sed -i '' 's/let enablePreviews = .*/let enablePreviews = #{config == "Debug" ? "true" : "false"}/' "#{package_file}"`
|
||||
|
||||
g = Git.open('.')
|
||||
build_app(
|
||||
project: "./Example/Pow Example.xcodeproj",
|
||||
|
|
|
|||
24
Gemfile.lock
24
Gemfile.lock
|
|
@ -3,21 +3,21 @@ GEM
|
|||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
addressable (2.8.5)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.864.0)
|
||||
aws-sdk-core (3.190.0)
|
||||
aws-partitions (1.873.0)
|
||||
aws-sdk-core (3.190.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.74.0)
|
||||
aws-sdk-kms (1.75.0)
|
||||
aws-sdk-core (~> 3, >= 3.188.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.141.0)
|
||||
aws-sdk-s3 (1.142.0)
|
||||
aws-sdk-core (~> 3, >= 3.189.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
|
|
@ -35,7 +35,7 @@ GEM
|
|||
domain_name (0.6.20231109)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.105.0)
|
||||
excon (0.108.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
|
|
@ -64,7 +64,7 @@ GEM
|
|||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.7)
|
||||
fastimage (2.3.0)
|
||||
fastlane (2.217.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
|
|
@ -111,7 +111,7 @@ GEM
|
|||
git (1.18.0)
|
||||
addressable (~> 2.8)
|
||||
rchardet (~> 1.8)
|
||||
google-apis-androidpublisher_v3 (0.53.0)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.2)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
|
|
@ -131,7 +131,7 @@ GEM
|
|||
google-cloud-core (1.6.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (2.0.1)
|
||||
google-cloud-env (2.1.0)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.45.0)
|
||||
|
|
@ -142,9 +142,9 @@ GEM
|
|||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.9.0)
|
||||
googleauth (1.9.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.0, >= 2.0.1)
|
||||
google-cloud-env (~> 2.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
|
|
@ -191,7 +191,7 @@ GEM
|
|||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
|
||||
import PackageDescription
|
||||
|
||||
let enablePreviews = false
|
||||
|
||||
let package = Package(
|
||||
name: "Pow",
|
||||
platforms: [
|
||||
.iOS(.v15),
|
||||
.macOS(.v12),
|
||||
.macCatalyst(.v15)
|
||||
.macCatalyst(.v15),
|
||||
.tvOS(.v15)
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
|
|
@ -19,15 +22,19 @@ let package = Package(
|
|||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(url: "https://github.com/EmergeTools/SnapshotPreviews-iOS", exact: "0.10.21")
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "Pow",
|
||||
dependencies: []),
|
||||
dependencies: enablePreviews ? [.product(name: "SnapshotPreferences", package: "SnapshotPreviews-iOS", condition: .when(platforms: [.iOS]))] : [],
|
||||
resources: [.process("Assets.xcassets")],
|
||||
swiftSettings: enablePreviews ? [.define("EMG_PREVIEWS")] : nil),
|
||||
.testTarget(
|
||||
name: "PowTests",
|
||||
dependencies: ["Pow"]),
|
||||
]
|
||||
],
|
||||
swiftLanguageVersions: [.v5]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
# Pow
|
||||
|
||||
[](https://swiftpackageindex.com/EmergeTools/Pow)
|
||||
[](https://swiftpackageindex.com/EmergeTools/Pow)
|
||||
[](https://www.emergetools.com/app/example/ios/pow.Pow/release?utm_campaign=badge-data)
|
||||
|
||||
Delightful SwiftUI effects for your app.
|
||||
|
||||
[Check out other open source projects from Emerge Tools](https://www.emergetools.com/open-source)
|
||||
|
|
@ -26,7 +30,7 @@ If you are moving from the previously closed source Pow framework to the new ope
|
|||
|
||||
Pow features a selection of [SwiftUI transitions](#transitions) as well as [Change Effects](#change-effects) that trigger every time a value is updated.
|
||||
|
||||
You can find previews of all effects [on the Pow website](https://movingparts.io/pow). If you have an iOS Developer Environment, you can check out the [Pow Example App](https://github.com/movingparts-io/Pow-Examples).
|
||||
You can find previews of all effects [on the Pow website](https://movingparts.io/pow). If you have an iOS Developer Environment, you can check out the [Pow Example App](https://github.com/EmergeTools/Pow/tree/main/Example).
|
||||
|
||||
# Feedback & Contribution
|
||||
|
||||
|
|
@ -216,7 +220,7 @@ static func shine(angle: Angle, duration: Double = 1.0) -> AnyChangeEffect
|
|||
|
||||
Triggers a sound effect as feedback whenever a value changes.
|
||||
|
||||
This effect will not interrupt or duck any other audio that may currently playing. It may also not triggered based on the setting of the user's silent switch or playback device.
|
||||
This effect will not interrupt or duck any other audio that may be currently playing. This effect is not guaranteed to be triggered; the effect running depends on the user's silent switch position and the current playback device.
|
||||
|
||||
To relay important information to the user, you should always accompany audio effects with visual cues.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ public extension AnyChangeEffect {
|
|||
enum ShakeRate {
|
||||
case `default`
|
||||
case fast
|
||||
case phaseLength(CGFloat)
|
||||
|
||||
fileprivate var phaseLength: CGFloat {
|
||||
switch self {
|
||||
case .default: return 0.8
|
||||
case .fast: return 0.3
|
||||
case .phaseLength(let phaseLength): return phaseLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import SwiftUI
|
||||
import simd
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
import SnapshotPreferences
|
||||
#endif
|
||||
|
||||
public extension AnyConditionalEffect {
|
||||
/// An effect that emits smoke from the view.
|
||||
|
|
@ -54,7 +57,7 @@ private struct SmokeEffect: ViewModifier, Continuous {
|
|||
GeometryReader { proxy in
|
||||
ZStack {
|
||||
ForEach(Array(particles.enumerated()), id: \.element) { (offset, particle) in
|
||||
#if os(iOS) || os(visionOS)
|
||||
#if os(iOS) || os(visionOS) || os(tvOS)
|
||||
let image = UIImage(named: particle, in: .module, with: nil)!.cgImage!
|
||||
#elseif os(macOS)
|
||||
let image = Bundle.module.image(forResource: particle)!.cgImage(forProposedRect: nil, context: nil, hints: nil)!
|
||||
|
|
@ -67,7 +70,7 @@ private struct SmokeEffect: ViewModifier, Continuous {
|
|||
}
|
||||
}
|
||||
|
||||
#if os(iOS) || os(visionOS)
|
||||
#if os(iOS) || os(visionOS) || os(tvOS)
|
||||
private class EmitterView: UIView {
|
||||
override class var layerClass : AnyClass {
|
||||
return CAEmitterLayer.self
|
||||
|
|
@ -169,7 +172,7 @@ private struct SmokeLayerView: ViewRepresentable {
|
|||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
#if DEBUG && !os(tvOS)
|
||||
struct ContinuousParticleEffect_Previews: PreviewProvider {
|
||||
private struct Preview: View {
|
||||
@State
|
||||
|
|
@ -336,30 +339,35 @@ struct ContinuousParticleEffect_Previews: PreviewProvider {
|
|||
}
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
Preview()
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Dark")
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Dark")
|
||||
Preview()
|
||||
.preferredColorScheme(.light)
|
||||
.previewDisplayName("Light")
|
||||
.preferredColorScheme(.light)
|
||||
.previewDisplayName("Light")
|
||||
PreviewS()
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Small")
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Small")
|
||||
Preview2()
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Large")
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Large")
|
||||
Preview3()
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Particle Layer")
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Particle Layer")
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
PreviewLayer()
|
||||
.previewDisplayName("Emitter Layer")
|
||||
#endif
|
||||
.previewDisplayName("Emitter Layer")
|
||||
#endif
|
||||
|
||||
PreviewAlt()
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Emitter Dark")
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDisplayName("Emitter Dark")
|
||||
}
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -5,18 +5,21 @@ public extension AnyChangeEffect {
|
|||
enum SpinRate {
|
||||
case `default`
|
||||
case fast
|
||||
|
||||
fileprivate var maximumVelocity: Angle {
|
||||
switch self {
|
||||
case .fast: return .degrees(360 * 4)
|
||||
case .default: return .degrees(360 * 2)
|
||||
}
|
||||
}
|
||||
case velocity(initial: Angle, maximum: Angle, additional: Angle)
|
||||
|
||||
fileprivate var initialVelocity: Angle {
|
||||
switch self {
|
||||
case .fast: return .degrees(900)
|
||||
case .default: return .degrees(360)
|
||||
case .velocity(let initial, _, _): return initial
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var maximumVelocity: Angle {
|
||||
switch self {
|
||||
case .fast: return .degrees(360 * 4)
|
||||
case .default: return .degrees(360 * 2)
|
||||
case .velocity(_, let maximum, _): return maximum
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +27,7 @@ public extension AnyChangeEffect {
|
|||
switch self {
|
||||
case .fast: return .degrees(900)
|
||||
case .default: return .degrees(360)
|
||||
case .velocity(_, _, let additional): return additional
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,10 +44,11 @@ public extension AnyChangeEffect {
|
|||
/// - anchor: The location with a default of center that defines a point in 3D space about which the rotation is anchored.
|
||||
/// - anchorZ: The location with a default of 0 that defines a point in 3D space about which the rotation is anchored.
|
||||
/// - perspective: The relative vanishing point with a default of 1 / 6 for this rotation.
|
||||
/// - perspective: An additional multipler you can provide to speed up the animation's runtime.
|
||||
/// - rate: The rate of the spin.
|
||||
static func spin(axis: (x: CGFloat, y: CGFloat, z: CGFloat), anchor: UnitPoint = .center, anchorZ: CGFloat = 0, perspective: CGFloat = 1 / 6, rate: SpinRate = .default) -> AnyChangeEffect {
|
||||
static func spin(axis: (x: CGFloat, y: CGFloat, z: CGFloat), anchor: UnitPoint = .center, anchorZ: CGFloat = 0, perspective: CGFloat = 1 / 6, multiplier speedBoost: CGFloat = 0.0, rate: SpinRate = .default) -> AnyChangeEffect {
|
||||
.simulation { change in
|
||||
SpinSimulationModifier(impulseCount: change, axis: axis, anchor: anchor, anchorZ: anchorZ, perspective: perspective, rate: rate)
|
||||
SpinSimulationModifier(impulseCount: change, axis: axis, anchor: anchor, anchorZ: anchorZ, perspective: perspective, additionalSpeed: speedBoost, rate: rate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +68,8 @@ internal struct SpinSimulationModifier: ViewModifier, Simulative {
|
|||
|
||||
var perspective: CGFloat
|
||||
|
||||
var additionalSpeed: CGFloat
|
||||
|
||||
var rate: AnyChangeEffect.SpinRate
|
||||
|
||||
@State
|
||||
|
|
@ -120,7 +127,7 @@ internal struct SpinSimulationModifier: ViewModifier, Simulative {
|
|||
|
||||
if abs(angleVelocity.degrees) > 240 {
|
||||
newValue = angle.degrees + angleVelocity.degrees * step
|
||||
newVelocity = angleVelocity.degrees * 0.99
|
||||
newVelocity = angleVelocity.degrees * (0.99 - self.additionalSpeed)
|
||||
targetAngle = .degrees((angle.degrees / 360.0).rounded(.up) * 360.0)
|
||||
} else if spring.response > 0 {
|
||||
(newValue, newVelocity) = spring.value(
|
||||
|
|
|
|||
|
|
@ -10,11 +10,13 @@ public extension AnyChangeEffect {
|
|||
enum WiggleRate {
|
||||
case `default`
|
||||
case fast
|
||||
case phaseLength(CGFloat)
|
||||
|
||||
fileprivate var phaseLength: CGFloat {
|
||||
switch self {
|
||||
case .default: return 0.8
|
||||
case .fast: return 0.3
|
||||
case .phaseLength(let phaseLength): return phaseLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Foundation
|
|||
|
||||
@available(iOS 16.0, *)
|
||||
@available(macOS 13.0, *)
|
||||
@available(tvOS 16.0, *)
|
||||
internal extension Duration {
|
||||
var timeInterval: TimeInterval {
|
||||
TimeInterval(components.seconds) + TimeInterval(components.attoseconds) / 1e18
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#if !os(tvOS)
|
||||
import SwiftUI
|
||||
|
||||
struct AngleControl<Label: View>: View {
|
||||
|
|
@ -132,3 +133,4 @@ struct AngleControl_Previews: PreviewProvider {
|
|||
.padding()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -5,8 +5,21 @@ import CoreHaptics
|
|||
|
||||
internal struct Haptics {
|
||||
private static var engine: CHHapticEngine? = {
|
||||
return try? CHHapticEngine()
|
||||
let engine = try? CHHapticEngine()
|
||||
addHapticEngineObservers()
|
||||
return engine
|
||||
}()
|
||||
|
||||
private static func addHapticEngineObservers() {
|
||||
// Without stopping the CHHapticEngine when entering background mode, haptics are not played when the app enters the foreground.
|
||||
// See https://github.com/EmergeTools/Pow/issues/69
|
||||
NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in
|
||||
engine?.stop()
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { _ in
|
||||
try? engine?.start()
|
||||
}
|
||||
}
|
||||
|
||||
private static var supportsHaptics = CHHapticEngine.capabilitiesForHardware().supportsHaptics
|
||||
|
||||
|
|
|
|||
62
Sources/Pow/Infrastructure/ProgressableAnimation.swift
Normal file
62
Sources/Pow/Infrastructure/ProgressableAnimation.swift
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// ProgressableAnimation.swift
|
||||
//
|
||||
//
|
||||
// Created by Noah Martin on 11/30/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
#if DEBUG
|
||||
typealias DebugProgressableAnimation = ProgressableAnimation
|
||||
#else
|
||||
typealias DebugProgressableAnimation = Animatable
|
||||
#endif
|
||||
|
||||
protocol ProgressableAnimation: Animatable {
|
||||
var progress: CGFloat { get set }
|
||||
}
|
||||
|
||||
extension ProgressableAnimation where AnimatableData == CGFloat {
|
||||
var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
protocol PreviewableAnimation {
|
||||
associatedtype Animation: ProgressableAnimation & ViewModifier
|
||||
|
||||
static var animation: Animation { get }
|
||||
|
||||
static var content: any View { get }
|
||||
}
|
||||
|
||||
extension PreviewableAnimation {
|
||||
static var content: any View {
|
||||
RoundedRectangle(
|
||||
cornerRadius: 8,
|
||||
style: .continuous)
|
||||
.fill(Color.blue)
|
||||
.frame(width: 80, height: 80)
|
||||
}
|
||||
}
|
||||
|
||||
extension PreviewableAnimation {
|
||||
static var previews: AnyView {
|
||||
let c = self.content
|
||||
let anyContent = AnyView(c)
|
||||
let modifiers = [0, 0.25, 0.5, 0.75, 1].map { i in
|
||||
var copy = self.animation
|
||||
copy.progress = i
|
||||
return copy
|
||||
}
|
||||
return AnyView(ForEach(Array(modifiers.enumerated()), id: \.offset) { i, modifier in
|
||||
anyContent.modifier(modifier)
|
||||
.previewDisplayName("\(String(describing: Animation.self))-\(i)")
|
||||
})
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -26,3 +26,5 @@ internal struct Scaled<V: ViewModifier & Animatable>: ViewModifier, Animatable {
|
|||
content.modifier(base.animation(nil))
|
||||
}
|
||||
}
|
||||
|
||||
extension Scaled: ProgressableAnimation where V.AnimatableData == CGFloat { }
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ public struct AnyConditionalEffect {
|
|||
/// - interval: The duration between each change effect.
|
||||
@available(iOS 16.0, *)
|
||||
@available(macOS 13.0, *)
|
||||
@available(tvOS 16.0, *)
|
||||
public static func `repeat`(_ effect: AnyChangeEffect, every interval: Duration) -> AnyConditionalEffect {
|
||||
AnyConditionalEffect(guts: .repeating(effect, interval.timeInterval))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import SwiftUI
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
import SnapshotPreferences
|
||||
#endif
|
||||
|
||||
public extension AnyTransition.MovingParts {
|
||||
/// A transition that drops the view down from the top.
|
||||
|
|
@ -16,7 +19,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
|
||||
internal struct Anvil: ViewModifier, ProgressableAnimation, AnimatableModifier {
|
||||
var animatableData: CGFloat = 0
|
||||
|
||||
#if os(iOS)
|
||||
|
|
@ -28,11 +31,6 @@ internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
|
|||
self.animatableData = animatableData
|
||||
}
|
||||
|
||||
var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
/// Fraction of the animation spent on the view falling down.
|
||||
let fall: CGFloat = 0.1
|
||||
|
|
@ -111,7 +109,7 @@ internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
|
|||
let offsetX = maxOffsetX * (relativeX - 0.5) * 2 * .random(in: 0.8 ... 1.2, using: &rng)
|
||||
let offsetY = CGFloat.random(in: -maxOffsetY / 2 ... maxOffsetY / 2, using: &rng) + (t * t) * -50
|
||||
|
||||
var scale = 1 + 0.6 * (1 - pow(sin(relativeX * .pi), 0.4)) + .random(in: 0 ... 0.2, using: &rng)
|
||||
var scale = 1 + 0.6 * (1 - pow(sin(relativeX * CGFloat.pi), 0.4)) + .random(in: 0 ... 0.2, using: &rng)
|
||||
scale *= 0.8 + (dustT * 0.2)
|
||||
scale /= 3
|
||||
scale *= 1 - pow(2, -50 * dustT)
|
||||
|
|
@ -190,7 +188,7 @@ internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
|
|||
ctx.translateBy(x: center.x, y: center.y)
|
||||
ctx.scaleBy(x: scale, y: scale)
|
||||
|
||||
ctx.opacity = Double(pow(sin(speckT * .pi), 0.2))
|
||||
ctx.opacity = Double(pow(sin(speckT * CGFloat.pi), 0.2))
|
||||
ctx.fill(speck, with: .color(Color(white: .random(in: 0.75 ... 0.9, using: &rng))))
|
||||
}
|
||||
}
|
||||
|
|
@ -209,6 +207,21 @@ extension EdgeInsets {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Anvil_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Anvil {
|
||||
Anvil(animatableData: 0)
|
||||
}
|
||||
|
||||
static var content: any View {
|
||||
RoundedRectangle(
|
||||
cornerRadius: 8,
|
||||
style: .continuous)
|
||||
.fill(Color.blue)
|
||||
.frame(width: 80, height: 80)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Anvil_Previews: PreviewProvider {
|
||||
struct Item: Identifiable {
|
||||
|
|
@ -280,6 +293,9 @@ struct Anvil_Previews: PreviewProvider {
|
|||
.navigationBarHidden(true)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Blinds: ViewModifier, Animatable, AnimatableModifier, Hashable {
|
||||
internal struct Blinds: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
|
||||
var slatWidth: CGFloat
|
||||
|
||||
var style: AnyTransition.MovingParts.BlindsStyle
|
||||
|
|
@ -39,11 +39,6 @@ internal struct Blinds: ViewModifier, Animatable, AnimatableModifier, Hashable {
|
|||
|
||||
var animatableData: CGFloat
|
||||
|
||||
private var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.mask {
|
||||
|
|
@ -108,6 +103,13 @@ private struct BlindsShape: Shape {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Blinds_Preview: PreviewableAnimation & PreviewProvider {
|
||||
|
||||
static var animation: Blinds {
|
||||
Blinds(slatWidth: 20, style: .venetian, isStaggered: false, animatableData: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Blinds_Previews: PreviewProvider {
|
||||
struct Preview: View {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Blur: ViewModifier, Animatable, AnimatableModifier, Hashable {
|
||||
internal struct Blur: ViewModifier, DebugProgressableAnimation, AnimatableModifier, Hashable {
|
||||
var animatableData: CGFloat {
|
||||
get { radius }
|
||||
set { radius = newValue }
|
||||
|
|
@ -37,6 +37,12 @@ internal struct Blur: ViewModifier, Animatable, AnimatableModifier, Hashable {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Blur_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Blur {
|
||||
Blur(radius: 30)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Blur_Previews: PreviewProvider {
|
||||
struct Preview: View {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import SwiftUI
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
import SnapshotPreferences
|
||||
#endif
|
||||
|
||||
public extension AnyTransition.MovingParts {
|
||||
/// A transition that moves the view down with any overshoot resulting in an
|
||||
|
|
@ -18,7 +21,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Boing: Animatable, GeometryEffect {
|
||||
internal struct Boing: DebugProgressableAnimation, GeometryEffect {
|
||||
var edge: Edge
|
||||
|
||||
var animatableData: CGFloat = 0
|
||||
|
|
@ -104,6 +107,12 @@ internal struct Boing: Animatable, GeometryEffect {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Boing_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Scaled<Boing> {
|
||||
Scaled(Boing(.top, animatableData: 0))
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Bounce_Previews: PreviewProvider {
|
||||
struct Item: Identifiable {
|
||||
|
|
@ -218,6 +227,9 @@ struct Bounce_Previews: PreviewProvider {
|
|||
.navigationBarHidden(true)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -290,6 +302,9 @@ struct Boing_2_Previews: PreviewProvider {
|
|||
NavigationView {
|
||||
Preview()
|
||||
}
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import SwiftUI
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
import SnapshotPreferences
|
||||
#endif
|
||||
|
||||
public extension AnyTransition.MovingParts {
|
||||
/// A transition using a clockwise sweep around the centerpoint of the view.
|
||||
|
|
@ -18,7 +21,8 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Clock: ViewModifier, Animatable, AnimatableModifier {
|
||||
internal struct Clock: ViewModifier, DebugProgressableAnimation, AnimatableModifier {
|
||||
|
||||
var origin: UnitPoint
|
||||
|
||||
var animatableData: AnimatablePair<CGFloat, CGFloat>
|
||||
|
|
@ -29,7 +33,8 @@ internal struct Clock: ViewModifier, Animatable, AnimatableModifier {
|
|||
}
|
||||
|
||||
var progress: CGFloat {
|
||||
animatableData.first
|
||||
get { animatableData.first }
|
||||
set { animatableData.first = newValue }
|
||||
}
|
||||
|
||||
var blurRadius: CGFloat {
|
||||
|
|
@ -81,6 +86,12 @@ internal struct Clock: ViewModifier, Animatable, AnimatableModifier {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Clock_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Clock {
|
||||
Clock(origin: .center, blurRadius: 0, progress: 0)
|
||||
}
|
||||
}
|
||||
|
||||
struct Clock_Previews: PreviewProvider {
|
||||
struct Item: Identifiable {
|
||||
var color: Color
|
||||
|
|
@ -225,6 +236,9 @@ struct Clock_Previews: PreviewProvider {
|
|||
.navigationBarHidden(true)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -20,14 +20,9 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Snapshot: ViewModifier, Animatable, AnimatableModifier, Hashable {
|
||||
internal struct Snapshot: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
|
||||
var animatableData: CGFloat = 0
|
||||
|
||||
var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.saturation(0.5 + 0.5 * clamp(progress))
|
||||
|
|
@ -38,14 +33,9 @@ internal struct Snapshot: ViewModifier, Animatable, AnimatableModifier, Hashable
|
|||
}
|
||||
}
|
||||
|
||||
internal struct ExposureFade: ViewModifier, Animatable, AnimatableModifier, Hashable {
|
||||
internal struct ExposureFade: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
|
||||
var animatableData: CGFloat = 0
|
||||
|
||||
var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.opacity(Double(1.0 - pow(2.0, -10.0 * progress)))
|
||||
|
|
@ -55,6 +45,18 @@ internal struct ExposureFade: ViewModifier, Animatable, AnimatableModifier, Hash
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Snapshot_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Snapshot {
|
||||
Snapshot(animatableData: 0)
|
||||
}
|
||||
}
|
||||
|
||||
struct FilmExposure_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: ExposureFade {
|
||||
ExposureFade(animatableData: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct ExoposureFade_Previews: PreviewProvider {
|
||||
struct Preview: View {
|
||||
|
|
|
|||
|
|
@ -21,16 +21,11 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Flicker: ViewModifier, Animatable, AnimatableModifier, Hashable {
|
||||
internal struct Flicker: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
|
||||
var count: Int
|
||||
|
||||
var animatableData: CGFloat
|
||||
|
||||
private var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
|
||||
private var isVisible: Bool {
|
||||
(progress * CGFloat(count)).remainder(dividingBy: 1) >= 0
|
||||
}
|
||||
|
|
@ -43,6 +38,12 @@ internal struct Flicker: ViewModifier, Animatable, AnimatableModifier, Hashable
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Flicker_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Flicker {
|
||||
Flicker(count: 1, animatableData: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Flicker_Previews: PreviewProvider {
|
||||
struct Preview: View {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import SwiftUI
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
import SnapshotPreferences
|
||||
#endif
|
||||
|
||||
public extension AnyTransition.MovingParts {
|
||||
/// A transitions that shows the view by combining a diagonal wipe with a
|
||||
|
|
@ -44,7 +47,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Glare: ViewModifier, Animatable, AnimatableModifier {
|
||||
internal struct Glare: ViewModifier, DebugProgressableAnimation, AnimatableModifier {
|
||||
var animatableData: CGFloat = 0
|
||||
|
||||
var angle: Angle
|
||||
|
|
@ -129,8 +132,37 @@ internal struct Glare: ViewModifier, Animatable, AnimatableModifier {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
@available(iOS 16.0, *)
|
||||
struct Glare_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Glare {
|
||||
Glare(.degrees(45), color: .white, increasedBrightness: true, animatableData: 0)
|
||||
}
|
||||
|
||||
static var content: any View {
|
||||
Glare_Previews.makeRect(start: .indigo, end: .purple)
|
||||
.frame(width: 100, height: 100)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct Glare_Previews: PreviewProvider {
|
||||
static func makeRect(start: Color, end: Color) -> some View {
|
||||
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
||||
.fill(LinearGradient(
|
||||
colors: [start, end],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottom
|
||||
))
|
||||
.compositingGroup()
|
||||
.overlay {
|
||||
Text("Hello\nWorld")
|
||||
.foregroundStyle(.white.shadow(.inner(radius: 0.5)))
|
||||
}
|
||||
.font(.system(.largeTitle, design: .rounded).weight(.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
struct Item: Identifiable {
|
||||
var color1: Color
|
||||
var color2: Color
|
||||
|
|
@ -211,19 +243,7 @@ struct Glare_Previews: PreviewProvider {
|
|||
ForEach(items.indices, id: \.self) { index in
|
||||
let item = items[index]
|
||||
|
||||
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
||||
.fill(LinearGradient(
|
||||
colors: [item.color1, item.color2],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottom
|
||||
))
|
||||
.compositingGroup()
|
||||
.overlay {
|
||||
Text("Hello\nWorld")
|
||||
.foregroundStyle(.white.shadow(.inner(radius: 0.5)))
|
||||
}
|
||||
.font(.system(.largeTitle, design: .rounded).weight(.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
Glare_Previews.makeRect(start: item.color1, end: item.color2)
|
||||
.transition(
|
||||
.asymmetric(
|
||||
insertion: .movingParts.glare(angle: angle),
|
||||
|
|
@ -254,6 +274,9 @@ struct Glare_Previews: PreviewProvider {
|
|||
.navigationBarHidden(true)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
private struct Iris: ViewModifier, Animatable, AnimatableModifier {
|
||||
struct Iris: ViewModifier, DebugProgressableAnimation, AnimatableModifier {
|
||||
var origin: UnitPoint
|
||||
|
||||
var blurRadius: CGFloat
|
||||
|
|
@ -58,6 +58,12 @@ private struct Iris: ViewModifier, Animatable, AnimatableModifier {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Iris_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Iris {
|
||||
Iris(origin: .center, animatableData: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Mask_Previews: PreviewProvider {
|
||||
struct Preview: View {
|
||||
|
|
|
|||
|
|
@ -16,18 +16,13 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
private struct Poof: ViewModifier, Animatable, AnimatableModifier {
|
||||
struct Poof: ViewModifier, ProgressableAnimation, AnimatableModifier {
|
||||
var animatableData: CGFloat = 0
|
||||
|
||||
internal init(animatableData: CGFloat) {
|
||||
self.animatableData = animatableData
|
||||
}
|
||||
|
||||
var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
let frame = (6 * progress).rounded()
|
||||
|
||||
|
|
@ -56,6 +51,25 @@ private struct Poof: ViewModifier, Animatable, AnimatableModifier {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Proof_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Poof {
|
||||
Poof(animatableData: 0)
|
||||
}
|
||||
|
||||
static var content: any View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 32, style: .continuous)
|
||||
.fill(Color.accentColor)
|
||||
|
||||
Text("Hello\nWorld!")
|
||||
.foregroundColor(.white)
|
||||
.multilineTextAlignment(.center)
|
||||
.font(.system(.title, design: .rounded))
|
||||
}
|
||||
.frame(width: 300, height: 150)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Poof_Previews: PreviewProvider {
|
||||
struct Preview: View {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
private struct Pop: AnimatableModifier, Animatable, ViewModifier {
|
||||
struct Pop: AnimatableModifier, ProgressableAnimation, ViewModifier {
|
||||
var animatableData: CGFloat = 0
|
||||
|
||||
var style: AnyShapeStyle
|
||||
|
|
@ -63,11 +63,6 @@ private struct Pop: AnimatableModifier, Animatable, ViewModifier {
|
|||
self.style = style
|
||||
}
|
||||
|
||||
var progress: CGFloat {
|
||||
get { animatableData }
|
||||
set { animatableData = newValue }
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
let t = clamp(2 * (progress - 1/2.5))
|
||||
|
||||
|
|
@ -170,6 +165,19 @@ private struct Pop: AnimatableModifier, Animatable, ViewModifier {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Pop_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Pop {
|
||||
Pop(style: AnyShapeStyle(.tint), animatableData: 0)
|
||||
}
|
||||
|
||||
static var content: any View {
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundColor(.red)
|
||||
.tint(.red)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Pop_Previews: PreviewProvider {
|
||||
struct Preview: View {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import SwiftUI
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
import SnapshotPreferences
|
||||
#endif
|
||||
|
||||
public extension AnyTransition.MovingParts {
|
||||
/// The direction from which to animate in during a `skid` transition's insertion.
|
||||
|
|
@ -26,7 +29,7 @@ public extension AnyTransition.MovingParts {
|
|||
}
|
||||
}
|
||||
|
||||
internal struct Skid: Animatable, GeometryEffect {
|
||||
internal struct Skid: DebugProgressableAnimation, GeometryEffect {
|
||||
var direction: AnyTransition.MovingParts.SkidDirection
|
||||
|
||||
var animatableData: CGFloat = 0
|
||||
|
|
@ -70,6 +73,29 @@ internal struct Skid: Animatable, GeometryEffect {
|
|||
}
|
||||
|
||||
#if os(iOS) && DEBUG
|
||||
struct Skid_Preview: PreviewableAnimation, PreviewProvider {
|
||||
static var animation: Skid {
|
||||
Skid(.leading)
|
||||
}
|
||||
|
||||
static var content: some View {
|
||||
RoundedRectangle(cornerRadius: 8, style: .continuous)
|
||||
.fill(Color.orange)
|
||||
.overlay {
|
||||
Text("Jell-O\nWorld")
|
||||
.blendMode(.difference)
|
||||
.offset(x: 2, y: 2)
|
||||
}
|
||||
.compositingGroup()
|
||||
.overlay {
|
||||
Text("Jell-O\nWorld")
|
||||
}
|
||||
.font(.system(.headline, design: .rounded).weight(.black))
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(width: 150, height: 150)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct Skid_Previews: PreviewProvider {
|
||||
struct Item: Identifiable {
|
||||
|
|
@ -182,6 +208,9 @@ struct Skid_Previews: PreviewProvider {
|
|||
.navigationBarHidden(true)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import SwiftUI
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
import SnapshotPreferences
|
||||
#endif
|
||||
|
||||
public extension AnyTransition.MovingParts {
|
||||
/// A transition that dissolves the view into many small particles.
|
||||
|
|
@ -236,6 +239,9 @@ struct Vanish_Previews: PreviewProvider {
|
|||
.navigationBarHidden(true)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
#if os(iOS) && EMG_PREVIEWS
|
||||
.emergeSnapshotPrecision(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in a new issue