craft-native 0.0.184 → 0.0.185
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/templates/CraftNativeScreen.swift +136 -67
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3303,7 +3303,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
3303
3303
|
`);
|
|
3304
3304
|
}
|
|
3305
3305
|
// package.json
|
|
3306
|
-
var version = "0.0.
|
|
3306
|
+
var version = "0.0.185";
|
|
3307
3307
|
|
|
3308
3308
|
// bin/cli.ts
|
|
3309
3309
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -6925,10 +6925,15 @@ final class CraftNativeAnimationRecord {
|
|
|
6925
6925
|
let fontSize: CGFloat?
|
|
6926
6926
|
let textColor: UIColor?
|
|
6927
6927
|
let transitionID: String?
|
|
6928
|
+
/// Where it sat in its parent, to be put back after it faded elsewhere.
|
|
6929
|
+
let index: Int?
|
|
6930
|
+
let arrangedIndex: Int?
|
|
6928
6931
|
|
|
6929
6932
|
init(view: UIView, screen: UIView, transitionID: String?) {
|
|
6930
6933
|
self.view = view
|
|
6931
6934
|
superview = view.superview
|
|
6935
|
+
index = view.superview?.subviews.firstIndex(of: view)
|
|
6936
|
+
arrangedIndex = (view.superview as? UIStackView)?.arrangedSubviews.firstIndex(of: view)
|
|
6932
6937
|
frame = view.frame
|
|
6933
6938
|
frameOnScreen = view.superview?.convert(view.frame, to: screen) ?? view.frame
|
|
6934
6939
|
alpha = view.alpha
|
|
@@ -6947,6 +6952,10 @@ final class CraftNativeAnimationTransaction {
|
|
|
6947
6952
|
var screenBackground: UIColor?
|
|
6948
6953
|
/// Reduce Motion: the screen as it was, crossfaded into the new one.
|
|
6949
6954
|
var picture: UIView?
|
|
6955
|
+
/// Commits the change as the run loop finishes the pass it came in.
|
|
6956
|
+
var observer: CFRunLoopObserver?
|
|
6957
|
+
/// The screen as it was, drawn over it until the animation is set up.
|
|
6958
|
+
var cover: UIView?
|
|
6950
6959
|
|
|
6951
6960
|
init(spec: CraftNativeAnimationSpec, reduceMotion: Bool) {
|
|
6952
6961
|
self.spec = spec
|
|
@@ -6956,7 +6965,11 @@ final class CraftNativeAnimationTransaction {
|
|
|
6956
6965
|
|
|
6957
6966
|
/// An animation under way: finished at once if another change comes first.
|
|
6958
6967
|
final class CraftNativeRunningAnimation {
|
|
6968
|
+
/// The Core Animation key every animation of a change goes under.
|
|
6969
|
+
static let key = "craft.change"
|
|
6959
6970
|
var animators: [UIViewPropertyAnimator] = []
|
|
6971
|
+
/// Live layers carrying an animation of the change, under `key` + a suffix.
|
|
6972
|
+
var layers: [(CALayer, String)] = []
|
|
6960
6973
|
var cleanup: [() -> Void] = []
|
|
6961
6974
|
private var done = false
|
|
6962
6975
|
|
|
@@ -6967,6 +6980,10 @@ final class CraftNativeRunningAnimation {
|
|
|
6967
6980
|
animator.stopAnimation(false)
|
|
6968
6981
|
animator.finishAnimation(at: .end)
|
|
6969
6982
|
}
|
|
6983
|
+
// A live view's own values were never touched: taking the animation
|
|
6984
|
+
// off leaves it exactly as the screen last drew it.
|
|
6985
|
+
for (layer, key) in layers { layer.removeAnimation(forKey: key) }
|
|
6986
|
+
layers.removeAll()
|
|
6970
6987
|
cleanup.forEach { $0() }
|
|
6971
6988
|
cleanup.removeAll()
|
|
6972
6989
|
}
|
|
@@ -6991,16 +7008,40 @@ extension CraftNativeScreenController {
|
|
|
6991
7008
|
let transaction = CraftNativeAnimationTransaction(spec: CraftNativeAnimationSpec(payload), reduceMotion: UIAccessibility.isReduceMotionEnabled)
|
|
6992
7009
|
transaction.screenBackground = view.backgroundColor
|
|
6993
7010
|
if transaction.reduceMotion {
|
|
6994
|
-
|
|
7011
|
+
// Held over the screen until the change is in, then faded away.
|
|
7012
|
+
if view.window != nil, let picture = view.snapshotView(afterScreenUpdates: false) {
|
|
7013
|
+
picture.frame = view.bounds
|
|
7014
|
+
picture.isUserInteractionEnabled = false
|
|
7015
|
+
view.addSubview(picture)
|
|
7016
|
+
transaction.picture = picture
|
|
7017
|
+
}
|
|
6995
7018
|
} else {
|
|
6996
7019
|
view.layoutIfNeeded()
|
|
6997
7020
|
recordAnimationState(renderedRoot, into: transaction)
|
|
7021
|
+
// The screen as it is, held over it until the change's animation
|
|
7022
|
+
// is set up: UIKit can draw the new screen before the run loop's
|
|
7023
|
+
// pass ends, which showed it for a frame before it animated.
|
|
7024
|
+
if view.window != nil, let cover = view.snapshotView(afterScreenUpdates: false) {
|
|
7025
|
+
cover.frame = view.bounds
|
|
7026
|
+
cover.isUserInteractionEnabled = false
|
|
7027
|
+
view.addSubview(cover)
|
|
7028
|
+
transaction.cover = cover
|
|
7029
|
+
}
|
|
6998
7030
|
}
|
|
6999
7031
|
animationTransaction = transaction
|
|
7000
7032
|
CraftNativeAnimationClock.duration = transaction.spec.duration
|
|
7001
7033
|
// The change comes from the same turn of the script (the state it
|
|
7002
|
-
// changed renders in a microtask)
|
|
7003
|
-
|
|
7034
|
+
// changed renders in a microtask), so it is set up as the run loop
|
|
7035
|
+
// finishes this pass, before Core Animation draws it: drawn first, the
|
|
7036
|
+
// new screen showed for a frame and then jumped back to animate. A
|
|
7037
|
+
// change that comes later still animates.
|
|
7038
|
+
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.beforeWaiting.rawValue, false, 0) { [weak self] _, _ in
|
|
7039
|
+
self?.commitAnimatedChange()
|
|
7040
|
+
}
|
|
7041
|
+
if let observer {
|
|
7042
|
+
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, .commonModes)
|
|
7043
|
+
transaction.observer = observer
|
|
7044
|
+
}
|
|
7004
7045
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in self?.commitAnimatedChange() }
|
|
7005
7046
|
}
|
|
7006
7047
|
|
|
@@ -7020,6 +7061,13 @@ extension CraftNativeScreenController {
|
|
|
7020
7061
|
private func commitAnimatedChange() {
|
|
7021
7062
|
guard let transaction = animationTransaction else { return }
|
|
7022
7063
|
animationTransaction = nil
|
|
7064
|
+
if let observer = transaction.observer {
|
|
7065
|
+
CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, .commonModes)
|
|
7066
|
+
transaction.observer = nil
|
|
7067
|
+
}
|
|
7068
|
+
// Taken off in the same transaction the animations start in.
|
|
7069
|
+
transaction.cover?.removeFromSuperview()
|
|
7070
|
+
transaction.cover = nil
|
|
7023
7071
|
let spec = transaction.spec
|
|
7024
7072
|
let running = CraftNativeRunningAnimation()
|
|
7025
7073
|
runningAnimation = running
|
|
@@ -7027,9 +7075,6 @@ extension CraftNativeScreenController {
|
|
|
7027
7075
|
|
|
7028
7076
|
if transaction.reduceMotion {
|
|
7029
7077
|
guard let picture = transaction.picture else { running.finish(); return }
|
|
7030
|
-
picture.frame = view.bounds
|
|
7031
|
-
picture.isUserInteractionEnabled = false
|
|
7032
|
-
view.addSubview(picture)
|
|
7033
7078
|
let fade = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { picture.alpha = 0 }
|
|
7034
7079
|
fade.addCompletion { _ in running.finish() }
|
|
7035
7080
|
running.animators.append(fade)
|
|
@@ -7073,26 +7118,69 @@ extension CraftNativeScreenController {
|
|
|
7073
7118
|
view.addSubview(overlay)
|
|
7074
7119
|
running.cleanup.append { overlay.removeFromSuperview() }
|
|
7075
7120
|
|
|
7076
|
-
|
|
7077
|
-
//
|
|
7121
|
+
// Views that are going are the change's own now: they move into the
|
|
7122
|
+
// overlay where they were and fade there. Put back as they were
|
|
7123
|
+
// afterwards, in case the screen shows them again.
|
|
7078
7124
|
let fadeOut = UIViewPropertyAnimator(duration: spec.duration * 0.5, curve: .easeOut)
|
|
7079
|
-
let
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
for record in removedRoots {
|
|
7125
|
+
let quickFade = UIViewPropertyAnimator(duration: spec.duration * 0.35, curve: .easeOut)
|
|
7126
|
+
let travel = UIViewPropertyAnimator(duration: spec.duration, timingParameters: spec.timing)
|
|
7127
|
+
running.animators = [fadeOut, quickFade, travel]
|
|
7128
|
+
func adopt(_ record: CraftNativeAnimationRecord, fading fade: UIViewPropertyAnimator) {
|
|
7084
7129
|
let going = record.view
|
|
7130
|
+
let resizes = going.translatesAutoresizingMaskIntoConstraints
|
|
7131
|
+
let base = going.transform
|
|
7085
7132
|
going.translatesAutoresizingMaskIntoConstraints = true
|
|
7086
7133
|
going.transform = .identity
|
|
7087
7134
|
going.frame = record.frameOnScreen
|
|
7135
|
+
going.alpha = record.alpha
|
|
7088
7136
|
overlay.addSubview(going)
|
|
7089
|
-
|
|
7137
|
+
fade.addAnimations { going.alpha = 0 }
|
|
7138
|
+
running.cleanup.append {
|
|
7139
|
+
going.removeFromSuperview()
|
|
7140
|
+
going.transform = base
|
|
7141
|
+
going.alpha = record.alpha
|
|
7142
|
+
going.translatesAutoresizingMaskIntoConstraints = resizes
|
|
7143
|
+
going.frame = record.frame
|
|
7144
|
+
// Back in a parent that went with it, as it was: a screen
|
|
7145
|
+
// that shows that subtree again finds it whole.
|
|
7146
|
+
if let parent = record.superview, parent.window == nil {
|
|
7147
|
+
if let stack = parent as? UIStackView, let at = record.arrangedIndex {
|
|
7148
|
+
stack.insertArrangedSubview(going, at: min(at, stack.arrangedSubviews.count))
|
|
7149
|
+
} else if let at = record.index {
|
|
7150
|
+
parent.insertSubview(going, at: min(at, parent.subviews.count))
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
for record in removedRoots { adopt(record, fading: fadeOut) }
|
|
7156
|
+
|
|
7157
|
+
// Live views are animated on their layers only, from how they were to
|
|
7158
|
+
// how the screen draws them now: nothing the screen sets on them
|
|
7159
|
+
// meanwhile (the next second on a timer, a style patch) is undone.
|
|
7160
|
+
let fades = spec.duration * 0.6
|
|
7161
|
+
func animate(_ layer: CALayer, _ keyPath: String, from: Any, duration: TimeInterval? = nil, spring: Bool = true) {
|
|
7162
|
+
let animation: CABasicAnimation
|
|
7163
|
+
if spring, duration == nil, spec.curve == "spring" {
|
|
7164
|
+
let bouncy = CASpringAnimation(keyPath: keyPath)
|
|
7165
|
+
bouncy.mass = 1
|
|
7166
|
+
bouncy.stiffness = pow(2 * .pi / spec.duration, 2)
|
|
7167
|
+
bouncy.damping = 2 * sqrt(bouncy.stiffness) * (1 - spec.bounce)
|
|
7168
|
+
bouncy.duration = bouncy.settlingDuration
|
|
7169
|
+
animation = bouncy
|
|
7170
|
+
} else {
|
|
7171
|
+
animation = CABasicAnimation(keyPath: keyPath)
|
|
7172
|
+
animation.duration = duration ?? spec.duration
|
|
7173
|
+
animation.timingFunction = CAMediaTimingFunction(name: spec.curve == "linear" ? .linear : .easeInEaseOut)
|
|
7174
|
+
}
|
|
7175
|
+
animation.fromValue = from
|
|
7176
|
+
let key = "\(CraftNativeRunningAnimation.key).\(keyPath)"
|
|
7177
|
+
layer.add(animation, forKey: key)
|
|
7178
|
+
running.layers.append((layer, key))
|
|
7090
7179
|
}
|
|
7091
7180
|
|
|
7092
|
-
// The background, the screen's own included,
|
|
7181
|
+
// The background, the screen's own included, comes from the old colour.
|
|
7093
7182
|
if let before = transaction.screenBackground, let now = view.backgroundColor, !before.isEqual(now) {
|
|
7094
|
-
view.backgroundColor
|
|
7095
|
-
main.addAnimations { self.view.backgroundColor = now }
|
|
7183
|
+
animate(view.layer, "backgroundColor", from: before.cgColor, spring: false)
|
|
7096
7184
|
}
|
|
7097
7185
|
|
|
7098
7186
|
// Shared elements: the new view starts where the old one was, at its
|
|
@@ -7100,58 +7188,56 @@ extension CraftNativeScreenController {
|
|
|
7100
7188
|
for (old, new) in pairs {
|
|
7101
7189
|
let target = new.superview?.convert(new.frame, to: view) ?? new.frame
|
|
7102
7190
|
let move = morph(from: old.frameOnScreen, to: target, uniform: true)
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
new.
|
|
7106
|
-
|
|
7107
|
-
main.addAnimations { new.transform = base }
|
|
7108
|
-
fadeIn.addAnimations { new.alpha = finalAlpha }
|
|
7191
|
+
animate(new.layer, "transform", from: CATransform3DMakeAffineTransform(new.transform.concatenating(move)))
|
|
7192
|
+
// Crossed quickly, so the two are never both half there for long.
|
|
7193
|
+
animate(new.layer, "opacity", from: 0, duration: spec.duration * 0.35)
|
|
7194
|
+
adopt(old, fading: quickFade)
|
|
7109
7195
|
let going = old.view
|
|
7110
|
-
going.
|
|
7111
|
-
going.transform = .identity
|
|
7112
|
-
going.frame = old.frameOnScreen
|
|
7113
|
-
going.alpha = old.alpha
|
|
7114
|
-
overlay.addSubview(going)
|
|
7115
|
-
main.addAnimations { going.transform = move.inverted() }
|
|
7116
|
-
fadeOut.addAnimations { going.alpha = 0 }
|
|
7117
|
-
running.cleanup.append { new.transform = base; new.alpha = finalAlpha }
|
|
7196
|
+
travel.addAnimations { going.transform = move.inverted() }
|
|
7118
7197
|
}
|
|
7119
7198
|
|
|
7120
7199
|
// New views fade in. A new container holding a shared element keeps it
|
|
7121
7200
|
// in sight: only the rest of it fades, and its colour comes up.
|
|
7201
|
+
func fadeIn(_ added: UIView) {
|
|
7202
|
+
if pairedNew.contains(ObjectIdentifier(added)) { return }
|
|
7203
|
+
guard containsShared(added, pairedNew: pairedNew) else {
|
|
7204
|
+
animate(added.layer, "opacity", from: 0, duration: fades)
|
|
7205
|
+
return
|
|
7206
|
+
}
|
|
7207
|
+
if let colour = added.backgroundColor, colour != .clear {
|
|
7208
|
+
animate(added.layer, "backgroundColor", from: colour.withAlphaComponent(0).cgColor, duration: fades)
|
|
7209
|
+
}
|
|
7210
|
+
added.subviews.forEach(fadeIn)
|
|
7211
|
+
}
|
|
7122
7212
|
for added in inserted where !(added.superview.map { insertedIds.contains(ObjectIdentifier($0)) } ?? false) {
|
|
7123
|
-
|
|
7213
|
+
fadeIn(added)
|
|
7124
7214
|
}
|
|
7125
7215
|
|
|
7126
7216
|
// Views that stay go to their new places and sizes.
|
|
7127
7217
|
for record in records.values where present.contains(ObjectIdentifier(record.view)) {
|
|
7128
7218
|
let stays = record.view
|
|
7129
7219
|
if let label = stays as? UILabel, let before = record.fontSize, abs(before - label.font.pointSize) > 0.5 {
|
|
7130
|
-
let base = label.transform
|
|
7131
7220
|
let target = label.superview?.convert(label.frame, to: view) ?? label.frame
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7221
|
+
let move = morph(from: record.frameOnScreen, to: target, uniform: true)
|
|
7222
|
+
animate(label.layer, "transform", from: CATransform3DMakeAffineTransform(label.transform.concatenating(move)))
|
|
7223
|
+
} else if record.superview === stays.superview, record.frame != stays.frame, stays.transform.isIdentity {
|
|
7224
|
+
animate(stays.layer, "position", from: CGPoint(x: record.frame.midX, y: record.frame.midY))
|
|
7225
|
+
if record.frame.size != stays.frame.size {
|
|
7226
|
+
animate(stays.layer, "bounds.size", from: record.frame.size)
|
|
7227
|
+
}
|
|
7139
7228
|
}
|
|
7140
7229
|
if let before = record.backgroundColor, let now = stays.backgroundColor, !before.isEqual(now) {
|
|
7141
|
-
stays.backgroundColor
|
|
7142
|
-
main.addAnimations { stays.backgroundColor = now }
|
|
7143
|
-
}
|
|
7144
|
-
if let label = stays as? UILabel, let before = record.textColor, !before.isEqual(label.textColor) {
|
|
7145
|
-
let now = label.textColor
|
|
7146
|
-
label.textColor = before
|
|
7147
|
-
UIView.transition(with: label, duration: spec.duration * 0.6, options: [.transitionCrossDissolve, .allowAnimatedContent]) { label.textColor = now }
|
|
7230
|
+
animate(stays.layer, "backgroundColor", from: before.cgColor, spring: false)
|
|
7148
7231
|
}
|
|
7149
7232
|
}
|
|
7150
7233
|
|
|
7151
|
-
|
|
7234
|
+
travel.addCompletion { _ in running.finish() }
|
|
7152
7235
|
fadeOut.startAnimation()
|
|
7153
|
-
|
|
7154
|
-
|
|
7236
|
+
quickFade.startAnimation()
|
|
7237
|
+
travel.startAnimation()
|
|
7238
|
+
// The springs settle a little after `duration`; the views are already
|
|
7239
|
+
// where the screen draws them, so finishing then changes nothing.
|
|
7240
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + spec.duration * 2) { running.finish() }
|
|
7155
7241
|
}
|
|
7156
7242
|
|
|
7157
7243
|
/// The transform that puts a view at `final` where `start` was: moved
|
|
@@ -7166,23 +7252,6 @@ extension CraftNativeScreenController {
|
|
|
7166
7252
|
return CGAffineTransform(translationX: dx, y: dy).scaledBy(x: sx, y: sy)
|
|
7167
7253
|
}
|
|
7168
7254
|
|
|
7169
|
-
private func fadeInInserted(_ view: UIView, pairedNew: Set<ObjectIdentifier>, animator: UIViewPropertyAnimator, running: CraftNativeRunningAnimation) {
|
|
7170
|
-
if pairedNew.contains(ObjectIdentifier(view)) { return }
|
|
7171
|
-
guard containsShared(view, pairedNew: pairedNew) else {
|
|
7172
|
-
let finalAlpha = view.alpha
|
|
7173
|
-
view.alpha = 0
|
|
7174
|
-
animator.addAnimations { view.alpha = finalAlpha }
|
|
7175
|
-
running.cleanup.append { view.alpha = finalAlpha }
|
|
7176
|
-
return
|
|
7177
|
-
}
|
|
7178
|
-
if let colour = view.backgroundColor, colour != .clear {
|
|
7179
|
-
view.backgroundColor = colour.withAlphaComponent(0)
|
|
7180
|
-
animator.addAnimations { view.backgroundColor = colour }
|
|
7181
|
-
running.cleanup.append { view.backgroundColor = colour }
|
|
7182
|
-
}
|
|
7183
|
-
for child in view.subviews { fadeInInserted(child, pairedNew: pairedNew, animator: animator, running: running) }
|
|
7184
|
-
}
|
|
7185
|
-
|
|
7186
7255
|
private func containsShared(_ view: UIView, pairedNew: Set<ObjectIdentifier>) -> Bool {
|
|
7187
7256
|
if pairedNew.contains(ObjectIdentifier(view)) { return true }
|
|
7188
7257
|
return view.subviews.contains { containsShared($0, pairedNew: pairedNew) }
|