Skip to content

Commit 4b04a6c

Browse files
author
Isaac
committed
Bounce experiment
1 parent 1ff5608 commit 4b04a6c

File tree

7 files changed

+165
-19
lines changed

7 files changed

+165
-19
lines changed

submodules/Display/Source/CAAnimationUtils.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,9 @@ public extension CALayer {
322322
return animation
323323
}
324324

325-
func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
325+
func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, stiffness: CGFloat = 900.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
326326
let animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping)
327+
animation.stiffness = stiffness
327328
animation.fromValue = from
328329
animation.toValue = to
329330
animation.isRemovedOnCompletion = removeOnCompletion

submodules/Display/Source/ContainedViewLayoutTransition.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ private extension CALayer {
138138
}
139139
}
140140

141+
private func bounceParameters(duration: Double) -> (duration: Double, damping: CGFloat, stiffness: CGFloat) {
142+
return (duration: duration * 1.25, damping: 88.0, stiffness: 750.0)
143+
}
144+
141145
public extension ContainedViewLayoutTransition {
142146
func animation() -> CABasicAnimation? {
143147
switch self {
@@ -469,6 +473,106 @@ public extension ContainedViewLayoutTransition {
469473
}
470474
}
471475

476+
func updatePositionSpring(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
477+
if layer.position.equalTo(position) {
478+
completion?(true)
479+
} else {
480+
switch self {
481+
case .immediate:
482+
layer.removeAnimation(forKey: "position")
483+
if let view = layer.delegate as? UIView {
484+
view.center = position
485+
} else {
486+
layer.position = position
487+
}
488+
if let completion = completion {
489+
completion(true)
490+
}
491+
case let .animated(duration, curve):
492+
let _ = curve
493+
let previousPosition = layer.position
494+
if let view = layer.delegate as? UIView {
495+
view.center = position
496+
} else {
497+
layer.position = position
498+
}
499+
let params = bounceParameters(duration: duration)
500+
layer.animateSpring(from: NSValue(cgPoint: previousPosition), to: NSValue(cgPoint: position), keyPath: "position", duration: params.duration, stiffness: params.stiffness, damping: params.damping, completion: { flag in
501+
if let completion {
502+
completion(flag)
503+
}
504+
})
505+
}
506+
}
507+
}
508+
509+
func updateScaleSpring(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
510+
let t = layer.transform
511+
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
512+
if abs(CGFloat(currentScale) - scale) <= CGFloat(Float.ulpOfOne) {
513+
completion?(true)
514+
} else {
515+
switch self {
516+
case .immediate:
517+
layer.removeAnimation(forKey: "transform.scale")
518+
if let view = layer.delegate as? UIView {
519+
view.transform = CGAffineTransformMakeScale(scale, scale)
520+
} else {
521+
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
522+
}
523+
if let completion = completion {
524+
completion(true)
525+
}
526+
case let .animated(duration, curve):
527+
let _ = curve
528+
if let view = layer.delegate as? UIView {
529+
view.transform = CGAffineTransformMakeScale(scale, scale)
530+
} else {
531+
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
532+
}
533+
let params = bounceParameters(duration: duration)
534+
layer.animateSpring(from: currentScale as NSNumber, to: scale as NSNumber, keyPath: "transform.scale", duration: params.duration, stiffness: params.stiffness, damping: params.damping, completion: { flag in
535+
if let completion {
536+
completion(flag)
537+
}
538+
})
539+
}
540+
}
541+
}
542+
543+
func updateBoundsSpring(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)? = nil) {
544+
if layer.bounds.equalTo(bounds) {
545+
completion?(true)
546+
} else {
547+
switch self {
548+
case .immediate:
549+
layer.removeAnimation(forKey: "bounds")
550+
if let view = layer.delegate as? UIView {
551+
view.bounds = bounds
552+
} else {
553+
layer.bounds = bounds
554+
}
555+
if let completion = completion {
556+
completion(true)
557+
}
558+
case let .animated(duration, curve):
559+
let _ = curve
560+
let previousBounds = layer.bounds
561+
if let view = layer.delegate as? UIView {
562+
view.bounds = bounds
563+
} else {
564+
layer.bounds = bounds
565+
}
566+
let params = bounceParameters(duration: duration)
567+
layer.animateSpring(from: NSValue(cgRect: previousBounds), to: NSValue(cgRect: bounds), keyPath: "bounds", duration: params.duration, stiffness: params.stiffness, damping: params.damping, completion: { result in
568+
if let completion = completion {
569+
completion(result)
570+
}
571+
})
572+
}
573+
}
574+
}
575+
472576
func updateAnchorPoint(layer: CALayer, anchorPoint: CGPoint, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
473577
if layer.anchorPoint.equalTo(anchorPoint) && !force {
474578
completion?(true)

submodules/Display/Source/UIKitUtils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public func makeSpringAnimation(_ keyPath: String, duration: Double) -> CABasicA
1212
return makeSpringAnimationImpl(keyPath, duration)
1313
}
1414

15-
public func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CABasicAnimation {
15+
public func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CASpringAnimation {
1616
return makeSpringBounceAnimationImpl(keyPath, initialVelocity, damping)
1717
}
1818

submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
288288
private var rightSlowModeInset: CGFloat = 0.0
289289
private var currentTextInputBackgroundWidthOffset: CGFloat = 0.0
290290

291+
private var enableBounceAnimations: Bool = false
292+
291293
public var displayAttachmentMenu: () -> Void = { }
292294
public var sendMessage: () -> Void = { }
293295
public var paste: (ChatTextInputPanelPasteData) -> Void = { _ in }
@@ -320,6 +322,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
320322

321323
private let hapticFeedback = HapticFeedback()
322324

325+
private var currentInputHasText: Bool = false
326+
323327
public var inputTextState: ChatTextInputState {
324328
if let textInputNode = self.textInputNode {
325329
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
@@ -636,6 +640,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
636640

637641
self.context = context
638642

643+
self.enableBounceAnimations = true
644+
if let data = context.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_input_bounce"] != nil {
645+
self.enableBounceAnimations = false
646+
}
647+
639648
self.addSubnode(self.clippingNode)
640649

641650
self.sendAsAvatarContainerNode.activated = { [weak self] gesture, _ in
@@ -1416,6 +1425,16 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
14161425
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
14171426
inputHasText = true
14181427
}
1428+
let inputHadText = self.currentInputHasText
1429+
self.currentInputHasText = inputHasText
1430+
1431+
var useBounceAnimation = inputHasText && !inputHadText
1432+
if accessoryPanel != nil || self.accessoryPanel != nil {
1433+
useBounceAnimation = false
1434+
}
1435+
if !self.enableBounceAnimations {
1436+
useBounceAnimation = false
1437+
}
14191438

14201439
var hasMenuButton = false
14211440
var menuButtonExpanded = false
@@ -1957,7 +1976,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
19571976
}
19581977
menuButtonTitleTransition.updateFrame(node: self.menuButtonTextNode, frame: CGRect(origin: CGPoint(x: 16.0, y: 11.0), size: menuTextSize))
19591978
transition.updateAlpha(node: self.menuButtonTextNode, alpha: menuButtonExpanded ? 1.0 : 0.0)
1960-
transition.updateFrame(node: self.menuButtonIconNode, frame: CGRect(x: 5.0, y: isSendAsButton ? 5.0 : (5.0 - UIScreenPixel), width: 30.0, height: 30.0))
1979+
transition.updateFrame(node: self.menuButtonIconNode, frame: CGRect(x: 7.0, y: 7.0, width: 26.0, height: 26.0))
19611980

19621981
transition.updateFrame(node: self.sendAsAvatarButtonNode, frame: menuButtonFrame)
19631982
transition.updateFrame(node: self.sendAsAvatarContainerNode, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
@@ -2384,7 +2403,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
23842403
transition.updateFrame(view: self.accessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size))
23852404
transition.updateFrame(view: self.textInputContainerBackgroundView, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size))
23862405

2387-
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
2406+
var textInputContainerBackgroundTransition = ComponentTransition(transition)
2407+
if useBounceAnimation, case let .animated(_, curve) = transition, case .spring = curve {
2408+
textInputContainerBackgroundTransition = textInputContainerBackgroundTransition.withUserData(GlassBackgroundView.TransitionFlagBounce())
2409+
}
2410+
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: textInputContainerBackgroundTransition)
23882411

23892412
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputContainerBackgroundFrame)
23902413
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
@@ -2626,13 +2649,22 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
26262649
}
26272650

26282651
var sendActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX - sendActionButtonsSize.width, y: textInputContainerBackgroundFrame.maxY - sendActionButtonsSize.height), size: sendActionButtonsSize)
2652+
2653+
let sendActionsScale: CGFloat
26292654
if inputHasText || hasMediaDraft || hasForward {
2630-
transition.updateTransformScale(node: self.sendActionButtons, scale: CGPoint(x: 1.0, y: 1.0))
2655+
sendActionsScale = 1.0
26312656
} else {
2657+
sendActionsScale = 0.001
26322658
sendActionButtonsFrame.origin.x += (sendActionButtonsSize.width - 3.0 * 2.0) * 0.5 - 3.0
2633-
transition.updateTransformScale(node: self.sendActionButtons, scale: CGPoint(x: 0.001, y: 0.001))
26342659
}
2635-
transition.updatePosition(node: self.sendActionButtons, position: sendActionButtonsFrame.center)
2660+
2661+
if useBounceAnimation, case let .animated(duration, curve) = transition, case .spring = curve {
2662+
ContainedViewLayoutTransition.animated(duration: duration, curve: curve).updateScaleSpring(layer: self.sendActionButtons.layer, scale: sendActionsScale)
2663+
ContainedViewLayoutTransition.animated(duration: duration, curve: curve).updatePositionSpring(layer: self.sendActionButtons.layer, position: sendActionButtonsFrame.center)
2664+
} else {
2665+
transition.updateTransformScale(node: self.sendActionButtons, scale: CGPoint(x: sendActionsScale, y: sendActionsScale))
2666+
transition.updatePosition(node: self.sendActionButtons, position: sendActionButtonsFrame.center)
2667+
}
26362668
transition.updateBounds(node: self.sendActionButtons, bounds: CGRect(origin: CGPoint(), size: sendActionButtonsFrame.size))
26372669
if let (rect, containerSize) = self.absoluteRect {
26382670
self.sendActionButtons.updateAbsoluteRect(CGRect(x: rect.origin.x + sendActionButtonsFrame.origin.x, y: rect.origin.y + sendActionButtonsFrame.origin.y, width: sendActionButtonsFrame.width, height: sendActionButtonsFrame.height), within: containerSize, transition: transition)
@@ -3806,7 +3838,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
38063838
if self.sendActionButtons.sendContainerNode.alpha.isZero && self.rightSlowModeInset.isZero {
38073839
alphaTransition.updateAlpha(node: self.sendActionButtons.sendContainerNode, alpha: 1.0)
38083840
blurTransitionIn.animateBlur(layer: self.sendActionButtons.sendContainerNode.layer, fromRadius: sendButtonBlurOut, toRadius: 0.0)
3809-
transition.animatePositionAdditive(layer: self.sendActionButtons.sendButton.imageNode.layer, offset: CGPoint(x: -18.0, y: 14.0))
3841+
transition.animatePositionAdditive(layer: self.sendActionButtons.sendButton.imageNode.layer, offset: CGPoint(x: -22.0, y: 18.0))
38103842
if let sendButtonRadialStatusNode = self.sendActionButtons.sendButtonRadialStatusNode {
38113843
alphaTransition.updateAlpha(node: sendButtonRadialStatusNode, alpha: 1.0)
38123844
}

submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ private final class ContentContainer: UIView {
4848
}
4949

5050
public class GlassBackgroundView: UIView {
51+
public final class TransitionFlagBounce {
52+
public init() {
53+
}
54+
}
55+
5156
public protocol ContentView: UIView {
5257
var tintMask: UIView { get }
5358
}
@@ -377,23 +382,27 @@ public class GlassBackgroundView: UIView {
377382

378383
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, transition: ComponentTransition) {
379384
if let nativeContainerView = self.nativeContainerView, let nativeView = self.nativeView, nativeView.bounds.size != size {
380-
//let previousFrame = nativeView.frame
381385

382386
if transition.animation.isImmediate {
383387
nativeView.layer.cornerRadius = cornerRadius
384388
nativeView.frame = CGRect(origin: CGPoint(), size: size)
385389
nativeContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0)))
386390
} else {
387-
/*transition.containedViewLayoutTransition.animateView {
388-
nativeView.layer.cornerRadius = cornerRadius
389-
nativeView.frame = CGRect(origin: CGPoint(), size: size)
390-
nativeContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0)))
391-
}*/
392391
nativeView.layer.cornerRadius = cornerRadius
393-
transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size))
394-
transition.setFrame(view: nativeContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0))))
395392

396-
//nativeView.layer.animateFrame(from: previousFrame, to: CGRect(origin: CGPoint(), size: size), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
393+
let nativeFrame = CGRect(origin: CGPoint(), size: size)
394+
let nativeContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0)))
395+
396+
if transition.userData(TransitionFlagBounce.self) != nil {
397+
transition.containedViewLayoutTransition.updatePositionSpring(layer: nativeView.layer, position: nativeFrame.center)
398+
transition.containedViewLayoutTransition.updateBoundsSpring(layer: nativeView.layer, bounds: CGRect(origin: CGPoint(), size: nativeFrame.size))
399+
400+
transition.containedViewLayoutTransition.updatePositionSpring(layer: nativeContainerView.layer, position: nativeContainerFrame.center)
401+
transition.containedViewLayoutTransition.updateBoundsSpring(layer: nativeContainerView.layer, bounds: CGRect(origin: CGPoint(), size: nativeContainerFrame.size))
402+
} else {
403+
transition.setFrame(view: nativeView, frame: nativeFrame)
404+
transition.setFrame(view: nativeContainerView, frame: nativeContainerFrame)
405+
}
397406
}
398407
}
399408
if let backgroundNode = self.backgroundNode {

submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ double animationDurationFactorImpl();
55

66
CABasicAnimation * _Nonnull makeSpringAnimationImpl(NSString * _Nonnull keyPath, double duration);
77
CABasicAnimation * _Nonnull make26SpringAnimationImpl(NSString * _Nonnull keyPath, double duration);
8-
CABasicAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping);
8+
CASpringAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping);
99
CGFloat springAnimationValueAtImpl(CABasicAnimation * _Nonnull animation, CGFloat t);
1010

1111
UIBlurEffect * _Nonnull makeCustomZoomBlurEffectImpl(bool isLight);

submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ - (CGFloat)valueAt:(CGFloat)t {
8383
return springAnimation;
8484
}
8585

86-
CABasicAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) {
86+
CASpringAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) {
8787
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath];
8888
springAnimation.mass = 5.0f;
8989
springAnimation.stiffness = 900.0f;

0 commit comments

Comments
 (0)