From 9786748565bfe58001a47af57e3056d808660dc2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 27 Feb 2022 20:07:08 +0400 Subject: [PATCH] Attachment menu improvements --- submodules/ChatMessageBackground/BUILD | 1 + .../Sources/ChatMessageBackground.swift | 249 +++++++++++++++++ .../Sources/MediaPickerSelectedListNode.swift | 70 ++++- .../Sources/ChatMessageBubbleBackdrop.swift | 255 ------------------ 4 files changed, 314 insertions(+), 261 deletions(-) delete mode 100644 submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift diff --git a/submodules/ChatMessageBackground/BUILD b/submodules/ChatMessageBackground/BUILD index 51dc28d4b1..84fd9a5074 100644 --- a/submodules/ChatMessageBackground/BUILD +++ b/submodules/ChatMessageBackground/BUILD @@ -12,6 +12,7 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", + "//submodules/Postbox:Postbox", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", ], diff --git a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift index 72314a0f33..e800808d51 100644 --- a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift +++ b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import Postbox import TelegramPresentationData import WallpaperBackgroundNode @@ -339,3 +340,251 @@ public final class ChatMessageShadowNode: ASDisplayNode { } } + +private let maskInset: CGFloat = 1.0 + +public func bubbleMaskForType(_ type: ChatMessageBackgroundType, graphics: PrincipalThemeEssentialGraphics) -> UIImage? { + let image: UIImage? + switch type { + case .none: + image = nil + case let .incoming(mergeType): + switch mergeType { + case .None: + image = graphics.chatMessageBackgroundIncomingMaskImage + case let .Top(side): + if side { + image = graphics.chatMessageBackgroundIncomingMergedTopSideMaskImage + } else { + image = graphics.chatMessageBackgroundIncomingMergedTopMaskImage + } + case .Bottom: + image = graphics.chatMessageBackgroundIncomingMergedBottomMaskImage + case .Both: + image = graphics.chatMessageBackgroundIncomingMergedBothMaskImage + case .Side: + image = graphics.chatMessageBackgroundIncomingMergedSideMaskImage + case .Extracted: + image = graphics.chatMessageBackgroundIncomingExtractedMaskImage + } + case let .outgoing(mergeType): + switch mergeType { + case .None: + image = graphics.chatMessageBackgroundOutgoingMaskImage + case let .Top(side): + if side { + image = graphics.chatMessageBackgroundOutgoingMergedTopSideMaskImage + } else { + image = graphics.chatMessageBackgroundOutgoingMergedTopMaskImage + } + case .Bottom: + image = graphics.chatMessageBackgroundOutgoingMergedBottomMaskImage + case .Both: + image = graphics.chatMessageBackgroundOutgoingMergedBothMaskImage + case .Side: + image = graphics.chatMessageBackgroundOutgoingMergedSideMaskImage + case .Extracted: + image = graphics.chatMessageBackgroundOutgoingExtractedMaskImage + } + } + return image +} + +public final class ChatMessageBubbleBackdrop: ASDisplayNode { + private var backgroundContent: WallpaperBubbleBackgroundNode? + + private var currentType: ChatMessageBackgroundType? + private var currentMaskMode: Bool? + private var theme: ChatPresentationThemeData? + private var essentialGraphics: PrincipalThemeEssentialGraphics? + private weak var backgroundNode: WallpaperBackgroundNode? + + private var maskView: UIImageView? + private var fixedMaskMode: Bool? + + private var absolutePosition: (CGRect, CGSize)? + + public var hasImage: Bool { + return self.backgroundContent != nil + } + + public override var frame: CGRect { + didSet { + if let maskView = self.maskView { + let maskFrame = self.bounds.insetBy(dx: -maskInset, dy: -maskInset) + if maskView.frame != maskFrame { + maskView.frame = maskFrame + } + } + if let backgroundContent = self.backgroundContent { + backgroundContent.frame = self.bounds + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + } + } + + public override init() { + super.init() + + self.clipsToBounds = true + } + + public func setMaskMode(_ maskMode: Bool, mediaBox: MediaBox) { + if let currentType = self.currentType, let theme = self.theme, let essentialGraphics = self.essentialGraphics, let backgroundNode = self.backgroundNode { + self.setType(type: currentType, theme: theme, essentialGraphics: essentialGraphics, maskMode: maskMode, backgroundNode: backgroundNode) + } + } + + public func setType(type: ChatMessageBackgroundType, theme: ChatPresentationThemeData, essentialGraphics: PrincipalThemeEssentialGraphics, maskMode inputMaskMode: Bool, backgroundNode: WallpaperBackgroundNode?) { + let maskMode = self.fixedMaskMode ?? inputMaskMode + + if self.currentType != type || self.theme != theme || self.currentMaskMode != maskMode || self.essentialGraphics !== essentialGraphics || self.backgroundNode !== backgroundNode { + let typeUpdated = self.currentType != type || self.theme != theme || self.currentMaskMode != maskMode || self.backgroundNode !== backgroundNode + + self.currentType = type + self.theme = theme + self.essentialGraphics = essentialGraphics + self.backgroundNode = backgroundNode + + if maskMode != self.currentMaskMode { + self.currentMaskMode = maskMode + + if maskMode { + let maskView: UIImageView + if let current = self.maskView { + maskView = current + } else { + maskView = UIImageView() + maskView.frame = self.bounds.insetBy(dx: -maskInset, dy: -maskInset) + self.maskView = maskView + self.view.mask = maskView + } + } else { + if let _ = self.maskView { + self.view.mask = nil + self.maskView = nil + } + } + } + + if let backgroundContent = self.backgroundContent { + backgroundContent.frame = self.bounds + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + + if typeUpdated { + if let backgroundContent = self.backgroundContent { + self.backgroundContent = nil + backgroundContent.removeFromSupernode() + } + + switch type { + case .none: + break + case .incoming: + if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .incoming) { + backgroundContent.frame = self.bounds + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + self.backgroundContent = backgroundContent + self.insertSubnode(backgroundContent, at: 0) + } + case .outgoing: + if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .outgoing) { + backgroundContent.frame = self.bounds + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + self.backgroundContent = backgroundContent + self.insertSubnode(backgroundContent, at: 0) + } + } + } + + if let maskView = self.maskView { + maskView.image = bubbleMaskForType(type, graphics: essentialGraphics) + } + } + } + + public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + self.absolutePosition = (rect, containerSize) + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition) + } + } + + public func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + self.backgroundContent?.offset(value: value, animationCurve: animationCurve, duration: duration) + } + + public func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + self.backgroundContent?.offsetSpring(value: value, duration: duration, damping: damping) + } + + public func updateFrame(_ value: CGRect, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) { + if let maskView = self.maskView { + transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset)) + } + if let backgroundContent = self.backgroundContent { + transition.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height))) + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition) + } + } + transition.updateFrame(node: self, frame: value, completion: { _ in + completion() + }) + } + + public func updateFrame(_ value: CGRect, transition: CombinedTransition, completion: @escaping () -> Void = {}) { + if let maskView = self.maskView { + transition.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset)) + } + if let backgroundContent = self.backgroundContent { + transition.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height))) + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition) + } + } + transition.updateFrame(layer: self.layer, frame: value, completion: { _ in + completion() + }) + } + + public func animateFrom(sourceView: UIView, mediaBox: MediaBox, transition: CombinedTransition) { + if transition.isAnimated { + let previousFrame = self.frame + self.updateFrame(CGRect(origin: CGPoint(x: previousFrame.minX, y: sourceView.frame.minY), size: sourceView.frame.size), transition: .immediate) + self.updateFrame(previousFrame, transition: transition) + + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } + } +} diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 4d62d5bf84..50b5269a08 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -11,6 +11,8 @@ import CheckNode import MosaicLayout import WallpaperBackgroundNode import AccountContext +import ChatMessageBackground + private class MediaPickerSelectedItemNode: ASDisplayNode { let asset: TGMediaAsset @@ -189,12 +191,56 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { } } +private class MessageBackgroundNode: ASDisplayNode { + private let backgroundWallpaperNode: ChatMessageBubbleBackdrop + private let backgroundNode: ChatMessageBackground + private let shadowNode: ChatMessageShadowNode + + override init() { + self.backgroundWallpaperNode = ChatMessageBubbleBackdrop() + self.backgroundNode = ChatMessageBackground() + self.shadowNode = ChatMessageShadowNode() + + super.init() + + self.addSubnode(self.backgroundWallpaperNode) + self.addSubnode(self.backgroundNode) + } + + private var absoluteRect: (CGRect, CGSize)? + + func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode, transition: ContainedViewLayoutTransition) { + + self.backgroundNode.setType(type: .outgoing(.Extracted), highlighted: false, graphics: graphics, maskMode: false, hasWallpaper: wallpaper.hasWallpaper, transition: transition, backgroundNode: wallpaperBackgroundNode) + self.backgroundWallpaperNode.setType(type: .outgoing(.Extracted), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: wallpaperBackgroundNode) + self.shadowNode.setType(type: .outgoing(.Extracted), hasWallpaper: wallpaper.hasWallpaper, graphics: graphics) + + let backgroundFrame = CGRect(origin: CGPoint(), size: size) + self.backgroundNode.updateLayout(size: backgroundFrame.size, transition: transition) + self.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition) + self.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: transition) + + if let (rect, size) = self.absoluteRect { + self.updateAbsoluteRect(rect, within: size) + } + } + + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absoluteRect = (rect, containerSize) + + var backgroundWallpaperFrame = self.backgroundWallpaperNode.frame + backgroundWallpaperFrame.origin.x += rect.minX + backgroundWallpaperFrame.origin.y += rect.minY + self.backgroundWallpaperNode.update(rect: backgroundWallpaperFrame, within: containerSize) + } +} + final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private let context: AccountContext fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode private let scrollNode: ASScrollNode - private var backgroundNodes: [Int: ASImageNode] = [:] + private var backgroundNodes: [Int: MessageBackgroundNode] = [:] private var itemNodes: [String: MediaPickerSelectedItemNode] = [:] private var reorderFeedback: HapticFeedback? @@ -231,7 +277,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI self.scrollNode.view.showsVerticalScrollIndicator = false self.view.addGestureRecognizer(ReorderingGestureRecognizer(shouldBegin: { [weak self] point in - if let strongSelf = self, !strongSelf.scrollNode.view.isDragging { + if let strongSelf = self, !strongSelf.scrollNode.view.isDragging && strongSelf.itemNodes.count > 1 { let point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view) for (_, itemNode) in strongSelf.itemNodes { if itemNode.frame.contains(point) { @@ -292,6 +338,15 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI absoluteRect.origin.y = size.height - absoluteRect.origin.y - absoluteRect.size.height itemNode.updateAbsoluteRect(absoluteRect, within: self.bounds.size) } + + for (_, itemNode) in self.backgroundNodes { + var absoluteRect = itemNode.frame + if let supernode = self.supernode { + absoluteRect = supernode.convert(itemNode.bounds, from: itemNode) + } + absoluteRect.origin.y = size.height - absoluteRect.origin.y - absoluteRect.size.height + itemNode.updateAbsoluteRect(absoluteRect, within: self.bounds.size) + } } private func beginReordering(itemNode: MediaPickerSelectedItemNode) { @@ -486,21 +541,24 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI previewNode.updateFrame(previewNodeFrame, within: size, updateFrame: false) } + let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: wallpaper, bubbleCorners: bubbleCorners) + var groupIndex = 0 for (items, groupSize) in groupLayouts { let groupRect = CGRect(origin: CGPoint(x: insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0), y: insets.top + contentHeight), size: groupSize) - let groupBackgroundNode: ASImageNode + let groupBackgroundNode: MessageBackgroundNode if let current = self.backgroundNodes[groupIndex] { groupBackgroundNode = current } else { - groupBackgroundNode = ASImageNode() + groupBackgroundNode = MessageBackgroundNode() groupBackgroundNode.displaysAsynchronously = false self.backgroundNodes[groupIndex] = groupBackgroundNode self.scrollNode.insertSubnode(groupBackgroundNode, at: 0) } - groupBackgroundNode.image = self.graphics?.chatMessageBackgroundOutgoingExtractedImage - transition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -6.0, dy: -3.0).offsetBy(dx: 3.0, dy: 0.0)) + + transition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0)) + groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: transition) for (item, itemRect, itemPosition) in items { if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift deleted file mode 100644 index 3b7ba324ed..0000000000 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift +++ /dev/null @@ -1,255 +0,0 @@ -import Foundation -import AsyncDisplayKit -import Display -import Postbox -import TelegramPresentationData -import WallpaperBackgroundNode -import ChatMessageBackground - -private let maskInset: CGFloat = 1.0 - -func bubbleMaskForType(_ type: ChatMessageBackgroundType, graphics: PrincipalThemeEssentialGraphics) -> UIImage? { - let image: UIImage? - switch type { - case .none: - image = nil - case let .incoming(mergeType): - switch mergeType { - case .None: - image = graphics.chatMessageBackgroundIncomingMaskImage - case let .Top(side): - if side { - image = graphics.chatMessageBackgroundIncomingMergedTopSideMaskImage - } else { - image = graphics.chatMessageBackgroundIncomingMergedTopMaskImage - } - case .Bottom: - image = graphics.chatMessageBackgroundIncomingMergedBottomMaskImage - case .Both: - image = graphics.chatMessageBackgroundIncomingMergedBothMaskImage - case .Side: - image = graphics.chatMessageBackgroundIncomingMergedSideMaskImage - case .Extracted: - image = graphics.chatMessageBackgroundIncomingExtractedMaskImage - } - case let .outgoing(mergeType): - switch mergeType { - case .None: - image = graphics.chatMessageBackgroundOutgoingMaskImage - case let .Top(side): - if side { - image = graphics.chatMessageBackgroundOutgoingMergedTopSideMaskImage - } else { - image = graphics.chatMessageBackgroundOutgoingMergedTopMaskImage - } - case .Bottom: - image = graphics.chatMessageBackgroundOutgoingMergedBottomMaskImage - case .Both: - image = graphics.chatMessageBackgroundOutgoingMergedBothMaskImage - case .Side: - image = graphics.chatMessageBackgroundOutgoingMergedSideMaskImage - case .Extracted: - image = graphics.chatMessageBackgroundOutgoingExtractedMaskImage - } - } - return image -} - -final class ChatMessageBubbleBackdrop: ASDisplayNode { - private var backgroundContent: WallpaperBubbleBackgroundNode? - - private var currentType: ChatMessageBackgroundType? - private var currentMaskMode: Bool? - private var theme: ChatPresentationThemeData? - private var essentialGraphics: PrincipalThemeEssentialGraphics? - private weak var backgroundNode: WallpaperBackgroundNode? - - private var maskView: UIImageView? - private var fixedMaskMode: Bool? - - private var absolutePosition: (CGRect, CGSize)? - - var hasImage: Bool { - return self.backgroundContent != nil - } - - override var frame: CGRect { - didSet { - if let maskView = self.maskView { - let maskFrame = self.bounds.insetBy(dx: -maskInset, dy: -maskInset) - if maskView.frame != maskFrame { - maskView.frame = maskFrame - } - } - if let backgroundContent = self.backgroundContent { - backgroundContent.frame = self.bounds - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } - } - } - - override init() { - super.init() - - self.clipsToBounds = true - } - - func setMaskMode(_ maskMode: Bool, mediaBox: MediaBox) { - if let currentType = self.currentType, let theme = self.theme, let essentialGraphics = self.essentialGraphics, let backgroundNode = self.backgroundNode { - self.setType(type: currentType, theme: theme, essentialGraphics: essentialGraphics, maskMode: maskMode, backgroundNode: backgroundNode) - } - } - - func setType(type: ChatMessageBackgroundType, theme: ChatPresentationThemeData, essentialGraphics: PrincipalThemeEssentialGraphics, maskMode inputMaskMode: Bool, backgroundNode: WallpaperBackgroundNode?) { - let maskMode = self.fixedMaskMode ?? inputMaskMode - - if self.currentType != type || self.theme != theme || self.currentMaskMode != maskMode || self.essentialGraphics !== essentialGraphics || self.backgroundNode !== backgroundNode { - let typeUpdated = self.currentType != type || self.theme != theme || self.currentMaskMode != maskMode || self.backgroundNode !== backgroundNode - - self.currentType = type - self.theme = theme - self.essentialGraphics = essentialGraphics - self.backgroundNode = backgroundNode - - if maskMode != self.currentMaskMode { - self.currentMaskMode = maskMode - - if maskMode { - let maskView: UIImageView - if let current = self.maskView { - maskView = current - } else { - maskView = UIImageView() - maskView.frame = self.bounds.insetBy(dx: -maskInset, dy: -maskInset) - self.maskView = maskView - self.view.mask = maskView - } - } else { - if let _ = self.maskView { - self.view.mask = nil - self.maskView = nil - } - } - } - - if let backgroundContent = self.backgroundContent { - backgroundContent.frame = self.bounds - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } - - if typeUpdated { - if let backgroundContent = self.backgroundContent { - self.backgroundContent = nil - backgroundContent.removeFromSupernode() - } - - switch type { - case .none: - break - case .incoming: - if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .incoming) { - backgroundContent.frame = self.bounds - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - self.backgroundContent = backgroundContent - self.insertSubnode(backgroundContent, at: 0) - } - case .outgoing: - if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .outgoing) { - backgroundContent.frame = self.bounds - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - self.backgroundContent = backgroundContent - self.insertSubnode(backgroundContent, at: 0) - } - } - } - - if let maskView = self.maskView { - maskView.image = bubbleMaskForType(type, graphics: essentialGraphics) - } - } - } - - func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { - self.absolutePosition = (rect, containerSize) - if let backgroundContent = self.backgroundContent { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition) - } - } - - func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { - self.backgroundContent?.offset(value: value, animationCurve: animationCurve, duration: duration) - } - - func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { - self.backgroundContent?.offsetSpring(value: value, duration: duration, damping: damping) - } - - func updateFrame(_ value: CGRect, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) { - if let maskView = self.maskView { - transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset)) - } - if let backgroundContent = self.backgroundContent { - transition.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height))) - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition) - } - } - transition.updateFrame(node: self, frame: value, completion: { _ in - completion() - }) - } - - func updateFrame(_ value: CGRect, transition: CombinedTransition, completion: @escaping () -> Void = {}) { - if let maskView = self.maskView { - transition.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset)) - } - if let backgroundContent = self.backgroundContent { - transition.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height))) - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition) - } - } - transition.updateFrame(layer: self.layer, frame: value, completion: { _ in - completion() - }) - } - - func animateFrom(sourceView: UIView, mediaBox: MediaBox, transition: CombinedTransition) { - if transition.isAnimated { - let previousFrame = self.frame - self.updateFrame(CGRect(origin: CGPoint(x: previousFrame.minX, y: sourceView.frame.minY), size: sourceView.frame.size), transition: .immediate) - self.updateFrame(previousFrame, transition: transition) - - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) - } - } -}