diff --git a/submodules/AccountContext/BUILD b/submodules/AccountContext/BUILD index 19689e50b1..fb2ccfc448 100644 --- a/submodules/AccountContext/BUILD +++ b/submodules/AccountContext/BUILD @@ -20,7 +20,6 @@ swift_library( "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", - "//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/Utils/RangeSet:RangeSet", "//submodules/InAppPurchaseManager:InAppPurchaseManager", "//submodules/TextFormat:TextFormat", diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 0869ada5a6..49f3d885c0 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -10,7 +10,6 @@ import AsyncDisplayKit import Display import DeviceLocationManager import TemporaryCachedPeerDataManager -import MeshAnimationCache import InAppPurchaseManager import AnimationCache import MultiAnimationRenderer @@ -935,7 +934,6 @@ public protocol AccountContext: AnyObject { var currentCountriesConfiguration: Atomic { get } var cachedGroupCallContexts: AccountGroupCallContextCache { get } - var meshAnimationCache: MeshAnimationCache { get } var animationCache: AnimationCache { get } var animationRenderer: MultiAnimationRenderer { get } diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index 51debe0e34..3281f18dee 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -95,8 +95,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation } } -public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? { - func extractFileMedia(_ message: Message) -> TelegramMediaFile? { +public func peerMessageMediaPlayerType(_ message: EngineMessage) -> MediaManagerPlayerType? { + func extractFileMedia(_ message: EngineMessage) -> TelegramMediaFile? { var file: TelegramMediaFile? for media in message.media { if let media = media as? TelegramMediaFile { @@ -120,7 +120,7 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer return nil } -public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { +public func peerMessagesMediaPlaylistAndItemId(_ message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { if isGlobalSearch && !isDownloadList { return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } else if isRecentActions && !isDownloadList { diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index c293f8802c..d07300b31f 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -371,7 +370,7 @@ private final class DayComponent: Component { private var currentSelection: DaySelection? private(set) var timestamp: Int32? - private(set) var index: MessageIndex? + private(set) var index: EngineMessage.Index? private var isHighlightingEnabled: Bool = false init() { @@ -983,12 +982,12 @@ public final class CalendarMessageScreen: ViewController { private weak var controller: CalendarMessageScreen? private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let initialTimestamp: Int32 private let enableMessageRangeDeletion: Bool private let canNavigateToEmptyDays: Bool private let navigateToOffset: (Int, Int32) -> Void - private let previewDay: (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + private let previewDay: (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void private var presentationData: PresentationData private var scrollView: Scroller @@ -1019,13 +1018,13 @@ public final class CalendarMessageScreen: ViewController { init( controller: CalendarMessageScreen, context: AccountContext, - peerId: PeerId, + peerId: EnginePeer.Id, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, enableMessageRangeDeletion: Bool, canNavigateToEmptyDays: Bool, navigateToOffset: @escaping (Int, Int32) -> Void, - previewDay: @escaping (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + previewDay: @escaping (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void ) { self.controller = controller self.context = context @@ -1783,9 +1782,9 @@ public final class CalendarMessageScreen: ViewController { guard let calendarState = self.calendarState else { return } - var messageMap: [Message] = [] + var messageMap: [EngineMessage] = [] for (_, entry) in calendarState.messagesByDay { - messageMap.append(entry.message) + messageMap.append(EngineMessage(entry.message)) } var updatedMedia: [Int: [Int: DayMedia]] = [:] @@ -1805,7 +1804,7 @@ public final class CalendarMessageScreen: ViewController { mediaLoop: for media in message.media { switch media { case _ as TelegramMediaImage, _ as TelegramMediaFile: - updatedMedia[i]![day] = DayMedia(message: EngineMessage(message), media: EngineMedia(media)) + updatedMedia[i]![day] = DayMedia(message: message, media: EngineMedia(media)) break mediaLoop default: break @@ -1830,13 +1829,13 @@ public final class CalendarMessageScreen: ViewController { } private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let calendarSource: SparseMessageCalendar private let initialTimestamp: Int32 private let enableMessageRangeDeletion: Bool private let canNavigateToEmptyDays: Bool private let navigateToDay: (CalendarMessageScreen, Int, Int32) -> Void - private let previewDay: (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + private let previewDay: (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void private var presentationData: PresentationData @@ -1844,13 +1843,13 @@ public final class CalendarMessageScreen: ViewController { public init( context: AccountContext, - peerId: PeerId, + peerId: EnginePeer.Id, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, enableMessageRangeDeletion: Bool, canNavigateToEmptyDays: Bool, navigateToDay: @escaping (CalendarMessageScreen, Int, Int32) -> Void, - previewDay: @escaping (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + previewDay: @escaping (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void ) { self.context = context self.peerId = peerId diff --git a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift index 1761a2d008..eaa0290715 100644 --- a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift +++ b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift @@ -3,7 +3,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import PresentationDataUtils @@ -17,6 +16,26 @@ import ConfettiEffect import TelegramUniversalVideoContent import SolidRoundedButtonNode +private func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? { + if useTotalFileAllocatedSize { + let url = URL(fileURLWithPath: path) + if let values = (try? url.resourceValues(forKeys: Set([.isRegularFileKey, .totalFileAllocatedSizeKey]))) { + if values.isRegularFile ?? false { + if let fileSize = values.totalFileAllocatedSize { + return Int64(fileSize) + } + } + } + } + + var value = stat() + if stat(path, &value) == 0 { + return value.st_size + } else { + return nil + } +} + private final class ProgressEstimator { private var averageProgressPerSecond: Double = 0.0 private var lastMeasurement: (Double, Float)? @@ -91,7 +110,7 @@ private final class ImportManager { return self.statePromise.get() } - init(account: Account, peerId: PeerId, mainFile: TempBoxFile, archivePath: String?, entries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { + init(account: Account, peerId: EnginePeer.Id, mainFile: EngineTempBox.File, archivePath: String?, entries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { self.account = account self.archivePath = archivePath self.entries = entries @@ -234,8 +253,8 @@ private final class ImportManager { Logger.shared.log("ChatImportScreen", "updateState take pending entry \(entry.1)") - let unpackedFile = Signal { subscriber in - let tempFile = TempBox.shared.tempFile(fileName: entry.0.path) + let unpackedFile = Signal { subscriber in + let tempFile = EngineTempBox.shared.tempFile(fileName: entry.0.path) Logger.shared.log("ChatImportScreen", "Extracting \(entry.0.path) to \(tempFile.path)...") let startTime = CACurrentMediaTime() if SSZipArchive.extractFileFromArchive(atPath: archivePath, filePath: entry.0.path, toPath: tempFile.path) { @@ -440,9 +459,9 @@ public final class ChatImportActivityScreen: ViewController { if let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) - let videoContent = NativeVideoContent(id: .message(1, MediaId(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) + let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) videoNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)) @@ -724,9 +743,9 @@ public final class ChatImportActivityScreen: ViewController { private let context: AccountContext private var presentationData: PresentationData fileprivate let cancel: () -> Void - fileprivate var peerId: PeerId + fileprivate var peerId: EnginePeer.Id private let archivePath: String? - private let mainEntry: TempBoxFile + private let mainEntry: EngineTempBox.File private let totalBytes: Int64 private let totalMediaBytes: Int64 private let otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)] @@ -746,7 +765,7 @@ public final class ChatImportActivityScreen: ViewController { } } - public init(context: AccountContext, cancel: @escaping () -> Void, peerId: PeerId, archivePath: String?, mainEntry: TempBoxFile, otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { + public init(context: AccountContext, cancel: @escaping () -> Void, peerId: EnginePeer.Id, archivePath: String?, mainEntry: EngineTempBox.File, otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { self.context = context self.cancel = cancel self.peerId = peerId @@ -818,7 +837,7 @@ public final class ChatImportActivityScreen: ViewController { self.progressEstimator = ProgressEstimator() self.beganCompletion = false - let resolvedPeerId: Signal + let resolvedPeerId: Signal if self.peerId.namespace == Namespaces.Peer.CloudGroup { resolvedPeerId = self.context.engine.peers.convertGroupToSupergroup(peerId: self.peerId) |> mapError { _ -> ImportManager.ImportError in diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 0b888606a1..5d69ea440b 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import PresentationDataUtils @@ -30,8 +29,8 @@ private final class ChatListFilterPresetControllerArguments { let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void let openAddIncludePeer: () -> Void let openAddExcludePeer: () -> Void - let deleteIncludePeer: (PeerId) -> Void - let deleteExcludePeer: (PeerId) -> Void + let deleteIncludePeer: (EnginePeer.Id) -> Void + let deleteExcludePeer: (EnginePeer.Id) -> Void let setItemIdWithRevealedOptions: (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void let deleteIncludeCategory: (ChatListFilterIncludeCategory) -> Void let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void @@ -49,8 +48,8 @@ private final class ChatListFilterPresetControllerArguments { updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, openAddIncludePeer: @escaping () -> Void, openAddExcludePeer: @escaping () -> Void, - deleteIncludePeer: @escaping (PeerId) -> Void, - deleteExcludePeer: @escaping (PeerId) -> Void, + deleteIncludePeer: @escaping (EnginePeer.Id) -> Void, + deleteExcludePeer: @escaping (EnginePeer.Id) -> Void, setItemIdWithRevealedOptions: @escaping (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void, deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void, deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void, @@ -93,7 +92,7 @@ private enum ChatListFilterPresetControllerSection: Int32 { private enum ChatListFilterPresetEntryStableId: Hashable { case index(Int) - case peer(PeerId) + case peer(EnginePeer.Id) case includePeerInfo case excludePeerInfo case includeCategory(ChatListFilterIncludeCategory) @@ -311,7 +310,7 @@ private extension ChatListFilterCategoryIcon { } private enum ChatListFilterRevealedItemId: Equatable { - case peer(PeerId) + case peer(EnginePeer.Id) case includeCategory(ChatListFilterIncludeCategory) case excludeCategory(ChatListFilterExcludeCategory) } @@ -573,8 +572,8 @@ private struct ChatListFilterPresetControllerState: Equatable { var excludeMuted: Bool var excludeRead: Bool var excludeArchived: Bool - var additionallyIncludePeers: [PeerId] - var additionallyExcludePeers: [PeerId] + var additionallyIncludePeers: [EnginePeer.Id] + var additionallyExcludePeers: [EnginePeer.Id] var revealedItemId: ChatListFilterRevealedItemId? var expandedSections: Set @@ -825,7 +824,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f return } - var includePeers: [PeerId] = [] + var includePeers: [EnginePeer.Id] = [] for peerId in peerIds { switch peerId { case let .peer(id): @@ -838,7 +837,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f if filter.id > 1, case let .filter(_, _, _, data) = filter, data.hasSharedLinks { let newPeers = includePeers.filter({ !(filter.data?.includePeers.peers.contains($0) ?? false) }) - var removedPeers: [PeerId] = [] + var removedPeers: [EnginePeer.Id] = [] if let data = filter.data { removedPeers = data.includePeers.peers.filter({ !includePeers.contains($0) }) } @@ -951,7 +950,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex return } - var excludePeers: [PeerId] = [] + var excludePeers: [EnginePeer.Id] = [] for peerId in peerIds { switch peerId { case let .peer(id): @@ -1144,7 +1143,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi sharedLinks.set(Signal<[ExportedChatFolderLink]?, NoError>.single(nil) |> then(context.engine.peers.getExportedChatFolderLinks(id: initialPreset.id))) } - let currentPeers = Atomic<[PeerId: EngineRenderedPeer]>(value: [:]) + let currentPeers = Atomic<[EnginePeer.Id: EngineRenderedPeer]>(value: [:]) let stateWithPeers = statePromise.get() |> mapToSignal { state -> Signal<(ChatListFilterPresetControllerState, [EngineRenderedPeer], [EngineRenderedPeer]), NoError> in let currentPeersValue = currentPeers.with { $0 } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index e658314834..4a6d7ce254 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -39,7 +38,7 @@ private enum ChatListFilterPresetListSection: Int32 { case list } -private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings: PresentationStrings) -> String { +private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String { if peers.isEmpty { return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder } else { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index 2050206093..609c9ae333 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index b0d1b6d8a6..92af4bf216 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabInlineContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabInlineContainerNode.swift deleted file mode 100644 index 38f9888047..0000000000 --- a/submodules/ChatListUI/Sources/ChatListFilterTabInlineContainerNode.swift +++ /dev/null @@ -1,1028 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import Postbox -import TelegramCore -import TelegramPresentationData - -private final class ItemNodeDeleteButtonNode: HighlightableButtonNode { - private let pressed: () -> Void - - private let contentImageNode: ASImageNode - - private var theme: PresentationTheme? - - init(pressed: @escaping () -> Void) { - self.pressed = pressed - - self.contentImageNode = ASImageNode() - - super.init() - - self.addSubnode(self.contentImageNode) - - self.addTarget(self, action: #selector(self.pressedEvent), forControlEvents: .touchUpInside) - } - - @objc private func pressedEvent() { - self.pressed() - } - - func update(theme: PresentationTheme) -> CGSize { - let size = CGSize(width: 18.0, height: 18.0) - if self.theme !== theme { - self.theme = theme - self.contentImageNode.image = generateImage(size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor(rgb: 0xbbbbbb).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(UIColor(rgb: 0xffffff).cgColor) - context.setLineWidth(1.5) - context.setLineCap(.round) - context.move(to: CGPoint(x: 6.38, y: 6.38)) - context.addLine(to: CGPoint(x: 11.63, y: 11.63)) - context.strokePath() - context.move(to: CGPoint(x: 6.38, y: 11.63)) - context.addLine(to: CGPoint(x: 11.63, y: 6.38)) - context.strokePath() - }) - } - - self.contentImageNode.frame = CGRect(origin: CGPoint(), size: size) - - return size - } -} - -private final class ItemNode: ASDisplayNode { - private let pressed: () -> Void - private let requestedDeletion: () -> Void - - private let extractedContainerNode: ContextExtractedContentContainingNode - private let containerNode: ContextControllerSourceNode - - private let extractedBackgroundNode: ASImageNode - private let titleNode: ImmediateTextNode - private let shortTitleNode: ImmediateTextNode - private let badgeContainerNode: ASDisplayNode - private let badgeTextNode: ImmediateTextNode - private let badgeBackgroundActiveNode: ASImageNode - private let badgeBackgroundInactiveNode: ASImageNode - - private var deleteButtonNode: ItemNodeDeleteButtonNode? - private let buttonNode: HighlightTrackingButtonNode - - private var isSelected: Bool = false - private(set) var unreadCount: Int = 0 - - private var isReordering: Bool = false - - private var theme: PresentationTheme? - - init(pressed: @escaping () -> Void, requestedDeletion: @escaping () -> Void, contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) { - self.pressed = pressed - self.requestedDeletion = requestedDeletion - - self.extractedContainerNode = ContextExtractedContentContainingNode() - self.containerNode = ContextControllerSourceNode() - - self.extractedBackgroundNode = ASImageNode() - self.extractedBackgroundNode.alpha = 0.0 - - let titleInset: CGFloat = 4.0 - - self.titleNode = ImmediateTextNode() - self.titleNode.displaysAsynchronously = false - self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) - - self.shortTitleNode = ImmediateTextNode() - self.shortTitleNode.displaysAsynchronously = false - self.shortTitleNode.alpha = 0.0 - self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) - - self.badgeContainerNode = ASDisplayNode() - - self.badgeTextNode = ImmediateTextNode() - self.badgeTextNode.displaysAsynchronously = false - - self.badgeBackgroundActiveNode = ASImageNode() - self.badgeBackgroundActiveNode.displaysAsynchronously = false - self.badgeBackgroundActiveNode.displayWithoutProcessing = true - - self.badgeBackgroundInactiveNode = ASImageNode() - self.badgeBackgroundInactiveNode.displaysAsynchronously = false - self.badgeBackgroundInactiveNode.displayWithoutProcessing = true - self.badgeBackgroundInactiveNode.isHidden = true - - self.buttonNode = HighlightTrackingButtonNode() - - super.init() - - self.extractedContainerNode.contentNode.addSubnode(self.extractedBackgroundNode) - self.extractedContainerNode.contentNode.addSubnode(self.titleNode) - self.extractedContainerNode.contentNode.addSubnode(self.shortTitleNode) - self.badgeContainerNode.addSubnode(self.badgeBackgroundActiveNode) - self.badgeContainerNode.addSubnode(self.badgeBackgroundInactiveNode) - self.badgeContainerNode.addSubnode(self.badgeTextNode) - self.extractedContainerNode.contentNode.addSubnode(self.badgeContainerNode) - self.extractedContainerNode.contentNode.addSubnode(self.buttonNode) - - self.containerNode.addSubnode(self.extractedContainerNode) - self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode - self.addSubnode(self.containerNode) - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - - self.containerNode.activated = { [weak self] gesture, _ in - guard let strongSelf = self else { - return - } - contextGesture(strongSelf.extractedContainerNode, gesture) - } - - self.extractedContainerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in - guard let strongSelf = self else { - return - } - - if isExtracted { - strongSelf.extractedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 32.0, color: strongSelf.isSelected ? UIColor(rgb: 0xbbbbbb) : UIColor(rgb: 0xf1f1f1)) - } - transition.updateAlpha(node: strongSelf.extractedBackgroundNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in - if !isExtracted { - self?.extractedBackgroundNode.image = nil - } - }) - } - } - - @objc private func buttonPressed() { - self.pressed() - } - - func updateText(title: String, shortTitle: String, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, isSelected: Bool, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { - if self.theme !== presentationData.theme { - self.theme = presentationData.theme - - self.badgeBackgroundActiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeActiveBackgroundColor) - self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor) - } - - self.containerNode.isGestureEnabled = !isEditing && !isReordering - self.buttonNode.isUserInteractionEnabled = !isEditing && !isReordering - - self.isSelected = isSelected - self.unreadCount = unreadCount - - transition.updateAlpha(node: self.containerNode, alpha: isReordering && isAllChats ? 0.5 : 1.0) - - if isReordering && !isAllChats { - if self.deleteButtonNode == nil { - let deleteButtonNode = ItemNodeDeleteButtonNode(pressed: { [weak self] in - self?.requestedDeletion() - }) - self.extractedContainerNode.contentNode.addSubnode(deleteButtonNode) - self.deleteButtonNode = deleteButtonNode - if case .animated = transition { - deleteButtonNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.25) - deleteButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - } - } else if let deleteButtonNode = self.deleteButtonNode { - self.deleteButtonNode = nil - transition.updateTransformScale(node: deleteButtonNode, scale: 0.1) - transition.updateAlpha(node: deleteButtonNode, alpha: 0.0, completion: { [weak deleteButtonNode] _ in - deleteButtonNode?.removeFromSupernode() - }) - } - - transition.updateAlpha(node: self.badgeContainerNode, alpha: (isReordering || unreadCount == 0) ? 0.0 : 1.0) - - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: isSelected ? presentationData.theme.contextMenu.badgeForegroundColor : presentationData.theme.list.itemSecondaryTextColor) - self.shortTitleNode.attributedText = NSAttributedString(string: shortTitle, font: Font.bold(17.0), textColor: isSelected ? presentationData.theme.contextMenu.badgeForegroundColor : presentationData.theme.list.itemSecondaryTextColor) - if unreadCount != 0 { - self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - self.badgeBackgroundActiveNode.isHidden = !isSelected && !unreadHasUnmuted - self.badgeBackgroundInactiveNode.isHidden = isSelected || unreadHasUnmuted - } - - if self.isReordering != isReordering { - self.isReordering = isReordering - if self.isReordering && !isAllChats { - self.startShaking() - } else { - self.layer.removeAnimation(forKey: "shaking_position") - self.layer.removeAnimation(forKey: "shaking_rotation") - } - } - } - - func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> (width: CGFloat, shortWidth: CGFloat) { - let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) - self.titleNode.frame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - - let shortTitleSize = self.shortTitleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) - self.shortTitleNode.frame = CGRect(origin: CGPoint(x: -self.shortTitleNode.insets.left, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize) - - if let deleteButtonNode = self.deleteButtonNode { - if let theme = self.theme { - let deleteButtonSize = deleteButtonNode.update(theme: theme) - deleteButtonNode.frame = CGRect(origin: CGPoint(x: -deleteButtonSize.width + 7.0, y: 5.0), size: deleteButtonSize) - } - } - - let badgeSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) - let badgeInset: CGFloat = 4.0 - let badgeBackgroundFrame = CGRect(origin: CGPoint(x: titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + 5.0, y: floor((height - 18.0) / 2.0)), size: CGSize(width: max(18.0, badgeSize.width + badgeInset * 2.0), height: 18.0)) - self.badgeContainerNode.frame = badgeBackgroundFrame - self.badgeBackgroundActiveNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size) - self.badgeBackgroundInactiveNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size) - self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((badgeBackgroundFrame.width - badgeSize.width) / 2.0), y: floor((badgeBackgroundFrame.height - badgeSize.height) / 2.0)), size: badgeSize) - - let width: CGFloat - if self.unreadCount == 0 || self.isReordering { - if !self.isReordering { - self.badgeContainerNode.alpha = 0.0 - } - width = titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right - } else { - if !self.isReordering { - self.badgeContainerNode.alpha = 1.0 - } - width = badgeBackgroundFrame.maxX - } - - return (width, shortTitleSize.width - self.shortTitleNode.insets.left - self.shortTitleNode.insets.right) - } - - func updateArea(size: CGSize, sideInset: CGFloat, useShortTitle: Bool, transition: ContainedViewLayoutTransition) { - transition.updateAlpha(node: self.titleNode, alpha: useShortTitle ? 0.0 : 1.0) - transition.updateAlpha(node: self.shortTitleNode, alpha: useShortTitle ? 1.0 : 0.0) - - self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) - - self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size) - self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size) - self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: self.extractedBackgroundNode.frame.minX, y: 0.0), size: CGSize(width: self.extractedBackgroundNode.frame.width, height: size.height)) - self.containerNode.frame = CGRect(origin: CGPoint(), size: size) - - self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset) - self.extractedContainerNode.hitTestSlop = self.hitTestSlop - self.extractedContainerNode.contentNode.hitTestSlop = self.hitTestSlop - self.containerNode.hitTestSlop = self.hitTestSlop - - let extractedBackgroundHeight: CGFloat = 32.0 - let extractedBackgroundInset: CGFloat = 14.0 - self.extractedBackgroundNode.frame = CGRect(origin: CGPoint(x: -extractedBackgroundInset, y: floor((size.height - extractedBackgroundHeight) / 2.0)), size: CGSize(width: size.width + extractedBackgroundInset * 2.0, height: extractedBackgroundHeight)) - } - - func animateBadgeIn() { - if !self.isReordering { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - self.badgeContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) - transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) - } - } - - func animateBadgeOut() { - if !self.isReordering { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - self.badgeContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) - ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) - transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) - } - } - - private func startShaking() { - func degreesToRadians(_ x: CGFloat) -> CGFloat { - return .pi * x / 180.0 - } - - let duration: Double = 0.4 - let displacement: CGFloat = 1.0 - let degreesRotation: CGFloat = 2.0 - - let negativeDisplacement = -1.0 * displacement - let position = CAKeyframeAnimation.init(keyPath: "position") - position.beginTime = 0.8 - position.duration = duration - position.values = [ - NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)), - NSValue(cgPoint: CGPoint(x: 0, y: 0)), - NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)), - NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)), - NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)) - ] - position.calculationMode = .linear - position.isRemovedOnCompletion = false - position.repeatCount = Float.greatestFiniteMagnitude - position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) - position.isAdditive = true - - let transform = CAKeyframeAnimation.init(keyPath: "transform") - transform.beginTime = 2.6 - transform.duration = 0.3 - transform.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ) - transform.values = [ - degreesToRadians(-1.0 * degreesRotation), - degreesToRadians(degreesRotation), - degreesToRadians(-1.0 * degreesRotation) - ] - transform.calculationMode = .linear - transform.isRemovedOnCompletion = false - transform.repeatCount = Float.greatestFiniteMagnitude - transform.isAdditive = true - transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) - - self.layer.add(position, forKey: "shaking_position") - self.layer.add(transform, forKey: "shaking_rotation") - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let deleteButtonNode = self.deleteButtonNode { - if deleteButtonNode.frame.insetBy(dx: -4.0, dy: -4.0).contains(point) { - return deleteButtonNode.view - } - } - return super.hitTest(point, with: event) - } -} - -private final class ItemNodePair { - let regular: ItemNode - let highlighted: ItemNode - - init(regular: ItemNode, highlighted: ItemNode) { - self.regular = regular - self.highlighted = highlighted - } -} - -final class ChatListFilterTabInlineContainerNode: ASDisplayNode { - private let scrollNode: ASScrollNode - private let itemsBackgroundView: UIVisualEffectView - private let itemsBackgroundTintNode: ASImageNode - - private let selectedBackgroundNode: ASImageNode - private var itemNodePairs: [ChatListFilterTabEntryId: ItemNodePair] = [:] - private var itemsContainer: ASDisplayNode - private var highlightedItemsClippingContainer: ASDisplayNode - private var highlightedItemsContainer: ASDisplayNode - - var tabSelected: ((ChatListFilterTabEntryId) -> Void)? - var tabRequestedDeletion: ((ChatListFilterTabEntryId) -> Void)? - var addFilter: (() -> Void)? - var contextGesture: ((Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void)? - - private var reorderingGesture: ReorderingGestureRecognizer? - private var reorderingItem: ChatListFilterTabEntryId? - private var reorderingItemPosition: (initial: CGFloat, offset: CGFloat)? - private var reorderingAutoScrollAnimator: ConstantDisplayLinkAnimator? - private var reorderedItemIds: [ChatListFilterTabEntryId]? - private lazy var hapticFeedback = { HapticFeedback() }() - - private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData)? - - var reorderedFilterIds: [Int32]? { - return self.reorderedItemIds.flatMap { - $0.compactMap { - switch $0 { - case .all: - return 0 - case let .filter(id): - return id - } - } - } - } - - override init() { - self.scrollNode = ASScrollNode() - - self.itemsBackgroundView = UIVisualEffectView() - self.itemsBackgroundView.clipsToBounds = true - self.itemsBackgroundView.layer.cornerRadius = 20.0 - - self.itemsBackgroundTintNode = ASImageNode() - self.itemsBackgroundTintNode.displaysAsynchronously = false - self.itemsBackgroundTintNode.displayWithoutProcessing = true - - self.selectedBackgroundNode = ASImageNode() - self.selectedBackgroundNode.displaysAsynchronously = false - self.selectedBackgroundNode.displayWithoutProcessing = true - - self.itemsContainer = ASDisplayNode() - - self.highlightedItemsClippingContainer = ASDisplayNode() - self.highlightedItemsClippingContainer.clipsToBounds = true - self.highlightedItemsClippingContainer.layer.cornerRadius = 16.0 - - self.highlightedItemsContainer = ASDisplayNode() - - super.init() - - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.scrollsToTop = false - self.scrollNode.view.delaysContentTouches = false - self.scrollNode.view.canCancelContentTouches = true - if #available(iOS 11.0, *) { - self.scrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.addSubnode(self.scrollNode) - self.scrollNode.view.addSubview(self.itemsBackgroundView) - self.scrollNode.addSubnode(self.itemsBackgroundTintNode) - self.scrollNode.addSubnode(self.itemsContainer) - self.scrollNode.addSubnode(self.selectedBackgroundNode) - self.scrollNode.addSubnode(self.highlightedItemsClippingContainer) - self.highlightedItemsClippingContainer.addSubnode(self.highlightedItemsContainer) - - let reorderingGesture = ReorderingGestureRecognizer(shouldBegin: { [weak self] point in - guard let strongSelf = self else { - return false - } - for (id, itemNodePair) in strongSelf.itemNodePairs { - if itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.view).contains(point) { - if case .all = id { - return false - } - return true - } - } - return false - }, began: { [weak self] point in - guard let strongSelf = self, let _ = strongSelf.currentParams else { - return - } - for (id, itemNodePair) in strongSelf.itemNodePairs { - let itemFrame = itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.view) - if itemFrame.contains(point) { - strongSelf.hapticFeedback.impact() - - strongSelf.reorderingItem = id - itemNodePair.regular.frame = itemFrame - strongSelf.reorderingAutoScrollAnimator = ConstantDisplayLinkAnimator(update: { - guard let strongSelf = self, let currentLocation = strongSelf.reorderingGesture?.currentLocation else { - return - } - let edgeWidth: CGFloat = 20.0 - if currentLocation.x <= edgeWidth { - var contentOffset = strongSelf.scrollNode.view.contentOffset - contentOffset.x = max(0.0, contentOffset.x - 3.0) - strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false) - } else if currentLocation.x >= strongSelf.bounds.width - edgeWidth { - var contentOffset = strongSelf.scrollNode.view.contentOffset - contentOffset.x = max(0.0, min(strongSelf.scrollNode.view.contentSize.width - strongSelf.scrollNode.bounds.width, contentOffset.x + 3.0)) - strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false) - } - }) - strongSelf.reorderingAutoScrollAnimator?.isPaused = false - strongSelf.addSubnode(itemNodePair.regular) - - strongSelf.reorderingItemPosition = (itemNodePair.regular.frame.minX, 0.0) - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) - } - return - } - } - }, ended: { [weak self] in - guard let strongSelf = self, let reorderingItem = strongSelf.reorderingItem else { - return - } - if let itemNodePair = strongSelf.itemNodePairs[reorderingItem] { - let projectedItemFrame = itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.scrollNode.view) - itemNodePair.regular.frame = projectedItemFrame - strongSelf.itemsContainer.addSubnode(itemNodePair.regular) - } - - strongSelf.reorderingItem = nil - strongSelf.reorderingItemPosition = nil - strongSelf.reorderingAutoScrollAnimator?.invalidate() - strongSelf.reorderingAutoScrollAnimator = nil - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) - } - }, moved: { [weak self] offset in - guard let strongSelf = self, let reorderingItem = strongSelf.reorderingItem else { - return - } - if let reorderingItemNodePair = strongSelf.itemNodePairs[reorderingItem], let (initial, _) = strongSelf.reorderingItemPosition, let reorderedItemIds = strongSelf.reorderedItemIds, let currentItemIndex = reorderedItemIds.firstIndex(of: reorderingItem) { - for (id, itemNodePair) in strongSelf.itemNodePairs { - guard let itemIndex = reorderedItemIds.firstIndex(of: id) else { - continue - } - if id != reorderingItem { - let itemFrame = itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.view) - if reorderingItemNodePair.regular.frame.intersects(itemFrame) { - let targetIndex: Int - if reorderingItemNodePair.regular.frame.midX < itemFrame.midX { - targetIndex = max(1, itemIndex - 1) - } else { - targetIndex = max(1, min(reorderedItemIds.count - 1, itemIndex)) - } - if targetIndex != currentItemIndex { - strongSelf.hapticFeedback.tap() - - var updatedReorderedItemIds = reorderedItemIds - if targetIndex > currentItemIndex { - updatedReorderedItemIds.insert(reorderingItem, at: targetIndex + 1) - updatedReorderedItemIds.remove(at: currentItemIndex) - } else { - updatedReorderedItemIds.remove(at: currentItemIndex) - updatedReorderedItemIds.insert(reorderingItem, at: targetIndex) - } - strongSelf.reorderedItemIds = updatedReorderedItemIds - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) - } - } - break - } - } - } - - strongSelf.reorderingItemPosition = (initial, offset) - } - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .immediate) - } - }) - self.reorderingGesture = reorderingGesture - self.view.addGestureRecognizer(reorderingGesture) - reorderingGesture.isEnabled = false - } - - private var previousSelectedAbsFrame: CGRect? - private var previousSelectedFrame: CGRect? - - func cancelAnimations() { - self.selectedBackgroundNode.layer.removeAllAnimations() - self.scrollNode.layer.removeAllAnimations() - self.highlightedItemsContainer.layer.removeAllAnimations() - self.highlightedItemsClippingContainer.layer.removeAllAnimations() - } - - func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) { - let isFirstTime = self.currentParams == nil - let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition - - var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter - let previousScrollBounds = self.scrollNode.bounds - let previousContentWidth = self.scrollNode.view.contentSize.width - - if self.currentParams?.presentationData.theme !== presentationData.theme { - if presentationData.theme.rootController.keyboardColor == .dark { - self.itemsBackgroundView.effect = UIBlurEffect(style: .dark) - } else { - self.itemsBackgroundView.effect = UIBlurEffect(style: .light) - } - - self.itemsBackgroundTintNode.image = generateStretchableFilledCircleImage(diameter: 40.0, color: UIColor(rgb: 0xf1f1f1)) - - self.selectedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 32.0, color: UIColor(rgb: 0xbbbbbb)) - } - - if isReordering { - if let reorderedItemIds = self.reorderedItemIds { - let currentIds = Set(reorderedItemIds) - if currentIds != Set(filters.map { $0.id }) { - var updatedReorderedItemIds = reorderedItemIds.filter { id in - return filters.contains(where: { $0.id == id }) - } - for filter in filters { - if !currentIds.contains(filter.id) { - updatedReorderedItemIds.append(filter.id) - } - } - self.reorderedItemIds = updatedReorderedItemIds - } - } else { - self.reorderedItemIds = filters.map { $0.id } - } - } else if self.reorderedItemIds != nil { - self.reorderedItemIds = nil - } - - self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering, isEditing, transitionFraction, presentationData: presentationData) - - self.reorderingGesture?.isEnabled = isEditing || isReordering - - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) - - enum BadgeAnimation { - case `in` - case out - } - - var badgeAnimations: [ChatListFilterTabEntryId: BadgeAnimation] = [:] - - var reorderedFilters: [ChatListFilterTabEntry] = filters - if let reorderedItemIds = self.reorderedItemIds { - reorderedFilters = reorderedItemIds.compactMap { id -> ChatListFilterTabEntry? in - if let index = filters.firstIndex(where: { $0.id == id }) { - return filters[index] - } else { - return nil - } - } - } - - for filter in reorderedFilters { - let itemNodePair: ItemNodePair - var itemNodeTransition = transition - var wasAdded = false - if let current = self.itemNodePairs[filter.id] { - itemNodePair = current - } else { - itemNodeTransition = .immediate - wasAdded = true - itemNodePair = ItemNodePair(regular: ItemNode(pressed: { [weak self] in - self?.tabSelected?(filter.id) - }, requestedDeletion: { [weak self] in - self?.tabRequestedDeletion?(filter.id) - }, contextGesture: { [weak self] sourceNode, gesture in - guard let strongSelf = self else { - return - } - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true - strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false) - switch filter { - case let .filter(id, _, _): - strongSelf.contextGesture?(id, sourceNode, gesture, false) - default: - strongSelf.contextGesture?(nil, sourceNode, gesture, false) - } - }), highlighted: ItemNode(pressed: { [weak self] in - self?.tabSelected?(filter.id) - }, requestedDeletion: { [weak self] in - self?.tabRequestedDeletion?(filter.id) - }, contextGesture: { [weak self] sourceNode, gesture in - guard let strongSelf = self else { - return - } - switch filter { - case let .filter(id, _, _): - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true - strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false) - strongSelf.contextGesture?(id, sourceNode, gesture, false) - default: - strongSelf.contextGesture?(nil, sourceNode, gesture, false) - } - })) - self.itemNodePairs[filter.id] = itemNodePair - } - let unreadCount: Int - let unreadHasUnmuted: Bool - var isNoFilter: Bool = false - switch filter { - case let .all(count): - unreadCount = count - unreadHasUnmuted = true - isNoFilter = true - case let .filter(_, _, unread): - unreadCount = unread.value - unreadHasUnmuted = unread.hasUnmuted - } - if !wasAdded && (itemNodePair.regular.unreadCount != 0) != (unreadCount != 0) { - badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out - } - itemNodePair.regular.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, isSelected: false, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition) - itemNodePair.highlighted.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, isSelected: true, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition) - } - var removeKeys: [ChatListFilterTabEntryId] = [] - for (id, _) in self.itemNodePairs { - if !filters.contains(where: { $0.id == id }) { - removeKeys.append(id) - } - } - for id in removeKeys { - if let itemNodePair = self.itemNodePairs.removeValue(forKey: id) { - let regular = itemNodePair.regular - let highlighted = itemNodePair.highlighted - transition.updateAlpha(node: regular, alpha: 0.0, completion: { [weak regular] _ in - regular?.removeFromSupernode() - }) - transition.updateTransformScale(node: regular, scale: 0.1) - transition.updateAlpha(node: highlighted, alpha: 0.0, completion: { [weak highlighted] _ in - highlighted?.removeFromSupernode() - }) - transition.updateTransformScale(node: highlighted, scale: 0.1) - } - } - - var tabSizes: [(ChatListFilterTabEntryId, CGSize, CGSize, ItemNodePair, Bool)] = [] - var totalRawTabSize: CGFloat = 0.0 - var selectionFrames: [CGRect] = [] - - for filter in reorderedFilters { - guard let itemNodePair = self.itemNodePairs[filter.id] else { - continue - } - let wasAdded = itemNodePair.regular.supernode == nil - var itemNodeTransition = transition - if wasAdded { - itemNodeTransition = .immediate - self.itemsContainer.addSubnode(itemNodePair.regular) - self.highlightedItemsContainer.addSubnode(itemNodePair.highlighted) - } - let (paneNodeWidth, paneNodeShortWidth) = itemNodePair.regular.updateLayout(height: size.height, transition: itemNodeTransition) - let _ = itemNodePair.highlighted.updateLayout(height: size.height, transition: itemNodeTransition) - let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) - let paneNodeShortSize = CGSize(width: paneNodeShortWidth, height: size.height) - tabSizes.append((filter.id, paneNodeSize, paneNodeShortSize, itemNodePair, wasAdded)) - totalRawTabSize += paneNodeSize.width - - if case .animated = transition, let badgeAnimation = badgeAnimations[filter.id] { - switch badgeAnimation { - case .in: - itemNodePair.regular.animateBadgeIn() - itemNodePair.highlighted.animateBadgeIn() - case .out: - itemNodePair.regular.animateBadgeOut() - itemNodePair.highlighted.animateBadgeOut() - } - } - } - - let minSpacing: CGFloat = 30.0 - - let resolvedInitialSideInset: CGFloat = 8.0 + 14.0 + 4.0 + sideInset - - var longTitlesWidth: CGFloat = 0.0 - var shortTitlesWidth: CGFloat = 0.0 - for i in 0 ..< tabSizes.count { - let (_, paneNodeSize, paneNodeShortSize, _, _) = tabSizes[i] - longTitlesWidth += paneNodeSize.width - shortTitlesWidth += paneNodeShortSize.width - } - let totalSpacing = CGFloat(tabSizes.count - 1) * minSpacing - let useShortTitles = (longTitlesWidth + totalSpacing + resolvedInitialSideInset * 2.0) > size.width - - var rawContentWidth = useShortTitles ? shortTitlesWidth : longTitlesWidth - rawContentWidth += totalSpacing - - let resolvedSideInset = max(resolvedInitialSideInset, floor((size.width - rawContentWidth) / 2.0)) - - var leftOffset: CGFloat = resolvedSideInset - - let itemsBackgroundLeftX = leftOffset - 14.0 - 4.0 - - for i in 0 ..< tabSizes.count { - let (itemId, paneNodeLongSize, paneNodeShortSize, itemNodePair, wasAdded) = tabSizes[i] - var itemNodeTransition = transition - if wasAdded { - itemNodeTransition = .immediate - } - - let useShortTitle = itemId == .all && useShortTitles - let paneNodeSize = useShortTitle ? paneNodeShortSize : paneNodeLongSize - - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - if itemId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition { - itemNodeTransition.updateSublayerTransformScale(node: itemNodePair.regular, scale: 1.2) - itemNodeTransition.updateAlpha(node: itemNodePair.regular, alpha: 0.9) - let offsetFrame = CGRect(origin: CGPoint(x: initial + offset, y: paneFrame.minY), size: paneFrame.size) - itemNodeTransition.updateFrameAdditive(node: itemNodePair.regular, frame: offsetFrame) - selectionFrames.append(offsetFrame) - } else { - itemNodeTransition.updateSublayerTransformScale(node: itemNodePair.regular, scale: 1.0) - itemNodeTransition.updateAlpha(node: itemNodePair.regular, alpha: 1.0) - if wasAdded { - itemNodePair.regular.frame = paneFrame - itemNodePair.regular.alpha = 0.0 - itemNodeTransition.updateAlpha(node: itemNodePair.regular, alpha: 1.0) - } else { - itemNodeTransition.updateFrameAdditive(node: itemNodePair.regular, frame: paneFrame) - } - selectionFrames.append(paneFrame) - } - - if wasAdded { - itemNodePair.highlighted.frame = paneFrame - itemNodePair.highlighted.alpha = 0.0 - itemNodeTransition.updateAlpha(node: itemNodePair.highlighted, alpha: 1.0) - } else { - itemNodeTransition.updateFrameAdditive(node: itemNodePair.highlighted, frame: paneFrame) - } - - itemNodePair.regular.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0, useShortTitle: useShortTitle, transition: itemNodeTransition) - itemNodePair.regular.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) - - itemNodePair.highlighted.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0, useShortTitle: useShortTitle, transition: itemNodeTransition) - itemNodePair.highlighted.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) - - leftOffset += paneNodeSize.width + minSpacing - } - leftOffset -= minSpacing - let itemsBackgroundRightX = leftOffset + 14.0 + 4.0 - - leftOffset += resolvedSideInset - - let backgroundFrame = CGRect(origin: CGPoint(x: itemsBackgroundLeftX, y: 0.0), size: CGSize(width: itemsBackgroundRightX - itemsBackgroundLeftX, height: size.height)) - transition.updateFrame(view: self.itemsBackgroundView, frame: backgroundFrame) - transition.updateFrame(node: self.itemsBackgroundTintNode, frame: backgroundFrame) - - self.scrollNode.view.contentSize = CGSize(width: itemsBackgroundRightX + 8.0, height: size.height) - - var selectedFrame: CGRect? - if let selectedFilter = selectedFilter, let currentIndex = reorderedFilters.firstIndex(where: { $0.id == selectedFilter }) { - func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect { - return CGRect(x: floorToScreenPixels(toValue.origin.x * t + fromValue.origin.x * (1.0 - t)), y: floorToScreenPixels(toValue.origin.y * t + fromValue.origin.y * (1.0 - t)), width: floorToScreenPixels(toValue.size.width * t + fromValue.size.width * (1.0 - t)), height: floorToScreenPixels(toValue.size.height * t + fromValue.size.height * (1.0 - t))) - } - - if currentIndex != 0 && transitionFraction > 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex - 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else if currentIndex != filters.count - 1 && transitionFraction < 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex + 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else { - selectedFrame = selectionFrames[currentIndex] - } - } - - transition.updateFrame(node: self.itemsContainer, frame: CGRect(origin: CGPoint(), size: self.scrollNode.view.contentSize)) - - if let selectedFrame = selectedFrame { - let wasAdded = self.selectedBackgroundNode.isHidden - self.selectedBackgroundNode.isHidden = false - let lineFrame = CGRect(origin: CGPoint(x: selectedFrame.minX - 14.0, y: floor((size.height - 32.0) / 2.0)), size: CGSize(width: selectedFrame.width + 14.0 * 2.0, height: 32.0)) - if wasAdded { - self.selectedBackgroundNode.frame = lineFrame - self.selectedBackgroundNode.alpha = 0.0 - } else { - transition.updateFrame(node: self.selectedBackgroundNode, frame: lineFrame) - } - transition.updateFrame(node: self.highlightedItemsClippingContainer, frame: lineFrame) - transition.updateFrame(node: self.highlightedItemsContainer, frame: CGRect(origin: CGPoint(x: -lineFrame.minX, y: -lineFrame.minY), size: self.scrollNode.view.contentSize)) - transition.updateAlpha(node: self.selectedBackgroundNode, alpha: isReordering ? 0.0 : 1.0) - transition.updateAlpha(node: self.highlightedItemsClippingContainer, alpha: isReordering ? 0.0 : 1.0) - - if let previousSelectedFrame = self.previousSelectedFrame { - let previousContentOffsetX = max(0.0, min(previousContentWidth - previousScrollBounds.width, floor(previousSelectedFrame.midX - previousScrollBounds.width / 2.0))) - if abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 { - focusOnSelectedFilter = true - } - } - - if focusOnSelectedFilter && self.reorderingItem == nil { - let updatedBounds: CGRect - if transitionFraction.isZero && selectedFilter == reorderedFilters.first?.id { - updatedBounds = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size) - } else if transitionFraction.isZero && selectedFilter == reorderedFilters.last?.id { - updatedBounds = CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size) - } else { - let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0))) - updatedBounds = CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size) - } - self.scrollNode.bounds = updatedBounds - } - transition.animateHorizontalOffsetAdditive(node: self.scrollNode, offset: previousScrollBounds.minX - self.scrollNode.bounds.minX) - - self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0) - self.previousSelectedFrame = selectedFrame - } else { - self.selectedBackgroundNode.isHidden = true - self.previousSelectedAbsFrame = nil - self.previousSelectedFrame = nil - } - } -} - -private class ReorderingGestureRecognizerTimerTarget: NSObject { - private let f: () -> Void - - init(_ f: @escaping () -> Void) { - self.f = f - - super.init() - } - - @objc func timerEvent() { - self.f() - } -} - -private final class ReorderingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { - private let shouldBegin: (CGPoint) -> Bool - private let began: (CGPoint) -> Void - private let ended: () -> Void - private let moved: (CGFloat) -> Void - - private var initialLocation: CGPoint? - private var delayTimer: Foundation.Timer? - - var currentLocation: CGPoint? - - init(shouldBegin: @escaping (CGPoint) -> Bool, began: @escaping (CGPoint) -> Void, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { - self.shouldBegin = shouldBegin - self.began = began - self.ended = ended - self.moved = moved - - super.init(target: nil, action: nil) - - self.delegate = self - } - - override func reset() { - super.reset() - - self.initialLocation = nil - self.delayTimer?.invalidate() - self.delayTimer = nil - self.currentLocation = nil - } - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if otherGestureRecognizer is UIPanGestureRecognizer { - return true - } else { - return false - } - } - - override func touchesBegan(_ touches: Set, with event: UIEvent) { - super.touchesBegan(touches, with: event) - - guard let location = touches.first?.location(in: self.view) else { - self.state = .failed - return - } - - if self.state == .possible { - if self.delayTimer == nil { - if !self.shouldBegin(location) { - self.state = .failed - return - } - self.initialLocation = location - let timer = Foundation.Timer(timeInterval: 0.2, target: ReorderingGestureRecognizerTimerTarget { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.delayTimer = nil - strongSelf.state = .began - strongSelf.began(location) - }, selector: #selector(ReorderingGestureRecognizerTimerTarget.timerEvent), userInfo: nil, repeats: false) - self.delayTimer = timer - RunLoop.main.add(timer, forMode: .common) - } else { - self.state = .failed - } - } - } - - override func touchesEnded(_ touches: Set, with event: UIEvent) { - super.touchesEnded(touches, with: event) - - self.delayTimer?.invalidate() - - if self.state == .began || self.state == .changed { - self.ended() - } - - self.state = .failed - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent) { - super.touchesCancelled(touches, with: event) - - if self.state == .began || self.state == .changed { - self.delayTimer?.invalidate() - self.ended() - self.state = .failed - } - } - - override func touchesMoved(_ touches: Set, with event: UIEvent) { - super.touchesMoved(touches, with: event) - - guard let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) else { - return - } - let offset = location.x - initialLocation.x - self.currentLocation = location - - if self.delayTimer != nil { - if abs(offset) > 4.0 { - self.delayTimer?.invalidate() - self.state = .failed - return - } - } else { - if self.state == .began || self.state == .changed { - self.state = .changed - self.moved(offset) - } - } - } -} diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 0dd0e80308..e8a666aa55 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -665,13 +665,9 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction?.openPasswordSetup() case .premiumUpgrade, .premiumAnnualDiscount: nodeInteraction?.openPremiumIntro() - case .chatFolderUpdates: - nodeInteraction?.openChatFolderUpdates() } case .hide: switch notice { - case .chatFolderUpdates: - nodeInteraction?.hideChatFolderUpdates() default: break } @@ -966,13 +962,9 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction?.openPasswordSetup() case .premiumUpgrade, .premiumAnnualDiscount: nodeInteraction?.openPremiumIntro() - case .chatFolderUpdates: - nodeInteraction?.openChatFolderUpdates() } case .hide: switch notice { - case .chatFolderUpdates: - nodeInteraction?.hideChatFolderUpdates() default: break } diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift index d9a9c13a7a..31ead65ee0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift @@ -210,8 +210,6 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) switch item.notice { - case .chatFolderUpdates: - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.ChatList_HideAction, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) default: strongSelf.setRevealOptions((left: [], right: [])) } diff --git a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift index dd9de01a1a..a8428853f7 100644 --- a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift +++ b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift @@ -1,12 +1,11 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import UniversalMediaPlayer import AccountContext -private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { +private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .single(nil) } @@ -21,7 +20,7 @@ private func internalMessageFileMediaPlaybackStatus(context: AccountContext, fil } } -public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { +public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { var duration = 0.0 if let value = file.duration { duration = Double(value) @@ -33,7 +32,7 @@ public func messageFileMediaPlaybackStatus(context: AccountContext, file: Telegr } } -public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { +public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .never() } @@ -45,7 +44,7 @@ public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, fi } } -public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false, isDownloadList: Bool = false) -> Signal { +public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false, isDownloadList: Bool = false) -> Signal { let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) |> map { status -> MediaPlayerPlaybackStatus? in return status?.status } @@ -99,7 +98,7 @@ public func messageFileMediaResourceStatus(context: AccountContext, file: Telegr } } -public func messageImageMediaResourceStatus(context: AccountContext, image: TelegramMediaImage, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal { +public func messageImageMediaResourceStatus(context: AccountContext, image: TelegramMediaImage, message: EngineMessage, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal { if message.flags.isSending { return combineLatest(messageMediaImageStatus(context: context, messageId: message.id, image: image), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 10acbaaa3e..a429a45efd 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -37,13 +37,13 @@ private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, med switch block { case let .image(id, caption, _, _): if let m = media[id] { - let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: EngineMedia(m), url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } case let .video(id, caption, _, _): if let m = media[id] { - let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: EngineMedia(m), url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } @@ -82,7 +82,7 @@ public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galle } if !found { - result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0) + result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: EngineMedia(galleryMedia), url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0) } for i in 0 ..< result.count { @@ -123,7 +123,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati if case .suggestedProfilePhoto = action.action { isSuggested = true } - let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer._asPeer(), message.timestamp, nil, message.id, image.immediateThumbnailData, "action", false, nil)]) + let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action", false, nil)]) let sourceCorners: AvatarGalleryController.SourceCorners if case .photoUpdated = action.action { @@ -131,7 +131,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati } else { sourceCorners = .round } - let galleryController = AvatarGalleryController(context: context, peer: peer._asPeer(), sourceCorners: sourceCorners, remoteEntries: promise, isSuggested: isSuggested, skipInitial: true, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: sourceCorners, remoteEntries: promise, isSuggested: isSuggested, skipInitial: true, replaceRootController: { controller, ready in }) return .chatAvatars(galleryController, image) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index c24a292438..8f35db5067 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramBaseController diff --git a/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift b/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift index b0ba996cad..cc08759bd6 100644 --- a/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift +++ b/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Vision import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -27,8 +26,8 @@ private final class CachedImageRecognizedContent: Codable { } } -private func cachedImageRecognizedContent(engine: TelegramEngine, messageId: MessageId) -> Signal { - let key = ValueBoxKey(length: 20) +private func cachedImageRecognizedContent(engine: TelegramEngine, messageId: EngineMessage.Id) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) @@ -40,8 +39,8 @@ private func cachedImageRecognizedContent(engine: TelegramEngine, messageId: Mes } } -private func updateCachedImageRecognizedContent(engine: TelegramEngine, messageId: MessageId, content: CachedImageRecognizedContent?) -> Signal { - let key = ValueBoxKey(length: 20) +private func updateCachedImageRecognizedContent(engine: TelegramEngine, messageId: EngineMessage.Id, content: CachedImageRecognizedContent?) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) @@ -333,7 +332,7 @@ private func recognizeContent(in image: UIImage?) -> Signal<[RecognizedContent], } } -public func recognizedContent(context: AccountContext, image: @escaping () -> UIImage?, messageId: MessageId) -> Signal<[RecognizedContent], NoError> { +public func recognizedContent(context: AccountContext, image: @escaping () -> UIImage?, messageId: EngineMessage.Id) -> Signal<[RecognizedContent], NoError> { if context.sharedContext.immediateExperimentalUISettings.disableImageContentAnalysis { return .single([]) } diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift index 922ad9a4d8..070a81fe86 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore enum StickerVerificationStatus { @@ -74,7 +73,7 @@ public class ImportStickerPack { let emojis: [String] let keywords: String let uuid: UUID - var resource: MediaResource? + var resource: EngineMediaResource? init(content: Content, emojis: [String], keywords: String, uuid: UUID = UUID()) { self.content = content diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift index 3fdf142b98..01c79d7df3 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences @@ -87,23 +86,23 @@ public final class ImportStickerPackController: ViewController, StandalonePresen return } - var signals: [Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError>] = [] + var signals: [Signal<(UUID, StickerVerificationStatus, EngineMediaResource?), NoError>] = [] for sticker in strongSelf.stickerPack.stickers { if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] { - signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), mimeType: sticker.mimeType) - |> map { result -> (UUID, StickerVerificationStatus, MediaResource?) in + signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource._asResource(), alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), mimeType: sticker.mimeType) + |> map { result -> (UUID, StickerVerificationStatus, EngineMediaResource?) in switch result { case .progress: return (sticker.uuid, .loading, nil) case let .complete(resource, mimeType): if ["application/x-tgsticker", "video/webm"].contains(mimeType) { - return (sticker.uuid, .verified, resource) + return (sticker.uuid, .verified, EngineMediaResource(resource)) } else { return (sticker.uuid, .declined, nil) } } } - |> `catch` { _ -> Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError> in + |> `catch` { _ -> Signal<(UUID, StickerVerificationStatus, EngineMediaResource?), NoError> in return .single((sticker.uuid, .declined, nil)) }) } @@ -115,7 +114,7 @@ public final class ImportStickerPackController: ViewController, StandalonePresen } var verifiedStickers = Set() var declinedStickers = Set() - var uploadedStickerResources: [UUID: MediaResource] = [:] + var uploadedStickerResources: [UUID: EngineMediaResource] = [:] for (uuid, result, resource) in results { switch result { case .verified: diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index 7a9600b7a0..1a64cdec19 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -52,8 +51,8 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll private let context: AccountContext private var presentationData: PresentationData private var stickerPack: ImportStickerPack? - var stickerResources: [UUID: MediaResource] = [:] - private var uploadedStickerResources: [UUID: MediaResource] = [:] + var stickerResources: [UUID: EngineMediaResource] = [:] + private var uploadedStickerResources: [UUID: EngineMediaResource] = [:] private var stickerPackReady = true private var containerLayout: (ContainerViewLayout, CGFloat)? @@ -623,11 +622,11 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll } if let resource = self.uploadedStickerResources[item.stickerItem.uuid] { if let localResource = item.stickerItem.resource { - self.context.account.postbox.mediaBox.copyResourceData(from: localResource.id, to: resource.id) + self.context.account.postbox.mediaBox.copyResourceData(from: localResource._asResource().id, to: resource._asResource().id) } - stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) + stickers.append(ImportSticker(resource: resource._asResource(), emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) } else if let resource = item.stickerItem.resource { - stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) + stickers.append(ImportSticker(resource: resource._asResource(), emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) } } var thumbnailSticker: ImportSticker? @@ -695,23 +694,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll let context = strongSelf.context Queue.mainQueue().after(1.0) { - var firstItem: StickerPackItem? - if let firstStickerItem = firstStickerItem, let resource = firstStickerItem.resource as? TelegramMediaResource { - var fileAttributes: [TelegramMediaFileAttribute] = [] - if firstStickerItem.mimeType == "video/webm" { - fileAttributes.append(.FileName(fileName: "sticker.webm")) - fileAttributes.append(.Animated) - fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - } else if firstStickerItem.mimeType == "application/x-tgsticker" { - fileAttributes.append(.FileName(fileName: "sticker.tgs")) - fileAttributes.append(.Animated) - fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - } else { - fileAttributes.append(.FileName(fileName: "sticker.webp")) - } - fileAttributes.append(.ImageSize(size: firstStickerItem.dimensions)) - firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: firstStickerItem.mimeType, size: nil, attributes: fileAttributes), indexKeys: []) - } + let firstItem: StickerPackItem? = firstStickerItem?.stickerPackItem strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: firstItem ?? items.first, context: strongSelf.context), elevatedLayout: false, action: { action in if case .info = action { (navigationController?.viewControllers.last as? ViewController)?.present(StickerPackScreen(context: context, mode: .settings, mainStickerPack: .id(id: info.id.id, accessHash: info.accessHash), stickerPacks: [], parentNavigationController: navigationController, actionPerformed: { _ in @@ -800,7 +783,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll }) } - func updateStickerPack(_ stickerPack: ImportStickerPack, verifiedStickers: Set, declinedStickers: Set, uploadedStickerResources: [UUID: MediaResource]) { + func updateStickerPack(_ stickerPack: ImportStickerPack, verifiedStickers: Set, declinedStickers: Set, uploadedStickerResources: [UUID: EngineMediaResource]) { self.stickerPack = stickerPack self.uploadedStickerResources = uploadedStickerResources var updatedItems: [StickerPackPreviewGridEntry] = [] @@ -813,8 +796,8 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll } else { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.data) - item.resource = resource - self.stickerResources[item.uuid] = resource + item.resource = EngineMediaResource(resource) + self.stickerResources[item.uuid] = EngineMediaResource(resource) } var isInitiallyVerified = false if case .image = item.content { diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift index 6ff204a44a..c8f34868f9 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift b/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift index d60f0656bf..38d89c37e4 100644 --- a/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import StickerResources import AccountContext import AnimatedStickerNode @@ -142,7 +141,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { if case .video = stickerItem.content { isVideo = true } - animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource, isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil)) + animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource._asResource(), isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil)) } animationNode.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true } else { diff --git a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift index 4e815ee4f8..73bd3744ea 100644 --- a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import StickerResources @@ -87,7 +86,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController if case .video = item.content { isVideo = true } - self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: resource, isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: resource._asResource(), isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) } self.animationNode?.visibility = true } diff --git a/submodules/InstantPageCache/Sources/CachedInstantPages.swift b/submodules/InstantPageCache/Sources/CachedInstantPages.swift index aef6b86364..182e3d47bf 100644 --- a/submodules/InstantPageCache/Sources/CachedInstantPages.swift +++ b/submodules/InstantPageCache/Sources/CachedInstantPages.swift @@ -1,8 +1,8 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore +import Postbox import TelegramUIPreferences import PersistentStringHash diff --git a/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift b/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift index 5ac6bde65b..efea3cbf91 100644 --- a/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift +++ b/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import UrlHandling diff --git a/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift b/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift index 3fc1125c2a..3ba3e794a6 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift b/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift index 831d1082a1..38d2a817e1 100644 --- a/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData @@ -20,11 +19,11 @@ public final class InstantPageArticleItem: InstantPageItem { let contentSize: CGSize let cover: TelegramMediaImage? let url: String - let webpageId: MediaId + let webpageId: EngineMedia.Id let rtl: Bool let hasRTL: Bool - init(frame: CGRect, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, rtl: Bool, hasRTL: Bool) { + init(frame: CGRect, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: EngineMedia.Id, rtl: Bool, hasRTL: Bool) { self.frame = frame self.userLocation = userLocation self.webPage = webPage @@ -73,7 +72,7 @@ public final class InstantPageArticleItem: InstantPageItem { } } -func layoutArticleItem(theme: InstantPageTheme, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, title: NSAttributedString, description: NSAttributedString, cover: TelegramMediaImage?, url: String, webpageId: MediaId, boundingWidth: CGFloat, rtl: Bool) -> InstantPageArticleItem { +func layoutArticleItem(theme: InstantPageTheme, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, title: NSAttributedString, description: NSAttributedString, cover: TelegramMediaImage?, url: String, webpageId: EngineMedia.Id, boundingWidth: CGFloat, rtl: Bool) -> InstantPageArticleItem { let inset: CGFloat = 17.0 let imageSpacing: CGFloat = 10.0 var sideInset = inset diff --git a/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift b/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift index 26b67ecd55..9b48a70b68 100644 --- a/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -20,14 +19,14 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode { private var imageNode: TransformImageNode? let url: String - let webpageId: MediaId + let webpageId: EngineMedia.Id let cover: TelegramMediaImage? private let openUrl: (InstantPageUrlItem) -> Void private var fetchedDisposable = MetaDisposable() - init(context: AccountContext, item: InstantPageArticleItem, webPage: TelegramMediaWebpage, strings: PresentationStrings, theme: InstantPageTheme, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, openUrl: @escaping (InstantPageUrlItem) -> Void) { + init(context: AccountContext, item: InstantPageArticleItem, webPage: TelegramMediaWebpage, strings: PresentationStrings, theme: InstantPageTheme, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: EngineMedia.Id, openUrl: @escaping (InstantPageUrlItem) -> Void) { self.item = item self.url = url self.webpageId = webpageId diff --git a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift index 0d3005a60e..b1962eb684 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift b/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift index b4d8b96eb1..4da50a07c0 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import AsyncDisplayKit import Display @@ -36,7 +35,7 @@ private func generatePauseButton(color: UIColor) -> UIImage? { private func titleString(media: InstantPageMedia, theme: InstantPageTheme, strings: PresentationStrings) -> NSAttributedString { let string = NSMutableAttributedString() - if let file = media.media as? TelegramMediaFile { + if case let .file(file) = media.media { loop: for attribute in file.attributes { if case let .Audio(isVoice, _, title, performer, _) = attribute, !isVoice { let titleText: String = title ?? strings.MediaPlayer_UnknownTrack @@ -101,7 +100,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .line, backgroundColor: theme.textCategories.paragraph.color.withAlphaComponent(backgroundAlpha), foregroundColor: theme.textCategories.paragraph.color, bufferingColor: theme.textCategories.paragraph.color.withAlphaComponent(0.5), chapters: [])) let playlistType: MediaManagerPlayerType - if let file = self.media.media as? TelegramMediaFile { + if case let .file(file) = self.media.media { playlistType = file.isVoice ? .voice : .music } else { playlistType = .music diff --git a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift index 9cf0244341..b2c8810209 100644 --- a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 6a64e41cf9..ab7a175820 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -555,7 +555,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if item is InstantPageWebEmbedItem { embedIndex += 1 } - if let imageItem = item as? InstantPageImageItem, imageItem.media.media is TelegramMediaWebpage { + if let imageItem = item as? InstantPageImageItem, case .webpage = imageItem.media.media { embedIndex += 1 } if item is InstantPageDetailsItem { @@ -1003,17 +1003,17 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private func longPressMedia(_ media: InstantPageMedia) { let controller = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in - if let strongSelf = self, let image = media.media as? TelegramMediaImage { + if let strongSelf = self, case let .image(image) = media.media { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) let _ = copyToPasteboard(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.strings.Conversation_LinkDialogSave, accessibilityLabel: self.strings.Conversation_LinkDialogSave), action: { [weak self] in - if let strongSelf = self, let image = media.media as? TelegramMediaImage { + if let strongSelf = self, case let .image(image) = media.media { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) let _ = saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in - if let strongSelf = self, let webPage = strongSelf.webPage, let image = media.media as? TelegramMediaImage { + if let strongSelf = self, let webPage = strongSelf.webPage, case let .image(image) = media.media { strongSelf.present(ShareController(context: strongSelf.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil) } })], catchTapsOutside: true) @@ -1406,7 +1406,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return } - if let map = media.media as? TelegramMediaMap { + if case let .geo(map) = media.media { let controllerParams = LocationViewParams(sendLiveLocation: { _ in }, stopLiveLocation: { _ in }, openUrl: { _ in }, openPeer: { _ in @@ -1420,12 +1420,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return } - if let file = media.media as? TelegramMediaFile, (file.isVoice || file.isMusic) { + if case let .file(file) = media.media, (file.isVoice || file.isMusic) { var medias: [InstantPageMedia] = [] var initialIndex = 0 for item in items { for itemMedia in item.medias { - if let itemFile = itemMedia.media as? TelegramMediaFile, (itemFile.isVoice || itemFile.isMusic) { + if case let .file(itemFile) = itemMedia.media, (itemFile.isVoice || itemFile.isMusic) { if itemMedia.index == media.index { initialIndex = medias.count } @@ -1440,16 +1440,21 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { var fromPlayingVideo = false var entries: [InstantPageGalleryEntry] = [] - if media.media is TelegramMediaWebpage { + if case let .webpage(webPage) = media.media { entries.append(InstantPageGalleryEntry(index: 0, pageId: webPage.webpageId, media: media, caption: nil, credit: nil, location: nil)) - } else if let file = media.media as? TelegramMediaFile, file.isAnimated { + } else if case let .file(file) = media.media, file.isAnimated { fromPlayingVideo = true entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: nil)) } else { fromPlayingVideo = true var medias: [InstantPageMedia] = mediasFromItems(items) - medias = medias.filter { - return $0.media is TelegramMediaImage || $0.media is TelegramMediaFile + medias = medias.filter { item in + switch item.media { + case .image, .file: + return true + default: + return false + } } for media in medias { diff --git a/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift b/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift index b3e93eb27a..a590813aa1 100644 --- a/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import Display diff --git a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift index d3fafb2a9c..ccb2c59081 100644 --- a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift b/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift index a6f4080321..84d2350655 100644 --- a/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift b/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift index bc641baa98..9a756f1997 100644 --- a/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift index caefa5bdf4..144a5f833b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift @@ -96,9 +96,9 @@ public struct InstantPageGalleryEntry: Equatable { } } - if let image = self.media.media as? TelegramMediaImage { + if case let .image(image) = self.media.media { return InstantImageGalleryItem(context: context, presentationData: presentationData, itemId: self.index, userLocation: userLocation, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions) - } else if let file = self.media.media as? TelegramMediaFile { + } else if case let .file(file) = self.media.media { if file.isVideo { var indexData: GalleryItemIndexData? if let location = self.location { @@ -122,7 +122,7 @@ public struct InstantPageGalleryEntry: Equatable { let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) return InstantImageGalleryItem(context: context, presentationData: presentationData, itemId: self.index, userLocation: userLocation, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions) } - } else if let embedWebpage = self.media.media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = embedWebpage.content { + } else if case let .webpage(embedWebpage) = self.media.media, case let .Loaded(webpageContent) = embedWebpage.content { if webpageContent.url.hasSuffix(".m3u8") { let content = PlatformVideoContent(id: .instantPage(embedWebpage.webpageId, embedWebpage.webpageId), userLocation: userLocation, content: .url(webpageContent.url), streamVideo: true, loopVideo: false) return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage, { makeArguments, navigationController, present in diff --git a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift index 585b50cc1c..7d3ac039cf 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift index 4b2a6f0329..3607bbdff3 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -43,7 +42,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private var currentSize: CGSize? - private var fetchStatus: MediaResourceStatus? + private var fetchStatus: EngineMediaResource.FetchStatus? private var fetchedDisposable = MetaDisposable() private var statusDisposable = MetaDisposable() @@ -72,7 +71,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.pinchContainerNode.contentNode.addSubnode(self.imageNode) self.addSubnode(self.pinchContainerNode) - if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = media.media, let largest = largestImageRepresentation(image.representations) { let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference)) @@ -92,7 +91,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = status + strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) strongSelf.updateFetchStatus() } } @@ -105,7 +104,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.pinchContainerNode.contentNode.addSubnode(self.statusNode) } - } else if let file = media.media as? TelegramMediaFile { + } else if case let .file(file) = media.media { let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) if file.mimeType.hasPrefix("image/") { if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: file) { @@ -119,7 +118,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.statusNode.transitionToState(.play(.white), animated: false, completion: {}) self.pinchContainerNode.contentNode.addSubnode(self.statusNode) } - } else if let map = media.media as? TelegramMediaMap { + } else if case let .geo(map) = media.media { self.addSubnode(self.pinNode) var dimensions = CGSize(width: 200.0, height: 100.0) @@ -131,7 +130,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height)) self.imageNode.setSignal(chatMapSnapshotImage(engine: context.engine, resource: resource)) - } else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image { + } else if case let .webpage(webPage) = media.media, case let .Loaded(content) = webPage.content, let image = content.image { let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference)) self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, userLocation: sourceLocation.userLocation, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerId: nil).start()) @@ -211,7 +210,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { if self.currentSize != size || self.themeUpdated { self.currentSize = size self.themeUpdated = false - + self.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: size) self.pinchContainerNode.update(size: size, transition: .immediate) self.imageNode.frame = CGRect(origin: CGPoint(), size: size) @@ -219,7 +218,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let radialStatusSize: CGFloat = 50.0 self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - radialStatusSize) / 2.0), y: floorToScreenPixels((size.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize) - if let image = self.media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = self.media.media, let largest = largestImageRepresentation(image.representations) { let imageSize = largest.dimensions.cgSize.aspectFilled(size) let boundingSize = size let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0 @@ -228,15 +227,15 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { apply() self.linkIconNode.frame = CGRect(x: size.width - 38.0, y: 14.0, width: 24.0, height: 24.0) - } else if let file = self.media.media as? TelegramMediaFile, let dimensions = file.dimensions { + } else if case let .file(file) = self.media.media, let dimensions = file.dimensions { let emptyColor = file.mimeType.hasPrefix("image/") ? self.theme.imageTintColor : nil - + let imageSize = dimensions.cgSize.aspectFilled(size) let boundingSize = size let makeLayout = self.imageNode.asyncLayout() let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: emptyColor)) apply() - } else if self.media.media is TelegramMediaMap { + } else if case .geo = self.media.media { for attribute in self.attributes { if let mapAttribute = attribute as? InstantPageMapAttribute { let imageSize = mapAttribute.dimensions.aspectFilled(size) @@ -254,7 +253,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let (pinSize, pinApply) = makePinLayout(self.context, theme, .location(nil)) self.pinNode.frame = CGRect(origin: CGPoint(x: floor((size.width - pinSize.width) / 2.0), y: floor(size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize) pinApply() - } else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) { + } else if case let .webpage(webPage) = media.media, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) { let imageSize = largest.dimensions.cgSize.aspectFilled(size) let boundingSize = size let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0 @@ -290,7 +289,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { case .Local: switch gesture { case .tap: - if self.media.media is TelegramMediaImage && self.media.index == -1 { + if case .image = self.media.media, self.media.index == -1 { return } self.openMedia(self.media) @@ -311,7 +310,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } else { switch gesture { case .tap: - if self.media.media is TelegramMediaImage && self.media.index == -1 { + if case .image = self.media.media, self.media.index == -1 { return } self.openMedia(self.media) diff --git a/submodules/InstantPageUI/Sources/InstantPageItem.swift b/submodules/InstantPageUI/Sources/InstantPageItem.swift index ab26eabba9..9daf839f5c 100644 --- a/submodules/InstantPageUI/Sources/InstantPageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index c91f2c9c83..686cebb5a5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import Display import TelegramPresentationData import TelegramUIPreferences @@ -48,7 +47,7 @@ private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantP } } -public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: MediaResourceUserLocation, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], excludeCaptions: Bool) -> InstantPageLayout { +public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: MediaResourceUserLocation, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [EngineMedia.Id: EngineMedia], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], excludeCaptions: Bool) -> InstantPageLayout { let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in var items: [InstantPageItem] = [] @@ -373,7 +372,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: contentSize.height += verticalInset return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .image(id, caption, url, webpageId): - if let image = media[id] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = media[id], let largest = largestImageRepresentation(image.representations) { let imageSize = largest.dimensions var filledSize = imageSize.cgSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) @@ -397,7 +396,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: mediaUrl = InstantPageUrlItem(url: url, webpageId: webpageId) } - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: image, url: mediaUrl, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: .image(image), url: mediaUrl, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height @@ -413,7 +412,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } case let .video(id, caption, autoplay, _): - if let file = media[id] as? TelegramMediaFile, let dimensions = file.dimensions { + if case let .file(file) = media[id], let dimensions = file.dimensions { let imageSize = dimensions var filledSize = imageSize.cgSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) @@ -433,11 +432,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var items: [InstantPageItem] = [] if autoplay { - let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, url: nil, caption: caption.text, credit: caption.credit), interactive: true) + let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: .file(file), url: nil, caption: caption.text, credit: caption.credit), interactive: true) items.append(mediaItem) } else { - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, url: nil, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: .file(file), url: nil, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) } @@ -460,11 +459,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var size = CGSize() switch subItem { case let .image(id, _, _, _): - if let image = media[id] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = media[id], let largest = largestImageRepresentation(image.representations) { size = largest.dimensions.cgSize } case let .video(id, _, _, _): - if let file = media[id] as? TelegramMediaFile, let dimensions = file.dimensions { + if case let .file(file) = media[id], let dimensions = file.dimensions { size = dimensions.cgSize } default: @@ -502,9 +501,15 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var items: [InstantPageItem] = [] if !author.isEmpty { - let avatar: TelegramMediaImage? = avatarId.flatMap { media[$0] as? TelegramMediaImage } + let avatar: TelegramMediaImage? = avatarId.flatMap { id -> TelegramMediaImage? in + if case let .image(image) = media[id] { + return image + } else { + return nil + } + } if let avatar = avatar { - let avatarItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: horizontalInset + lineInset + 1.0, y: contentSize.height - 2.0), size: CGSize(width: 50.0, height: 50.0)), webPage: webpage, media: InstantPageMedia(index: -1, media: avatar, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: true, fit: false) + let avatarItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: horizontalInset + lineInset + 1.0, y: contentSize.height - 2.0), size: CGSize(width: 50.0, height: 50.0)), webPage: webpage, media: InstantPageMedia(index: -1, media: .image(avatar), url: nil, caption: nil, credit: nil), interactive: false, roundCorners: true, fit: false) items.append(avatarItem) avatarInset += 62.0 @@ -572,7 +577,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: for subBlock in subItems { switch subBlock { case let .image(id, caption, url, webpageId): - if let image = media[id] as? TelegramMediaImage, let imageSize = largestImageRepresentation(image.representations)?.dimensions { + if case let .image(image) = media[id], let imageSize = largestImageRepresentation(image.representations)?.dimensions { let mediaIndex = mediaIndexCounter mediaIndexCounter += 1 @@ -583,7 +588,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: if let url = url { mediaUrl = InstantPageUrlItem(url: url, webpageId: webpageId) } - itemMedias.append(InstantPageMedia(index: mediaIndex, media: image, url: mediaUrl, caption: caption.text, credit: caption.credit)) + itemMedias.append(InstantPageMedia(index: mediaIndex, media: .image(image), url: mediaUrl, caption: caption.text, credit: caption.credit)) } break default: @@ -626,11 +631,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var contentSize: CGSize let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size) let item: InstantPageItem - if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage { + if let url = url, let coverId = coverId, case let .image(image) = media[coverId] { let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, attributes: [], instantPage: nil) let content = TelegramMediaWebpageContent.Loaded(loadedContent) - item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false) + item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: .webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content)), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false) } else { item = InstantPageWebEmbedItem(frame: frame, url: url, html: html, enableScrolling: allowScrolling) @@ -665,7 +670,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } if let peer = peer { - let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(x: 0.0, y: offset), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, safeInset: safeInset, transparent: !offset.isZero, rtl: rtl || previousItemHasRTL) + let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(x: 0.0, y: offset), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: .channel(peer), safeInset: safeInset, transparent: !offset.isZero, rtl: rtl || previousItemHasRTL) items.append(item) if offset.isZero { contentSize.height += 40.0 @@ -679,10 +684,10 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] - if let file = media[audioId] as? TelegramMediaFile { + if case let .file(file) = media[audioId] { let mediaIndex = mediaIndexCounter mediaIndexCounter += 1 - let item = InstantPageAudioItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: 48.0)), media: InstantPageMedia(index: mediaIndex, media: file, url: nil, caption: nil, credit: nil), webpage: webpage) + let item = InstantPageAudioItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: 48.0)), media: InstantPageMedia(index: mediaIndex, media: .file(file), url: nil, caption: nil, credit: nil), webpage: webpage) contentSize.height += item.frame.height items.append(item) @@ -765,7 +770,9 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: for (i, article) in articles.enumerated() { var cover: TelegramMediaImage? if let coverId = article.photoId { - cover = media[coverId] as? TelegramMediaImage + if case let .image(image) = media[coverId] { + cover = image + } } var styleStack = InstantPageTextStyleStack() @@ -820,7 +827,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) var items: [InstantPageItem] = [] - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: -1, media: map, url: nil, caption: caption.text, credit: caption.credit), attributes: attributes, interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: -1, media: .geo(map), url: nil, caption: caption.text, credit: caption.credit), attributes: attributes, interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height @@ -850,12 +857,12 @@ public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, userLoc var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] - var media = instantPage.media + var media = instantPage.media.mapValues(EngineMedia.init) if let image = loadedContent.image, let id = image.id { - media[id] = image + media[id] = .image(image) } if let video = loadedContent.file, let id = video.id { - media[id] = video + media[id] = .file(video) } var mediaIndexCounter: Int = 0 diff --git a/submodules/InstantPageUI/Sources/InstantPageMedia.swift b/submodules/InstantPageUI/Sources/InstantPageMedia.swift index 757ba68a04..ff9b2bc3f5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMedia.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMedia.swift @@ -1,15 +1,14 @@ import Foundation -import Postbox import TelegramCore public struct InstantPageMedia: Equatable { public let index: Int - public let media: Media + public let media: EngineMedia public let url: InstantPageUrlItem? public let caption: RichText? public let credit: RichText? - public init(index: Int, media: Media, url: InstantPageUrlItem?, caption: RichText?, credit: RichText?) { + public init(index: Int, media: EngineMedia, url: InstantPageUrlItem?, caption: RichText?, credit: RichText?) { self.index = index self.media = media self.url = url @@ -18,6 +17,6 @@ public struct InstantPageMedia: Equatable { } public static func ==(lhs: InstantPageMedia, rhs: InstantPageMedia) -> Bool { - return lhs.index == rhs.index && lhs.media.isEqual(to: rhs.media) && lhs.url == rhs.url && lhs.caption == rhs.caption && lhs.credit == rhs.credit + return lhs.index == rhs.index && lhs.media == rhs.media && lhs.url == rhs.url && lhs.caption == rhs.caption && lhs.credit == rhs.credit } } diff --git a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift index ec14e033a1..7a29e48b2d 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -22,7 +21,11 @@ struct InstantPageMediaPlaylistItemId: SharedMediaPlaylistItemId { } private func extractFileMedia(_ item: InstantPageMedia) -> TelegramMediaFile? { - return item.media as? TelegramMediaFile + if case let .file(file) = item.media { + return file + } else { + return nil + } } final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { @@ -114,7 +117,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { } struct InstantPageMediaPlaylistId: SharedMediaPlaylistId { - let webpageId: MediaId + let webpageId: EngineMedia.Id func isEqual(to: SharedMediaPlaylistId) -> Bool { if let to = to as? InstantPageMediaPlaylistId { @@ -125,7 +128,7 @@ struct InstantPageMediaPlaylistId: SharedMediaPlaylistId { } struct InstantPagePlaylistLocation: Equatable, SharedMediaPlaylistLocation { - let webpageId: MediaId + let webpageId: EngineMedia.Id func isEqual(to: SharedMediaPlaylistLocation) -> Bool { guard let to = to as? InstantPagePlaylistLocation else { diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift index 5aa04bf6da..a5f7adf60a 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData @@ -14,12 +13,12 @@ public final class InstantPagePeerReferenceItem: InstantPageItem { public let separatesTiles: Bool = false public let medias: [InstantPageMedia] = [] - let initialPeer: Peer + let initialPeer: EnginePeer let safeInset: CGFloat let transparent: Bool let rtl: Bool - init(frame: CGRect, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool) { + init(frame: CGRect, initialPeer: EnginePeer, safeInset: CGFloat, transparent: Bool, rtl: Bool) { self.frame = frame self.initialPeer = initialPeer self.safeInset = safeInset diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift index ab4b3d8caa..8958c6aef3 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import AsyncDisplayKit import Display @@ -64,13 +63,13 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { private let activityIndicator: ActivityIndicator private let checkNode: ASImageNode - var peer: Peer? + var peer: EnginePeer? private var peerDisposable: Disposable? private let joinDisposable = MetaDisposable() private var joinState: JoinState = .none - init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (EnginePeer) -> Void) { + init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: EnginePeer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.strings = strings self.nameDisplayOrder = nameDisplayOrder @@ -147,26 +146,26 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { let account = self.context.account let context = self.context - let signal = actualizedPeer(postbox: account.postbox, network: account.network, peer: initialPeer) - |> mapToSignal({ peer -> Signal in + let signal: Signal = actualizedPeer(postbox: account.postbox, network: account.network, peer: initialPeer._asPeer()) + |> mapToSignal({ peer -> Signal in if let peer = peer as? TelegramChannel, let username = peer.addressName, peer.accessHash == nil { - return .single(peer) |> then(context.engine.peers.resolvePeerByName(name: username) - |> mapToSignal({ updatedPeer -> Signal in + return .single(.channel(peer)) |> then(context.engine.peers.resolvePeerByName(name: username) + |> mapToSignal({ updatedPeer -> Signal in if let updatedPeer = updatedPeer { - return .single(updatedPeer._asPeer()) + return .single(updatedPeer) } else { - return .single(peer) + return .single(.channel(peer)) } })) } else { - return .single(peer) + return .single(EnginePeer(peer)) } }) self.peerDisposable = (signal |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self { strongSelf.peer = peer - if let peer = peer as? TelegramChannel { + if case let .channel(peer) = peer { var joinState = strongSelf.joinState if case .member = peer.participationStatus { switch joinState { @@ -210,7 +209,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { private func applyThemeAndStrings(themeUpdated: Bool) { if let peer = self.peer { let textColor = self.transparent ? UIColor.white : self.theme.panelPrimaryColor - self.nameNode.attributedText = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: textColor) + self.nameNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: textColor) } let accentColor = self.transparent ? UIColor.white : self.theme.panelAccentColor self.joinNode.setAttributedTitle(NSAttributedString(string: self.strings.Channel_JoinChannel, font: Font.medium(17.0), textColor: accentColor), for: []) @@ -300,7 +299,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { @objc func buttonPressed() { if let peer = self.peer { - self.openPeer(EnginePeer(peer)) + self.openPeer(peer) } } diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift index 91e0442785..3470e9d9c6 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift index 7f03324a4f..df36f4c416 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -29,7 +28,7 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler private var currentSize: CGSize? - private var fetchStatus: MediaResourceStatus? + private var fetchStatus: EngineMediaResource.FetchStatus? private var fetchedDisposable = MetaDisposable() private var statusDisposable = MetaDisposable() @@ -47,17 +46,19 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler self.openMedia = openMedia var imageReference: ImageMediaReference? - if let file = media.media as? TelegramMediaFile, let presentation = smallestImageRepresentation(file.previewRepresentations) { - let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [presentation], immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + if case let .file(file) = media.media, let presentation = smallestImageRepresentation(file.previewRepresentations) { + let image = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [presentation], immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) } var streamVideo = false - if let file = media.media as? TelegramMediaFile { + var fileValue: TelegramMediaFile? + if case let .file(file) = media.media { streamVideo = isMediaStreamable(media: file) + fileValue = file } - self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), userLocation: userLocation, fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), imageReference: imageReference, streamVideo: streamVideo ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor, storeAfterDownload: nil), priority: .embedded, autoplay: true) + self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), userLocation: userLocation, fileReference: .webPage(webPage: WebpageReference(webPage), media: fileValue!), imageReference: imageReference, streamVideo: streamVideo ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor, storeAfterDownload: nil), priority: .embedded, autoplay: true) self.videoNode.isUserInteractionEnabled = false self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) @@ -66,13 +67,13 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler self.addSubnode(self.videoNode) - if let file = media.media as? TelegramMediaFile { + if case let .file(file) = media.media { self.fetchedDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .video, reference: AnyMediaReference.webPage(webPage: WebpageReference(webPage), media: file).resourceReference(file.resource)).start()) self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = status + strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) strongSelf.updateFetchStatus() } } diff --git a/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift b/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift index 64860688c0..e08a437250 100644 --- a/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift index 35c7193402..a393f70ef0 100644 --- a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SafariServices import TelegramPresentationData @@ -197,9 +196,9 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie if self.contentNode == nil || self.contentNode?.frame.width != width { self.contentNode?.removeFromSupernode() - var media: [MediaId: Media] = [:] + var media: [EngineMedia.Id: EngineMedia] = [:] if case let .Loaded(content) = self.webPage.content, let instantPage = content.instantPage { - media = instantPage.media + media = instantPage.media.mapValues(EngineMedia.init) } let sideInset: CGFloat = 16.0 diff --git a/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift b/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift index 27dd83326a..cf9902a482 100644 --- a/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import Display import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift b/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift index b269e96484..da94bff3d6 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift b/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift index 4c99765ada..0d463b3d90 100644 --- a/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift index ad1d996d50..d812d7e85e 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift index f0a902acf0..335f060cc2 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift @@ -186,9 +186,9 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe private func makeNodeForItem(at index: Int) -> InstantPageSlideshowItemNode { let media = self.items[index] let contentNode: ASDisplayNode - if let _ = media.media as? TelegramMediaImage { + if case .image = media.media { contentNode = InstantPageImageNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia, activatePinchPreview: self.activatePinchPreview, pinchPreviewFinished: self.pinchPreviewFinished) - } else if let _ = media.media as? TelegramMediaFile { + } else if case .file = media.media { contentNode = ASDisplayNode() } else { contentNode = ASDisplayNode() diff --git a/submodules/InstantPageUI/Sources/InstantPageStoredState.swift b/submodules/InstantPageUI/Sources/InstantPageStoredState.swift index 9d2606e675..e9184c3243 100644 --- a/submodules/InstantPageUI/Sources/InstantPageStoredState.swift +++ b/submodules/InstantPageUI/Sources/InstantPageStoredState.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences @@ -58,7 +57,7 @@ public final class InstantPageStoredState: Codable { } public func instantPageStoredState(engine: TelegramEngine, webPage: TelegramMediaWebpage) -> Signal { - let key = ValueBoxKey(length: 8) + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: webPage.webpageId.id) return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.instantPageStoredState, id: key)) @@ -68,7 +67,7 @@ public func instantPageStoredState(engine: TelegramEngine, webPage: TelegramMedi } public func updateInstantPageStoredStateInteractively(engine: TelegramEngine, webPage: TelegramMediaWebpage, state: InstantPageStoredState?) -> Signal { - let key = ValueBoxKey(length: 8) + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: webPage.webpageId.id) if let state = state { diff --git a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift index d8146a8b30..bc552dc995 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift index c94f059ab4..01895ba2ac 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import Display import TelegramPresentationData import TelegramUIPreferences @@ -277,7 +276,7 @@ private func offestForVerticalAlignment(_ verticalAlignment: TableVerticalAlignm } } -func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: InstantPageTextStyleStack, theme: InstantPageTheme, bordered: Bool, striped: Bool, boundingWidth: CGFloat, horizontalInset: CGFloat, media: [MediaId: Media], webpage: TelegramMediaWebpage) -> InstantPageTableItem { +func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: InstantPageTextStyleStack, theme: InstantPageTheme, bordered: Bool, striped: Bool, boundingWidth: CGFloat, horizontalInset: CGFloat, media: [EngineMedia.Id: EngineMedia], webpage: TelegramMediaWebpage) -> InstantPageTableItem { if rows.count == 0 { return InstantPageTableItem(frame: CGRect(), totalWidth: 0.0, horizontalInset: 0.0, borderWidth: 0.0, theme: theme, cells: [], rtl: rtl) } diff --git a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift index bdf7262ebf..6fa1ee6ec3 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import TelegramCore import Display -import Postbox import AsyncDisplayKit import TelegramPresentationData import TelegramUIPreferences @@ -12,9 +11,9 @@ import ContextUI public final class InstantPageUrlItem: Equatable { public let url: String - public let webpageId: MediaId? + public let webpageId: EngineMedia.Id? - public init(url: String, webpageId: MediaId?) { + public init(url: String, webpageId: EngineMedia.Id?) { self.url = url self.webpageId = webpageId } @@ -36,7 +35,7 @@ struct InstantPageTextStrikethroughItem { struct InstantPageTextImageItem { let frame: CGRect let range: NSRange - let id: MediaId + let id: EngineMedia.Id } struct InstantPageTextAnchorItem { @@ -649,7 +648,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt } } -func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0, opaqueBackground: Bool = false) -> (InstantPageTextItem?, [InstantPageItem], CGSize) { +func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [EngineMedia.Id: EngineMedia] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0, opaqueBackground: Bool = false) -> (InstantPageTextItem?, [InstantPageItem], CGSize) { if string.length == 0 { return (nil, [], CGSize()) } @@ -771,7 +770,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo extraDescent = max(extraDescent, imageFrame.maxY - (workingLineOrigin.y + fontLineHeight + minSpacing)) } maxImageHeight = max(maxImageHeight, imageFrame.height) - lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: MediaId(namespace: Namespaces.Media.CloudFile, id: id))) + lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: id))) } } } @@ -876,8 +875,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo for line in textItem.lines { let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) for imageItem in line.imageItems { - if let image = media[imageItem.id] as? TelegramMediaFile { - let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) + if case let .image(image) = media[imageItem.id] { + let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: .image(image), url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) additionalItems.append(item) if item.frame.minY < topInset { diff --git a/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift b/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift index ec8ebdf9a8..b8863934f5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift index f15d215be8..658898edae 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift @@ -389,7 +389,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode { let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context) avatarListWrapperNode.contentNode.clipsToBounds = true avatarListNode.backgroundColor = .clear - avatarListNode.peer = peer + avatarListNode.peer = EnginePeer(peer) avatarListNode.firstFullSizeOnly = true avatarListNode.offsetLocation = true avatarListNode.customCenterTapAction = { [weak self] in @@ -405,7 +405,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode { avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode) avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode) - avatarListNode.update(size: targetRect.size, peer: peer, customNode: nil, additionalEntry: .single(nil), isExpanded: true, transition: .immediate) + avatarListNode.update(size: targetRect.size, peer: EnginePeer(peer), customNode: nil, additionalEntry: .single(nil), isExpanded: true, transition: .immediate) strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode) strongSelf.avatarListWrapperNode = avatarListWrapperNode diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 45111f5b29..7902eedd0c 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -878,7 +878,7 @@ public final class ListMessageFileItemNode: ListMessageNode { if statusUpdated && item.displayFileInfo { if let file = selectedMedia as? TelegramMediaFile { - updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) + updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) |> mapToSignal { value -> Signal in if case .Fetching = value.fetchStatus, !item.isDownloadList { return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) @@ -905,10 +905,10 @@ public final class ListMessageFileItemNode: ListMessageNode { } } if isVoice { - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) } } else if let image = selectedMedia as? TelegramMediaImage { - updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList) + updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList) |> mapToSignal { value -> Signal in if case .Fetching = value.fetchStatus, !item.isDownloadList { return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) diff --git a/submodules/LottieMeshSwift/BUILD b/submodules/LottieMeshSwift/BUILD deleted file mode 100644 index ef088b0ef7..0000000000 --- a/submodules/LottieMeshSwift/BUILD +++ /dev/null @@ -1,156 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -load( - "@build_bazel_rules_apple//apple:resources.bzl", - "apple_resource_bundle", - "apple_resource_group", -) -load("//build-system/bazel-utils:plist_fragment.bzl", - "plist_fragment", -) - -filegroup( - name = "LottieMeshSwiftMetalResources", - srcs = glob([ - "Resources/**/*.metal", - ]), - visibility = ["//visibility:public"], -) - -plist_fragment( - name = "LottieMeshSwiftBundleInfoPlist", - extension = "plist", - template = - """ - CFBundleIdentifier - org.telegram.LottieMeshSwift - CFBundleDevelopmentRegion - en - CFBundleName - LottieMeshSwift - """ -) - -apple_resource_bundle( - name = "LottieMeshSwiftBundle", - infoplists = [ - ":LottieMeshSwiftBundleInfoPlist", - ], - resources = [ - ":LottieMeshSwiftMetalResources", - ], -) - -config_setting( - name = "debug_build", - values = { - "compilation_mode": "dbg", - }, -) - -optimization_flags = select({ - ":debug_build": [ - "-O2", - ], - "//conditions:default": [], -}) - -swift_optimization_flags = select({ - ":debug_build": [ - #"-O", - ], - "//conditions:default": [], -}) - -swift_library( - name = "LottieMeshSwift", - module_name = "LottieMeshSwift", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ] + swift_optimization_flags, - data = [ - ":LottieMeshSwiftBundle", - ], - deps = [ - ":LottieMeshBinding", - "//submodules/Postbox:Postbox", - "//submodules/ManagedFile:ManagedFile", - ], - visibility = [ - "//visibility:public", - ], -) - -objc_library( - name = "LottieMeshBinding", - enable_modules = True, - module_name = "LottieMeshBinding", - srcs = glob([ - "LottieMeshBinding/Sources/**/*.m", - "LottieMeshBinding/Sources/**/*.mm", - "LottieMeshBinding/Sources/**/*.h", - ]), - copts = optimization_flags, - hdrs = glob([ - "LottieMeshBinding/PublicHeaders/**/*.h", - ]), - includes = [ - "LottieMeshBinding/PublicHeaders", - ], - deps = [ - ":LottieMesh", - ], - sdk_frameworks = [ - "Foundation", - ], - visibility = [ - "//visibility:public", - ], -) - -cc_library( - name = "LottieMesh", - srcs = glob([ - "LottieMesh/Sources/**/*.cpp", - "LottieMesh/Sources/**/*.h", - "LottieMesh/Sources/**/*.hpp", - ]), - copts = [ - "-Isubmodules/LottieMeshSwift/libtess2/Include", - ] + optimization_flags, - hdrs = glob([ - "LottieMesh/PublicHeaders/**/*.h", - "LottieMesh/PublicHeaders/**/*.hpp", - ]), - includes = [ - "LottieMesh/PublicHeaders", - ], - deps = [ - ":libtess2", - ], - visibility = [ - "//visibility:public", - ], -) - -cc_library( - name = "libtess2", - srcs = glob([ - "libtess2/Sources/**/*.c", - "libtess2/Sources/**/*.h", - "libtess2/Include/**/*.h", - ]), - copts = [ - "-Isubmodules/LottieMeshSwift/libtess2/Include", - ] + optimization_flags, - hdrs = glob([ - "libtess2/Include/**/*.h", - ]), - deps = [ - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/LottieMesh.h b/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/LottieMesh.h deleted file mode 100644 index 2a2bb42154..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/LottieMesh.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef LottieMesh_h -#define LottieMesh_h - -#include -#include - -#include "Point.h" - -namespace MeshGenerator { - -struct Path { - std::vector points; -}; - -struct Fill { - enum class Rule { - EvenOdd, - NonZero - }; - - Rule rule = Rule::EvenOdd; - - explicit Fill(Rule rule_) : - rule(rule_) { - } -}; - -struct Stroke { - enum class LineJoin { - Miter, - Round, - Bevel - }; - - enum class LineCap { - Butt, - Round, - Square - }; - - float lineWidth = 0.0f; - LineJoin lineJoin = LineJoin::Round; - LineCap lineCap = LineCap::Round; - float miterLimit = 10.0f; - - explicit Stroke(float lineWidth_, LineJoin lineJoin_, LineCap lineCap_, float miterLimit_) : - lineWidth(lineWidth_), lineJoin(lineJoin_), lineCap(lineCap_), miterLimit(miterLimit_) { - } -}; - -struct Mesh { - std::vector vertices; - std::vector triangles; -}; - -std::unique_ptr generateMesh(std::vector const &paths, std::unique_ptr fill, std::unique_ptr stroke); - -} - -#endif /* LottieMesh_h */ diff --git a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/Point.h b/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/Point.h deleted file mode 100644 index 5c412f6252..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/Point.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef Point_h -#define Point_h - -#include -#include - -namespace MeshGenerator { - -struct Point { - float x = 0.0f; - float y = 0.0f; - - Point(float x_, float y_) : - x(x_), y(y_) { - } - - Point() : Point(0.0f, 0.0f) { - } - - bool isEqual(Point const &other, float epsilon = 0.0001f) const { - return std::abs(x - other.x) <= epsilon && std::abs(y - other.y) <= epsilon; - } - - float distance(Point const &other) const { - float dx = x - other.x; - float dy = y - other.y; - return sqrtf(dx * dx + dy * dy); - } - - bool operator< (Point const &other) const { - if (x < other.x) { - return true; - } - if (x > other.x) { - return false; - } - return y < other.y; - } - - bool operator== (Point const &other) const { - return isEqual(other); - } -}; - -} - -#endif diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/LineSegment.h b/submodules/LottieMeshSwift/LottieMesh/Sources/LineSegment.h deleted file mode 100644 index 890351c2bc..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/LineSegment.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "Vec2.h" -#include - -namespace crushedpixel { - -template -struct LineSegment { - LineSegment(const Vec2 &a, const Vec2 &b) : - a(a), b(b) {} - - Vec2 a, b; - - /** - * @return A copy of the line segment, offset by the given vector. - */ - LineSegment operator+(const Vec2 &toAdd) const { - return {Vec2Maths::add(a, toAdd), Vec2Maths::add(b, toAdd)}; - } - - /** - * @return A copy of the line segment, offset by the given vector. - */ - LineSegment operator-(const Vec2 &toRemove) const { - return {Vec2Maths::subtract(a, toRemove), Vec2Maths::subtract(b, toRemove)}; - } - - /** - * @return The line segment's normal vector. - */ - Vec2 normal() const { - auto dir = direction(); - - // return the direction vector - // rotated by 90 degrees counter-clockwise - return {-dir.y, dir.x}; - } - - /** - * @return The line segment's direction vector. - */ - Vec2 direction(bool normalized = true) const { - auto vec = Vec2Maths::subtract(b, a); - - return normalized - ? Vec2Maths::normalized(vec) - : vec; - } - - static Vec2 intersection(const LineSegment &a, const LineSegment &b, bool infiniteLines, bool &success) { - success = true; - - // calculate un-normalized direction vectors - auto r = a.direction(false); - auto s = b.direction(false); - - auto originDist = Vec2Maths::subtract(b.a, a.a); - - auto uNumerator = Vec2Maths::cross(originDist, r); - auto denominator = Vec2Maths::cross(r, s); - - if (std::abs(denominator) < 0.0001f) { - // The lines are parallel - success = false; - return Vec2(); - } - - // solve the intersection positions - auto u = uNumerator / denominator; - auto t = Vec2Maths::cross(originDist, s) / denominator; - - if (!infiniteLines && (t < 0 || t > 1 || u < 0 || u > 1)) { - // the intersection lies outside of the line segments - success = false; - return Vec2(); - } - - // calculate the intersection point - // a.a + r * t; - return Vec2Maths::add(a.a, Vec2Maths::multiply(r, t)); - } -}; - - -} // namespace crushedpixel diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/LottieMesh.cpp b/submodules/LottieMeshSwift/LottieMesh/Sources/LottieMesh.cpp deleted file mode 100644 index 06a75612be..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/LottieMesh.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include -#include "Triangulation.h" - -#include "tesselator.h" -#include "Polyline2D.h" - -namespace MeshGenerator { - -std::unique_ptr generateMesh(std::vector const &paths, std::unique_ptr fill, std::unique_ptr stroke) { - if (stroke) { - std::unique_ptr mesh = std::make_unique(); - - for (const auto &path : paths) { - crushedpixel::Polyline2D::JointStyle jointStyle = crushedpixel::Polyline2D::JointStyle::ROUND; - crushedpixel::Polyline2D::EndCapStyle endCapStyle = crushedpixel::Polyline2D::EndCapStyle::SQUARE; - switch (stroke->lineJoin) { - case Stroke::LineJoin::Miter: - jointStyle = crushedpixel::Polyline2D::JointStyle::MITER; - break; - case Stroke::LineJoin::Round: - jointStyle = crushedpixel::Polyline2D::JointStyle::ROUND; - break; - case Stroke::LineJoin::Bevel: - jointStyle = crushedpixel::Polyline2D::JointStyle::BEVEL; - break; - default: { - break; - } - } - switch (stroke->lineCap) { - case Stroke::LineCap::Round: { - endCapStyle = crushedpixel::Polyline2D::EndCapStyle::ROUND; - break; - } - case Stroke::LineCap::Square: { - endCapStyle = crushedpixel::Polyline2D::EndCapStyle::SQUARE; - break; - } - case Stroke::LineCap::Butt: { - endCapStyle = crushedpixel::Polyline2D::EndCapStyle::BUTT; - break; - } - default: { - break; - } - } - - auto vertices = crushedpixel::Polyline2D::create(path.points, stroke->lineWidth, jointStyle, endCapStyle); - for (const auto &vertex : vertices) { - mesh->triangles.push_back((int)mesh->vertices.size()); - mesh->vertices.push_back(vertex); - } - } - - assert(mesh->triangles.size() % 3 == 0); - return mesh; - } else if (fill) { - TESStesselator *tessellator = tessNewTess(NULL); - tessSetOption(tessellator, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, 1); - for (const auto &path : paths) { - tessAddContour(tessellator, 2, path.points.data(), sizeof(Point), (int)path.points.size()); - } - - switch (fill->rule) { - case Fill::Rule::EvenOdd: { - tessTesselate(tessellator, TESS_WINDING_ODD, TESS_POLYGONS, 3, 2, NULL); - break; - } - default: { - tessTesselate(tessellator, TESS_WINDING_NONZERO, TESS_POLYGONS, 3, 2, NULL); - break; - } - } - - int vertexCount = tessGetVertexCount(tessellator); - const TESSreal *vertices = tessGetVertices(tessellator); - int indexCount = tessGetElementCount(tessellator) * 3; - const TESSindex *indices = tessGetElements(tessellator); - - std::unique_ptr mesh = std::make_unique(); - for (int i = 0; i < vertexCount; i++) { - mesh->vertices.push_back(Point(vertices[i * 2 + 0], vertices[i * 2 + 1])); - } - for (int i = 0; i < indexCount; i++) { - mesh->triangles.push_back(indices[i]); - } - return mesh; - } else { - return nullptr; - } -} - -} diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Polyline2D.h b/submodules/LottieMeshSwift/LottieMesh/Sources/Polyline2D.h deleted file mode 100644 index 7c95177d1d..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Polyline2D.h +++ /dev/null @@ -1,446 +0,0 @@ -#pragma once - -#include "LineSegment.h" -#include -#include -#include - -namespace crushedpixel { - -class Polyline2D { -public: - enum class JointStyle { - /** - * Corners are drawn with sharp joints. - * If the joint's outer angle is too large, - * the joint is drawn as beveled instead, - * to avoid the miter extending too far out. - */ - MITER, - /** - * Corners are flattened. - */ - BEVEL, - /** - * Corners are rounded off. - */ - ROUND - }; - - enum class EndCapStyle { - /** - * Path ends are drawn flat, - * and don't exceed the actual end point. - */ - BUTT, // lol - /** - * Path ends are drawn flat, - * but extended beyond the end point - * by half the line thickness. - */ - SQUARE, - /** - * Path ends are rounded off. - */ - ROUND, - /** - * Path ends are connected according to the JointStyle. - * When using this EndCapStyle, don't specify the common start/end point twice, - * as Polyline2D connects the first and last input point itself. - */ - JOINT - }; - - /** - * Creates a vector of vertices describing a solid path through the input points. - * @param points The points of the path. - * @param thickness The path's thickness. - * @param jointStyle The path's joint style. - * @param endCapStyle The path's end cap style. - * @param allowOverlap Whether to allow overlapping vertices. - * This yields better results when dealing with paths - * whose points have a distance smaller than the thickness, - * but may introduce overlapping vertices, - * which is undesirable when rendering transparent paths. - * @return The vertices describing the path. - * @tparam Vec2 The vector type to use for the vertices. - * Must have public non-const float fields "x" and "y". - * Must have a two-args constructor taking x and y values. - * See crushedpixel::Vec2 for a type that satisfies these requirements. - * @tparam InputCollection The collection type of the input points. - * Must contain elements of type Vec2. - * Must expose size() and operator[] functions. - */ - template - static std::vector create(const InputCollection &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - std::vector vertices; - create(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap); - return vertices; - } - - template - static std::vector create(const std::vector &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - std::vector vertices; - create>(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap); - return vertices; - } - - template - static size_t create(std::vector &vertices, const InputCollection &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - auto numVerticesBefore = vertices.size(); - - create(std::back_inserter(vertices), points, thickness, - jointStyle, endCapStyle, allowOverlap); - - return vertices.size() - numVerticesBefore; - } - - template - static OutputIterator create(OutputIterator vertices, const InputCollection &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - // operate on half the thickness to make our lives easier - thickness /= 2; - - // create poly segments from the points - std::vector> segments; - for (size_t i = 0; i + 1 < points.size(); i++) { - auto &point1 = points[i]; - auto &point2 = points[i + 1]; - - // to avoid division-by-zero errors, - // only create a line segment for non-identical points - if (!Vec2Maths::equal(point1, point2)) { - segments.emplace_back(LineSegment(point1, point2), thickness); - } - } - - if (endCapStyle == EndCapStyle::JOINT) { - // create a connecting segment from the last to the first point - - auto &point1 = points[points.size() - 1]; - auto &point2 = points[0]; - - // to avoid division-by-zero errors, - // only create a line segment for non-identical points - if (!Vec2Maths::equal(point1, point2)) { - segments.emplace_back(LineSegment(point1, point2), thickness); - } - } - - if (segments.empty()) { - // handle the case of insufficient input points - return vertices; - } - - Vec2 nextStart1{0, 0}; - Vec2 nextStart2{0, 0}; - Vec2 start1{0, 0}; - Vec2 start2{0, 0}; - Vec2 end1{0, 0}; - Vec2 end2{0, 0}; - - // calculate the path's global start and end points - auto &firstSegment = segments[0]; - auto &lastSegment = segments[segments.size() - 1]; - - auto pathStart1 = firstSegment.edge1.a; - auto pathStart2 = firstSegment.edge2.a; - auto pathEnd1 = lastSegment.edge1.b; - auto pathEnd2 = lastSegment.edge2.b; - - // handle different end cap styles - if (endCapStyle == EndCapStyle::SQUARE) { - // extend the start/end points by half the thickness - pathStart1 = Vec2Maths::subtract(pathStart1, Vec2Maths::multiply(firstSegment.edge1.direction(), thickness)); - pathStart2 = Vec2Maths::subtract(pathStart2, Vec2Maths::multiply(firstSegment.edge2.direction(), thickness)); - pathEnd1 = Vec2Maths::add(pathEnd1, Vec2Maths::multiply(lastSegment.edge1.direction(), thickness)); - pathEnd2 = Vec2Maths::add(pathEnd2, Vec2Maths::multiply(lastSegment.edge2.direction(), thickness)); - - } else if (endCapStyle == EndCapStyle::ROUND) { - // draw half circle end caps - createTriangleFan(vertices, firstSegment.center.a, firstSegment.center.a, - firstSegment.edge1.a, firstSegment.edge2.a, false); - createTriangleFan(vertices, lastSegment.center.b, lastSegment.center.b, - lastSegment.edge1.b, lastSegment.edge2.b, true); - - } else if (endCapStyle == EndCapStyle::JOINT) { - // join the last (connecting) segment and the first segment - createJoint(vertices, lastSegment, firstSegment, jointStyle, - pathEnd1, pathEnd2, pathStart1, pathStart2, allowOverlap); - } - - // generate mesh data for path segments - for (size_t i = 0; i < segments.size(); i++) { - auto &segment = segments[i]; - - // calculate start - if (i == 0) { - // this is the first segment - start1 = pathStart1; - start2 = pathStart2; - } - - if (i + 1 == segments.size()) { - // this is the last segment - end1 = pathEnd1; - end2 = pathEnd2; - - } else { - createJoint(vertices, segment, segments[i + 1], jointStyle, - end1, end2, nextStart1, nextStart2, allowOverlap); - } - - // emit vertices - *vertices++ = start1; - *vertices++ = start2; - *vertices++ = end1; - - *vertices++ = end1; - *vertices++ = start2; - *vertices++ = end2; - - start1 = nextStart1; - start2 = nextStart2; - } - - return vertices; - } - -private: - static constexpr float pi = 3.14159265358979323846f; - - /** - * The threshold for mitered joints. - * If the joint's angle is smaller than this angle, - * the joint will be drawn beveled instead. - */ - static constexpr float miterMinAngle = 0.349066; // ~20 degrees - - /** - * The minimum angle of a round joint's triangles. - */ - static constexpr float roundMinAngle = 0.174533; // ~10 degrees - - template - struct PolySegment { - PolySegment(const LineSegment ¢er, float thickness) : - center(center), - // calculate the segment's outer edges by offsetting - // the central line by the normal vector - // multiplied with the thickness - - // center + center.normal() * thickness - edge1(center + Vec2Maths::multiply(center.normal(), thickness)), - edge2(center - Vec2Maths::multiply(center.normal(), thickness)) {} - - LineSegment center, edge1, edge2; - }; - - template - static OutputIterator createJoint(OutputIterator vertices, - const PolySegment &segment1, const PolySegment &segment2, - JointStyle jointStyle, Vec2 &end1, Vec2 &end2, - Vec2 &nextStart1, Vec2 &nextStart2, - bool allowOverlap) { - // calculate the angle between the two line segments - auto dir1 = segment1.center.direction(); - auto dir2 = segment2.center.direction(); - - auto angle = Vec2Maths::angle(dir1, dir2); - - // wrap the angle around the 180° mark if it exceeds 90° - // for minimum angle detection - auto wrappedAngle = angle; - if (wrappedAngle > pi / 2) { - wrappedAngle = pi - wrappedAngle; - } - - if (jointStyle == JointStyle::MITER && wrappedAngle < miterMinAngle) { - // the minimum angle for mitered joints wasn't exceeded. - // to avoid the intersection point being extremely far out, - // thus producing an enormous joint like a rasta on 4/20, - // we render the joint beveled instead. - jointStyle = JointStyle::BEVEL; - } - - if (jointStyle == JointStyle::MITER) { - // calculate each edge's intersection point - // with the next segment's central line - bool sec1Success = true; - bool sec2Success = true; - auto sec1 = LineSegment::intersection(segment1.edge1, segment2.edge1, true, sec1Success); - auto sec2 = LineSegment::intersection(segment1.edge2, segment2.edge2, true, sec2Success); - - end1 = sec1Success ? sec1 : segment1.edge1.b; - end2 = sec2Success ? sec2 : segment1.edge2.b; - - nextStart1 = end1; - nextStart2 = end2; - - } else { - // joint style is either BEVEL or ROUND - - // find out which are the inner edges for this joint - auto x1 = dir1.x; - auto x2 = dir2.x; - auto y1 = dir1.y; - auto y2 = dir2.y; - - auto clockwise = x1 * y2 - x2 * y1 < 0; - - const LineSegment *inner1, *inner2, *outer1, *outer2; - - // as the normal vector is rotated counter-clockwise, - // the first edge lies to the left - // from the central line's perspective, - // and the second one to the right. - if (clockwise) { - outer1 = &segment1.edge1; - outer2 = &segment2.edge1; - inner1 = &segment1.edge2; - inner2 = &segment2.edge2; - } else { - outer1 = &segment1.edge2; - outer2 = &segment2.edge2; - inner1 = &segment1.edge1; - inner2 = &segment2.edge1; - } - - // calculate the intersection point of the inner edges - bool innerSecOptSuccess = true; - auto innerSecOpt = LineSegment::intersection(*inner1, *inner2, allowOverlap, innerSecOptSuccess); - - auto innerSec = innerSecOptSuccess - ? innerSecOpt - // for parallel lines, simply connect them directly - : inner1->b; - - // if there's no inner intersection, flip - // the next start position for near-180° turns - Vec2 innerStart; - if (innerSecOptSuccess) { - innerStart = innerSec; - } else if (angle > pi / 2) { - innerStart = outer1->b; - } else { - innerStart = inner1->b; - } - - if (clockwise) { - end1 = outer1->b; - end2 = innerSec; - - nextStart1 = outer2->a; - nextStart2 = innerStart; - - } else { - end1 = innerSec; - end2 = outer1->b; - - nextStart1 = innerStart; - nextStart2 = outer2->a; - } - - // connect the intersection points according to the joint style - - if (jointStyle == JointStyle::BEVEL) { - // simply connect the intersection points - *vertices++ = outer1->b; - *vertices++ = outer2->a; - *vertices++ = innerSec; - - } else if (jointStyle == JointStyle::ROUND) { - // draw a circle between the ends of the outer edges, - // centered at the actual point - // with half the line thickness as the radius - createTriangleFan(vertices, innerSec, segment1.center.b, outer1->b, outer2->a, clockwise); - } else { - assert(false); - } - } - - return vertices; - } - - /** - * Creates a partial circle between two points. - * The points must be equally far away from the origin. - * @param vertices The vector to add vertices to. - * @param connectTo The position to connect the triangles to. - * @param origin The circle's origin. - * @param start The circle's starting point. - * @param end The circle's ending point. - * @param clockwise Whether the circle's rotation is clockwise. - */ - template - static OutputIterator createTriangleFan(OutputIterator vertices, Vec2 connectTo, Vec2 origin, - Vec2 start, Vec2 end, bool clockwise) { - - auto point1 = Vec2Maths::subtract(start, origin); - auto point2 = Vec2Maths::subtract(end, origin); - - // calculate the angle between the two points - auto angle1 = atan2(point1.y, point1.x); - auto angle2 = atan2(point2.y, point2.x); - - // ensure the outer angle is calculated - if (clockwise) { - if (angle2 > angle1) { - angle2 = angle2 - 2 * pi; - } - } else { - if (angle1 > angle2) { - angle1 = angle1 - 2 * pi; - } - } - - auto jointAngle = angle2 - angle1; - - // calculate the amount of triangles to use for the joint - auto numTriangles = std::max(1, (int) std::floor(std::abs(jointAngle) / roundMinAngle)); - - // calculate the angle of each triangle - auto triAngle = jointAngle / numTriangles; - - Vec2 startPoint = start; - Vec2 endPoint; - for (int t = 0; t < numTriangles; t++) { - if (t + 1 == numTriangles) { - // it's the last triangle - ensure it perfectly - // connects to the next line - endPoint = end; - } else { - auto rot = (t + 1) * triAngle; - - // rotate the original point around the origin - endPoint.x = std::cos(rot) * point1.x - std::sin(rot) * point1.y; - endPoint.y = std::sin(rot) * point1.x + std::cos(rot) * point1.y; - - // re-add the rotation origin to the target point - endPoint = Vec2Maths::add(endPoint, origin); - } - - // emit the triangle - *vertices++ = startPoint; - *vertices++ = endPoint; - *vertices++ = connectTo; - - startPoint = endPoint; - } - - return vertices; - } -}; - -} // namespace crushedpixel diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.cpp b/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.cpp deleted file mode 100644 index b293bc1b1b..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "Triangulation.h" - -#include -#include - -#include "earcut.hpp" - -namespace MeshGenerator { - -std::vector triangulatePolygon(std::vector const &points, std::vector &indices, std::vector> const &holeIndices) { - // The index type. Defaults to uint32_t, but you can also pass uint16_t if you know that your - // data won't have more than 65536 vertices. - using N = uint32_t; - - // Create array - using EarPoint = std::array; - std::vector> polygon; - - std::map facePointMapping; - int nextFacePointIndex = 0; - - std::vector facePoints; - for (auto index : indices) { - facePointMapping[nextFacePointIndex] = index; - nextFacePointIndex++; - - facePoints.push_back({ points[index].x, points[index].y }); - } - polygon.push_back(std::move(facePoints)); - - for (const auto &list : holeIndices) { - std::vector holePoints; - for (auto index : list) { - facePointMapping[nextFacePointIndex] = index; - nextFacePointIndex++; - - holePoints.push_back({ points[index].x, points[index].y }); - } - polygon.push_back(std::move(holePoints)); - } - - std::vector triangleIndices = mapbox::earcut(polygon); - - std::vector mappedIndices; - for (auto index : triangleIndices) { - mappedIndices.push_back(facePointMapping[index]); - } - return mappedIndices; -} - -} diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.h b/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.h deleted file mode 100644 index 4dfe9746a8..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef Triangulation_h -#define Triangulation_h - -#include - -#include - -namespace MeshGenerator { - -std::vector triangulatePolygon(std::vector const &points, std::vector &indices, std::vector> const &holeIndices); - -} - -#endif /* Triangulation_h */ diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Vec2.h b/submodules/LottieMeshSwift/LottieMesh/Sources/Vec2.h deleted file mode 100644 index 08b9ad1e30..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Vec2.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include - -namespace crushedpixel { - -/** - * A two-dimensional float vector. - * It exposes the x and y fields - * as required by the Polyline2D functions. - */ -struct Vec2 { - Vec2() : - Vec2(0, 0) {} - - Vec2(float x, float y) : - x(x), y(y) {} - - virtual ~Vec2() = default; - - float x, y; -}; - -namespace Vec2Maths { - -template -static bool equal(const Vec2 &a, const Vec2 &b) { - return a.x == b.x && a.y == b.y; -} - -template -static Vec2 multiply(const Vec2 &a, const Vec2 &b) { - return {a.x * b.x, a.y * b.y}; -} - -template -static Vec2 multiply(const Vec2 &vec, float factor) { - return {vec.x * factor, vec.y * factor}; -} - -template -static Vec2 divide(const Vec2 &vec, float factor) { - return {vec.x / factor, vec.y / factor}; -} - -template -static Vec2 add(const Vec2 &a, const Vec2 &b) { - return {a.x + b.x, a.y + b.y}; -} - -template -static Vec2 subtract(const Vec2 &a, const Vec2 &b) { - return {a.x - b.x, a.y - b.y}; -} - -template -static float magnitude(const Vec2 &vec) { - return std::sqrt(vec.x * vec.x + vec.y * vec.y); -} - -template -static Vec2 withLength(const Vec2 &vec, float len) { - auto mag = magnitude(vec); - auto factor = mag / len; - return divide(vec, factor); -} - -template -static Vec2 normalized(const Vec2 &vec) { - return withLength(vec, 1); -} - -/** - * Calculates the dot product of two vectors. - */ -template -static float dot(const Vec2 &a, const Vec2 &b) { - return a.x * b.x + a.y * b.y; -} - -/** - * Calculates the cross product of two vectors. - */ -template -static float cross(const Vec2 &a, const Vec2 &b) { - return a.x * b.y - a.y * b.x; -} - -/** - * Calculates the angle between two vectors. - */ -template -static float angle(const Vec2 &a, const Vec2 &b) { - return std::acos(dot(a, b) / (magnitude(a) * magnitude(b))); -} - -} // namespace Vec2Maths - -} \ No newline at end of file diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/earcut.hpp b/submodules/LottieMeshSwift/LottieMesh/Sources/earcut.hpp deleted file mode 100644 index e7d43088bf..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/earcut.hpp +++ /dev/null @@ -1,823 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace mapbox { - -namespace util { - -template struct nth { - inline static typename std::tuple_element::type - get(const T& t) { return std::get(t); }; -}; - -} - -namespace detail { - -template -class Earcut { -public: - std::vector indices; - std::size_t vertices = 0; - - template - void operator()(const Polygon& points); - -private: - struct Node { - Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {} - Node(const Node&) = delete; - Node& operator=(const Node&) = delete; - Node(Node&&) = delete; - Node& operator=(Node&&) = delete; - - const N i; - const double x; - const double y; - - // previous and next vertice nodes in a polygon ring - Node* prev = nullptr; - Node* next = nullptr; - - // z-order curve value - int32_t z = 0; - - // previous and next nodes in z-order - Node* prevZ = nullptr; - Node* nextZ = nullptr; - - // indicates whether this is a steiner point - bool steiner = false; - }; - - template Node* linkedList(const Ring& points, const bool clockwise); - Node* filterPoints(Node* start, Node* end = nullptr); - void earcutLinked(Node* ear, int pass = 0); - bool isEar(Node* ear); - bool isEarHashed(Node* ear); - Node* cureLocalIntersections(Node* start); - void splitEarcut(Node* start); - template Node* eliminateHoles(const Polygon& points, Node* outerNode); - Node* eliminateHole(Node* hole, Node* outerNode); - Node* findHoleBridge(Node* hole, Node* outerNode); - bool sectorContainsSector(const Node* m, const Node* p); - void indexCurve(Node* start); - Node* sortLinked(Node* list); - int32_t zOrder(const double x_, const double y_); - Node* getLeftmost(Node* start); - bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const; - bool isValidDiagonal(Node* a, Node* b); - double area(const Node* p, const Node* q, const Node* r) const; - bool equals(const Node* p1, const Node* p2); - bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2); - bool onSegment(const Node* p, const Node* q, const Node* r); - int sign(double val); - bool intersectsPolygon(const Node* a, const Node* b); - bool locallyInside(const Node* a, const Node* b); - bool middleInside(const Node* a, const Node* b); - Node* splitPolygon(Node* a, Node* b); - template Node* insertNode(std::size_t i, const Point& p, Node* last); - void removeNode(Node* p); - - bool hashing; - double minX, maxX; - double minY, maxY; - double inv_size = 0; - - template > - class ObjectPool { - public: - ObjectPool() { } - ObjectPool(std::size_t blockSize_) { - reset(blockSize_); - } - ~ObjectPool() { - clear(); - } - template - T* construct(Args&&... args) { - if (currentIndex >= blockSize) { - currentBlock = alloc_traits::allocate(alloc, blockSize); - allocations.emplace_back(currentBlock); - currentIndex = 0; - } - T* object = ¤tBlock[currentIndex++]; - alloc_traits::construct(alloc, object, std::forward(args)...); - return object; - } - void reset(std::size_t newBlockSize) { - for (auto allocation : allocations) { - alloc_traits::deallocate(alloc, allocation, blockSize); - } - allocations.clear(); - blockSize = std::max(1, newBlockSize); - currentBlock = nullptr; - currentIndex = blockSize; - } - void clear() { reset(blockSize); } - private: - T* currentBlock = nullptr; - std::size_t currentIndex = 1; - std::size_t blockSize = 1; - std::vector allocations; - Alloc alloc; - typedef typename std::allocator_traits alloc_traits; - }; - ObjectPool nodes; -}; - -template template -void Earcut::operator()(const Polygon& points) { - // reset - indices.clear(); - vertices = 0; - - if (points.empty()) return; - - double x; - double y; - int threshold = 80; - std::size_t len = 0; - - for (size_t i = 0; threshold >= 0 && i < points.size(); i++) { - threshold -= static_cast(points[i].size()); - len += points[i].size(); - } - - //estimate size of nodes and indices - nodes.reset(len * 3 / 2); - indices.reserve(len + points[0].size()); - - Node* outerNode = linkedList(points[0], true); - if (!outerNode || outerNode->prev == outerNode->next) return; - - if (points.size() > 1) outerNode = eliminateHoles(points, outerNode); - - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - hashing = threshold < 0; - if (hashing) { - Node* p = outerNode->next; - minX = maxX = outerNode->x; - minY = maxY = outerNode->y; - do { - x = p->x; - y = p->y; - minX = std::min(minX, x); - minY = std::min(minY, y); - maxX = std::max(maxX, x); - maxY = std::max(maxY, y); - p = p->next; - } while (p != outerNode); - - // minX, minY and size are later used to transform coords into integers for z-order calculation - inv_size = std::max(maxX - minX, maxY - minY); - inv_size = inv_size != .0 ? (1. / inv_size) : .0; - } - - earcutLinked(outerNode); - - nodes.clear(); -} - -// create a circular doubly linked list from polygon points in the specified winding order -template template -typename Earcut::Node* -Earcut::linkedList(const Ring& points, const bool clockwise) { - using Point = typename Ring::value_type; - double sum = 0; - const std::size_t len = points.size(); - std::size_t i, j; - Node* last = nullptr; - - // calculate original winding order of a polygon ring - for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) { - const auto& p1 = points[i]; - const auto& p2 = points[j]; - const double p20 = util::nth<0, Point>::get(p2); - const double p10 = util::nth<0, Point>::get(p1); - const double p11 = util::nth<1, Point>::get(p1); - const double p21 = util::nth<1, Point>::get(p2); - sum += (p20 - p10) * (p11 + p21); - } - - // link points into circular doubly-linked list in the specified winding order - if (clockwise == (sum > 0)) { - for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last); - } else { - for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last); - } - - if (last && equals(last, last->next)) { - removeNode(last); - last = last->next; - } - - vertices += len; - - return last; -} - -// eliminate colinear or duplicate points -template -typename Earcut::Node* -Earcut::filterPoints(Node* start, Node* end) { - if (!end) end = start; - - Node* p = start; - bool again; - do { - again = false; - - if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) { - removeNode(p); - p = end = p->prev; - - if (p == p->next) break; - again = true; - - } else { - p = p->next; - } - } while (again || p != end); - - return end; -} - -// main ear slicing loop which triangulates a polygon (given as a linked list) -template -void Earcut::earcutLinked(Node* ear, int pass) { - if (!ear) return; - - // interlink polygon nodes in z-order - if (!pass && hashing) indexCurve(ear); - - Node* stop = ear; - Node* prev; - Node* next; - - int iterations = 0; - - // iterate through ears, slicing them one by one - while (ear->prev != ear->next) { - iterations++; - prev = ear->prev; - next = ear->next; - - if (hashing ? isEarHashed(ear) : isEar(ear)) { - // cut off the triangle - indices.emplace_back(prev->i); - indices.emplace_back(ear->i); - indices.emplace_back(next->i); - - removeNode(ear); - - // skipping the next vertice leads to less sliver triangles - ear = next->next; - stop = next->next; - - continue; - } - - ear = next; - - // if we looped through the whole remaining polygon and can't find any more ears - if (ear == stop) { - // try filtering points and slicing again - if (!pass) earcutLinked(filterPoints(ear), 1); - - // if this didn't work, try curing all small self-intersections locally - else if (pass == 1) { - ear = cureLocalIntersections(filterPoints(ear)); - earcutLinked(ear, 2); - - // as a last resort, try splitting the remaining polygon into two - } else if (pass == 2) splitEarcut(ear); - - break; - } - } -} - -// check whether a polygon node forms a valid ear with adjacent nodes -template -bool Earcut::isEar(Node* ear) { - const Node* a = ear->prev; - const Node* b = ear; - const Node* c = ear->next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - // now make sure we don't have other points inside the potential ear - Node* p = ear->next->next; - - while (p != ear->prev) { - if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && - area(p->prev, p, p->next) >= 0) return false; - p = p->next; - } - - return true; -} - -template -bool Earcut::isEarHashed(Node* ear) { - const Node* a = ear->prev; - const Node* b = ear; - const Node* c = ear->next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - // triangle bbox; min & max are calculated like this for speed - const double minTX = std::min(a->x, std::min(b->x, c->x)); - const double minTY = std::min(a->y, std::min(b->y, c->y)); - const double maxTX = std::max(a->x, std::max(b->x, c->x)); - const double maxTY = std::max(a->y, std::max(b->y, c->y)); - - // z-order range for the current triangle bbox; - const int32_t minZ = zOrder(minTX, minTY); - const int32_t maxZ = zOrder(maxTX, maxTY); - - // first look for points inside the triangle in increasing z-order - Node* p = ear->nextZ; - - while (p && p->z <= maxZ) { - if (p != ear->prev && p != ear->next && - pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && - area(p->prev, p, p->next) >= 0) return false; - p = p->nextZ; - } - - // then look for points in decreasing z-order - p = ear->prevZ; - - while (p && p->z >= minZ) { - if (p != ear->prev && p != ear->next && - pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && - area(p->prev, p, p->next) >= 0) return false; - p = p->prevZ; - } - - return true; -} - -// go through all polygon nodes and cure small local self-intersections -template -typename Earcut::Node* -Earcut::cureLocalIntersections(Node* start) { - Node* p = start; - do { - Node* a = p->prev; - Node* b = p->next->next; - - // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) - if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) { - indices.emplace_back(a->i); - indices.emplace_back(p->i); - indices.emplace_back(b->i); - - // remove two nodes involved - removeNode(p); - removeNode(p->next); - - p = start = b; - } - p = p->next; - } while (p != start); - - return filterPoints(p); -} - -// try splitting polygon into two and triangulate them independently -template -void Earcut::splitEarcut(Node* start) { - // look for a valid diagonal that divides the polygon into two - Node* a = start; - do { - Node* b = a->next->next; - while (b != a->prev) { - if (a->i != b->i && isValidDiagonal(a, b)) { - // split the polygon in two by the diagonal - Node* c = splitPolygon(a, b); - - // filter colinear points around the cuts - a = filterPoints(a, a->next); - c = filterPoints(c, c->next); - - // run earcut on each half - earcutLinked(a); - earcutLinked(c); - return; - } - b = b->next; - } - a = a->next; - } while (a != start); -} - -// link every hole into the outer loop, producing a single-ring polygon without holes -template template -typename Earcut::Node* -Earcut::eliminateHoles(const Polygon& points, Node* outerNode) { - const size_t len = points.size(); - - std::vector queue; - for (size_t i = 1; i < len; i++) { - Node* list = linkedList(points[i], false); - if (list) { - if (list == list->next) list->steiner = true; - queue.push_back(getLeftmost(list)); - } - } - std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) { - return a->x < b->x; - }); - - // process holes from left to right - for (size_t i = 0; i < queue.size(); i++) { - outerNode = eliminateHole(queue[i], outerNode); - outerNode = filterPoints(outerNode, outerNode->next); - } - - return outerNode; -} - -// find a bridge between vertices that connects hole with an outer ring and and link it -template -typename Earcut::Node* -Earcut::eliminateHole(Node* hole, Node* outerNode) { - Node* bridge = findHoleBridge(hole, outerNode); - if (!bridge) { - return outerNode; - } - - Node* bridgeReverse = splitPolygon(bridge, hole); - - // filter collinear points around the cuts - Node* filteredBridge = filterPoints(bridge, bridge->next); - filterPoints(bridgeReverse, bridgeReverse->next); - - // Check if input node was removed by the filtering - return outerNode == bridge ? filteredBridge : outerNode; -} - -// David Eberly's algorithm for finding a bridge between hole and outer polygon -template -typename Earcut::Node* -Earcut::findHoleBridge(Node* hole, Node* outerNode) { - Node* p = outerNode; - double hx = hole->x; - double hy = hole->y; - double qx = -std::numeric_limits::infinity(); - Node* m = nullptr; - - // find a segment intersected by a ray from the hole's leftmost Vertex to the left; - // segment's endpoint with lesser x will be potential connection Vertex - do { - if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) { - double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y); - if (x <= hx && x > qx) { - qx = x; - if (x == hx) { - if (hy == p->y) return p; - if (hy == p->next->y) return p->next; - } - m = p->x < p->next->x ? p : p->next; - } - } - p = p->next; - } while (p != outerNode); - - if (!m) return 0; - - if (hx == qx) return m; // hole touches outer segment; pick leftmost endpoint - - // look for points inside the triangle of hole Vertex, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the Vertex of the minimum angle with the ray as connection Vertex - - const Node* stop = m; - double tanMin = std::numeric_limits::infinity(); - double tanCur = 0; - - p = m; - double mx = m->x; - double my = m->y; - - do { - if (hx >= p->x && p->x >= mx && hx != p->x && - pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) { - - tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential - - if (locallyInside(p, hole) && - (tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) { - m = p; - tanMin = tanCur; - } - } - - p = p->next; - } while (p != stop); - - return m; -} - -// whether sector in vertex m contains sector in vertex p in the same coordinates -template -bool Earcut::sectorContainsSector(const Node* m, const Node* p) { - return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0; -} - -// interlink polygon nodes in z-order -template -void Earcut::indexCurve(Node* start) { - assert(start); - Node* p = start; - - do { - p->z = p->z ? p->z : zOrder(p->x, p->y); - p->prevZ = p->prev; - p->nextZ = p->next; - p = p->next; - } while (p != start); - - p->prevZ->nextZ = nullptr; - p->prevZ = nullptr; - - sortLinked(p); -} - -// Simon Tatham's linked list merge sort algorithm -// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -template -typename Earcut::Node* -Earcut::sortLinked(Node* list) { - assert(list); - Node* p; - Node* q; - Node* e; - Node* tail; - int i, numMerges, pSize, qSize; - int inSize = 1; - - for (;;) { - p = list; - list = nullptr; - tail = nullptr; - numMerges = 0; - - while (p) { - numMerges++; - q = p; - pSize = 0; - for (i = 0; i < inSize; i++) { - pSize++; - q = q->nextZ; - if (!q) break; - } - - qSize = inSize; - - while (pSize > 0 || (qSize > 0 && q)) { - - if (pSize == 0) { - e = q; - q = q->nextZ; - qSize--; - } else if (qSize == 0 || !q) { - e = p; - p = p->nextZ; - pSize--; - } else if (p->z <= q->z) { - e = p; - p = p->nextZ; - pSize--; - } else { - e = q; - q = q->nextZ; - qSize--; - } - - if (tail) tail->nextZ = e; - else list = e; - - e->prevZ = tail; - tail = e; - } - - p = q; - } - - tail->nextZ = nullptr; - - if (numMerges <= 1) return list; - - inSize *= 2; - } -} - -// z-order of a Vertex given coords and size of the data bounding box -template -int32_t Earcut::zOrder(const double x_, const double y_) { - // coords are transformed into non-negative 15-bit integer range - int32_t x = static_cast(32767.0 * (x_ - minX) * inv_size); - int32_t y = static_cast(32767.0 * (y_ - minY) * inv_size); - - x = (x | (x << 8)) & 0x00FF00FF; - x = (x | (x << 4)) & 0x0F0F0F0F; - x = (x | (x << 2)) & 0x33333333; - x = (x | (x << 1)) & 0x55555555; - - y = (y | (y << 8)) & 0x00FF00FF; - y = (y | (y << 4)) & 0x0F0F0F0F; - y = (y | (y << 2)) & 0x33333333; - y = (y | (y << 1)) & 0x55555555; - - return x | (y << 1); -} - -// find the leftmost node of a polygon ring -template -typename Earcut::Node* -Earcut::getLeftmost(Node* start) { - Node* p = start; - Node* leftmost = start; - do { - if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y)) - leftmost = p; - p = p->next; - } while (p != start); - - return leftmost; -} - -// check if a point lies within a convex triangle -template -bool Earcut::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const { - return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && - (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && - (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0; -} - -// check if a diagonal between two polygon nodes is valid (lies in polygon interior) -template -bool Earcut::isValidDiagonal(Node* a, Node* b) { - return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges - ((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible - (area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors - (equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case -} - -// signed area of a triangle -template -double Earcut::area(const Node* p, const Node* q, const Node* r) const { - return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); -} - -// check if two points are equal -template -bool Earcut::equals(const Node* p1, const Node* p2) { - return p1->x == p2->x && p1->y == p2->y; -} - -// check if two segments intersect -template -bool Earcut::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) { - int o1 = sign(area(p1, q1, p2)); - int o2 = sign(area(p1, q1, q2)); - int o3 = sign(area(p2, q2, p1)); - int o4 = sign(area(p2, q2, q1)); - - if (o1 != o2 && o3 != o4) return true; // general case - - if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 - - return false; -} - -// for collinear points p, q, r, check if point q lies on segment pr -template -bool Earcut::onSegment(const Node* p, const Node* q, const Node* r) { - return q->x <= std::max(p->x, r->x) && - q->x >= std::min(p->x, r->x) && - q->y <= std::max(p->y, r->y) && - q->y >= std::min(p->y, r->y); -} - -template -int Earcut::sign(double val) { - return (0.0 < val) - (val < 0.0); -} - -// check if a polygon diagonal intersects any polygon segments -template -bool Earcut::intersectsPolygon(const Node* a, const Node* b) { - const Node* p = a; - do { - if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i && - intersects(p, p->next, a, b)) return true; - p = p->next; - } while (p != a); - - return false; -} - -// check if a polygon diagonal is locally inside the polygon -template -bool Earcut::locallyInside(const Node* a, const Node* b) { - return area(a->prev, a, a->next) < 0 ? - area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 : - area(a, b, a->prev) < 0 || area(a, a->next, b) < 0; -} - -// check if the middle Vertex of a polygon diagonal is inside the polygon -template -bool Earcut::middleInside(const Node* a, const Node* b) { - const Node* p = a; - bool inside = false; - double px = (a->x + b->x) / 2; - double py = (a->y + b->y) / 2; - do { - if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y && - (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x)) - inside = !inside; - p = p->next; - } while (p != a); - - return inside; -} - -// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits -// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a -// single ring -template -typename Earcut::Node* -Earcut::splitPolygon(Node* a, Node* b) { - Node* a2 = nodes.construct(a->i, a->x, a->y); - Node* b2 = nodes.construct(b->i, b->x, b->y); - Node* an = a->next; - Node* bp = b->prev; - - a->next = b; - b->prev = a; - - a2->next = an; - an->prev = a2; - - b2->next = a2; - a2->prev = b2; - - bp->next = b2; - b2->prev = bp; - - return b2; -} - -// create a node and util::optionally link it with previous one (in a circular doubly linked list) -template template -typename Earcut::Node* -Earcut::insertNode(std::size_t i, const Point& pt, Node* last) { - Node* p = nodes.construct(static_cast(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt)); - - if (!last) { - p->prev = p; - p->next = p; - - } else { - assert(last); - p->next = last->next; - p->prev = last; - last->next->prev = p; - last->next = p; - } - return p; -} - -template -void Earcut::removeNode(Node* p) { - p->next->prev = p->prev; - p->prev->next = p->next; - - if (p->prevZ) p->prevZ->nextZ = p->nextZ; - if (p->nextZ) p->nextZ->prevZ = p->prevZ; -} -} - -template -std::vector earcut(const Polygon& poly) { - mapbox::detail::Earcut earcut; - earcut(poly); - return std::move(earcut.indices); -} -} diff --git a/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h b/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h deleted file mode 100644 index 18d6073a22..0000000000 --- a/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LOTTIE_MESH_BINDING_H -#define LOTTIE_MESH_BINDING_H - -#import -#import - -typedef NS_CLOSED_ENUM(NSInteger, LottieMeshFillRule) { - LottieMeshFillRuleEvenOdd, - LottieMeshFillRuleNonZero -}; - -@interface LottieMeshFill : NSObject - -@property (nonatomic, readonly) LottieMeshFillRule fillRule; - -- (instancetype _Nonnull)initWithFillRule:(LottieMeshFillRule)fillRule; - -@end - -@interface LottieMeshStroke : NSObject - -@property (nonatomic, readonly) CGFloat lineWidth; -@property (nonatomic, readonly) CGLineJoin lineJoin; -@property (nonatomic, readonly) CGLineCap lineCap; -@property (nonatomic, readonly) CGFloat miterLimit; - -- (instancetype _Nonnull)initWithLineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit; - -@end - -@interface LottieMeshData : NSObject - -- (NSInteger)vertexCount; -- (void)getVertexAt:(NSInteger)index x:(float * _Nullable)x y:(float * _Nullable)y; - -- (NSInteger)triangleCount; -- (void * _Nonnull)getTriangles; - -+ (LottieMeshData * _Nullable)generateWithPath:(UIBezierPath * _Nonnull)path fill:(LottieMeshFill * _Nullable)fill stroke:(LottieMeshStroke * _Nullable)stroke; - -@end - -#endif diff --git a/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm b/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm deleted file mode 100644 index 9e61e1ea33..0000000000 --- a/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm +++ /dev/null @@ -1,315 +0,0 @@ -#import - -#import - -namespace { - -MeshGenerator::Point bezierQuadraticPointAt(MeshGenerator::Point const &p0, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2, float t) { - float x = powf((1.0 - t), 2.0) * p0.x + 2.0 * (1.0 - t) * t * p1.x + powf(t, 2.0) * p2.x; - float y = powf((1.0 - t), 2.0) * p0.y + 2.0 * (1.0 - t) * t * p1.y + powf(t, 2.0) * p2.y; - return MeshGenerator::Point(x, y); -} - -float approximateBezierQuadraticLength(MeshGenerator::Point const &p0, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2) { - float length = 0.0f; - float t = 0.1; - MeshGenerator::Point last = p0; - while (t < 1.01) { - auto point = bezierQuadraticPointAt(p0, p1, p2, t); - length += last.distance(point); - last = point; - t += 0.1; - } - return length; -} - -void tesselateBezier(MeshGenerator::Path &path, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2, MeshGenerator::Point const &p3, MeshGenerator::Point const &p4, int level) { - const float tessTol = 0.25f / 0.1f; - - float x1 = p1.x; - float y1 = p1.y; - float x2 = p2.x; - float y2 = p2.y; - float x3 = p3.x; - float y3 = p3.y; - float x4 = p4.x; - float y4 = p4.y; - - float x12, y12, x23, y23, x34, y34, x123, y123, x234, y234, x1234, y1234; - float dx, dy, d2, d3; - - if (level > 10) { - return; - } - - x12 = (x1 + x2) * 0.5f; - y12 = (y1 + y2) * 0.5f; - x23 = (x2 + x3) * 0.5f; - y23 = (y2 + y3) * 0.5f; - x34 = (x3 + x4) * 0.5f; - y34 = (y3 + y4) * 0.5f; - x123 = (x12 + x23) * 0.5f; - y123 = (y12 + y23) * 0.5f; - - dx = x4 - x1; - dy = y4 - y1; - d2 = std::abs(((x2 - x4) * dy - (y2 - y4) * dx)); - d3 = std::abs(((x3 - x4) * dy - (y3 - y4) * dx)); - - if ((d2 + d3) * (d2 + d3) < tessTol * (dx * dx + dy * dy)) { - path.points.emplace_back(x4, y4); - return; - } - - x234 = (x23+x34) * 0.5f; - y234 = (y23+y34) * 0.5f; - x1234 = (x123 + x234) * 0.5f; - y1234 = (y123 + y234) * 0.5f; - - tesselateBezier(path, MeshGenerator::Point(x1, y1), MeshGenerator::Point(x12, y12), MeshGenerator::Point(x123, y123), MeshGenerator::Point(x1234, y1234), level + 1); - tesselateBezier(path, MeshGenerator::Point(x1234, y1234), MeshGenerator::Point(x234, y234), MeshGenerator::Point(x34, y34), MeshGenerator::Point(x4, y4), level + 1); -} - -} - -@interface LottieMeshData () { - std::unique_ptr _mesh; -} - -- (instancetype _Nonnull)initWithMesh:(std::unique_ptr &&)mesh; - -@end - -@implementation LottieMeshData - -- (instancetype _Nonnull)initWithMesh:(std::unique_ptr &&)mesh { - self = [super init]; - if (self != nil) { - _mesh = std::move(mesh); - } - return self; -} - -- (NSInteger)vertexCount { - return (NSInteger)_mesh->vertices.size(); -} - -- (void)getVertexAt:(NSInteger)index x:(float * _Nullable)x y:(float * _Nullable)y { - MeshGenerator::Point const &point = _mesh->vertices[index]; - if (x) { - *x = point.x; - } - if (y) { - *y = point.y; - } -} - -- (NSInteger)triangleCount { - return (NSInteger)(_mesh->triangles.size() / 3); -} - -- (void * _Nonnull)getTriangles { - return _mesh->triangles.data(); -} - -/*- (void)getTriangleAt:(NSInteger)index v0:(NSInteger * _Nullable)v0 v1:(NSInteger * _Nullable)v1 v2:(NSInteger * _Nullable)v2 { - if (v0) { - *v0 = (NSInteger)_mesh->triangles[index * 3 + 0]; - } - if (v1) { - *v1 = (NSInteger)_mesh->triangles[index * 3 + 1]; - } - if (v2) { - *v2 = (NSInteger)_mesh->triangles[index * 3 + 2]; - } -}*/ - -+ (LottieMeshData * _Nullable)generateWithPath:(UIBezierPath * _Nonnull)path fill: (LottieMeshFill * _Nullable)fill stroke:(LottieMeshStroke * _Nullable)stroke { - float scale = 1.0f; - float flatness = 1.0; - __block MeshGenerator::Point startingPoint(0.0f, 0.0f); - __block bool hasStartingPoint = false; - __block std::vector paths; - paths.push_back(MeshGenerator::Path()); - - CGPathApplyWithBlock(path.CGPath, ^(const CGPathElement * _Nonnull element) { - switch (element->type) { - case kCGPathElementMoveToPoint: { - if (!paths[paths.size() - 1].points.empty()) { - if (!paths[paths.size() - 1].points[0].isEqual(paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1])) { - paths[paths.size() - 1].points.push_back(paths[paths.size() - 1].points[0]); - } - paths.push_back(MeshGenerator::Path()); - } - - startingPoint = MeshGenerator::Point((float)(element->points[0].x) * scale, (float)(element->points[0].y) * scale); - hasStartingPoint = true; - break; - } - case kCGPathElementAddLineToPoint: { - bool canAddPoints = false; - if (paths[paths.size() - 1].points.empty()) { - if (hasStartingPoint) { - paths[paths.size() - 1].points.push_back(startingPoint); - canAddPoints = true; - } - } else { - canAddPoints = true; - } - if (canAddPoints) { - paths[paths.size() - 1].points.push_back(MeshGenerator::Point((float)(element->points[0].x) * scale, (float)(element->points[0].y) * scale)); - } - break; - } - case kCGPathElementAddQuadCurveToPoint: { - bool canAddPoints = false; - if (paths[paths.size() - 1].points.empty()) { - if (hasStartingPoint) { - paths[paths.size() - 1].points.push_back(startingPoint); - canAddPoints = true; - } - } else { - canAddPoints = true; - } - if (canAddPoints) { - float t = 0.001f; - - MeshGenerator::Point p0 = paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1]; - MeshGenerator::Point p1(element->points[0].x * scale, element->points[0].y * scale); - MeshGenerator::Point p2(element->points[1].x * scale, element->points[1].y * scale); - - float step = 10.0f * flatness / approximateBezierQuadraticLength(p0, p1, p2); - while (t < 1.0f) { - auto point = bezierQuadraticPointAt(p0, p1, p2, t); - paths[paths.size() - 1].points.push_back(point); - t += step; - } - paths[paths.size() - 1].points.push_back(p2); - } - break; - } - case kCGPathElementAddCurveToPoint: { - bool canAddPoints = false; - if (paths[paths.size() - 1].points.empty()) { - if (hasStartingPoint) { - paths[paths.size() - 1].points.push_back(startingPoint); - canAddPoints = true; - } - } else { - canAddPoints = true; - } - if (canAddPoints) { - float t = 0.001f; - - MeshGenerator::Point p0 = paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1]; - MeshGenerator::Point p1(element->points[0].x * scale, element->points[0].y * scale); - MeshGenerator::Point p2(element->points[1].x * scale, element->points[1].y * scale); - MeshGenerator::Point p3(element->points[2].x * scale, element->points[2].y * scale); - - tesselateBezier(paths[paths.size() - 1], p0, p1, p2, p3, 0); - } - break; - } - case kCGPathElementCloseSubpath: { - if (!paths[paths.size() - 1].points.empty()) { - if (!paths[paths.size() - 1].points[0].isEqual(paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1])) { - paths[paths.size() - 1].points.push_back(paths[paths.size() - 1].points[0]); - } - - hasStartingPoint = true; - startingPoint = paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1]; - paths.push_back(MeshGenerator::Path()); - } - } - default: { - break; - } - } - }); - - if (!paths[paths.size() - 1].points.empty()) { - if (stroke == nil && !paths[paths.size() - 1].points[0].isEqual(paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1])) { - paths[paths.size() - 1].points.push_back(paths[paths.size() - 1].points[0]); - } - } else { - paths.pop_back(); - } - - std::unique_ptr mappedFill; - if (fill) { - mappedFill = std::make_unique(fill.fillRule == LottieMeshFillRuleEvenOdd ? MeshGenerator::Fill::Rule::EvenOdd : MeshGenerator::Fill::Rule::NonZero); - } - - std::unique_ptr mappedStroke; - if (stroke) { - MeshGenerator::Stroke::LineJoin lineJoin; - switch (stroke.lineJoin) { - case kCGLineJoinRound: - lineJoin = MeshGenerator::Stroke::LineJoin::Round; - break; - case kCGLineJoinBevel: - lineJoin = MeshGenerator::Stroke::LineJoin::Bevel; - break; - case kCGLineJoinMiter: - lineJoin = MeshGenerator::Stroke::LineJoin::Miter; - break; - default: - lineJoin = MeshGenerator::Stroke::LineJoin::Round; - break; - } - - MeshGenerator::Stroke::LineCap lineCap; - switch (stroke.lineCap) { - case kCGLineCapRound: - lineCap = MeshGenerator::Stroke::LineCap::Round; - break; - case kCGLineCapButt: - lineCap = MeshGenerator::Stroke::LineCap::Butt; - break; - case kCGLineCapSquare: - lineCap = MeshGenerator::Stroke::LineCap::Square; - break; - default: - lineCap = MeshGenerator::Stroke::LineCap::Round; - break; - } - - mappedStroke = std::make_unique((float)stroke.lineWidth, lineJoin, lineCap, (float)stroke.miterLimit); - } - - std::unique_ptr resultMesh = MeshGenerator::generateMesh(paths, std::move(mappedFill), std::move(mappedStroke)); - if (resultMesh) { - return [[LottieMeshData alloc] initWithMesh:std::move(resultMesh)]; - } else { - return nil; - } -} - -@end - -@implementation LottieMeshFill - -- (instancetype _Nonnull)initWithFillRule:(LottieMeshFillRule)fillRule { - self = [super init]; - if (self != nil) { - _fillRule = fillRule; - } - return self; -} - -@end - -@implementation LottieMeshStroke - -- (instancetype _Nonnull)initWithLineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit { - self = [super init]; - if (self != nil) { - _lineWidth = lineWidth; - _lineJoin = lineJoin; - _lineCap = lineCap; - _miterLimit = miterLimit; - } - return self; -} - -@end diff --git a/submodules/LottieMeshSwift/Resources/Shapes.metal b/submodules/LottieMeshSwift/Resources/Shapes.metal deleted file mode 100644 index d042bf25f8..0000000000 --- a/submodules/LottieMeshSwift/Resources/Shapes.metal +++ /dev/null @@ -1,82 +0,0 @@ -#include -using namespace metal; - -typedef struct { - packed_float2 position; -} Vertex; - -typedef struct { - packed_float2 offset; -} Offset; - -typedef struct { - float4 position[[position]]; - float2 localPosition[[center_no_perspective]]; -} Varyings; - -float2 screenSpaceToRelative(float2 point, float2 viewSize) { - float2 inverseViewSize = 1 / viewSize; - float clipX = (2.0f * point.x * inverseViewSize.x) - 2.0f; - float clipY = (2.0f * -point.y * inverseViewSize.y) + 2.0f; - - return float2(clipX, clipY); -} - -vertex Varyings vertexPassthrough( - constant Vertex *verticies[[buffer(0)]], - constant float2 &offset[[buffer(1)]], - unsigned int vid[[vertex_id]], - constant float4x4 &transformMatrix[[buffer(2)]], - constant int &indexOffset[[buffer(3)]] -) { - Varyings out; - constant Vertex &v = verticies[vid + indexOffset]; - float2 viewSize(512.0f, 512.0f); - float4 transformedVertex = transformMatrix * float4(v.position, 0.0, 1.0); - out.position = float4(screenSpaceToRelative(float2(transformedVertex.x, transformedVertex.y) + offset, viewSize), 0.0, 1.0); - out.localPosition = float2(v.position); - - return out; -} - -fragment half4 fragmentPassthrough( - Varyings in[[stage_in]], - constant float4 &color[[buffer(1)]] -) { - float4 out = color; - - return half4(out); -} - -template -half4 mixGradientColors(float dist, constant float4 *colors, constant float *steps) { - float4 color = colors[0]; - for (int i = 1; i < N; i++) { - color = mix(color, colors[i], smoothstep(steps[i - 1], steps[i], dist)); - } - - return half4(color); -} - -#define radialGradientFunc(N) fragment half4 fragmentRadialGradient##N( \ - Varyings in[[stage_in]], \ - constant float2 &start[[buffer(1)]], \ - constant float2 &end[[buffer(2)]], \ - constant float4 *colors[[buffer(3)]], \ - constant float *steps[[buffer(4)]] \ -) { \ - float centerDistance = distance(in.localPosition, start); \ - float endDistance = distance(start, end); \ - float dist = min(1.0, centerDistance / endDistance); \ - return mixGradientColors(dist, colors, steps); \ -} - -radialGradientFunc(2) -radialGradientFunc(3) -radialGradientFunc(4) -radialGradientFunc(5) -radialGradientFunc(6) -radialGradientFunc(7) -radialGradientFunc(8) -radialGradientFunc(9) -radialGradientFunc(10) diff --git a/submodules/LottieMeshSwift/Sources/Buffer.swift b/submodules/LottieMeshSwift/Sources/Buffer.swift deleted file mode 100644 index 3601d4f89a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Buffer.swift +++ /dev/null @@ -1,122 +0,0 @@ -import Foundation -import Postbox -import ManagedFile - -private let emptyMemory = malloc(1)! - -public class MeshMemoryBuffer { - public internal(set) var data: Data - public internal(set) var length: Int - - public init(data: Data) { - self.data = data - self.length = data.count - } - - public func makeData() -> Data { - if self.data.count == self.length { - return self.data - } else { - return self.data.subdata(in: 0 ..< self.length) - } - } -} - -extension WriteBuffer { - func writeInt32(_ value: Int32) { - var value = value - self.write(&value, length: 4) - } - - func writeFloat(_ value: Float) { - var value: Float32 = value - self.write(&value, length: 4) - } -} - -public final class MeshWriteBuffer { - let file: ManagedFile - private(set) var offset: Int = 0 - - public init(file: ManagedFile) { - self.file = file - } - - public func write(_ data: UnsafeRawPointer, length: Int) { - let _ = self.file.write(data, count: length) - self.offset += length - } - - public func writeInt8(_ value: Int8) { - var value = value - self.write(&value, length: 1) - } - - public func writeInt32(_ value: Int32) { - var value = value - self.write(&value, length: 4) - } - - public func writeFloat(_ value: Float) { - var value: Float32 = value - self.write(&value, length: 4) - } - - public func write(_ data: Data) { - data.withUnsafeBytes { bytes in - self.write(bytes.baseAddress!, length: bytes.count) - } - } - - func write(_ data: DataRange) { - data.data.withUnsafeBytes { bytes in - self.write(bytes.baseAddress!.advanced(by: data.range.lowerBound), length: data.count) - } - } - - public func seek(offset: Int) { - let _ = self.file.seek(position: Int64(offset)) - self.offset = offset - } -} - -public final class MeshReadBuffer: MeshMemoryBuffer { - public var offset = 0 - - override public init(data: Data) { - super.init(data: data) - } - - public func read(_ data: UnsafeMutableRawPointer, length: Int) { - self.data.copyBytes(to: data.assumingMemoryBound(to: UInt8.self), from: self.offset ..< (self.offset + length)) - self.offset += length - } - - func readDataRange(count: Int) -> DataRange { - let result = DataRange(data: self.data, range: self.offset ..< (self.offset + count)) - self.offset += count - return result - } - - public func readInt8() -> Int8 { - var result: Int8 = 0 - self.read(&result, length: 1) - return result - } - - public func readInt32() -> Int32 { - var result: Int32 = 0 - self.read(&result, length: 4) - return result - } - - public func readFloat() -> Float { - var result: Float32 = 0 - self.read(&result, length: 4) - return result - } - - public func skip(_ length: Int) { - self.offset += length - } -} diff --git a/submodules/LottieMeshSwift/Sources/CapturedGeometry.swift b/submodules/LottieMeshSwift/Sources/CapturedGeometry.swift deleted file mode 100644 index 6f85f794cf..0000000000 --- a/submodules/LottieMeshSwift/Sources/CapturedGeometry.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Foundation -import UIKit - -final class CapturedGeometryNode { - final class DisplayItem { - enum Display { - enum Style { - enum GradientType { - case linear - case radial - } - - case color(color: UIColor, alpha: CGFloat) - case gradient(colors: [UIColor], positions: [CGFloat], start: CGPoint, end: CGPoint, type: GradientType) - } - - struct Fill { - var style: Style - var fillRule: CGPathFillRule - } - - struct Stroke { - var style: Style - var lineWidth: CGFloat - var lineCap: CGLineCap - var lineJoin: CGLineJoin - var miterLimit: CGFloat - } - - case fill(Fill) - case stroke(Stroke) - } - - let path: CGPath - let display: Display - - init(path: CGPath, display: Display) { - self.path = path - self.display = display - } - } - - var transform: CATransform3D - let alpha: CGFloat - let isHidden: Bool - let displayItem: DisplayItem? - let subnodes: [CapturedGeometryNode] - - init( - transform: CATransform3D, - alpha: CGFloat, - isHidden: Bool, - displayItem: DisplayItem?, - subnodes: [CapturedGeometryNode] - ) { - self.transform = transform - self.alpha = alpha - self.isHidden = isHidden - self.displayItem = displayItem - self.subnodes = subnodes - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyCompositionLayer.swift deleted file mode 100644 index 5728e6742c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyCompositionLayer.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Foundation -import QuartzCore - -/** - The base class for a child layer of CompositionContainer - */ -class MyCompositionLayer { - var bounds: CGRect = CGRect() - - let transformNode: LayerTransformNode - - //let contentsLayer: CALayer = CALayer() - - let maskLayer: MyMaskContainerLayer? - - let matteType: MatteType? - - var matteLayer: MyCompositionLayer? { - didSet { - //NOTE - /*if let matte = matteLayer { - if let type = matteType, type == .invert { - - mask = InvertedMatteLayer(inputMatte: matte) - } else { - mask = matte - } - } else { - mask = nil - }*/ - } - } - - let inFrame: CGFloat - let outFrame: CGFloat - let startFrame: CGFloat - let timeStretch: CGFloat - - init(layer: LayerModel, size: CGSize) { - self.transformNode = LayerTransformNode(transform: layer.transform) - if let masks = layer.masks { - maskLayer = MyMaskContainerLayer(masks: masks) - } else { - maskLayer = nil - } - self.matteType = layer.matte - self.inFrame = layer.inFrame.cgFloat - self.outFrame = layer.outFrame.cgFloat - self.timeStretch = layer.timeStretch.cgFloat - self.startFrame = layer.startTime.cgFloat - - //NOTE - //self.anchorPoint = .zero - - //NOTE - /*contentsLayer.anchorPoint = .zero - contentsLayer.bounds = CGRect(origin: .zero, size: size) - contentsLayer.actions = [ - "opacity" : NSNull(), - "transform" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "sublayerTransform" : NSNull(), - "hidden" : NSNull() - ] - addSublayer(contentsLayer) - - if let maskLayer = maskLayer { - contentsLayer.mask = maskLayer - }*/ - } - - private(set) var isHidden = false - - final func displayWithFrame(frame: CGFloat, forceUpdates: Bool) { - transformNode.updateTree(frame, forceUpdates: forceUpdates) - let layerVisible = frame.isInRangeOrEqual(inFrame, outFrame) - /// Only update contents if current time is within the layers time bounds. - if layerVisible { - displayContentsWithFrame(frame: frame, forceUpdates: forceUpdates) - maskLayer?.updateWithFrame(frame: frame, forceUpdates: forceUpdates) - } - self.isHidden = !layerVisible - //NOTE - /*contentsLayer.transform = transformNode.globalTransform - contentsLayer.opacity = transformNode.opacity - contentsLayer.isHidden = !layerVisible*/ - } - - func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - /// To be overridden by subclass - } - - func captureGeometry() -> CapturedGeometryNode { - return CapturedGeometryNode( - transform: self.transformNode.globalTransform, - alpha: CGFloat(self.transformNode.opacity), - isHidden: self.isHidden, - displayItem: self.captureDisplayItem(), - subnodes: self.captureChildren() - ) - } - - func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - preconditionFailure() - } - - func captureChildren() -> [CapturedGeometryNode] { - preconditionFailure() - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyImageCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyImageCompositionLayer.swift deleted file mode 100644 index 5ba14d64d1..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyImageCompositionLayer.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation -import CoreGraphics -import QuartzCore - -final class MyImageCompositionLayer: MyCompositionLayer { - - var image: CGImage? = nil { - didSet { - //NOTE - /*if let image = image { - contentsLayer.contents = image - } else { - contentsLayer.contents = nil - }*/ - } - } - - let imageReferenceID: String - - init(imageLayer: ImageLayerModel, size: CGSize) { - self.imageReferenceID = imageLayer.referenceID - super.init(layer: imageLayer, size: size) - - //NOTE - //contentsLayer.masksToBounds = true - //contentsLayer.contentsGravity = CALayerContentsGravity.resize - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - preconditionFailure() - } - - override func captureChildren() -> [CapturedGeometryNode] { - return [] - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyMaskContainerLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyMaskContainerLayer.swift deleted file mode 100644 index e132deaa87..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyMaskContainerLayer.swift +++ /dev/null @@ -1,150 +0,0 @@ -import Foundation -import QuartzCore - -extension MaskMode { - var usableMode: MaskMode { - switch self { - case .add: - return .add - case .subtract: - return .subtract - case .intersect: - return .intersect - case .lighten: - return .add - case .darken: - return .darken - case .difference: - return .intersect - case .none: - return .none - } - } -} - -extension CGRect { - static var veryLargeRect: CGRect { - return CGRect(x: -100_000_000, - y: -100_000_000, - width: 200_000_000, - height: 200_000_000) - } -} - -final class MyMaskContainerLayer { - - init(masks: [Mask]) { - //NOTE - //anchorPoint = .zero - var containerLayer = CALayer() - var firstObject: Bool = true - for mask in masks { - let maskLayer = MaskLayer(mask: mask) - maskLayers.append(maskLayer) - if mask.mode.usableMode == .none { - continue - } else if mask.mode.usableMode == .add || firstObject { - firstObject = false - containerLayer.addSublayer(maskLayer) - } else { - containerLayer.mask = maskLayer - let newContainer = CALayer() - newContainer.addSublayer(containerLayer) - containerLayer = newContainer - } - } - //NOTE - //addSublayer(containerLayer) - } - - fileprivate var maskLayers: [MaskLayer] = [] - - func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { - maskLayers.forEach({ $0.updateWithFrame(frame: frame, forceUpdates: forceUpdates) }) - } -} - -fileprivate class MaskLayer: CALayer { - - let properties: MaskNodeProperties? - - let maskLayer = CAShapeLayer() - - init(mask: Mask) { - self.properties = MaskNodeProperties(mask: mask) - super.init() - addSublayer(maskLayer) - anchorPoint = .zero - maskLayer.fillColor = mask.mode == .add ? CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1]) : - CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1]) - maskLayer.fillRule = CAShapeLayerFillRule.evenOdd - self.actions = [ - "opacity" : NSNull() - ] - - } - - override init(layer: Any) { - self.properties = nil - super.init(layer: layer) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { - guard let properties = properties else { return } - if properties.opacity.needsUpdate(frame: frame) || forceUpdates { - properties.opacity.update(frame: frame) - self.opacity = Float(properties.opacity.value.cgFloatValue) - } - - if properties.shape.needsUpdate(frame: frame) || forceUpdates { - properties.shape.update(frame: frame) - properties.expansion.update(frame: frame) - - let shapePath = properties.shape.value.cgPath() - var path = shapePath - if properties.mode.usableMode == .subtract && !properties.inverted || - (properties.mode.usableMode == .add && properties.inverted) { - /// Add a bounds rect to invert the mask - let newPath = CGMutablePath() - newPath.addRect(CGRect.veryLargeRect) - newPath.addPath(shapePath) - path = newPath - } - maskLayer.path = path - } - - } -} - -fileprivate class MaskNodeProperties: NodePropertyMap { - - var propertyMap: [String : AnyNodeProperty] - - var properties: [AnyNodeProperty] - - init(mask: Mask) { - self.mode = mask.mode - self.inverted = mask.inverted - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes)) - self.shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes)) - self.expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes)) - self.propertyMap = [ - "Opacity" : opacity, - "Shape" : shape, - "Expansion" : expansion - ] - self.properties = Array(self.propertyMap.values) - } - - let mode: MaskMode - let inverted: Bool - - let opacity: NodeProperty - let shape: NodeProperty - let expansion: NodeProperty -} - diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyNullCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyNullCompositionLayer.swift deleted file mode 100644 index ed771fd378..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyNullCompositionLayer.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -final class MyNullCompositionLayer: MyCompositionLayer { - init(layer: LayerModel) { - super.init(layer: layer, size: .zero) - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - return nil - } - - override func captureChildren() -> [CapturedGeometryNode] { - return [] - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyPreCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyPreCompositionLayer.swift deleted file mode 100644 index 55edee4c14..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyPreCompositionLayer.swift +++ /dev/null @@ -1,84 +0,0 @@ -import Foundation -import QuartzCore - -final class MyPreCompositionLayer: MyCompositionLayer { - - let frameRate: CGFloat - let remappingNode: NodeProperty? - fileprivate var animationLayers: [MyCompositionLayer] - - init(precomp: PreCompLayerModel, - asset: PrecompAsset, - assetLibrary: AssetLibrary?, - frameRate: CGFloat) { - self.animationLayers = [] - if let keyframes = precomp.timeRemapping?.keyframes { - self.remappingNode = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframes)) - } else { - self.remappingNode = nil - } - self.frameRate = frameRate - super.init(layer: precomp, size: CGSize(width: precomp.width, height: precomp.height)) - bounds = CGRect(origin: .zero, size: CGSize(width: precomp.width, height: precomp.height)) - - //NOTE - //contentsLayer.masksToBounds = true - //contentsLayer.bounds = bounds - - let layers = initializeCompositionLayers(layers: asset.layers, assetLibrary: assetLibrary, frameRate: frameRate) - - var imageLayers = [MyImageCompositionLayer]() - - var mattedLayer: MyCompositionLayer? = nil - - for layer in layers.reversed() { - layer.bounds = bounds - //NOTE - animationLayers.append(layer) - if let imageLayer = layer as? MyImageCompositionLayer { - imageLayers.append(imageLayer) - } - if let matte = mattedLayer { - /// The previous layer requires this layer to be its matte - matte.matteLayer = layer - mattedLayer = nil - continue - } - if let matte = layer.matteType, - (matte == .add || matte == .invert) { - /// We have a layer that requires a matte. - mattedLayer = layer - } - //NOTE - //contentsLayer.addSublayer(layer) - } - - //NOTE - //layerImageProvider.addImageLayers(imageLayers) - } - - override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - let localFrame: CGFloat - if let remappingNode = remappingNode { - remappingNode.update(frame: frame) - localFrame = remappingNode.value.cgFloatValue * frameRate - } else { - localFrame = (frame - startFrame) / timeStretch - } - for animationLayer in self.animationLayers { - animationLayer.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates) - } - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - return nil - } - - override func captureChildren() -> [CapturedGeometryNode] { - var result: [CapturedGeometryNode] = [] - for animationLayer in self.animationLayers { - result.append(animationLayer.captureGeometry()) - } - return result - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyShapeCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyShapeCompositionLayer.swift deleted file mode 100644 index 06697a0a29..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyShapeCompositionLayer.swift +++ /dev/null @@ -1,54 +0,0 @@ -import Foundation -import CoreGraphics - -/** - A CompositionLayer responsible for initializing and rendering shapes - */ -final class MyShapeCompositionLayer: MyCompositionLayer { - - let rootNode: AnimatorNode? - let renderContainer: ShapeContainerLayer? - - init(shapeLayer: ShapeLayerModel) { - let results = shapeLayer.items.initializeNodeTree() - let renderContainer = ShapeContainerLayer() - self.renderContainer = renderContainer - self.rootNode = results.rootNode - super.init(layer: shapeLayer, size: .zero) - - //NOTE - //contentsLayer.addSublayer(renderContainer) - for container in results.renderContainers { - renderContainer.insertRenderLayer(container) - } - rootNode?.updateTree(0, forceUpdates: true) - } - - override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - rootNode?.updateTree(frame, forceUpdates: forceUpdates) - renderContainer?.markRenderUpdates(forFrame: frame) - } - - override func captureGeometry() -> CapturedGeometryNode { - var subnodes: [CapturedGeometryNode] = [] - if let renderContainer = self.renderContainer { - subnodes.append(renderContainer.captureGeometry()) - } - - return CapturedGeometryNode( - transform: self.transformNode.globalTransform, - alpha: CGFloat(self.transformNode.opacity), - isHidden: self.isHidden, - displayItem: nil, - subnodes: subnodes - ) - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - preconditionFailure() - } - - override func captureChildren() -> [CapturedGeometryNode] { - preconditionFailure() - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MySolidCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MySolidCompositionLayer.swift deleted file mode 100644 index a39877bf16..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MySolidCompositionLayer.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation -import UIKit - -final class MySolidCompositionLayer: MyCompositionLayer { - let colorProperty: NodeProperty? - let solidShape: CAShapeLayer = CAShapeLayer() - - init(solid: SolidLayerModel) { - let components = solid.colorHex.hexColorComponents() - self.colorProperty = NodeProperty(provider: SingleValueProvider(Color(r: Double(components.red), g: Double(components.green), b: Double(components.blue), a: 1))) - - super.init(layer: solid, size: .zero) - solidShape.path = CGPath(rect: CGRect(x: 0, y: 0, width: solid.width, height: solid.height), transform: nil) - //NOTE - //contentsLayer.addSublayer(solidShape) - } - - override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - guard let colorProperty = colorProperty else { return } - colorProperty.update(frame: frame) - solidShape.fillColor = colorProperty.value.cgColorValue - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - guard let colorProperty = colorProperty else { return nil } - guard let path = self.solidShape.path else { - return nil - } - return CapturedGeometryNode.DisplayItem( - path: path, - display: .fill(CapturedGeometryNode.DisplayItem.Display.Fill( - style: .color(color: UIColor(cgColor: colorProperty.value.cgColorValue), alpha: 1.0), - fillRule: .evenOdd - )) - ) - } - - override func captureChildren() -> [CapturedGeometryNode] { - return [] - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/InvertedMatteLayer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/InvertedMatteLayer.swift deleted file mode 100644 index 4f4aff2385..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/InvertedMatteLayer.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// InvertedMatteLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/28/19. -// - -import Foundation -import QuartzCore - -/** - A layer that inverses the alpha output of its input layer. - - WARNING: This is experimental and probably not very performant. - */ -/*final class InvertedMatteLayer: CALayer, CompositionLayerDelegate { - - let inputMatte: CompositionLayer? - let wrapperLayer = CALayer() - - init(inputMatte: CompositionLayer) { - self.inputMatte = inputMatte - super.init() - inputMatte.layerDelegate = self - self.anchorPoint = .zero - self.bounds = inputMatte.bounds - self.setNeedsDisplay() - } - - override init(layer: Any) { - guard let layer = layer as? InvertedMatteLayer else { - fatalError("init(layer:) wrong class.") - } - self.inputMatte = nil - super.init(layer: layer) - } - - func frameUpdated(frame: CGFloat) { - self.setNeedsDisplay() - self.displayIfNeeded() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func draw(in ctx: CGContext) { - guard let inputMatte = inputMatte else { return } - guard let fillColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 1]) - else { return } - ctx.setFillColor(fillColor) - ctx.fill(bounds) - ctx.setBlendMode(.destinationOut) - inputMatte.render(in: ctx) - } - -} -*/ diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/LayerTransformNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/LayerTransformNode.swift deleted file mode 100644 index 3895900997..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/LayerTransformNode.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// LayerTransformPropertyMap.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -final class LayerTransformProperties: NodePropertyMap { - - init(transform: Transform) { - - self.anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchorPoint.keyframes)) - self.scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes)) - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotation.keyframes)) - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes)) - - var propertyMap: [String: AnyNodeProperty] = [ - "Anchor Point" : anchor, - "Scale" : scale, - "Rotation" : rotation, - "Opacity" : opacity - ] - - if let positionKeyframesX = transform.positionX?.keyframes, - let positionKeyframesY = transform.positionY?.keyframes { - let xPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesX)) - let yPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesY)) - propertyMap["X Position"] = xPosition - propertyMap["Y Position"] = yPosition - self.positionX = xPosition - self.positionY = yPosition - self.position = nil - } else if let positionKeyframes = transform.position?.keyframes { - let position: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframes)) - propertyMap["Position"] = position - self.position = position - self.positionX = nil - self.positionY = nil - } else { - self.position = nil - self.positionY = nil - self.positionX = nil - } - - self.properties = Array(propertyMap.values) - } - - let properties: [AnyNodeProperty] - - let anchor: NodeProperty - let scale: NodeProperty - let rotation: NodeProperty - let position: NodeProperty? - let positionX: NodeProperty? - let positionY: NodeProperty? - let opacity: NodeProperty - -} - -class LayerTransformNode: AnimatorNode { - let outputNode: NodeOutput = PassThroughOutputNode(parent: nil) - - init(transform: Transform) { - self.transformProperties = LayerTransformProperties(transform: transform) - } - - let transformProperties: LayerTransformProperties - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return transformProperties - } - - var parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true - - func shouldRebuildOutputs(frame: CGFloat) -> Bool { - return hasLocalUpdates || hasUpstreamUpdates - } - - func rebuildOutputs(frame: CGFloat) { - opacity = Float(transformProperties.opacity.value.cgFloatValue) * 0.01 - - let position: CGPoint - if let point = transformProperties.position?.value.pointValue { - position = point - } else if let xPos = transformProperties.positionX?.value.cgFloatValue, - let yPos = transformProperties.positionY?.value.cgFloatValue { - position = CGPoint(x: xPos, y: yPos) - } else { - position = .zero - } - - localTransform = CATransform3D.makeTransform(anchor: transformProperties.anchor.value.pointValue, - position: position, - scale: transformProperties.scale.value.sizeValue, - rotation: transformProperties.rotation.value.cgFloatValue, - skew: nil, - skewAxis: nil) - - if let parentNode = parentNode as? LayerTransformNode { - globalTransform = CATransform3DConcat(localTransform, parentNode.globalTransform) - } else { - globalTransform = localTransform - } - } - - var opacity: Float = 1 - var localTransform: CATransform3D = CATransform3DIdentity - var globalTransform: CATransform3D = CATransform3DIdentity - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Animation.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Animation.swift deleted file mode 100644 index 2ba10329de..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Animation.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// Animation.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation - -public enum CoordinateSpace: Int, Codable { - case type2d - case type3d -} - -/** - The `Animation` model is the top level model object in Lottie. - - An `Animation` holds all of the animation data backing a Lottie Animation. - Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). - */ -public final class Animation: Codable { - - /// The version of the JSON Schema. - let version: String - - /// The coordinate space of the composition. - let type: CoordinateSpace - - /// The start time of the composition in frameTime. - public let startFrame: AnimationFrameTime - - /// The end time of the composition in frameTime. - public let endFrame: AnimationFrameTime - - /// The frame rate of the composition. - public let framerate: Double - - /// The height of the composition in points. - let width: Int - - /// The width of the composition in points. - let height: Int - - /// The list of animation layers - let layers: [LayerModel] - - /// The list of glyphs used for text rendering - let glyphs: [Glyph]? - - /// The list of fonts used for text rendering - let fonts: FontList? - - /// Asset Library - let assetLibrary: AssetLibrary? - - /// Markers - let markers: [Marker]? - let markerMap: [String : Marker]? - - /// Return all marker names, in order, or an empty list if none are specified - public var markerNames: [String] { - guard let markers = markers else { return [] } - return markers.map { $0.name } - } - - enum CodingKeys : String, CodingKey { - case version = "v" - case type = "ddd" - case startFrame = "ip" - case endFrame = "op" - case framerate = "fr" - case width = "w" - case height = "h" - case layers = "layers" - case glyphs = "chars" - case fonts = "fonts" - case assetLibrary = "assets" - case markers = "markers" - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Animation.CodingKeys.self) - self.version = try container.decode(String.self, forKey: .version) - self.type = try container.decodeIfPresent(CoordinateSpace.self, forKey: .type) ?? .type2d - self.startFrame = try container.decode(AnimationFrameTime.self, forKey: .startFrame) - self.endFrame = try container.decode(AnimationFrameTime.self, forKey: .endFrame) - self.framerate = try container.decode(Double.self, forKey: .framerate) - self.width = try container.decode(Int.self, forKey: .width) - self.height = try container.decode(Int.self, forKey: .height) - self.layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) - self.glyphs = try container.decodeIfPresent([Glyph].self, forKey: .glyphs) - self.fonts = try container.decodeIfPresent(FontList.self, forKey: .fonts) - self.assetLibrary = try container.decodeIfPresent(AssetLibrary.self, forKey: .assetLibrary) - self.markers = try container.decodeIfPresent([Marker].self, forKey: .markers) - - if let markers = markers { - var markerMap: [String : Marker] = [:] - for marker in markers { - markerMap[marker.name] = marker - } - self.markerMap = markerMap - } else { - self.markerMap = nil - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/Asset.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/Asset.swift deleted file mode 100644 index 85d47f81ff..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/Asset.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Asset.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -public class Asset: Codable { - - /// The ID of the asset - public let id: String - - private enum CodingKeys : String, CodingKey { - case id = "id" - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Asset.CodingKeys.self) - if let id = try? container.decode(String.self, forKey: .id) { - self.id = id - } else { - self.id = String(try container.decode(Int.self, forKey: .id)) - } - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/AssetLibrary.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/AssetLibrary.swift deleted file mode 100644 index d9dbf9095d..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/AssetLibrary.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// AssetLibrary.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class AssetLibrary: Codable { - - /// The Assets - let assets: [String : Asset] - - let imageAssets: [String : ImageAsset] - let precompAssets: [String : PrecompAsset] - - required init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - var containerForKeys = container - - var decodedAssets = [String : Asset]() - - var imageAssets = [String : ImageAsset]() - var precompAssets = [String : PrecompAsset]() - - while !container.isAtEnd { - let keyContainer = try containerForKeys.nestedContainer(keyedBy: PrecompAsset.CodingKeys.self) - if keyContainer.contains(.layers) { - let precompAsset = try container.decode(PrecompAsset.self) - decodedAssets[precompAsset.id] = precompAsset - precompAssets[precompAsset.id] = precompAsset - } else { - let imageAsset = try container.decode(ImageAsset.self) - decodedAssets[imageAsset.id] = imageAsset - imageAssets[imageAsset.id] = imageAsset - } - } - self.assets = decodedAssets - self.precompAssets = precompAssets - self.imageAssets = imageAssets - } - - func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - try container.encode(contentsOf: Array(assets.values)) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/ImageAsset.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/ImageAsset.swift deleted file mode 100644 index aeff137341..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/ImageAsset.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// ImageAsset.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -public final class ImageAsset: Asset { - - /// Image name - public let name: String - - /// Image Directory - public let directory: String - - /// Image Size - public let width: Double - - public let height: Double - - enum CodingKeys : String, CodingKey { - case name = "p" - case directory = "u" - case width = "w" - case height = "h" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ImageAsset.CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.directory = try container.decode(String.self, forKey: .directory) - self.width = try container.decode(Double.self, forKey: .width) - self.height = try container.decode(Double.self, forKey: .height) - try super.init(from: decoder) - } - - override public func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(name, forKey: .name) - try container.encode(directory, forKey: .directory) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/PrecompAsset.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/PrecompAsset.swift deleted file mode 100644 index 963e0bb184..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/PrecompAsset.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// PrecompAsset.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class PrecompAsset: Asset { - - /// Layers of the precomp - let layers: [LayerModel] - - enum CodingKeys : String, CodingKey { - case layers = "layers" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: PrecompAsset.CodingKeys.self) - self.layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(layers, forKey: .layers) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/Bundle.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/Bundle.swift deleted file mode 100644 index 98d8a762d3..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/Bundle.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) -import UIKit -#endif - -extension Bundle { - func getAnimationData(_ name: String, subdirectory: String? = nil) throws -> Data? { - // Check for files in the bundle at the given path - if let url = self.url(forResource: name, withExtension: "json", subdirectory: subdirectory) { - return try Data(contentsOf: url) - } - - // Check for data assets (not available on macOS) - #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) - let assetKey = subdirectory != nil ? "\(subdirectory ?? "")/\(name)" : name - return NSDataAsset.init(name: assetKey, bundle: self)?.data - #else - return nil - #endif - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift deleted file mode 100644 index 29fe104ddd..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +++ /dev/null @@ -1,40 +0,0 @@ -// From: https://medium.com/@kewindannerfjordremeczki/swift-4-0-decodable-heterogeneous-collections-ecc0e6b468cf - -import Foundation - -/// To support a new class family, create an enum that conforms to this protocol and contains the different types. -protocol ClassFamily: Decodable { - /// The discriminator key. - static var discriminator: Discriminator { get } - - /// Returns the class type of the object corresponding to the value. - func getType() -> AnyObject.Type -} - -/// Discriminator key enum used to retrieve discriminator fields in JSON payloads. -enum Discriminator: String, CodingKey { - case type = "ty" -} - -extension KeyedDecodingContainer { - - /// Decode a heterogeneous list of objects for a given family. - /// - Parameters: - /// - heterogeneousType: The decodable type of the list. - /// - family: The ClassFamily enum for the type family. - /// - key: The CodingKey to look up the list in the current container. - /// - Returns: The resulting list of heterogeneousType elements. - func decode(_ heterogeneousType: [T].Type, ofFamily family: U.Type, forKey key: K) throws -> [T] { - var container = try self.nestedUnkeyedContainer(forKey: key) - var list = [T]() - var tmpContainer = container - while !container.isAtEnd { - let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self) - let family: U = try typeContainer.decode(U.self, forKey: U.discriminator) - if let type = family.getType() as? T.Type { - list.append(try tmpContainer.decode(type)) - } - } - return list - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/Keyframe.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/Keyframe.swift deleted file mode 100644 index c88065b2e4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/Keyframe.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// Keyframe.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation -import CoreGraphics - -/** - Keyframe represents a point in time and is the container for datatypes. - Note: This is a parent class and should not be used directly. - */ -final class Keyframe { - - /// The value of the keyframe - let value: T - /// The time in frames of the keyframe. - let time: CGFloat - /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. - let isHold: Bool - /// The in tangent for the time interpolation curve. - let inTangent: Vector2D? - /// The out tangent for the time interpolation curve. - let outTangent: Vector2D? - - /// The spacial in tangent of the vector. - let spatialInTangent: Vector3D? - /// The spacial out tangent of the vector. - let spatialOutTangent: Vector3D? - - /// Initialize a value-only keyframe with no time data. - init(_ value: T, - spatialInTangent: Vector3D? = nil, - spatialOutTangent: Vector3D? = nil) { - self.value = value - self.time = 0 - self.isHold = true - self.inTangent = nil - self.outTangent = nil - self.spatialInTangent = spatialInTangent - self.spatialOutTangent = spatialOutTangent - } - - /// Initialize a keyframe - init(value: T, - time: Double, - isHold: Bool, - inTangent: Vector2D?, - outTangent: Vector2D?, - spatialInTangent: Vector3D? = nil, - spatialOutTangent: Vector3D? = nil) { - self.value = value - self.time = CGFloat(time) - self.isHold = isHold - self.outTangent = outTangent - self.inTangent = inTangent - self.spatialInTangent = spatialInTangent - self.spatialOutTangent = spatialOutTangent - } - -} - -/** - A generic class used to parse and remap keyframe json. - - Keyframe json has a couple of different variations and formats depending on the - type of keyframea and also the version of the JSON. By parsing the raw data - we can reconfigure it into a constant format. - */ -final class KeyframeData: Codable { - - /// The start value of the keyframe - let startValue: T? - /// The End value of the keyframe. Note: Newer versions animation json do not have this field. - let endValue: T? - /// The time in frames of the keyframe. - let time: Double? - /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. - let hold: Int? - - /// The in tangent for the time interpolation curve. - let inTangent: Vector2D? - /// The out tangent for the time interpolation curve. - let outTangent: Vector2D? - - /// The spacial in tangent of the vector. - let spatialInTangent: Vector3D? - /// The spacial out tangent of the vector. - let spatialOutTangent:Vector3D? - - init(startValue: T?, - endValue: T?, - time: Double?, - hold: Int?, - inTangent: Vector2D?, - outTangent: Vector2D?, - spatialInTangent: Vector3D?, - spatialOutTangent: Vector3D?) { - self.startValue = startValue - self.endValue = endValue - self.time = time - self.hold = hold - self.inTangent = inTangent - self.outTangent = outTangent - self.spatialInTangent = spatialInTangent - self.spatialOutTangent = spatialOutTangent - } - - enum CodingKeys : String, CodingKey { - case startValue = "s" - case endValue = "e" - case time = "t" - case hold = "h" - case inTangent = "i" - case outTangent = "o" - case spatialInTangent = "ti" - case spatialOutTangent = "to" - } - - var isHold: Bool { - if let hold = hold { - return hold > 0 - } - return false - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.swift deleted file mode 100644 index f64adc0bda..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// KeyframeGroup.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation - -/** - Used for coding/decoding a group of Keyframes by type. - - Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. - The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. - This helper object is needed to properly decode the json. - */ - -final class KeyframeGroup: Codable where T: Codable, T: Interpolatable { - - let keyframes: ContiguousArray> - - private enum KeyframeWrapperKey: String, CodingKey { - case keyframeData = "k" - } - - init(keyframes: ContiguousArray>) { - self.keyframes = keyframes - } - - init(_ value: T) { - self.keyframes = [Keyframe(value)] - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: KeyframeWrapperKey.self) - - if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) { - /// Try to decode raw value; No keyframe data. - self.keyframes = [Keyframe(keyframeData)] - } else { - /** - Decode and array of keyframes. - - Body Movin and Lottie deal with keyframes in different ways. - - A keyframe object in Body movin defines a span of time with a START - and an END, from the current keyframe time to the next keyframe time. - - A keyframe object in Lottie defines a singular point in time/space. - This point has an in-tangent and an out-tangent. - - To properly decode this we must iterate through keyframes while holding - reference to the previous keyframe. - */ - - var keyframesContainer = try container.nestedUnkeyedContainer(forKey: .keyframeData) - var keyframes = ContiguousArray>() - var previousKeyframeData: KeyframeData? - while(!keyframesContainer.isAtEnd) { - // Ensure that Time and Value are present. - - let keyframeData = try keyframesContainer.decode(KeyframeData.self) - - guard let value: T = keyframeData.startValue ?? previousKeyframeData?.endValue, - let time = keyframeData.time else { - /// Missing keyframe data. JSON must be corrupt. - throw DecodingError.dataCorruptedError(forKey: KeyframeWrapperKey.keyframeData, in: container, debugDescription: "Missing keyframe data.") - } - - keyframes.append(Keyframe(value: value, - time: time, - isHold: keyframeData.isHold, - inTangent: previousKeyframeData?.inTangent, - outTangent: keyframeData.outTangent, - spatialInTangent: previousKeyframeData?.spatialInTangent, - spatialOutTangent: keyframeData.spatialOutTangent)) - previousKeyframeData = keyframeData - } - self.keyframes = keyframes - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: KeyframeWrapperKey.self) - - if keyframes.count == 1 { - let keyframe = keyframes[0] - try container.encode(keyframe.value, forKey: .keyframeData) - } else { - var keyframeContainer = container.nestedUnkeyedContainer(forKey: .keyframeData) - - for i in 1..(startValue: keyframe.value, - endValue: nextKeyframe.value, - time: Double(keyframe.time), - hold: keyframe.isHold ? 1 : nil, - inTangent: nextKeyframe.inTangent, - outTangent: keyframe.outTangent, - spatialInTangent: nil, - spatialOutTangent: nil) - try keyframeContainer.encode(keyframeData) - } - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ImageLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ImageLayerModel.swift deleted file mode 100644 index 696196239b..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ImageLayerModel.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ImageLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds an image. -final class ImageLayerModel: LayerModel { - - /// The reference ID of the image. - let referenceID: String - - private enum CodingKeys : String, CodingKey { - case referenceID = "refId" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ImageLayerModel.CodingKeys.self) - self.referenceID = try container.decode(String.self, forKey: .referenceID) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(referenceID, forKey: .referenceID) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/LayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/LayerModel.swift deleted file mode 100644 index 6b2667a0de..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/LayerModel.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// Layer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation - -/// Used for mapping a heterogeneous list to classes for parsing. -extension LayerType: ClassFamily { - static var discriminator: Discriminator = .type - - func getType() -> AnyObject.Type { - switch self { - case .precomp: - return PreCompLayerModel.self - case .solid: - return SolidLayerModel.self - case .image: - return ImageLayerModel.self - case .null: - return LayerModel.self - case .shape: - return ShapeLayerModel.self - case .text: - return TextLayerModel.self - } - } -} - -public enum LayerType: Int, Codable { - case precomp - case solid - case image - case null - case shape - case text - - public init(from decoder: Decoder) throws { - self = try LayerType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .null - } -} - -public enum MatteType: Int, Codable { - case none - case add - case invert - case unknown -} - -public enum BlendMode: Int, Codable { - case normal - case multiply - case screen - case overlay - case darken - case lighten - case colorDodge - case colorBurn - case hardLight - case softLight - case difference - case exclusion - case hue - case saturation - case color - case luminosity -} - -/** - A base top container for shapes, images, and other view objects. - */ -class LayerModel: Codable { - - /// The readable name of the layer - let name: String - - /// The index of the layer - let index: Int - - /// The type of the layer. - let type: LayerType - - /// The coordinate space - let coordinateSpace: CoordinateSpace - - /// The in time of the layer in frames. - let inFrame: Double - /// The out time of the layer in frames. - let outFrame: Double - - /// The start time of the layer in frames. - let startTime: Double - - /// The transform of the layer - let transform: Transform - - /// The index of the parent layer, if applicable. - let parent: Int? - - /// The blending mode for the layer - let blendMode: BlendMode - - /// An array of masks for the layer. - let masks: [Mask]? - - /// A number that stretches time by a multiplier - let timeStretch: Double - - /// The type of matte if any. - let matte: MatteType? - - let hidden: Bool - - private enum CodingKeys : String, CodingKey { - case name = "nm" - case index = "ind" - case type = "ty" - case coordinateSpace = "ddd" - case inFrame = "ip" - case outFrame = "op" - case startTime = "st" - case transform = "ks" - case parent = "parent" - case blendMode = "bm" - case masks = "masksProperties" - case timeStretch = "sr" - case matte = "tt" - case hidden = "hd" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: LayerModel.CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" - self.index = try container.decode(Int.self, forKey: .index) - self.type = try container.decode(LayerType.self, forKey: .type) - self.coordinateSpace = try container.decodeIfPresent(CoordinateSpace.self, forKey: .coordinateSpace) ?? .type2d - self.inFrame = try container.decode(Double.self, forKey: .inFrame) - self.outFrame = try container.decode(Double.self, forKey: .outFrame) - self.startTime = try container.decode(Double.self, forKey: .startTime) - self.transform = try container.decode(Transform.self, forKey: .transform) - self.parent = try container.decodeIfPresent(Int.self, forKey: .parent) - self.blendMode = try container.decodeIfPresent(BlendMode.self, forKey: .blendMode) ?? .normal - self.masks = try container.decodeIfPresent([Mask].self, forKey: .masks) - self.timeStretch = try container.decodeIfPresent(Double.self, forKey: .timeStretch) ?? 1 - self.matte = try container.decodeIfPresent(MatteType.self, forKey: .matte) - self.hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.swift deleted file mode 100644 index e107ea1b92..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// PreCompLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds another animation composition. -final class PreCompLayerModel: LayerModel { - - /// The reference ID of the precomp. - let referenceID: String - - /// A value that remaps time over time. - let timeRemapping: KeyframeGroup? - - /// Precomp Width - let width: Double - - /// Precomp Height - let height: Double - - private enum CodingKeys : String, CodingKey { - case referenceID = "refId" - case timeRemapping = "tm" - case width = "w" - case height = "h" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: PreCompLayerModel.CodingKeys.self) - self.referenceID = try container.decode(String.self, forKey: .referenceID) - self.timeRemapping = try container.decodeIfPresent(KeyframeGroup.self, forKey: .timeRemapping) - self.width = try container.decode(Double.self, forKey: .width) - self.height = try container.decode(Double.self, forKey: .height) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(referenceID, forKey: .referenceID) - try container.encode(timeRemapping, forKey: .timeRemapping) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.swift deleted file mode 100644 index eb6299f813..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ShapeLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds vector shape objects. -final class ShapeLayerModel: LayerModel { - - /// A list of shape items. - let items: [ShapeItem] - - private enum CodingKeys : String, CodingKey { - case items = "shapes" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ShapeLayerModel.CodingKeys.self) - self.items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.items, forKey: .items) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/SolidLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/SolidLayerModel.swift deleted file mode 100644 index afa00d7ea8..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/SolidLayerModel.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// SolidLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds a solid color. -final class SolidLayerModel: LayerModel { - - /// The color of the solid in Hex // Change to value provider. - let colorHex: String - - /// The Width of the color layer - let width: Double - - /// The height of the color layer - let height: Double - - private enum CodingKeys : String, CodingKey { - case colorHex = "sc" - case width = "sw" - case height = "sh" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: SolidLayerModel.CodingKeys.self) - self.colorHex = try container.decode(String.self, forKey: .colorHex) - self.width = try container.decode(Double.self, forKey: .width) - self.height = try container.decode(Double.self, forKey: .height) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(colorHex, forKey: .colorHex) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/TextLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/TextLayerModel.swift deleted file mode 100644 index f08bcef3bb..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/TextLayerModel.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// TextLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds text. -final class TextLayerModel: LayerModel { - - /// The text for the layer - let text: KeyframeGroup - - /// Text animators - let animators: [TextAnimator] - - private enum CodingKeys : String, CodingKey { - case textGroup = "t" - } - - private enum TextCodingKeys : String, CodingKey { - case text = "d" - case animators = "a" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: TextLayerModel.CodingKeys.self) - let textContainer = try container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) - self.text = try textContainer.decode(KeyframeGroup.self, forKey: .text) - self.animators = try textContainer.decode([TextAnimator].self, forKey: .animators) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - var textContainer = container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) - try textContainer.encode(text, forKey: .text) - try textContainer.encode(animators, forKey: .animators) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/DashPattern.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/DashPattern.swift deleted file mode 100644 index efc7532886..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/DashPattern.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// DashPattern.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation - -enum DashElementType: String, Codable { - case offset = "o" - case dash = "d" - case gap = "g" -} - -final class DashElement: Codable { - let type: DashElementType - let value: KeyframeGroup - - enum CodingKeys : String, CodingKey { - case type = "n" - case value = "v" - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Marker.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Marker.swift deleted file mode 100644 index 9c9512f934..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Marker.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Marker.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -/// A time marker -final class Marker: Codable { - - /// The Marker Name - let name: String - - /// The Frame time of the marker - let frameTime: AnimationFrameTime - - enum CodingKeys : String, CodingKey { - case name = "cm" - case frameTime = "tm" - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Mask.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Mask.swift deleted file mode 100644 index 1b7dc49939..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Mask.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Mask.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum MaskMode: String, Codable { - case add = "a" - case subtract = "s" - case intersect = "i" - case lighten = "l" - case darken = "d" - case difference = "f" - case none = "n" -} - -final class Mask: Codable { - - let mode: MaskMode - - let opacity: KeyframeGroup - - let shape: KeyframeGroup - - let inverted: Bool - - let expansion: KeyframeGroup - - enum CodingKeys : String, CodingKey { - case mode = "mode" - case opacity = "o" - case inverted = "inv" - case shape = "pt" - case expansion = "x" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Mask.CodingKeys.self) - self.mode = try container.decodeIfPresent(MaskMode.self, forKey: .mode) ?? .add - self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) - self.shape = try container.decode(KeyframeGroup.self, forKey: .shape) - self.inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false - self.expansion = try container.decodeIfPresent(KeyframeGroup.self, forKey: .expansion) ?? KeyframeGroup(Vector1D(0)) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Transform.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Transform.swift deleted file mode 100644 index c7e383490a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Transform.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// Transform.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation - -/// The animatable transform for a layer. Controls position, rotation, scale, and opacity. -final class Transform: Codable { - - /// The anchor point of the transform. - let anchorPoint: KeyframeGroup - - /// The position of the transform. This is nil if the position data was split. - let position: KeyframeGroup? - - /// The positionX of the transform. This is nil if the position property is set. - let positionX: KeyframeGroup? - - /// The positionY of the transform. This is nil if the position property is set. - let positionY: KeyframeGroup? - - /// The scale of the transform - let scale: KeyframeGroup - - /// The rotation of the transform. Note: This is single dimensional rotation. - let rotation: KeyframeGroup - - /// The opacity of the transform. - let opacity: KeyframeGroup - - /// Should always be nil. - let rotationZ: KeyframeGroup? - - enum CodingKeys : String, CodingKey { - case anchorPoint = "a" - case position = "p" - case positionX = "px" - case positionY = "py" - case scale = "s" - case rotation = "r" - case rotationZ = "rz" - case opacity = "o" - } - - enum PositionCodingKeys : String, CodingKey { - case split = "s" - case positionX = "x" - case positionY = "y" - } - - - required init(from decoder: Decoder) throws { - /** - This manual override of decode is required because we want to throw an error - in the case that there is not position data. - */ - let container = try decoder.container(keyedBy: Transform.CodingKeys.self) - - // AnchorPoint - self.anchorPoint = try container.decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - - // Position - if container.contains(.positionX), container.contains(.positionY) { - // Position dimensions are split into two keyframe groups - self.positionX = try container.decode(KeyframeGroup.self, forKey: .positionX) - self.positionY = try container.decode(KeyframeGroup.self, forKey: .positionY) - self.position = nil - } else if let positionKeyframes = try? container.decode(KeyframeGroup.self, forKey: .position) { - // Position dimensions are a single keyframe group. - self.position = positionKeyframes - self.positionX = nil - self.positionY = nil - } else if let positionContainer = try? container.nestedContainer(keyedBy: PositionCodingKeys.self, forKey: .position), - let positionX = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionX), - let positionY = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionY) { - /// Position keyframes are split and nested. - self.positionX = positionX - self.positionY = positionY - self.position = nil - } else { - /// Default value. - self.position = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.positionX = nil - self.positionY = nil - } - - - // Scale - self.scale = try container.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) - - // Rotation - if let rotationZ = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) { - self.rotation = rotationZ - } else { - self.rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) - } - self.rotationZ = nil - - // Opacity - self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Ellipse.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Ellipse.swift deleted file mode 100644 index 5e6b9185e2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Ellipse.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// EllipseItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum PathDirection: Int, Codable { - case clockwise = 1 - case userSetClockwise = 2 - case counterClockwise = 3 -} - -/// An item that define an ellipse shape -final class Ellipse: ShapeItem { - - /// The direction of the ellipse. - let direction: PathDirection - - /// The position of the ellipse - let position: KeyframeGroup - - /// The size of the ellipse - let size: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case direction = "d" - case position = "p" - case size = "s" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Ellipse.CodingKeys.self) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise - self.position = try container.decode(KeyframeGroup.self, forKey: .position) - self.size = try container.decode(KeyframeGroup.self, forKey: .size) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(direction, forKey: .direction) - try container.encode(position, forKey: .position) - try container.encode(size, forKey: .size) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/FillI.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/FillI.swift deleted file mode 100644 index 11aded7709..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/FillI.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// FillShape.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum FillRule: Int, Codable { - case none - case nonZeroWinding - case evenOdd -} - -/// An item that defines a fill render -final class Fill: ShapeItem { - - /// The opacity of the fill - let opacity: KeyframeGroup - - /// The color keyframes for the fill - let color: KeyframeGroup - - let fillRule: FillRule - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case color = "c" - case fillRule = "r" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Fill.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.color = try container.decode(KeyframeGroup.self, forKey: .color) - self.fillRule = try container.decodeIfPresent(FillRule.self, forKey: .fillRule) ?? .nonZeroWinding - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(color, forKey: .color) - try container.encode(fillRule, forKey: .fillRule) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientFill.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientFill.swift deleted file mode 100644 index 05627c861e..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientFill.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// GradientFill.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum GradientType: Int, Codable { - case none - case linear - case radial -} - -/// An item that define a gradient fill -final class GradientFill: ShapeItem { - - /// The opacity of the fill - let opacity: KeyframeGroup - - /// The start of the gradient - let startPoint: KeyframeGroup - - /// The end of the gradient - let endPoint: KeyframeGroup - - /// The type of gradient - let gradientType: GradientType - - /// Gradient Highlight Length. Only if type is Radial - let highlightLength: KeyframeGroup? - - /// Highlight Angle. Only if type is Radial - let highlightAngle: KeyframeGroup? - - /// The number of color points in the gradient - let numberOfColors: Int - - /// The Colors of the gradient. - let colors: KeyframeGroup<[Double]> - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case startPoint = "s" - case endPoint = "e" - case gradientType = "t" - case highlightLength = "h" - case highlightAngle = "a" - case colors = "g" - } - - private enum GradientDataKeys : String, CodingKey { - case numberOfColors = "p" - case colors = "k" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: GradientFill.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) - self.endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) - self.gradientType = try container.decode(GradientType.self, forKey: .gradientType) - self.highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) - self.highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) - let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - self.colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) - self.numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(startPoint, forKey: .startPoint) - try container.encode(endPoint, forKey: .endPoint) - try container.encode(gradientType, forKey: .gradientType) - try container.encodeIfPresent(highlightLength, forKey: .highlightLength) - try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) - var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) - try colorsContainer.encode(colors, forKey: .colors) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.swift deleted file mode 100644 index 163a6128a2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// GradientStroke.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum LineCap: Int, Codable { - case none - case butt - case round - case square -} - -enum LineJoin: Int, Codable { - case none - case miter - case round - case bevel -} - -/// An item that define an ellipse shape -final class GradientStroke: ShapeItem { - - /// The opacity of the fill - let opacity: KeyframeGroup - - /// The start of the gradient - let startPoint: KeyframeGroup - - /// The end of the gradient - let endPoint: KeyframeGroup - - /// The type of gradient - let gradientType: GradientType - - /// Gradient Highlight Length. Only if type is Radial - let highlightLength: KeyframeGroup? - - /// Highlight Angle. Only if type is Radial - let highlightAngle: KeyframeGroup? - - /// The number of color points in the gradient - let numberOfColors: Int - - /// The Colors of the gradient. - let colors: KeyframeGroup<[Double]> - - /// The width of the stroke - let width: KeyframeGroup - - /// Line Cap - let lineCap: LineCap - - /// Line Join - let lineJoin: LineJoin - - /// Miter Limit - let miterLimit: Double - - /// The dash pattern of the stroke - let dashPattern: [DashElement]? - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case startPoint = "s" - case endPoint = "e" - case gradientType = "t" - case highlightLength = "h" - case highlightAngle = "a" - case colors = "g" - case width = "w" - case lineCap = "lc" - case lineJoin = "lj" - case miterLimit = "ml" - case dashPattern = "d" - } - - private enum GradientDataKeys : String, CodingKey { - case numberOfColors = "p" - case colors = "k" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: GradientStroke.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) - self.endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) - self.gradientType = try container.decode(GradientType.self, forKey: .gradientType) - self.highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) - self.highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) - self.width = try container.decode(KeyframeGroup.self, forKey: .width) - self.lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round - self.lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round - self.miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 - // TODO Decode Color Objects instead of array. - let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - self.colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) - self.numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) - self.dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(startPoint, forKey: .startPoint) - try container.encode(endPoint, forKey: .endPoint) - try container.encode(gradientType, forKey: .gradientType) - try container.encodeIfPresent(highlightLength, forKey: .highlightLength) - try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) - try container.encode(width, forKey: .width) - try container.encode(lineCap, forKey: .lineCap) - try container.encode(lineJoin, forKey: .lineJoin) - try container.encode(miterLimit, forKey: .miterLimit) - var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) - try colorsContainer.encode(colors, forKey: .colors) - try container.encodeIfPresent(dashPattern, forKey: .dashPattern) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Group.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Group.swift deleted file mode 100644 index c676d04cff..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Group.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// GroupItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Group: ShapeItem { - - /// A list of shape items. - let items: [ShapeItem] - - private enum CodingKeys : String, CodingKey { - case items = "it" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Group.CodingKeys.self) - self.items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(items, forKey: .items) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Merge.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Merge.swift deleted file mode 100644 index 3143bb5bfb..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Merge.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Merge.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum MergeMode: Int, Codable { - case none - case merge - case add - case subtract - case intersect - case exclude -} - -/// An item that define an ellipse shape -final class Merge: ShapeItem { - - /// The mode of the merge path - let mode: MergeMode - - private enum CodingKeys : String, CodingKey { - case mode = "mm" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Merge.CodingKeys.self) - self.mode = try container.decode(MergeMode.self, forKey: .mode) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(mode, forKey: .mode) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Rectangle.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Rectangle.swift deleted file mode 100644 index b880e75414..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Rectangle.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Rectangle.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Rectangle: ShapeItem { - - /// The direction of the rect. - let direction: PathDirection - - /// The position - let position: KeyframeGroup - - /// The size - let size: KeyframeGroup - - /// The Corner radius of the rectangle - let cornerRadius: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case direction = "d" - case position = "p" - case size = "s" - case cornerRadius = "r" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Rectangle.CodingKeys.self) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise - self.position = try container.decode(KeyframeGroup.self, forKey: .position) - self.size = try container.decode(KeyframeGroup.self, forKey: .size) - self.cornerRadius = try container.decode(KeyframeGroup.self, forKey: .cornerRadius) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(direction, forKey: .direction) - try container.encode(position, forKey: .position) - try container.encode(size, forKey: .size) - try container.encode(cornerRadius, forKey: .cornerRadius) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Repeater.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Repeater.swift deleted file mode 100644 index e8780f4bf4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Repeater.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// Repeater.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Repeater: ShapeItem { - - /// The number of copies to repeat - let copies: KeyframeGroup - - /// The offset of each copy - let offset: KeyframeGroup - - /// Start Opacity - let startOpacity: KeyframeGroup - - /// End opacity - let endOpacity: KeyframeGroup - - /// The rotation - let rotation: KeyframeGroup - - /// Anchor Point - let anchorPoint: KeyframeGroup - - /// Position - let position: KeyframeGroup - - /// Scale - let scale: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case copies = "c" - case offset = "o" - case transform = "tr" - } - - private enum TransformKeys : String, CodingKey { - case rotation = "r" - case startOpacity = "so" - case endOpacity = "eo" - case anchorPoint = "a" - case position = "p" - case scale = "s" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Repeater.CodingKeys.self) - self.copies = try container.decodeIfPresent(KeyframeGroup.self, forKey: .copies) ?? KeyframeGroup(Vector1D(0)) - self.offset = try container.decodeIfPresent(KeyframeGroup.self, forKey: .offset) ?? KeyframeGroup(Vector1D(0)) - let transformContainer = try container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) - self.startOpacity = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .startOpacity) ?? KeyframeGroup(Vector1D(100)) - self.endOpacity = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .endOpacity) ?? KeyframeGroup(Vector1D(100)) - self.rotation = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) - self.position = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.anchorPoint = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.scale = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(copies, forKey: .copies) - try container.encode(offset, forKey: .offset) - var transformContainer = container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) - try transformContainer.encode(startOpacity, forKey: .startOpacity) - try transformContainer.encode(endOpacity, forKey: .endOpacity) - try transformContainer.encode(rotation, forKey: .rotation) - try transformContainer.encode(position, forKey: .position) - try transformContainer.encode(anchorPoint, forKey: .anchorPoint) - try transformContainer.encode(scale, forKey: .scale) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Shape.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Shape.swift deleted file mode 100644 index e2681e65b2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Shape.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// VectorShape.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Shape: ShapeItem { - - /// The Path - let path: KeyframeGroup - - let direction: PathDirection? - - private enum CodingKeys : String, CodingKey { - case path = "ks" - case direction = "d" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Shape.CodingKeys.self) - self.path = try container.decode(KeyframeGroup.self, forKey: .path) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(path, forKey: .path) - try container.encodeIfPresent(direction, forKey: .direction) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.swift deleted file mode 100644 index 0444361979..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// ShapeItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// Used for mapping a heterogeneous list to classes for parsing. -extension ShapeType: ClassFamily { - - static var discriminator: Discriminator = .type - - func getType() -> AnyObject.Type { - switch self { - case .ellipse: - return Ellipse.self - case .fill: - return Fill.self - case .gradientFill: - return GradientFill.self - case .group: - return Group.self - case .gradientStroke: - return GradientStroke.self - case .merge: - return Merge.self - case .rectangle: - return Rectangle.self - case .repeater: - return Repeater.self - case .shape: - return Shape.self - case .star: - return Star.self - case .stroke: - return Stroke.self - case .trim: - return Trim.self - case .transform: - return ShapeTransform.self - default: - return ShapeItem.self - } - } -} - -enum ShapeType: String, Codable { - case ellipse = "el" - case fill = "fl" - case gradientFill = "gf" - case group = "gr" - case gradientStroke = "gs" - case merge = "mm" - case rectangle = "rc" - case repeater = "rp" - case round = "rd" - case shape = "sh" - case star = "sr" - case stroke = "st" - case trim = "tm" - case transform = "tr" - case unknown - - public init(from decoder: Decoder) throws { - self = try ShapeType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown - } -} - -/// An item belonging to a Shape Layer -class ShapeItem: Codable { - - /// The name of the shape - let name: String - - /// The type of shape - let type: ShapeType - - let hidden: Bool - - private enum CodingKeys : String, CodingKey { - case name = "nm" - case type = "ty" - case hidden = "hd" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ShapeItem.CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" - self.type = try container.decode(ShapeType.self, forKey: .type) - self.hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.swift deleted file mode 100644 index 5fba165760..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// TransformItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class ShapeTransform: ShapeItem { - - /// Anchor Point - let anchor: KeyframeGroup - - /// Position - let position: KeyframeGroup - - /// Scale - let scale: KeyframeGroup - - /// Rotation - let rotation: KeyframeGroup - - /// opacity - let opacity: KeyframeGroup - - /// Skew - let skew: KeyframeGroup - - /// Skew Axis - let skewAxis: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case anchor = "a" - case position = "p" - case scale = "s" - case rotation = "r" - case opacity = "o" - case skew = "sk" - case skewAxis = "sa" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ShapeTransform.CodingKeys.self) - self.anchor = try container.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.position = try container.decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.scale = try container.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) - self.rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) - self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) - self.skew = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skew) ?? KeyframeGroup(Vector1D(0)) - self.skewAxis = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) ?? KeyframeGroup(Vector1D(0)) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(anchor, forKey: .anchor) - try container.encode(position, forKey: .position) - try container.encode(scale, forKey: .scale) - try container.encode(rotation, forKey: .rotation) - try container.encode(opacity, forKey: .opacity) - try container.encode(skew, forKey: .skew) - try container.encode(skewAxis, forKey: .skewAxis) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Star.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Star.swift deleted file mode 100644 index b280441860..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Star.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Star.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum StarType: Int, Codable { - case none - case star - case polygon -} - -/// An item that define an ellipse shape -final class Star: ShapeItem { - - /// The direction of the star. - let direction: PathDirection - - /// The position of the star - let position: KeyframeGroup - - /// The outer radius of the star - let outerRadius: KeyframeGroup - - /// The outer roundness of the star - let outerRoundness: KeyframeGroup - - /// The outer radius of the star - let innerRadius: KeyframeGroup? - - /// The outer roundness of the star - let innerRoundness: KeyframeGroup? - - /// The rotation of the star - let rotation: KeyframeGroup - - /// The number of points on the star - let points: KeyframeGroup - - /// The type of star - let starType: StarType - - private enum CodingKeys : String, CodingKey { - case direction = "d" - case position = "p" - case outerRadius = "or" - case outerRoundness = "os" - case innerRadius = "ir" - case innerRoundness = "is" - case rotation = "r" - case points = "pt" - case starType = "sy" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Star.CodingKeys.self) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise - self.position = try container.decode(KeyframeGroup.self, forKey: .position) - self.outerRadius = try container.decode(KeyframeGroup.self, forKey: .outerRadius) - self.outerRoundness = try container.decode(KeyframeGroup.self, forKey: .outerRoundness) - self.innerRadius = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRadius) - self.innerRoundness = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRoundness) - self.rotation = try container.decode(KeyframeGroup.self, forKey: .rotation) - self.points = try container.decode(KeyframeGroup.self, forKey: .points) - self.starType = try container.decode(StarType.self, forKey: .starType) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(direction, forKey: .direction) - try container.encode(position, forKey: .position) - try container.encode(outerRadius, forKey: .outerRadius) - try container.encode(outerRoundness, forKey: .outerRoundness) - try container.encode(innerRadius, forKey: .innerRadius) - try container.encode(innerRoundness, forKey: .innerRoundness) - try container.encode(rotation, forKey: .rotation) - try container.encode(points, forKey: .points) - try container.encode(starType, forKey: .starType) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Stroke.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Stroke.swift deleted file mode 100644 index 5e043edb4b..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Stroke.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// Stroke.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Stroke: ShapeItem { - - /// The opacity of the stroke - let opacity: KeyframeGroup - - /// The Color of the stroke - let color: KeyframeGroup - - /// The width of the stroke - let width: KeyframeGroup - - /// Line Cap - let lineCap: LineCap - - /// Line Join - let lineJoin: LineJoin - - /// Miter Limit - let miterLimit: Double - - /// The dash pattern of the stroke - let dashPattern: [DashElement]? - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case color = "c" - case width = "w" - case lineCap = "lc" - case lineJoin = "lj" - case miterLimit = "ml" - case dashPattern = "d" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Stroke.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.color = try container.decode(KeyframeGroup.self, forKey: .color) - self.width = try container.decode(KeyframeGroup.self, forKey: .width) - self.lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round - self.lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round - self.miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 - self.dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(color, forKey: .color) - try container.encode(width, forKey: .width) - try container.encode(lineCap, forKey: .lineCap) - try container.encode(lineJoin, forKey: .lineJoin) - try container.encode(miterLimit, forKey: .miterLimit) - try container.encodeIfPresent(dashPattern, forKey: .dashPattern) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Trim.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Trim.swift deleted file mode 100644 index dba3d67c9c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Trim.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Trim.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum TrimType: Int, Codable { - case simultaneously = 1 - case individually = 2 -} -/// An item that define an ellipse shape -final class Trim: ShapeItem { - - /// The start of the trim - let start: KeyframeGroup - - /// The end of the trim - let end: KeyframeGroup - - /// The offset of the trim - let offset: KeyframeGroup - - let trimType: TrimType - - private enum CodingKeys : String, CodingKey { - case start = "s" - case end = "e" - case offset = "o" - case trimType = "m" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Trim.CodingKeys.self) - self.start = try container.decode(KeyframeGroup.self, forKey: .start) - self.end = try container.decode(KeyframeGroup.self, forKey: .end) - self.offset = try container.decode(KeyframeGroup.self, forKey: .offset) - self.trimType = try container.decode(TrimType.self, forKey: .trimType) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(start, forKey: .start) - try container.encode(end, forKey: .end) - try container.encode(offset, forKey: .offset) - try container.encode(trimType, forKey: .trimType) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Font.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Font.swift deleted file mode 100644 index 54d75407d8..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Font.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Font.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class Font: Codable { - - let name: String - let familyName: String - let style: String - let ascent: Double - - private enum CodingKeys: String, CodingKey { - case name = "fName" - case familyName = "fFamily" - case style = "fStyle" - case ascent = "ascent" - } - -} - -/// A list of fonts -final class FontList: Codable { - - let fonts: [Font] - - enum CodingKeys : String, CodingKey { - case fonts = "list" - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Glyph.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Glyph.swift deleted file mode 100644 index 755f00a0c2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Glyph.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// Glyph.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -/// A model that holds a vector character -final class Glyph: Codable { - - /// The character - let character: String - - /// The font size of the character - let fontSize: Double - - /// The font family of the character - let fontFamily: String - - /// The Style of the character - let fontStyle: String - - /// The Width of the character - let width: Double - - /// The Shape Data of the Character - let shapes: [ShapeItem] - - private enum CodingKeys: String, CodingKey { - case character = "ch" - case fontSize = "size" - case fontFamily = "fFamily" - case fontStyle = "style" - case width = "w" - case shapeWrapper = "data" - } - - private enum ShapeKey: String, CodingKey { - case shapes = "shapes" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Glyph.CodingKeys.self) - self.character = try container.decode(String.self, forKey: .character) - self.fontSize = try container.decode(Double.self, forKey: .fontSize) - self.fontFamily = try container.decode(String.self, forKey: .fontFamily) - self.fontStyle = try container.decode(String.self, forKey: .fontStyle) - self.width = try container.decode(Double.self, forKey: .width) - if container.contains(.shapeWrapper), - let shapeContainer = try? container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper), - shapeContainer.contains(.shapes) { - self.shapes = try shapeContainer.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .shapes) - } else { - self.shapes = [] - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(character, forKey: .character) - try container.encode(fontSize, forKey: .fontSize) - try container.encode(fontFamily, forKey: .fontFamily) - try container.encode(fontStyle, forKey: .fontStyle) - try container.encode(width, forKey: .width) - - var shapeContainer = container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper) - try shapeContainer.encode(shapes, forKey: .shapes) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextAnimator.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextAnimator.swift deleted file mode 100644 index dd93cdf6f2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextAnimator.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// TextAnimator.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class TextAnimator: Codable { - - let name: String - - /// Anchor - let anchor: KeyframeGroup? - - /// Position - let position: KeyframeGroup? - - /// Scale - let scale: KeyframeGroup? - - /// Skew - let skew: KeyframeGroup? - - /// Skew Axis - let skewAxis: KeyframeGroup? - - /// Rotation - let rotation: KeyframeGroup? - - /// Opacity - let opacity: KeyframeGroup? - - /// Stroke Color - let strokeColor: KeyframeGroup? - - /// Fill Color - let fillColor: KeyframeGroup? - - /// Stroke Width - let strokeWidth: KeyframeGroup? - - /// Tracking - let tracking: KeyframeGroup? - - private enum CodingKeys: String, CodingKey { -// case textSelector = "s" TODO - case textAnimator = "a" - case name = "nm" - } - - private enum TextSelectorKeys: String, CodingKey { - case start = "s" - case end = "e" - case offset = "o" - } - - private enum TextAnimatorKeys: String, CodingKey { - case fillColor = "fc" - case strokeColor = "sc" - case strokeWidth = "sw" - case tracking = "t" - case anchor = "a" - case position = "p" - case scale = "s" - case skew = "sk" - case skewAxis = "sa" - case rotation = "r" - case opacity = "o" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: TextAnimator.CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" - let animatorContainer = try container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) - self.fillColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .fillColor) - self.strokeColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeColor) - self.strokeWidth = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeWidth) - self.tracking = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .tracking) - self.anchor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) - self.position = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) - self.scale = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) - self.skew = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skew) - self.skewAxis = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) - self.rotation = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) - self.opacity = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) - - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - var animatorContainer = container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) - try animatorContainer.encodeIfPresent(fillColor, forKey: .fillColor) - try animatorContainer.encodeIfPresent(strokeColor, forKey: .strokeColor) - try animatorContainer.encodeIfPresent(strokeWidth, forKey: .strokeWidth) - try animatorContainer.encodeIfPresent(tracking, forKey: .tracking) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextDocument.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextDocument.swift deleted file mode 100644 index 016fd76803..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextDocument.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// TextDocument.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -enum TextJustification: Int, Codable { - case left - case right - case center -} - -final class TextDocument: Codable { - - /// The Text - let text: String - - /// The Font size - let fontSize: Double - - /// The Font Family - let fontFamily: String - - /// Justification - let justification: TextJustification - - /// Tracking - let tracking: Int - - /// Line Height - let lineHeight: Double - - /// Baseline - let baseline: Double? - - /// Fill Color data - let fillColorData: Color? - - /// Scroke Color data - let strokeColorData: Color? - - /// Stroke Width - let strokeWidth: Double? - - /// Stroke Over Fill - let strokeOverFill: Bool? - - let textFramePosition: Vector3D? - - let textFrameSize: Vector3D? - - private enum CodingKeys : String, CodingKey { - case text = "t" - case fontSize = "s" - case fontFamily = "f" - case justification = "j" - case tracking = "tr" - case lineHeight = "lh" - case baseline = "ls" - case fillColorData = "fc" - case strokeColorData = "sc" - case strokeWidth = "sw" - case strokeOverFill = "of" - case textFramePosition = "ps" - case textFrameSize = "sz" - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Extensions/ItemsExtension.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Extensions/ItemsExtension.swift deleted file mode 100644 index 9220a637ab..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Extensions/ItemsExtension.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// ItemsExtension.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/18/19. -// - -import Foundation - -final class NodeTree { - var rootNode: AnimatorNode? = nil - var transform: ShapeTransform? = nil - var renderContainers: [ShapeContainerLayer] = [] - var paths: [PathOutputNode] = [] - var childrenNodes: [AnimatorNode] = [] -} - -extension Array where Element == ShapeItem { - func initializeNodeTree() -> NodeTree { - - let nodeTree = NodeTree() - - for item in self { - guard item.hidden == false, item.type != .unknown else { continue } - if let fill = item as? Fill { - let node = FillNode(parentNode: nodeTree.rootNode, fill: fill) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let stroke = item as? Stroke { - let node = StrokeNode(parentNode: nodeTree.rootNode, stroke: stroke) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let gradientFill = item as? GradientFill { - let node = GradientFillNode(parentNode: nodeTree.rootNode, gradientFill: gradientFill) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let gradientStroke = item as? GradientStroke { - let node = GradientStrokeNode(parentNode: nodeTree.rootNode, gradientStroke: gradientStroke) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let ellipse = item as? Ellipse { - let node = EllipseNode(parentNode: nodeTree.rootNode, ellipse: ellipse) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let rect = item as? Rectangle { - let node = RectangleNode(parentNode: nodeTree.rootNode, rectangle: rect) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let star = item as? Star { - switch star.starType { - case .none: - continue - case .polygon: - let node = PolygonNode(parentNode: nodeTree.rootNode, star: star) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - case .star: - let node = StarNode(parentNode: nodeTree.rootNode, star: star) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } - } else if let shape = item as? Shape { - let node = ShapeNode(parentNode: nodeTree.rootNode, shape: shape) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let trim = item as? Trim { - let node = TrimPathNode(parentNode: nodeTree.rootNode, trim: trim, upstreamPaths: nodeTree.paths) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let xform = item as? ShapeTransform { - nodeTree.transform = xform - continue - } else if let group = item as? Group { - - let tree = group.items.initializeNodeTree() - let node = GroupNode(name: group.name, parentNode: nodeTree.rootNode, tree: tree) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - /// Now add all child paths to current tree - nodeTree.paths.append(contentsOf: tree.paths) - nodeTree.renderContainers.append(node.container) - } - - if let pathNode = nodeTree.rootNode as? PathNode { - //// Add path container to the node tree - nodeTree.paths.append(pathNode.pathOutput) - } - - if let renderNode = nodeTree.rootNode as? RenderNode { - nodeTree.renderContainers.append(ShapeRenderLayer(renderer: renderNode.renderer)) - } - } - return nodeTree - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift deleted file mode 100644 index 5e1048153f..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// NodeProperty.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// A node property that holds a reference to a T ValueProvider and a T ValueContainer. -class NodeProperty: AnyNodeProperty { - - var valueType: Any.Type { return T.self } - - var value: T { - return typedContainer.outputValue - } - - var valueContainer: AnyValueContainer { - return typedContainer - } - - var valueProvider: AnyValueProvider - - init(provider: AnyValueProvider) { - self.valueProvider = provider - self.typedContainer = ValueContainer(provider.value(frame: 0) as! T) - self.typedContainer.setNeedsUpdate() - } - - func needsUpdate(frame: CGFloat) -> Bool { - return valueContainer.needsUpdate || valueProvider.hasUpdate(frame: frame) - } - - func setProvider(provider: AnyValueProvider) { - guard provider.valueType == valueType else { return } - self.valueProvider = provider - valueContainer.setNeedsUpdate() - } - - func update(frame: CGFloat) { - typedContainer.setValue(valueProvider.value(frame: frame), forFrame: frame) - } - - fileprivate var typedContainer: ValueContainer -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift deleted file mode 100644 index 7691f39eb9..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// AnyNodeProperty.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics -/// A property of a node. The node property holds a provider and a container -protocol AnyNodeProperty { - - /// Returns true if the property needs to recompute its stored value - func needsUpdate(frame: CGFloat) -> Bool - - /// Updates the property for the frame - func update(frame: CGFloat) - - /// The stored value container for the property - var valueContainer: AnyValueContainer { get } - - /// The value provider for the property - var valueProvider: AnyValueProvider { get } - - /// The Type of the value provider - var valueType: Any.Type { get } - - /// Sets the value provider for the property. - func setProvider(provider: AnyValueProvider) -} - -extension AnyNodeProperty { - - /// Returns the most recently computed value for the keypath, returns nil if property wasn't found - func getValueOfType() -> T? { - return valueContainer.value as? T - } - - /// Returns the most recently computed value for the keypath, returns nil if property wasn't found - func getValue() -> Any? { - return valueContainer.value - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift deleted file mode 100644 index 96def0b64d..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// AnyValueContainer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// The container for the value of a property. -protocol AnyValueContainer: AnyObject { - - /// The stored value of the container - var value: Any { get } - - /// Notifies the provider that it should update its container - func setNeedsUpdate() - - /// When true the container needs to have its value updated by its provider - var needsUpdate: Bool { get } - - /// The frame time of the last provided update - var lastUpdateFrame: CGFloat { get } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift deleted file mode 100644 index 8b13789179..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift deleted file mode 100644 index 11329248a6..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// NodePropertyMap.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/21/19. -// - -import Foundation -import QuartzCore - -protocol NodePropertyMap { - var properties: [AnyNodeProperty] { get } -} - -extension NodePropertyMap { - /// Checks if the node's local contents need to be rebuilt. - func needsLocalUpdate(frame: CGFloat) -> Bool { - for property in properties { - if property.needsUpdate(frame: frame) { - return true - } - } - return false - } - - /// Rebuilds only the local nodes that have an update for the frame - func updateNodeProperties(frame: CGFloat) { - properties.forEach { (property) in - property.update(frame: frame) - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift deleted file mode 100644 index 7f13644fb2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// ValueContainer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// A container for a node value that is Typed to T. -class ValueContainer: AnyValueContainer { - - private(set) var lastUpdateFrame: CGFloat = CGFloat.infinity - - func setValue(_ value: Any, forFrame: CGFloat) { - if let typedValue = value as? T { - needsUpdate = false - lastUpdateFrame = forFrame - outputValue = typedValue - } - } - - func setNeedsUpdate() { - needsUpdate = true - } - - var value: Any { - return outputValue as Any - } - - var outputValue: T { - didSet { - needsUpdate = false - } - } - - init(_ value: T) { - self.outputValue = value - } - - fileprivate(set) var needsUpdate: Bool = true -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift deleted file mode 100644 index 9470cd6c1d..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// KeyframeGroupInterpolator.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation -import CoreGraphics - -/// A value provider that produces an array of values from an array of Keyframe Interpolators -final class GroupInterpolator: AnyValueProvider where ValueType: Interpolatable { - var valueType: Any.Type { - return [ValueType].self - } - - func hasUpdate(frame: CGFloat) -> Bool { - let updated = keyframeInterpolators.first(where: {$0.hasUpdate(frame: frame)}) - return updated != nil - } - - func value(frame: CGFloat) -> Any { - let output = keyframeInterpolators.map({$0.value(frame: frame) as! ValueType}) - return output - } - - /// Initialize with an array of array of keyframes. - init(keyframeGroups: ContiguousArray>>) { - self.keyframeInterpolators = ContiguousArray(keyframeGroups.map({KeyframeInterpolator(keyframes: $0)})) - } - let keyframeInterpolators: ContiguousArray> - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift deleted file mode 100644 index baf46feff5..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift +++ /dev/null @@ -1,233 +0,0 @@ -// -// KeyframeInterpolator.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/15/19. -// - -import Foundation -import CoreGraphics - -/// A value provider that produces a value at Time from a group of keyframes -final class KeyframeInterpolator: AnyValueProvider where ValueType: Interpolatable { - - init(keyframes: ContiguousArray>) { - self.keyframes = keyframes - } - let keyframes: ContiguousArray> - - var valueType: Any.Type { - return ValueType.self - } - - /** - Returns true to trigger a frame update for this interpolator. - - An interpolator will be asked if it needs to update every frame. - If the interpolator needs updating it will be asked to compute its value for - the given frame. - - Cases a keyframe should not be updated: - - If time is in span and leading keyframe is hold - - If time is after the last keyframe. - - If time is before the first keyframe - - Cases for updating a keyframe: - - If time is in the span, and is not a hold - - If time is outside of the span, and there are more keyframes - - If a value delegate is set - - If leading and trailing are both nil. - */ - func hasUpdate(frame: CGFloat) -> Bool { - if lastUpdatedFrame == nil { - return true - } - - if let leading = leadingKeyframe, - trailingKeyframe == nil, - leading.time < frame { - /// Frame is after bounds of keyframes - return false - } - if let trailing = trailingKeyframe, - leadingKeyframe == nil, - frame < trailing.time { - /// Frame is before bounds of keyframes - return false - } - if let leading = leadingKeyframe, - let trailing = trailingKeyframe, - leading.isHold, - leading.time < frame, - frame < trailing.time { - return false - } - return true - } - - fileprivate var lastUpdatedFrame: CGFloat? - - @discardableResult - func value(frame: CGFloat) -> Any { - // First set the keyframe span for the frame. - updateSpanIndices(frame: frame) - lastUpdatedFrame = frame - // If only one keyframe return its value - let progress: CGFloat - let value: ValueType - - if let leading = leadingKeyframe, - let trailing = trailingKeyframe { - /// We have leading and trailing keyframe. - progress = leading.interpolatedProgress(trailing, keyTime: frame) - value = leading.interpolate(trailing, progress: progress) - } else if let leading = leadingKeyframe { - progress = 0 - value = leading.value - } else if let trailing = trailingKeyframe { - progress = 1 - value = trailing.value - } else { - /// Satisfy the compiler. - progress = 0 - value = keyframes[0].value - } - return value - } - - fileprivate var leadingIndex: Int? = nil - fileprivate var trailingIndex: Int? = nil - fileprivate var leadingKeyframe: Keyframe? = nil - fileprivate var trailingKeyframe: Keyframe? = nil - - /// Finds the appropriate Leading and Trailing keyframe index for the given time. - fileprivate func updateSpanIndices(frame: CGFloat) { - guard keyframes.count > 0 else { - leadingIndex = nil - trailingIndex = nil - leadingKeyframe = nil - trailingKeyframe = nil - return - } - - /** - This function searches through the array to find the span of two keyframes - that contain the current time. - - We could use Array.first(where:) but that would search through the entire array - each frame. - Instead we track the last used index and search either forwards or - backwards from there. This reduces the iterations and complexity from - - O(n), where n is the length of the sequence to - O(n), where n is the number of items after or before the last used index. - - */ - - if keyframes.count == 1 { - /// Only one keyframe. Set it as first and move on. - leadingIndex = 0 - trailingIndex = nil - leadingKeyframe = keyframes[0] - trailingKeyframe = nil - return - } - - /// Sets the initial keyframes. This is often only needed for the first check. - if leadingIndex == nil && - trailingIndex == nil { - if frame < keyframes[0].time { - /// Time is before the first keyframe. Set it as the trailing. - trailingIndex = 0 - } else { - /// Time is after the first keyframe. Set the keyframe and the trailing. - leadingIndex = 0 - trailingIndex = 1 - } - } - - if let currentTrailing = trailingIndex, - keyframes[currentTrailing].time <= frame { - /// Time is after the current span. Iterate forward. - var newLeading = currentTrailing - var keyframeFound: Bool = false - while !keyframeFound { - - leadingIndex = newLeading - trailingIndex = keyframes.validIndex(newLeading + 1) - - guard let trailing = trailingIndex else { - /// We have reached the end of our keyframes. Time is after the last keyframe. - keyframeFound = true - continue - } - if frame < keyframes[trailing].time { - /// Keyframe in current span. - keyframeFound = true - continue - } - /// Advance the array. - newLeading = trailing - } - - } else if let currentLeading = leadingIndex, - frame < keyframes[currentLeading].time { - - /// Time is before the current span. Iterate backwards - var newTrailing = currentLeading - - var keyframeFound: Bool = false - while !keyframeFound { - - leadingIndex = keyframes.validIndex(newTrailing - 1) - trailingIndex = newTrailing - - guard let leading = leadingIndex else { - /// We have reached the end of our keyframes. Time is after the last keyframe. - keyframeFound = true - continue - } - if keyframes[leading].time <= frame { - /// Keyframe in current span. - keyframeFound = true - continue - } - /// Step back - newTrailing = leading - } - } - if let keyFrame = leadingIndex { - leadingKeyframe = keyframes[keyFrame] - } else { - leadingKeyframe = nil - } - - if let keyFrame = trailingIndex { - trailingKeyframe = keyframes[keyFrame] - } else { - trailingKeyframe = nil - } - } -} - -fileprivate extension Array { - - func validIndex(_ index: Int) -> Int? { - if 0 <= index, index < endIndex { - return index - } - return nil - } - -} - -fileprivate extension ContiguousArray { - - func validIndex(_ index: Int) -> Int? { - if 0 <= index, index < endIndex { - return index - } - return nil - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift deleted file mode 100644 index ba244023ab..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// SingleValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -/// Returns a value for every frame. -final class SingleValueProvider: AnyValueProvider { - - var value: ValueType { - didSet { - hasUpdate = true - } - } - - init(_ value: ValueType) { - self.value = value - } - - var valueType: Any.Type { - return ValueType.self - } - - func hasUpdate(frame: CGFloat) -> Bool { - return hasUpdate - } - - func value(frame: CGFloat) -> Any { - hasUpdate = false - return value - } - - private var hasUpdate: Bool = true -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift deleted file mode 100644 index 0da2b98dca..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +++ /dev/null @@ -1,244 +0,0 @@ -// -// TrimPathNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/23/19. -// - -import Foundation -import QuartzCore - -final class TrimPathProperties: NodePropertyMap { - - init(trim: Trim) { - self.start = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.start.keyframes)) - self.end = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.end.keyframes)) - self.offset = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.offset.keyframes)) - self.type = trim.trimType - let keypathProperties: [String : AnyNodeProperty] = [ - "Start" : start, - "End" : end, - "Offset" : offset - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let start: NodeProperty - let end: NodeProperty - let offset: NodeProperty - let type: TrimType -} - -final class TrimPathNode: AnimatorNode { - - let properties: TrimPathProperties - - fileprivate let upstreamPaths: [PathOutputNode] - - init(parentNode: AnimatorNode?, trim: Trim, upstreamPaths: [PathOutputNode]) { - self.outputNode = PassThroughOutputNode(parent: parentNode?.outputNode) - self.parentNode = parentNode - self.properties = TrimPathProperties(trim: trim) - self.upstreamPaths = upstreamPaths - } - - // MARK: Animator Node - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - let outputNode: NodeOutput - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true - - func forceUpstreamOutputUpdates() -> Bool { - return hasLocalUpdates || hasUpstreamUpdates - } - - func rebuildOutputs(frame: CGFloat) { - /// Make sure there is a trim. - let startValue = properties.start.value.cgFloatValue * 0.01 - let endValue = properties.end.value.cgFloatValue * 0.01 - let start = min(startValue, endValue) - let end = max(startValue, endValue) - - let offset = properties.offset.value.cgFloatValue.truncatingRemainder(dividingBy: 360) / 360 - - /// No need to trim, it's a full path - if start == 0, end == 1 { - return - } - - /// All paths are empty. - if start == end { - for pathContainer in upstreamPaths { - pathContainer.removePaths(updateFrame: frame) - } - return - } - - if properties.type == .simultaneously { - /// Just trim each path - for pathContainer in upstreamPaths { - let pathObjects = pathContainer.removePaths(updateFrame: frame) - for path in pathObjects { - // We are treating each compount path as an individual path. Its subpaths are treated as a whole. - pathContainer.appendPath(path.trim(fromPosition: start, toPosition: end, offset: offset, trimSimultaneously: false), updateFrame: frame) - } - } - return - } - - /// Individual path trimming. - - /// Brace yourself for the below code. - - /// Normalize lengths with offset. - var startPosition = (start+offset).truncatingRemainder(dividingBy: 1) - var endPosition = (end+offset).truncatingRemainder(dividingBy: 1) - - if startPosition < 0 { - startPosition = 1 + startPosition - } - - if endPosition < 0 { - endPosition = 1 + endPosition - } - if startPosition == 1 { - startPosition = 0 - } - if endPosition == 0 { - endPosition = 1 - } - - - /// First get the total length of all paths. - var totalLength: CGFloat = 0 - upstreamPaths.forEach({ totalLength = totalLength + $0.totalLength }) - - /// Now determine the start and end cut lengths - let startLength = startPosition * totalLength - let endLength = endPosition * totalLength - var pathStart: CGFloat = 0 - - /// Now loop through all path containers - for pathContainer in upstreamPaths { - - let pathEnd = pathStart + pathContainer.totalLength - - if !startLength.isInRange(pathStart, pathEnd) && - endLength.isInRange(pathStart, pathEnd) { - // pathStart|=======E----------------------|pathEnd - // Cut path components, removing after end. - - let pathCutLength = endLength - pathStart - let subpaths = pathContainer.removePaths(updateFrame: frame) - var subpathStart: CGFloat = 0 - for path in subpaths { - let subpathEnd = subpathStart + path.length - if pathCutLength < subpathEnd { - /// This is the subpath that needs to be cut. - let cutLength = pathCutLength - subpathStart - let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - break - } else { - /// Add to container and move on - pathContainer.appendPath(path, updateFrame: frame) - } - if pathCutLength == subpathEnd { - /// Right on the end. The next subpath is not included. Break. - break - } - subpathStart = subpathEnd - } - - } else if !endLength.isInRange(pathStart, pathEnd) && - startLength.isInRange(pathStart, pathEnd) { - // pathStart|-------S======================|pathEnd - // - - // Cut path components, removing before beginning. - let pathCutLength = startLength - pathStart - // Clear paths from container - let subpaths = pathContainer.removePaths(updateFrame: frame) - var subpathStart: CGFloat = 0 - for path in subpaths { - let subpathEnd = subpathStart + path.length - - if subpathStart < pathCutLength, pathCutLength < subpathEnd { - /// This is the subpath that needs to be cut. - let cutLength = pathCutLength - subpathStart - let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - } else if pathCutLength <= subpathStart { - pathContainer.appendPath(path, updateFrame: frame) - } - subpathStart = subpathEnd - } - } else if endLength.isInRange(pathStart, pathEnd) && - startLength.isInRange(pathStart, pathEnd) { - // pathStart|-------S============E---------|endLength - // pathStart|=====E----------------S=======|endLength - // trim from path beginning to endLength. - - // Cut path components, removing before beginnings. - let startCutLength = startLength - pathStart - let endCutLength = endLength - pathStart - // Clear paths from container - let subpaths = pathContainer.removePaths(updateFrame: frame) - var subpathStart: CGFloat = 0 - for path in subpaths { - - let subpathEnd = subpathStart + path.length - - if !startCutLength.isInRange(subpathStart, subpathEnd) && - !endCutLength.isInRange(subpathStart, subpathEnd) { - // The whole path is included. Add - // S|==============================|E - pathContainer.appendPath(path, updateFrame: frame) - - } else if startCutLength.isInRange(subpathStart, subpathEnd) && - !endCutLength.isInRange(subpathStart, subpathEnd) { - /// The start of the path needs to be trimmed - // |-------S======================|E - let cutLength = startCutLength - subpathStart - let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - } else if !startCutLength.isInRange(subpathStart, subpathEnd) && - endCutLength.isInRange(subpathStart, subpathEnd) { - // S|=======E----------------------| - let cutLength = endCutLength - subpathStart - let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - break - } else if startCutLength.isInRange(subpathStart, subpathEnd) && - endCutLength.isInRange(subpathStart, subpathEnd) { - // |-------S============E---------| - let cutFromLength = startCutLength - subpathStart - let cutToLength = endCutLength - subpathStart - let newPath = path.trim(fromPosition: cutFromLength / path.length, toPosition: cutToLength / path.length, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - break - } - - subpathStart = subpathEnd - } - } else if (endLength <= pathStart && pathEnd <= startLength) || - (startLength <= pathStart && endLength <= pathStart) || - (pathEnd <= startLength && pathEnd <= endLength) { - /// The Path needs to be cleared - pathContainer.removePaths(updateFrame: frame) - } - - pathStart = pathEnd - } - - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift deleted file mode 100644 index 2f48762b7c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// TransformNodeOutput.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -class GroupOutputNode: NodeOutput { - - init(parent: NodeOutput?, rootNode: NodeOutput?) { - self.parent = parent - self.rootNode = rootNode - } - - let parent: NodeOutput? - let rootNode: NodeOutput? - var isEnabled: Bool = true - - private(set) var outputPath: CGPath? = nil - private(set) var transform: CATransform3D = CATransform3DIdentity - - func setTransform(_ xform: CATransform3D, forFrame: CGFloat) { - transform = xform - outputPath = nil - } - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - guard isEnabled else { - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - outputPath = parent?.outputPath - return upstreamUpdates - } - - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - if upstreamUpdates { - outputPath = nil - } - let rootUpdates = rootNode?.hasOutputUpdates(forFrame) ?? false - if rootUpdates { - outputPath = nil - } - - var localUpdates: Bool = false - if outputPath == nil { - localUpdates = true - - let newPath = CGMutablePath() - if let parentNode = parent, let parentPath = parentNode.outputPath { - /// First add parent path. - newPath.addPath(parentPath) - } - var xform = CATransform3DGetAffineTransform(transform) - if let rootNode = rootNode, - let rootPath = rootNode.outputPath, - let xformedPath = rootPath.copy(using: &xform) { - /// Now add root path. Note root path is transformed. - newPath.addPath(xformedPath) - } - - outputPath = newPath - } - - return upstreamUpdates || localUpdates - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift deleted file mode 100644 index 8a911db243..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// PassThroughOutputNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -class PassThroughOutputNode: NodeOutput { - - init(parent: NodeOutput?) { - self.parent = parent - } - - let parent: NodeOutput? - - var hasUpdate: Bool = false - var isEnabled: Bool = true - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - /// Changes to this node do not affect downstream nodes. - let parentUpdate = parent?.hasOutputUpdates(forFrame) ?? false - /// Changes to upstream nodes do, however, affect this nodes state. - hasUpdate = hasUpdate || parentUpdate - return parentUpdate - } - - var outputPath: CGPath? { - if let parent = parent { - return parent.outputPath - } - return nil - } - - func hasRenderUpdates(_ forFrame: CGFloat) -> Bool { - /// Return true if there are upstream updates or if this node has updates - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - hasUpdate = hasUpdate || upstreamUpdates - return hasUpdate - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift deleted file mode 100644 index f71655b384..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// PathNodeOutput.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// A node that has an output of a BezierPath -class PathOutputNode: NodeOutput { - - init(parent: NodeOutput?) { - self.parent = parent - } - - let parent: NodeOutput? - - fileprivate(set) var outputPath: CGPath? = nil - - var lastUpdateFrame: CGFloat? = nil - var lastPathBuildFrame: CGFloat? = nil - var isEnabled: Bool = true - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - guard isEnabled else { - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - outputPath = parent?.outputPath - return upstreamUpdates - } - - /// Ask if parent was updated - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - - /// If parent was updated and the path hasn't been built for this frame, clear the path. - if upstreamUpdates && lastPathBuildFrame != forFrame { - outputPath = nil - } - - if outputPath == nil { - /// If the path is clear, build the new path. - lastPathBuildFrame = forFrame - let newPath = CGMutablePath() - if let parentNode = parent, let parentPath = parentNode.outputPath { - newPath.addPath(parentPath) - } - for path in pathObjects { - for subPath in path.paths { - newPath.addPath(subPath.cgPath()) - } - } - outputPath = newPath - } - - /// Return true if there were upstream updates or if this node was updated. - return upstreamUpdates || (lastUpdateFrame == forFrame) - } - - // MARK: Internal - - fileprivate(set) var totalLength: CGFloat = 0 - fileprivate(set) var pathObjects: [CompoundBezierPath] = [] - - @discardableResult func removePaths(updateFrame: CGFloat?) -> [CompoundBezierPath] { - lastUpdateFrame = updateFrame - let returnPaths = pathObjects - outputPath = nil - totalLength = 0 - pathObjects = [] - return returnPaths - } - - func setPath(_ path: BezierPath, updateFrame: CGFloat) { - lastUpdateFrame = updateFrame - outputPath = nil - totalLength = path.length - pathObjects = [CompoundBezierPath(path: path)] - } - - func appendPath(_ path: CompoundBezierPath, updateFrame: CGFloat) { - lastUpdateFrame = updateFrame - outputPath = nil - totalLength = totalLength + path.length - pathObjects.append(path) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift deleted file mode 100644 index dc781210ff..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// FillRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore -import CoreGraphics - -extension FillRule { - var cgFillRule: CGPathFillRule { - switch self { - case .evenOdd: - return .evenOdd - default: - return .winding - } - } - - var caFillRule: CAShapeLayerFillRule { - switch self { - case .evenOdd: - return CAShapeLayerFillRule.evenOdd - default: - return CAShapeLayerFillRule.nonZero - } - } -} - -/// A rendered for a Path Fill -final class FillRenderer: PassThroughOutputNode, Renderable { - - let shouldRenderInContext: Bool = false - - func updateShapeLayer(layer: CAShapeLayer) { - layer.fillColor = color - layer.opacity = Float(opacity) - layer.fillRule = fillRule.caFillRule - hasUpdate = false - } - - var color: CGColor? { - didSet { - hasUpdate = true - } - } - - var opacity: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var fillRule: FillRule = .none { - didSet { - hasUpdate = true - } - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - guard let color = color else { return } - hasUpdate = false - inContext.setAlpha(opacity * 0.01) - inContext.setFillColor(color) - inContext.fillPath(using: fillRule.cgFillRule) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift deleted file mode 100644 index cd54a286ba..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// GradientFillRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -/// A rendered for a Path Fill -final class GradientFillRenderer: PassThroughOutputNode, Renderable { - - var shouldRenderInContext: Bool = true - - func updateShapeLayer(layer: CAShapeLayer) { - // Not applicable - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - hasUpdate = false - var alphaColors = [CGColor]() - var alphaLocations = [CGFloat]() - - var gradientColors = [CGColor]() - var colorLocations = [CGFloat]() - let colorSpace = CGColorSpaceCreateDeviceRGB() - let maskColorSpace = CGColorSpaceCreateDeviceGray() - for i in 0.. ix, let color = CGColor(colorSpace: colorSpace, components: [colors[ix + 1], colors[ix + 2], colors[ix + 3], 1]) { - gradientColors.append(color) - colorLocations.append(colors[ix]) - } - } - - var drawMask = false - for i in stride(from: (numberOfColors * 4), to: colors.endIndex, by: 2) { - let alpha = colors[i + 1] - if alpha < 1 { - drawMask = true - } - if let color = CGColor(colorSpace: maskColorSpace, components: [alpha, 1]) { - alphaLocations.append(colors[i]) - alphaColors.append(color) - } - } - - inContext.setAlpha(opacity) - inContext.clip() - - /// First draw a mask is necessary. - if drawMask { - guard let maskGradient = CGGradient(colorsSpace: maskColorSpace, - colors: alphaColors as CFArray, - locations: alphaLocations), - let maskContext = CGContext(data: nil, - width: inContext.width, - height: inContext.height, - bitsPerComponent: 8, - bytesPerRow: inContext.width, - space: maskColorSpace, - bitmapInfo: 0) else { return } - let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height)) - maskContext.concatenate(flipVertical) - maskContext.concatenate(inContext.ctm) - if type == .linear { - maskContext.drawLinearGradient(maskGradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } else { - maskContext.drawRadialGradient(maskGradient, startCenter: start, startRadius: 0, endCenter: start, endRadius: start.distanceTo(end), options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } - /// Clips the gradient - if let alphaMask = maskContext.makeImage() { - inContext.clip(to: inContext.boundingBoxOfClipPath, mask: alphaMask) - } - } - - /// Now draw the gradient - guard let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors as CFArray, locations: colorLocations) else { return } - if type == .linear { - inContext.drawLinearGradient(gradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } else { - inContext.drawRadialGradient(gradient, startCenter: start, startRadius: 0, endCenter: start, endRadius: start.distanceTo(end), options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } - } - - var start: CGPoint = .zero { - didSet { - hasUpdate = true - } - } - - var numberOfColors: Int = 0 { - didSet { - hasUpdate = true - } - } - - var colors: [CGFloat] = [] { - didSet { - hasUpdate = true - } - } - - var end: CGPoint = .zero { - didSet { - hasUpdate = true - } - } - - var opacity: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var type: GradientType = .none { - didSet { - hasUpdate = true - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift deleted file mode 100644 index 7639fa8a4a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// GradientStrokeRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -// MARK: - Renderer - -final class GradientStrokeRenderer: PassThroughOutputNode, Renderable { - - override func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - let updates = super.hasOutputUpdates(forFrame) - return updates || strokeRender.hasUpdate || gradientRender.hasUpdate - } - - var shouldRenderInContext: Bool = true - - func updateShapeLayer(layer: CAShapeLayer) { - /// Not Applicable - } - - let strokeRender: StrokeRenderer - let gradientRender: GradientFillRenderer - - override init(parent: NodeOutput?) { - self.strokeRender = StrokeRenderer(parent: nil) - self.gradientRender = GradientFillRenderer(parent: nil) - self.strokeRender.color = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 1, 1, 1]) - super.init(parent: parent) - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - - strokeRender.hasUpdate = false - hasUpdate = false - gradientRender.hasUpdate = false - - strokeRender.setupForStroke(inContext) - - inContext.replacePathWithStrokedPath() - - /// Now draw the gradient. - gradientRender.render(inContext) - - } - - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { - return strokeRender.renderBoundsFor(boundingBox) - } - - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift deleted file mode 100644 index e8d0e8f67a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +++ /dev/null @@ -1,162 +0,0 @@ -// -// StrokeRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -extension LineJoin { - var cgLineJoin: CGLineJoin { - switch self { - case .bevel: - return .bevel - case .none: - return .miter - case .miter: - return .miter - case .round: - return .round - } - } - - var caLineJoin: CAShapeLayerLineJoin { - switch self { - case .none: - return CAShapeLayerLineJoin.miter - case .miter: - return CAShapeLayerLineJoin.miter - case .round: - return CAShapeLayerLineJoin.round - case .bevel: - return CAShapeLayerLineJoin.bevel - } - } -} - -extension LineCap { - var cgLineCap: CGLineCap { - switch self { - case .none: - return .butt - case .butt: - return .butt - case .round: - return .round - case .square: - return .square - } - } - - var caLineCap: CAShapeLayerLineCap { - switch self { - case .none: - return CAShapeLayerLineCap.butt - case .butt: - return CAShapeLayerLineCap.butt - case .round: - return CAShapeLayerLineCap.round - case .square: - return CAShapeLayerLineCap.square - } - } -} - -// MARK: - Renderer - -/// A rendered that renders a stroke on a path. -final class StrokeRenderer: PassThroughOutputNode, Renderable { - - var shouldRenderInContext: Bool = false - - var color: CGColor? { - didSet { - hasUpdate = true - } - } - - var opacity: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var width: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var miterLimit: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var lineCap: LineCap = .none { - didSet { - hasUpdate = true - } - } - - var lineJoin: LineJoin = .none { - didSet { - hasUpdate = true - } - } - - var dashPhase: CGFloat? { - didSet { - hasUpdate = true - } - } - - var dashLengths: [CGFloat]? { - didSet { - hasUpdate = true - } - } - - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { - return boundingBox.insetBy(dx: -width, dy: -width) - } - - func setupForStroke(_ inContext: CGContext) { - inContext.setLineWidth(width) - inContext.setMiterLimit(miterLimit) - inContext.setLineCap(lineCap.cgLineCap) - inContext.setLineJoin(lineJoin.cgLineJoin) - if let dashPhase = dashPhase, let lengths = dashLengths { - inContext.setLineDash(phase: dashPhase, lengths: lengths) - } else { - inContext.setLineDash(phase: 0, lengths: []) - } - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - guard let color = color else { return } - hasUpdate = false - setupForStroke(inContext) - inContext.setAlpha(opacity) - inContext.setStrokeColor(color) - inContext.strokePath() - } - - func updateShapeLayer(layer: CAShapeLayer) { - layer.strokeColor = color - layer.opacity = Float(opacity) - layer.lineWidth = width - layer.lineJoin = lineJoin.caLineJoin - layer.lineCap = lineCap.caLineCap - layer.lineDashPhase = dashPhase ?? 0 - layer.fillColor = nil - if let dashPattern = dashLengths { - layer.lineDashPattern = dashPattern.map({ NSNumber(value: Double($0)) }) - } - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift deleted file mode 100644 index e5e3e7dd5b..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// EllipseNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation -import QuartzCore - -final class EllipseNodeProperties: NodePropertyMap { - - init(ellipse: Ellipse) { - self.direction = ellipse.direction - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.position.keyframes)) - self.size = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.size.keyframes)) - self.properties = [] - } - - let direction: PathDirection - let position: NodeProperty - let size: NodeProperty - - let properties: [AnyNodeProperty] -} - -final class EllipseNode: AnimatorNode, PathNode { - - let pathOutput: PathOutputNode - - let properties: EllipseNodeProperties - - init(parentNode: AnimatorNode?, ellipse: Ellipse) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = EllipseNodeProperties(ellipse: ellipse) - self.parentNode = parentNode - } - - // MARK: Animator Node - - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - func rebuildOutputs(frame: CGFloat) { - let ellipseSize = properties.size.value.sizeValue - let center = properties.position.value.pointValue - - // Unfortunately we HAVE to manually build out the ellipse. - // Every Apple method constructs an ellipse from the 3 o-clock position - // After effects constructs from the Noon position. - // After effects does clockwise, but also has a flag for reversed. - - var half = ellipseSize * 0.5 - if properties.direction == .counterClockwise { - half.width = half.width * -1 - } - - - let q1 = CGPoint(x: center.x, y: center.y - half.height) - let q2 = CGPoint(x: center.x + half.width, y: center.y) - let q3 = CGPoint(x: center.x, y: center.y + half.height) - let q4 = CGPoint(x: center.x - half.width, y: center.y) - - let cp = half * EllipseNode.ControlPointConstant - - var path = BezierPath(startPoint: CurveVertex(point: q1, - inTangentRelative: CGPoint(x: -cp.width, y: 0), - outTangentRelative: CGPoint(x: cp.width, y: 0))) - path.addVertex(CurveVertex(point: q2, - inTangentRelative: CGPoint(x: 0, y: -cp.height), - outTangentRelative: CGPoint(x: 0, y: cp.height))) - - path.addVertex(CurveVertex(point: q3, - inTangentRelative: CGPoint(x: cp.width, y: 0), - outTangentRelative: CGPoint(x: -cp.width, y: 0))) - - path.addVertex(CurveVertex(point: q4, - inTangentRelative: CGPoint(x: 0, y: cp.height), - outTangentRelative: CGPoint(x: 0, y: -cp.height))) - - path.addVertex(CurveVertex(point: q1, - inTangentRelative: CGPoint(x: -cp.width, y: 0), - outTangentRelative: CGPoint(x: cp.width, y: 0))) - path.close() - pathOutput.setPath(path, updateFrame: frame) - } - - static let ControlPointConstant: CGFloat = 0.55228 - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift deleted file mode 100644 index 50b4c53f10..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// PolygonNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/21/19. -// - -import Foundation -import QuartzCore - -final class PolygonNodeProperties: NodePropertyMap { - - init(star: Star) { - self.direction = star.direction - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) - self.outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) - self.outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) - self.points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) - let keypathProperties: [String : AnyNodeProperty] = [ - "Position" : position, - "Outer Radius" : outerRadius, - "Outer Roundedness" : outerRoundedness, - "Rotation" : rotation, - "Points" : points - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let direction: PathDirection - let position: NodeProperty - let outerRadius: NodeProperty - let outerRoundedness: NodeProperty - let rotation: NodeProperty - let points: NodeProperty -} - -final class PolygonNode: AnimatorNode, PathNode { - - let properties: PolygonNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, star: Star) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = PolygonNodeProperties(star: star) - self.parentNode = parentNode - } - - // MARK: Animator Node - - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - /// Magic number needed for constructing path. - static let PolygonConstant: CGFloat = 0.25 - - func rebuildOutputs(frame: CGFloat) { - let outerRadius = properties.outerRadius.value.cgFloatValue - let outerRoundedness = properties.outerRoundedness.value.cgFloatValue * 0.01 - let numberOfPoints = properties.points.value.cgFloatValue - let rotation = properties.rotation.value.cgFloatValue - let position = properties.position.value.pointValue - - var currentAngle = (rotation - 90).toRadians() - let anglePerPoint = ((2 * CGFloat.pi) / numberOfPoints) - - var point = CGPoint(x: (outerRadius * cos(currentAngle)), - y: (outerRadius * sin(currentAngle))) - var vertices = [CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)] - - var previousPoint = point - currentAngle += anglePerPoint; - for _ in 0.. - let size: NodeProperty - let cornerRadius: NodeProperty - -} - -final class RectangleNode: AnimatorNode, PathNode { - - let properties: RectNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, rectangle: Rectangle) { - self.properties = RectNodeProperties(rectangle: rectangle) - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.parentNode = parentNode - } - - // MARK: Animator Node - - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - func rebuildOutputs(frame: CGFloat) { - - let size = properties.size.value.sizeValue * 0.5 - let radius = min(min(properties.cornerRadius.value.cgFloatValue, size.width) , size.height) - let position = properties.position.value.pointValue - var bezierPath = BezierPath() - let points: [CurveVertex] - - if radius <= 0 { - /// No Corners - points = [ - /// Lead In - CurveVertex(point: CGPoint(x: size.width, y: -size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 1 - CurveVertex(point: CGPoint(x: size.width, y: size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 2 - CurveVertex(point: CGPoint(x: -size.width, y: size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 3 - CurveVertex(point: CGPoint(x: -size.width, y: -size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 4 - CurveVertex(point: CGPoint(x: size.width, y: -size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - ] - } else { - let controlPoint = radius * EllipseNode.ControlPointConstant - points = [ - /// Lead In - CurveVertex( - CGPoint(x: radius, y: 0), - CGPoint(x: radius, y: 0), - CGPoint(x: radius, y: 0)) - .translated(CGPoint(x: -radius, y: radius)) - .translated(CGPoint(x: size.width, y: -size.height)) - .translated(position), - /// Corner 1 - CurveVertex( - CGPoint(x: radius, y: 0), // In tangent - CGPoint(x: radius, y: 0), // Point - CGPoint(x: radius, y: controlPoint)) - .translated(CGPoint(x: -radius, y: -radius)) - .translated(CGPoint(x: size.width, y: size.height)) - .translated(position), - CurveVertex( - CGPoint(x: controlPoint, y: radius), // In tangent - CGPoint(x: 0, y: radius), // Point - CGPoint(x: 0, y: radius)) // Out Tangent - .translated(CGPoint(x: -radius, y: -radius)) - .translated(CGPoint(x: size.width, y: size.height)) - .translated(position), - /// Corner 2 - CurveVertex( - CGPoint(x: 0, y: radius), // In tangent - CGPoint(x: 0, y: radius), // Point - CGPoint(x: -controlPoint, y: radius))// Out tangent - .translated(CGPoint(x: radius, y: -radius)) - .translated(CGPoint(x: -size.width, y: size.height)) - .translated(position), - CurveVertex( - CGPoint(x: -radius, y: controlPoint), // In tangent - CGPoint(x: -radius, y: 0), // Point - CGPoint(x: -radius, y: 0)) // Out tangent - .translated(CGPoint(x: radius, y: -radius)) - .translated(CGPoint(x: -size.width, y: size.height)) - .translated(position), - /// Corner 3 - CurveVertex( - CGPoint(x: -radius, y: 0), // In tangent - CGPoint(x: -radius, y: 0), // Point - CGPoint(x: -radius, y: -controlPoint)) // Out tangent - .translated(CGPoint(x: radius, y: radius)) - .translated(CGPoint(x: -size.width, y: -size.height)) - .translated(position), - CurveVertex( - CGPoint(x: -controlPoint, y: -radius), // In tangent - CGPoint(x: 0, y: -radius), // Point - CGPoint(x: 0, y: -radius)) // Out tangent - .translated(CGPoint(x: radius, y: radius)) - .translated(CGPoint(x: -size.width, y: -size.height)) - .translated(position), - /// Corner 4 - CurveVertex( - CGPoint(x: 0, y: -radius), // In tangent - CGPoint(x: 0, y: -radius), // Point - CGPoint(x: controlPoint, y: -radius)) // Out tangent - .translated(CGPoint(x: -radius, y: radius)) - .translated(CGPoint(x: size.width, y: -size.height)) - .translated(position), - CurveVertex( - CGPoint(x: radius, y: -controlPoint), // In tangent - CGPoint(x: radius, y: 0), // Point - CGPoint(x: radius, y: 0)) // Out tangent - .translated(CGPoint(x: -radius, y: radius)) - .translated(CGPoint(x: size.width, y: -size.height)) - .translated(position), - ] - } - let reversed = properties.direction == .counterClockwise - let pathPoints = reversed ? points.reversed() : points - for point in pathPoints { - bezierPath.addVertex(reversed ? point.reversed() : point) - } - bezierPath.close() - pathOutput.setPath(bezierPath, updateFrame: frame) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift deleted file mode 100644 index 9d05b85bdd..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// PathNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/16/19. -// - -import Foundation -import CoreGraphics - -final class ShapeNodeProperties: NodePropertyMap { - - init(shape: Shape) { - self.path = NodeProperty(provider: KeyframeInterpolator(keyframes: shape.path.keyframes)) - let keypathProperties: [String : AnyNodeProperty] = [ - "Path" : path - ] - self.properties = Array(keypathProperties.values) - } - - let path: NodeProperty - let properties: [AnyNodeProperty] - -} - -final class ShapeNode: AnimatorNode, PathNode { - - let properties: ShapeNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, shape: Shape) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = ShapeNodeProperties(shape: shape) - self.parentNode = parentNode - } - - // MARK: Animator Node - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - func rebuildOutputs(frame: CGFloat) { - pathOutput.setPath(properties.path.value, updateFrame: frame) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift deleted file mode 100644 index 50bad3d9d4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// StarNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/21/19. -// - -import Foundation -import QuartzCore - -final class StarNodeProperties: NodePropertyMap { - - init(star: Star) { - self.direction = star.direction - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) - self.outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) - self.outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) - if let innerRadiusKeyframes = star.innerRadius?.keyframes { - self.innerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: innerRadiusKeyframes)) - } else { - self.innerRadius = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - if let innderRoundedness = star.innerRoundness?.keyframes { - self.innerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: innderRoundedness)) - } else { - self.innerRoundedness = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) - self.points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) - let keypathProperties: [String : AnyNodeProperty] = [ - "Position" : position, - "Outer Radius" : outerRadius, - "Outer Roundedness" : outerRoundedness, - "Inner Radius" : innerRadius, - "Inner Roundedness" : innerRoundedness, - "Rotation" : rotation, - "Points" : points - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let direction: PathDirection - let position: NodeProperty - let outerRadius: NodeProperty - let outerRoundedness: NodeProperty - let innerRadius: NodeProperty - let innerRoundedness: NodeProperty - let rotation: NodeProperty - let points: NodeProperty -} - -final class StarNode: AnimatorNode, PathNode { - - let properties: StarNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, star: Star) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = StarNodeProperties(star: star) - self.parentNode = parentNode - } - - // MARK: Animator Node - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - /// Magic number needed for building path data - static let PolystarConstant: CGFloat = 0.47829 - - func rebuildOutputs(frame: CGFloat) { - let outerRadius = properties.outerRadius.value.cgFloatValue - let innerRadius = properties.innerRadius.value.cgFloatValue - let outerRoundedness = properties.outerRoundedness.value.cgFloatValue * 0.01 - let innerRoundedness = properties.innerRoundedness.value.cgFloatValue * 0.01 - let numberOfPoints = properties.points.value.cgFloatValue - let rotation = properties.rotation.value.cgFloatValue - let position = properties.position.value.pointValue - - var currentAngle = (rotation - 90).toRadians() - let anglePerPoint = (2 * CGFloat.pi) / numberOfPoints - let halfAnglePerPoint = anglePerPoint / 2.0 - let partialPointAmount = numberOfPoints - floor(numberOfPoints) - - var point: CGPoint = .zero - - var partialPointRadius: CGFloat = 0 - if partialPointAmount != 0 { - currentAngle += halfAnglePerPoint * (1 - partialPointAmount) - partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius) - point.x = (partialPointRadius * cos(currentAngle)) - point.y = (partialPointRadius * sin(currentAngle)) - currentAngle += anglePerPoint * partialPointAmount / 2 - } else { - point.x = (outerRadius * cos(currentAngle)) - point.y = (outerRadius * sin(currentAngle)) - currentAngle += halfAnglePerPoint - } - - var vertices = [CurveVertex]() - vertices.append(CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)) - - var previousPoint = point - var longSegment = false - let numPoints: Int = Int(ceil(numberOfPoints) * 2) - for i in 0.. - let position: NodeProperty - let scale: NodeProperty - let rotation: NodeProperty - let opacity: NodeProperty - let skew: NodeProperty - let skewAxis: NodeProperty - - var caTransform: CATransform3D { - return CATransform3D.makeTransform(anchor: anchor.value.pointValue, - position: position.value.pointValue, - scale: scale.value.sizeValue, - rotation: rotation.value.cgFloatValue, - skew: skew.value.cgFloatValue, - skewAxis: skewAxis.value.cgFloatValue) - } -} - -final class GroupNode: AnimatorNode { - - // MARK: Properties - let groupOutput: GroupOutputNode - - let properties: GroupNodeProperties - - let rootNode: AnimatorNode? - - var container: ShapeContainerLayer = ShapeContainerLayer() - - // MARK: Initializer - init(name: String, parentNode: AnimatorNode?, tree: NodeTree) { - self.parentNode = parentNode - self.rootNode = tree.rootNode - self.properties = GroupNodeProperties(transform: tree.transform) - self.groupOutput = GroupOutputNode(parent: parentNode?.outputNode, rootNode: rootNode?.outputNode) - - for childContainer in tree.renderContainers { - container.insertRenderLayer(childContainer) - } - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return properties - } - - var outputNode: NodeOutput { - return groupOutput - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - container.isHidden = !isEnabled - } - } - - func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { - return rootNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false - } - - func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { - rootNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) - } - - func rebuildOutputs(frame: CGFloat) { - container.opacity = Float(properties.opacity.value.cgFloatValue) * 0.01 - container.transform = properties.caTransform - groupOutput.setTransform(container.transform, forFrame: frame) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift deleted file mode 100644 index 9347d6c7d9..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// FillNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation -import CoreGraphics - -final class FillNodeProperties: NodePropertyMap { - - init(fill: Fill) { - self.color = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.color.keyframes)) - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.opacity.keyframes)) - self.type = fill.fillRule - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Color" : color - ] - self.properties = Array(keypathProperties.values) - } - - let opacity: NodeProperty - let color: NodeProperty - let type: FillRule - - let properties: [AnyNodeProperty] - -} - -final class FillNode: AnimatorNode, RenderNode { - - let fillRender: FillRenderer - var renderer: NodeOutput & Renderable { - return fillRender - } - - let fillProperties: FillNodeProperties - - init(parentNode: AnimatorNode?, fill: Fill) { - self.fillRender = FillRenderer(parent: parentNode?.outputNode) - self.fillProperties = FillNodeProperties(fill: fill) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return fillProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - fillRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - fillRender.color = fillProperties.color.value.cgColorValue - fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 - fillRender.fillRule = fillProperties.type - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift deleted file mode 100644 index 54de040e9a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// GradientFillNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation -import QuartzCore - -final class GradientFillProperties: NodePropertyMap { - - init(gradientfill: GradientFill) { - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.opacity.keyframes)) - self.startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.startPoint.keyframes)) - self.endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.endPoint.keyframes)) - self.colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.colors.keyframes)) - self.gradientType = gradientfill.gradientType - self.numberOfColors = gradientfill.numberOfColors - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Start Point" : startPoint, - "End Point" : endPoint, - "Colors" : colors - ] - self.properties = Array(keypathProperties.values) - } - - let opacity: NodeProperty - let startPoint: NodeProperty - let endPoint: NodeProperty - let colors: NodeProperty<[Double]> - - let gradientType: GradientType - let numberOfColors: Int - - let properties: [AnyNodeProperty] - -} - -final class GradientFillNode: AnimatorNode, RenderNode { - - let fillRender: GradientFillRenderer - - var renderer: NodeOutput & Renderable { - return fillRender - } - - let fillProperties: GradientFillProperties - - init(parentNode: AnimatorNode?, gradientFill: GradientFill) { - self.fillRender = GradientFillRenderer(parent: parentNode?.outputNode) - self.fillProperties = GradientFillProperties(gradientfill: gradientFill) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return fillProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - fillRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - fillRender.start = fillProperties.startPoint.value.pointValue - fillRender.end = fillProperties.endPoint.value.pointValue - fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 - fillRender.colors = fillProperties.colors.value.map { CGFloat($0) } - fillRender.type = fillProperties.gradientType - fillRender.numberOfColors = fillProperties.numberOfColors - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift deleted file mode 100644 index 0c71467076..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// GradientStrokeNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/23/19. -// - -import Foundation -import CoreGraphics - -// MARK: - Properties - -final class GradientStrokeProperties: NodePropertyMap { - - init(gradientStroke: GradientStroke) { - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.opacity.keyframes)) - self.startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.startPoint.keyframes)) - self.endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.endPoint.keyframes)) - self.colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.colors.keyframes)) - self.gradientType = gradientStroke.gradientType - self.numberOfColors = gradientStroke.numberOfColors - self.width = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.width.keyframes)) - self.miterLimit = CGFloat(gradientStroke.miterLimit) - self.lineCap = gradientStroke.lineCap - self.lineJoin = gradientStroke.lineJoin - - if let dashes = gradientStroke.dashPattern { - var dashPatterns = ContiguousArray>>() - var dashPhase = ContiguousArray>() - for dash in dashes { - if dash.type == .offset { - dashPhase = dash.value.keyframes - } else { - dashPatterns.append(dash.value.keyframes) - } - } - self.dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) - self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) - } else { - self.dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) - self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Start Point" : startPoint, - "End Point" : endPoint, - "Colors" : colors, - "Stroke Width" : width, - "Dashes" : dashPattern, - "Dash Phase" : dashPhase - ] - self.properties = Array(keypathProperties.values) - } - - let opacity: NodeProperty - let startPoint: NodeProperty - let endPoint: NodeProperty - let colors: NodeProperty<[Double]> - let width: NodeProperty - - let dashPattern: NodeProperty<[Vector1D]> - let dashPhase: NodeProperty - - let lineCap: LineCap - let lineJoin: LineJoin - let miterLimit: CGFloat - let gradientType: GradientType - let numberOfColors: Int - - - let properties: [AnyNodeProperty] - -} - -// MARK: - Node - -final class GradientStrokeNode: AnimatorNode, RenderNode { - - let strokeRender: GradientStrokeRenderer - - var renderer: NodeOutput & Renderable { - return strokeRender - } - - let strokeProperties: GradientStrokeProperties - - init(parentNode: AnimatorNode?, gradientStroke: GradientStroke) { - self.strokeRender = GradientStrokeRenderer(parent: parentNode?.outputNode) - self.strokeProperties = GradientStrokeProperties(gradientStroke: gradientStroke) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return strokeProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - strokeRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - /// Update gradient properties - strokeRender.gradientRender.start = strokeProperties.startPoint.value.pointValue - strokeRender.gradientRender.end = strokeProperties.endPoint.value.pointValue - strokeRender.gradientRender.opacity = strokeProperties.opacity.value.cgFloatValue - strokeRender.gradientRender.colors = strokeProperties.colors.value.map { CGFloat($0) } - strokeRender.gradientRender.type = strokeProperties.gradientType - strokeRender.gradientRender.numberOfColors = strokeProperties.numberOfColors - - /// Now update stroke properties - strokeRender.strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue - strokeRender.strokeRender.width = strokeProperties.width.value.cgFloatValue - strokeRender.strokeRender.miterLimit = strokeProperties.miterLimit - strokeRender.strokeRender.lineCap = strokeProperties.lineCap - strokeRender.strokeRender.lineJoin = strokeProperties.lineJoin - - /// Get dash lengths - let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } - if dashLengths.count > 0 { - strokeRender.strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue - strokeRender.strokeRender.dashLengths = dashLengths - } else { - strokeRender.strokeRender.dashLengths = nil - strokeRender.strokeRender.dashPhase = nil - } - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift deleted file mode 100644 index 5942268724..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// StrokeNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation -import QuartzCore -// MARK: - Properties - -final class StrokeNodeProperties: NodePropertyMap { - - init(stroke: Stroke) { - self.color = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.color.keyframes)) - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.opacity.keyframes)) - self.width = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.width.keyframes)) - self.miterLimit = CGFloat(stroke.miterLimit) - self.lineCap = stroke.lineCap - self.lineJoin = stroke.lineJoin - - if let dashes = stroke.dashPattern { - var dashPatterns = ContiguousArray>>() - var dashPhase = ContiguousArray>() - for dash in dashes { - if dash.type == .offset { - dashPhase = dash.value.keyframes - } else { - dashPatterns.append(dash.value.keyframes) - } - } - self.dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) - if dashPhase.count == 0 { - self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } else { - self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) - } - } else { - self.dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) - self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Color" : color, - "Stroke Width" : width, - "Dashes" : dashPattern, - "Dash Phase" : dashPhase - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let opacity: NodeProperty - let color: NodeProperty - let width: NodeProperty - - let dashPattern: NodeProperty<[Vector1D]> - let dashPhase: NodeProperty - - let lineCap: LineCap - let lineJoin: LineJoin - let miterLimit: CGFloat - -} - -// MARK: - Node - -/// Node that manages stroking a path -final class StrokeNode: AnimatorNode, RenderNode { - - let strokeRender: StrokeRenderer - var renderer: NodeOutput & Renderable { - return strokeRender - } - - let strokeProperties: StrokeNodeProperties - - init(parentNode: AnimatorNode?, stroke: Stroke) { - self.strokeRender = StrokeRenderer(parent: parentNode?.outputNode) - self.strokeProperties = StrokeNodeProperties(stroke: stroke) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return strokeProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - strokeRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - strokeRender.color = strokeProperties.color.value.cgColorValue - strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue * 0.01 - strokeRender.width = strokeProperties.width.value.cgFloatValue - strokeRender.miterLimit = strokeProperties.miterLimit - strokeRender.lineCap = strokeProperties.lineCap - strokeRender.lineJoin = strokeProperties.lineJoin - - /// Get dash lengths - let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } - if dashLengths.count > 0 { - strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue - strokeRender.dashLengths = dashLengths - } else { - strokeRender.dashLengths = nil - strokeRender.dashPhase = nil - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift deleted file mode 100644 index fbe1117628..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +++ /dev/null @@ -1,245 +0,0 @@ -// -// TextAnimatorNode.swift -// lottie-ios-iOS -// -// Created by Brandon Withrow on 2/19/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -final class TextAnimatorNodeProperties: NodePropertyMap { - - init(textAnimator: TextAnimator) { - var properties = [String : AnyNodeProperty]() - - if let keyframeGroup = textAnimator.anchor { - self.anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Anchor"] = self.anchor - } else { - self.anchor = nil - } - - if let keyframeGroup = textAnimator.position { - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Position"] = self.position - } else { - self.position = nil - } - - if let keyframeGroup = textAnimator.scale { - self.scale = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Scale"] = self.scale - } else { - self.scale = nil - } - - if let keyframeGroup = textAnimator.skew { - self.skew = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Skew"] = self.skew - } else { - self.skew = nil - } - - if let keyframeGroup = textAnimator.skewAxis { - self.skewAxis = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Skew Axis"] = self.skewAxis - } else { - self.skewAxis = nil - } - - if let keyframeGroup = textAnimator.rotation { - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Rotation"] = self.rotation - } else { - self.rotation = nil - } - - if let keyframeGroup = textAnimator.opacity { - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Opacity"] = self.opacity - } else { - self.opacity = nil - } - - if let keyframeGroup = textAnimator.strokeColor { - self.strokeColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Stroke Color"] = self.strokeColor - } else { - self.strokeColor = nil - } - - if let keyframeGroup = textAnimator.fillColor { - self.fillColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Fill Color"] = self.fillColor - } else { - self.fillColor = nil - } - - if let keyframeGroup = textAnimator.strokeWidth { - self.strokeWidth = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Stroke Width"] = self.strokeWidth - } else { - self.strokeWidth = nil - } - - if let keyframeGroup = textAnimator.tracking { - self.tracking = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Tracking"] = self.tracking - } else { - self.tracking = nil - } - - self.properties = Array(properties.values) - } - - let anchor: NodeProperty? - let position: NodeProperty? - let scale: NodeProperty? - let skew: NodeProperty? - let skewAxis: NodeProperty? - let rotation: NodeProperty? - let opacity: NodeProperty? - let strokeColor: NodeProperty? - let fillColor: NodeProperty? - let strokeWidth: NodeProperty? - let tracking: NodeProperty? - - let properties: [AnyNodeProperty] - - var caTransform: CATransform3D { - return CATransform3D.makeTransform(anchor: anchor?.value.pointValue ?? .zero, - position: position?.value.pointValue ?? .zero, - scale: scale?.value.sizeValue ?? CGSize(width: 100, height: 100), - rotation: rotation?.value.cgFloatValue ?? 0, - skew: skew?.value.cgFloatValue, - skewAxis: skewAxis?.value.cgFloatValue) - } -} - -final class TextOutputNode: NodeOutput { - - var parent: NodeOutput? { - return parentTextNode - } - - var parentTextNode: TextOutputNode? - var isEnabled: Bool = true - - init(parent: TextOutputNode?) { - self.parentTextNode = parent - } - - fileprivate var _xform: CATransform3D? - fileprivate var _opacity: CGFloat? - fileprivate var _strokeColor: CGColor? - fileprivate var _fillColor: CGColor? - fileprivate var _tracking: CGFloat? - fileprivate var _strokeWidth: CGFloat? - - var xform: CATransform3D { - get { - return _xform ?? parentTextNode?.xform ?? CATransform3DIdentity - } - set { - _xform = newValue - } - } - - var opacity: CGFloat { - get { - return _opacity ?? parentTextNode?.opacity ?? 1 - } - set { - _opacity = newValue - } - } - - var strokeColor: CGColor? { - get { - return _strokeColor ?? parentTextNode?.strokeColor - } - set { - _strokeColor = newValue - } - } - - var fillColor: CGColor? { - get { - return _fillColor ?? parentTextNode?.fillColor - } - set { - _fillColor = newValue - } - } - - var tracking: CGFloat { - get { - return _tracking ?? parentTextNode?.tracking ?? 0 - } - set { - _tracking = newValue - } - } - - var strokeWidth: CGFloat { - get { - return _strokeWidth ?? parentTextNode?.strokeWidth ?? 0 - } - set { - _strokeWidth = newValue - } - } - - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - // TODO Fix This - return true - } - - var outputPath: CGPath? - -} - -class TextAnimatorNode: AnimatorNode { - - let textOutputNode: TextOutputNode - - var outputNode: NodeOutput { - return textOutputNode - } - - let textAnimatorProperties: TextAnimatorNodeProperties - - init(parentNode: TextAnimatorNode?, textAnimator: TextAnimator) { - self.textOutputNode = TextOutputNode(parent: parentNode?.textOutputNode) - self.textAnimatorProperties = TextAnimatorNodeProperties(textAnimator: textAnimator) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return textAnimatorProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true - - func localUpdatesPermeateDownstream() -> Bool { - return true - } - - func rebuildOutputs(frame: CGFloat) { - textOutputNode.xform = textAnimatorProperties.caTransform - textOutputNode.opacity = (textAnimatorProperties.opacity?.value.cgFloatValue ?? 100) * 0.01 - textOutputNode.strokeColor = textAnimatorProperties.strokeColor?.value.cgColorValue - textOutputNode.fillColor = textAnimatorProperties.fillColor?.value.cgColorValue - textOutputNode.tracking = textAnimatorProperties.tracking?.value.cgFloatValue ?? 1 - textOutputNode.strokeWidth = textAnimatorProperties.strokeWidth?.value.cgFloatValue ?? 0 - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/AnimatorNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/AnimatorNode.swift deleted file mode 100644 index 69413eafd6..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/AnimatorNode.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// AnimatorNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/15/19. -// - -import Foundation -import QuartzCore - -/** - Defines the basic outputs of an animator node. - - */ -protocol NodeOutput { - - /// The parent node. - var parent: NodeOutput? { get } - - /// Returns true if there are any updates upstream. OutputPath must be built before returning. - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool - - var outputPath: CGPath? { get } - - var isEnabled: Bool { get set } -} - -/** - The Animator Node is the base node in the render system tree. - - It defines a single node that has an output path and option input node. - At animation time the root animation node is asked to update its contents for - the current frame. - The node reaches up its chain of nodes until the first node that does not need - updating is found. Then each node updates its contents down the render pipeline. - Each node adds its local path to its input path and passes it forward. - - An animator node holds a group of interpolators. These interpolators determine - if the node needs an update for the current frame. - - */ -protocol AnimatorNode: AnyObject { - - /** - The available properties of the Node. - - These properties are automatically updated each frame. - These properties are also settable and gettable through the dynamic - property system. - - */ - var propertyMap: NodePropertyMap { get } - - /// The upstream input node - var parentNode: AnimatorNode? { get } - - /// The output of the node. - var outputNode: NodeOutput { get } - - /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. - func rebuildOutputs(frame: CGFloat) - - /// Setters for marking current node state. - var isEnabled: Bool { get set } - var hasLocalUpdates: Bool { get set } - var hasUpstreamUpdates: Bool { get set } - var lastUpdateFrame: CGFloat? { get set } - - // MARK: Optional - - /// Marks if updates to this node affect nodes downstream. - func localUpdatesPermeateDownstream() -> Bool - func forceUpstreamOutputUpdates() -> Bool - - /// Called at the end of this nodes update cycle. Always called. Optional. - func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool - func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) - - /// The default simply returns `hasLocalUpdates` - func shouldRebuildOutputs(frame: CGFloat) -> Bool -} - -/// Basic Node Logic -extension AnimatorNode { - - func shouldRebuildOutputs(frame: CGFloat) -> Bool { - return hasLocalUpdates - } - - func localUpdatesPermeateDownstream() -> Bool { - /// Optional override - return true - } - - func forceUpstreamOutputUpdates() -> Bool { - /// Optional - return false - } - - func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { - /// Optional - return forceLocalUpdate - } - - func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { - /// Optional - } - - @discardableResult func updateOutputs(_ frame: CGFloat, forceOutputUpdate: Bool) -> Bool { - guard isEnabled else { - // Disabled node, pass through. - lastUpdateFrame = frame - return parentNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) ?? false - } - - if forceOutputUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { - /// This node has already updated for this frame. Go ahead and return the results. - return hasUpstreamUpdates || hasLocalUpdates - } - - /// Ask if this node should force output updates upstream. - let forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates() - - /// Perform upstream output updates. Optionally mark upstream updates if any. - hasUpstreamUpdates = (parentNode?.updateOutputs(frame, forceOutputUpdate: forceUpstreamUpdates) ?? false || hasUpstreamUpdates) - - /// Perform additional local output updates - performAdditionalOutputUpdates(frame, forceOutputUpdate: forceUpstreamUpdates) - - /// If there are local updates, or if updates have been force, rebuild outputs - if forceUpstreamUpdates || shouldRebuildOutputs(frame: frame) { - lastUpdateFrame = frame - rebuildOutputs(frame: frame) - } - return hasUpstreamUpdates || hasLocalUpdates - } - - - /// Rebuilds the content of this node, and upstream nodes if necessary. - @discardableResult func updateContents(_ frame: CGFloat, forceLocalUpdate: Bool) -> Bool { - guard isEnabled else { - // Disabled node, pass through. - return parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false - } - - if forceLocalUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { - /// This node has already updated for this frame. Go ahead and return the results. - return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates - } - - /// Are there local updates? If so mark the node. - hasLocalUpdates = forceLocalUpdate ? forceLocalUpdate : propertyMap.needsLocalUpdate(frame: frame) - - /// Were there upstream updates? If so mark the node - hasUpstreamUpdates = parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false - - /// Perform property updates if necessary. - if hasLocalUpdates { - /// Rebuild local properties - propertyMap.updateNodeProperties(frame: frame) - } - - /// Ask the node to perform any other updates it might have. - hasUpstreamUpdates = performAdditionalLocalUpdates(frame: frame, forceLocalUpdate: forceLocalUpdate) || hasUpstreamUpdates - - /// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream. - return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates - } - - func updateTree(_ frame: CGFloat, forceUpdates: Bool = false) { - updateContents(frame, forceLocalUpdate: forceUpdates) - updateOutputs(frame, forceOutputUpdate: forceUpdates) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/PathNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/PathNode.swift deleted file mode 100644 index e7b2bfbbed..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/PathNode.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// PathNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation - -protocol PathNode { - var pathOutput: PathOutputNode { get } -} - -extension PathNode where Self: AnimatorNode { - - var outputNode: NodeOutput { - return pathOutput - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/RenderNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/RenderNode.swift deleted file mode 100644 index 4738f14005..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/RenderNode.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// RenderNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -/// A protocol that defines a node that holds render instructions -protocol RenderNode { - var renderer: Renderable & NodeOutput { get } -} - -/// A protocol that defines anything with render instructions -protocol Renderable { - - /// The last frame in which this node was updated. - var hasUpdate: Bool { get } - - func hasRenderUpdates(_ forFrame: CGFloat) -> Bool - - /** - Determines if the renderer requires a custom context for drawing. - If yes the shape layer will perform a custom drawing pass. - If no the shape layer will be a standard CAShapeLayer - */ - var shouldRenderInContext: Bool { get } - - /// Passes in the CAShapeLayer to update - func updateShapeLayer(layer: CAShapeLayer) - - /// Asks the renderer what the renderable bounds is for the given box. - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect - - /// Renders the shape in a custom context - func render(_ inContext: CGContext) -} - -extension RenderNode where Self: AnimatorNode { - - var outputNode: NodeOutput { - return renderer - } - -} - -extension Renderable { - - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { - /// Optional - return boundingBox - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift deleted file mode 100644 index fcdb60da8e..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// ShapeContainerLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -/** - The base layer that holds Shapes and Shape Renderers - */ -class ShapeContainerLayer: CALayer { - private(set) var renderLayers: [ShapeContainerLayer] = [] - - override init() { - super.init() - self.actions = [ - "position" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "transform" : NSNull(), - "opacity" : NSNull(), - "hidden" : NSNull(), - ] - self.anchorPoint = .zero - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override init(layer: Any) { - guard let layer = layer as? ShapeContainerLayer else { - fatalError("init(layer:) wrong class.") - } - super.init(layer: layer) - } - - var renderScale: CGFloat = 1 { - didSet { - updateRenderScale() - } - } - - func insertRenderLayer(_ layer: ShapeContainerLayer) { - renderLayers.append(layer) - insertSublayer(layer, at: 0) - } - - func markRenderUpdates(forFrame: CGFloat) { - if self.hasRenderUpdate(forFrame: forFrame) { - self.rebuildContents(forFrame: forFrame) - } - guard self.isHidden == false else { return } - renderLayers.forEach { $0.markRenderUpdates(forFrame: forFrame) } - } - - func hasRenderUpdate(forFrame: CGFloat) -> Bool { - return false - } - - func rebuildContents(forFrame: CGFloat) { - /// Override - } - - func updateRenderScale() { - renderLayers.forEach( { $0.renderScale = renderScale } ) - } - - func captureGeometry() -> CapturedGeometryNode { - var children: [CapturedGeometryNode] = [] - for renderLayer in self.renderLayers.reversed() { - children.append(renderLayer.captureGeometry()) - } - return CapturedGeometryNode( - transform: self.transform, - alpha: CGFloat(self.opacity), - isHidden: false, - displayItem: self.captureDisplayItem(), - subnodes: children - ) - } - - func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - return nil - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift deleted file mode 100644 index f7cfce2c65..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift +++ /dev/null @@ -1,204 +0,0 @@ -// -// RenderLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/18/19. -// - -import Foundation -import UIKit - -/** - The layer responsible for rendering shape objects - */ -final class ShapeRenderLayer: ShapeContainerLayer { - - fileprivate(set) var renderer: Renderable & NodeOutput - - let shapeLayer: CAShapeLayer = CAShapeLayer() - - init(renderer: Renderable & NodeOutput) { - self.renderer = renderer - super.init() - self.anchorPoint = .zero - self.actions = [ - "position" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "path" : NSNull(), - "transform" : NSNull(), - "opacity" : NSNull(), - "hidden" : NSNull(), - ] - shapeLayer.actions = [ - "position" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "path" : NSNull(), - "fillColor" : NSNull(), - "strokeColor" : NSNull(), - "lineWidth" : NSNull(), - "miterLimit" : NSNull(), - "lineDashPhase" : NSNull(), - "hidden" : NSNull(), - ] - shapeLayer.anchorPoint = .zero - addSublayer(shapeLayer) - } - - override init(layer: Any) { - guard let layer = layer as? ShapeRenderLayer else { - fatalError("init(layer:) wrong class.") - } - self.renderer = layer.renderer - super.init(layer: layer) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func hasRenderUpdate(forFrame: CGFloat) -> Bool { - self.isHidden = !renderer.isEnabled - guard self.isHidden == false else { return false } - return renderer.hasRenderUpdates(forFrame) - } - - override func rebuildContents(forFrame: CGFloat) { - - if renderer.shouldRenderInContext { - if let newPath = renderer.outputPath { - self.bounds = renderer.renderBoundsFor(newPath.boundingBox) - } else { - self.bounds = .zero - } - self.position = bounds.origin - self.setNeedsDisplay() - } else { - shapeLayer.path = renderer.outputPath - renderer.updateShapeLayer(layer: shapeLayer) - } - } - - override func draw(in ctx: CGContext) { - if let path = renderer.outputPath { - if !path.isEmpty { - ctx.addPath(path) - } - } - renderer.render(ctx) - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - guard let path = renderer.outputPath, !path.isEmpty else { - return nil - } - let display: CapturedGeometryNode.DisplayItem.Display - if let renderer = self.renderer as? FillRenderer { - display = .fill(CapturedGeometryNode.DisplayItem.Display.Fill( - style: .color(color: UIColor(cgColor: renderer.color!), alpha: renderer.opacity), - fillRule: renderer.fillRule.cgFillRule - )) - } else if let renderer = self.renderer as? StrokeRenderer { - display = .stroke(CapturedGeometryNode.DisplayItem.Display.Stroke( - style: .color(color: UIColor(cgColor: renderer.color!), alpha: renderer.opacity), - lineWidth: renderer.width, - lineCap: renderer.lineCap.cgLineCap, - lineJoin: renderer.lineJoin.cgLineJoin, - miterLimit: renderer.miterLimit - )) - } else if let renderer = self.renderer as? GradientFillRenderer { - var gradientColors: [UIColor] = [] - var colorLocations: [CGFloat] = [] - - for i in 0 ..< renderer.numberOfColors { - let ix = i * 4 - if renderer.colors.count > ix { - let color = UIColor(red: CGFloat(renderer.colors[ix + 1]), green: CGFloat(renderer.colors[ix + 2]), blue: CGFloat(renderer.colors[ix + 3]), alpha: renderer.opacity) - gradientColors.append(color) - colorLocations.append(CGFloat(renderer.colors[ix])) - } - } - - var alphaIndex = 0 - for i in stride(from: (renderer.numberOfColors * 4), to: renderer.colors.endIndex, by: 2) { - let alpha = renderer.colors[i + 1] - var currentAlpha: CGFloat = 1.0 - if alphaIndex < gradientColors.count { - gradientColors[alphaIndex].getRed(nil, green: nil, blue: nil, alpha: ¤tAlpha) - gradientColors[alphaIndex] = gradientColors[alphaIndex].withAlphaComponent(alpha * currentAlpha) - } - alphaIndex += 1 - } - - let mappedType: CapturedGeometryNode.DisplayItem.Display.Style.GradientType - switch renderer.type { - case .linear: - mappedType = .linear - case .radial: - mappedType = .radial - case .none: - mappedType = .linear - } - - display = .fill(CapturedGeometryNode.DisplayItem.Display.Fill( - style: .gradient( - colors: gradientColors, - positions: colorLocations, - start: renderer.start, - end: renderer.end, - type: mappedType - ), - fillRule: .evenOdd - )) - } else if let renderer = renderer as? GradientStrokeRenderer { - var gradientColors: [UIColor] = [] - var colorLocations: [CGFloat] = [] - - let gradientRender = renderer.gradientRender - - for i in 0 ..< gradientRender.numberOfColors { - let ix = i * 4 - if gradientRender.colors.count > ix { - let color = UIColor(red: CGFloat(gradientRender.colors[ix + 1]), green: CGFloat(gradientRender.colors[ix + 2]), blue: CGFloat(gradientRender.colors[ix + 3]), alpha: 1.0) - gradientColors.append(color) - colorLocations.append(CGFloat(gradientRender.colors[ix])) - } - } - - var alphaIndex = 0 - for i in stride(from: (gradientRender.numberOfColors * 4), to: gradientRender.colors.endIndex, by: 2) { - let alpha = gradientRender.colors[i + 1] - gradientColors[alphaIndex] = gradientColors[alphaIndex].withAlphaComponent(alpha) - alphaIndex += 1 - } - - let mappedType: CapturedGeometryNode.DisplayItem.Display.Style.GradientType - switch gradientRender.type { - case .linear: - mappedType = .linear - case .radial: - mappedType = .radial - case .none: - mappedType = .linear - } - - display = .stroke(CapturedGeometryNode.DisplayItem.Display.Stroke( - style: .gradient( - colors: gradientColors, - positions: colorLocations, - start: gradientRender.start, - end: gradientRender.end, - type: mappedType - ), - lineWidth: renderer.strokeRender.width, - lineCap: renderer.strokeRender.lineCap.cgLineCap, - lineJoin: renderer.strokeRender.lineJoin.cgLineJoin, - miterLimit: renderer.strokeRender.miterLimit - )) - } else { - return nil - } - return CapturedGeometryNode.DisplayItem(path: path, display: display) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/AnimationKeypathExtension.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/AnimationKeypathExtension.swift deleted file mode 100644 index 8b13789179..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/AnimationKeypathExtension.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/CGFloatExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/CGFloatExtensions.swift deleted file mode 100644 index b7c0d9d283..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/CGFloatExtensions.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// CGFloatExtensions.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import QuartzCore - -extension CGFloat { - - func isInRangeOrEqual(_ from: CGFloat, _ to: CGFloat) -> Bool { - return (from <= self && self <= to) - } - - func isInRange(_ from: CGFloat, _ to: CGFloat) -> Bool { - return (from < self && self < to) - } - - var squared: CGFloat { - return self * self - } - - var cubed: CGFloat { - return self * self * self - } - - var cubicRoot: CGFloat { - return CGFloat(pow(Double(self), 1.0 / 3.0)) - } - - fileprivate static func SolveQuadratic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat) -> CGFloat { - var result = (-b + sqrt(b.squared - 4 * a * c)) / (2 * a); - guard !result.isInRangeOrEqual(0, 1) else { - return result - } - - result = (-b - sqrt(b.squared - 4 * a * c)) / (2 * a); - guard !result.isInRangeOrEqual(0, 1) else { - return result - } - - return -1; - } - - fileprivate static func SolveCubic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat, _ d: CGFloat) -> CGFloat { - if (a == 0) { - return SolveQuadratic(b, c, d) - } - if (d == 0) { - return 0 - } - let a = a - var b = b - var c = c - var d = d - b /= a - c /= a - d /= a - var q = (3.0 * c - b.squared) / 9.0 - let r = (-27.0 * d + b * (9.0 * c - 2.0 * b.squared)) / 54.0 - let disc = q.cubed + r.squared - let term1 = b / 3.0 - - if (disc > 0) { - var s = r + sqrt(disc) - s = (s < 0) ? -((-s).cubicRoot) : s.cubicRoot - var t = r - sqrt(disc) - t = (t < 0) ? -((-t).cubicRoot) : t.cubicRoot - - let result = -term1 + s + t; - if result.isInRangeOrEqual(0, 1) { - return result - } - } else if (disc == 0) { - let r13 = (r < 0) ? -((-r).cubicRoot) : r.cubicRoot; - - var result = -term1 + 2.0 * r13; - if result.isInRangeOrEqual(0, 1) { - return result - } - - result = -(r13 + term1); - if result.isInRangeOrEqual(0, 1) { - return result - } - - } else { - q = -q; - var dum1 = q * q * q; - dum1 = acos(r / sqrt(dum1)); - let r13 = 2.0 * sqrt(q); - - var result = -term1 + r13 * cos(dum1 / 3.0); - if result.isInRangeOrEqual(0, 1) { - return result - } - result = -term1 + r13 * cos((dum1 + 2.0 * .pi) / 3.0); - if result.isInRangeOrEqual(0, 1) { - return result - } - result = -term1 + r13 * cos((dum1 + 4.0 * .pi) / 3.0); - if result.isInRangeOrEqual(0, 1) { - return result - } - } - - return -1; - } - - func cubicBezierInterpolate(_ P0: CGPoint, _ P1: CGPoint, _ P2: CGPoint, _ P3: CGPoint) -> CGFloat { - var t: CGFloat - if (self == P0.x) { - // Handle corner cases explicitly to prevent rounding errors - t = 0 - } else if (self == P3.x) { - t = 1 - } else { - // Calculate t - let a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; - let b = 3 * P0.x - 6 * P1.x + 3 * P2.x; - let c = -3 * P0.x + 3 * P1.x; - let d = P0.x - self; - let tTemp = CGFloat.SolveCubic(a, b, c, d); - if (tTemp == -1) { - return -1; - } - t = tTemp - } - - // Calculate y from t - return (1 - t).cubed * P0.y + 3 * t * (1 - t).squared * P1.y + 3 * t.squared * (1 - t) * P2.y + t.cubed * P3.y; - } - - func cubicBezier(_ t: CGFloat, _ c1: CGFloat, _ c2: CGFloat, _ end: CGFloat) -> CGFloat { - let t_ = (1.0 - t) - let tt_ = t_ * t_ - let ttt_ = t_ * t_ * t_ - let tt = t * t - let ttt = t * t * t - - return self * ttt_ - + 3.0 * c1 * tt_ * t - + 3.0 * c2 * t_ * tt - + end * ttt; - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/MathKit.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/MathKit.swift deleted file mode 100644 index 7a2ad297a4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/MathKit.swift +++ /dev/null @@ -1,539 +0,0 @@ -// -// MathKit.swift -// UIToolBox -// -// Created by Brandon Withrow on 10/10/18. -// -// From https://github.com/buba447/UIToolBox - -import Foundation -import CoreGraphics - -extension Int { - var cgFloat: CGFloat { - return CGFloat(self) - } -} - -extension Double { - var cgFloat: CGFloat { - return CGFloat(self) - } -} - -extension CGFloat: Interpolatable { - - - /** - Interpolates the receiver to the given number by Amount. - - Parameter toNumber: The number to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let number = 5 - let interpolated = number.interpolateTo(10, amount: 0.5) - print(interpolated) - // Result: 7.5 - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: CGFloat, amount: CGFloat) -> CGFloat { - return self + ((to - self) * CGFloat(amount)) - } - - func interpolateTo(_ to: CGFloat, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CGFloat { - return interpolateTo(to, amount: amount) - } - - func remap(fromLow: CGFloat, fromHigh: CGFloat, toLow: CGFloat, toHigh: CGFloat) -> CGFloat { - guard (fromHigh - fromLow) != 0 else { - // Would produce NAN - return 0 - } - return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) - } - - /** - Returns a value that is clamped between the two numbers - - 1. The order of arguments does not matter. - */ - func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat { - return CGFloat(Double(self).clamp(Double(a), Double(b))) - } - - /** - Returns the difference between the receiver and the given number. - - Parameter absolute: If *true* (Default) the returned value will always be positive. - */ - func diff(_ a: CGFloat, absolute: Bool = true) -> CGFloat { - return absolute ? abs(a - self) : a - self - } - - func toRadians() -> CGFloat { return self * .pi / 180 } - func toDegrees() -> CGFloat { return self * 180 / .pi } - -} - -extension Double: Interpolatable { - - /** - Interpolates the receiver to the given number by Amount. - - Parameter toNumber: The number to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let number = 5 - let interpolated = number.interpolateTo(10, amount: 0.5) - print(interpolated) - // Result: 7.5 - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: Double, amount: CGFloat) -> Double { - return self + ((to - self) * Double(amount)) - } - - func interpolateTo(_ to: Double, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Double { - return interpolateTo(to, amount: amount) - } - - func remap(fromLow: Double, fromHigh: Double, toLow: Double, toHigh: Double) -> Double { - return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) - } - - /** - Returns a value that is clamped between the two numbers - - 1. The order of arguments does not matter. - */ - func clamp(_ a: Double, _ b: Double) -> Double { - let minValue = a <= b ? a : b - let maxValue = a <= b ? b : a - return max(min(self, maxValue), minValue) - } - -} - -extension CGRect { - - /// Initializes a new CGRect with a center point and size. - init(center: CGPoint, size: CGSize) { - self.init(x: center.x - (size.width * 0.5), - y: center.y - (size.height * 0.5), - width: size.width, - height: size.height) - } - - /// Returns the total area of the rect. - var area: CGFloat { - return width * height - } - - - /// The center point of the rect. Settable. - var center: CGPoint { - get { - return CGPoint(x: midX, y: midY) - } - set { - origin = CGPoint(x: newValue.x - (size.width * 0.5), - y: newValue.y - (size.height * 0.5)) - } - } - - /// The top left point of the rect. Settable. - var topLeft: CGPoint { - get { - return CGPoint(x: minX, y: minY) - } - set { - origin = CGPoint(x: newValue.x, - y: newValue.y) - } - } - - /// The bottom left point of the rect. Settable. - var bottomLeft: CGPoint { - get { - return CGPoint(x: minX, y: maxY) - } - set { - origin = CGPoint(x: newValue.x, - y: newValue.y - size.height) - } - } - - /// The top right point of the rect. Settable. - var topRight: CGPoint { - get { - return CGPoint(x: maxX, y: minY) - } - set { - origin = CGPoint(x: newValue.x - size.width, - y: newValue.y) - } - } - - /// The bottom right point of the rect. Settable. - var bottomRight: CGPoint { - get { - return CGPoint(x: maxX, y: maxY) - } - set { - origin = CGPoint(x: newValue.x - size.width, - y: newValue.y - size.height) - } - } - - /** - Interpolates the receiver to the given rect by Amount. - - Parameter to: The rect to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let rect = CGRect(x:0, y:0, width: 50, height: 50) - let interpolated = rect.interpolateTo(CGRect(x:100, y:100, width: 100, height: 100), amount: 0.5) - print(interpolated) - // Result: (x: 50, y: 50, width: 75, height: 75) - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: CGRect, amount: CGFloat) -> CGRect { - return CGRect(x: origin.x.interpolateTo(to.origin.x, amount: amount), - y: origin.y.interpolateTo(to.origin.y, amount: amount), - width: width.interpolateTo(to.width, amount: amount), - height: height.interpolateTo(to.height, amount: amount)) - } - -} - -extension CGSize { - - /** - Interpolates the receiver to the given size by Amount. - - Parameter to: The size to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let size = CGSize(width: 50, height: 50) - let interpolated = rect.interpolateTo(CGSize(width: 100, height: 100), amount: 0.5) - print(interpolated) - // Result: (width: 75, height: 75) - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: CGSize, amount: CGFloat) -> CGSize { - return CGSize(width: width.interpolateTo(to.width, amount: amount), - height: height.interpolateTo(to.height, amount: amount)) - } - - /// Returns the scale float that will fit the receive inside of the given size. - func scaleThatFits(_ size: CGSize) -> CGFloat { - return CGFloat.minimum(width / size.width, height / size.height) - } - - /// Adds receiver size to give size. - func add(_ size: CGSize) -> CGSize { - return CGSize(width: width + size.width, height: height + size.height) - } - - /// Subtracts given size from receiver size. - func subtract(_ size: CGSize) -> CGSize { - return CGSize(width: width - size.width, height: height - size.height) - } - - /// Multiplies receiver size by the given size. - func multiply(_ size: CGSize) -> CGSize { - return CGSize(width: width * size.width, height: height * size.height) - } - - /// Operator convenience to add sizes with + - static func +(left: CGSize, right: CGSize) -> CGSize { - return left.add(right) - } - - /// Operator convenience to subtract sizes with - - static func -(left: CGSize, right: CGSize) -> CGSize { - return left.subtract(right) - } - - /// Operator convenience to multiply sizes with * - static func *(left: CGSize, right: CGFloat) -> CGSize { - return CGSize(width: left.width * right, height: left.height * right) - } - -} - -/// A struct that defines a line segment with two CGPoints -struct CGLine { - - /// The Start of the line segment. - var start: CGPoint - /// The End of the line segment. - var end: CGPoint - - /// Initializes a line segment with start and end points - init(start: CGPoint, end: CGPoint) { - self.start = start - self.end = end - } - - /// The length of the line segment. - var length: CGFloat { - return end.distanceTo(start) - } - - /// Returns a line segment that is normalized to a length of 1 - func normalize() -> CGLine { - let len = length - guard len > 0 else { - return self - } - let relativeEnd = end - start - let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) - let absoluteVector = relativeVector + start - return CGLine(start: start, end: absoluteVector) - } - - /// Trims a line segment to the given length - func trimmedToLength(_ toLength: CGFloat) -> CGLine { - let len = length - guard len > 0 else { - return self - } - let relativeEnd = end - start - let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) - let sizedVector = CGPoint(x: relativeVector.x * toLength, y: relativeVector.y * toLength) - let absoluteVector = sizedVector + start - return CGLine(start: start, end: absoluteVector) - } - - /// Flips a line vertically and horizontally from the start point. - func flipped() -> CGLine { - let relativeEnd = end - start - let flippedEnd = CGPoint(x: relativeEnd.x * -1, y: relativeEnd.y * -1) - return CGLine(start: start, end: flippedEnd + start) - } - - /// Move the line to the new start point. - func transpose(_ toPoint: CGPoint) -> CGLine { - let diff = toPoint - start - let newEnd = end + diff - return CGLine(start: toPoint, end: newEnd) - } - -} - -infix operator +| -infix operator +- - -extension CGPoint: Interpolatable { - - /// Returns the distance between the receiver and the given point. - func distanceTo(_ a: CGPoint) -> CGFloat { - let xDist = a.x - x - let yDist = a.y - y - return CGFloat(sqrt((xDist * xDist) + (yDist * yDist))) - } - - /// Returns the length between the receiver and *CGPoint.zero* - var vectorLength: CGFloat { - return distanceTo(.zero) - } - - func rounded(decimal: CGFloat) -> CGPoint { - return CGPoint(x: (round(decimal * x) / decimal), y: (round(decimal * y) / decimal)) - } - - /** - Interpolates the receiver to the given Point by Amount. - - Parameter to: The Point to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let point = CGPoint(width: 50, height: 50) - let interpolated = rect.interpolateTo(CGPoint(width: 100, height: 100), amount: 0.5) - print(interpolated) - // Result: (x: 75, y: 75) - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - - func interpolate(_ to: CGPoint, amount: CGFloat) -> CGPoint { - return CGPoint(x: x.interpolateTo(to.x, amount: amount), - y: y.interpolateTo(to.y, amount: amount)) - } - - func interpolate(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat, maxIterations: Int = 3, samples: Int = 20, accuracy: CGFloat = 1) -> CGPoint { - if amount == 0 { - return self - } - if amount == 1 { - return to - } - - if self.colinear(outTangent, inTangent) == true, - outTangent.colinear(inTangent, to) == true { - return interpolate(to, amount: amount) - } - - let step = 1 / CGFloat(samples) - - var points: [(point: CGPoint, distance: CGFloat)] = [(point: self, distance: 0)] - var totalLength: CGFloat = 0 - - var previousPoint = self - var previousAmount = CGFloat(0) - - var closestPoint: Int = 0 - - while previousAmount < 1 { - - previousAmount = previousAmount + step - - if previousAmount < amount { - closestPoint = closestPoint + 1 - } - - let newPoint = self.pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: previousAmount) - let distance = previousPoint.distanceTo(newPoint) - totalLength = totalLength + distance - points.append((point: newPoint, distance: totalLength)) - previousPoint = newPoint - } - - let accurateDistance = amount * totalLength - var point = points[closestPoint] - - var foundPoint: Bool = false - - var pointAmount: CGFloat = CGFloat(closestPoint) * step - var nextPointAmount: CGFloat = pointAmount + step - - var refineIterations = 0 - while foundPoint == false { - refineIterations = refineIterations + 1 - /// First see if the next point is still less than the projected length. - let nextPoint = points[closestPoint + 1] - if nextPoint.distance < accurateDistance { - point = nextPoint - closestPoint = closestPoint + 1 - pointAmount = CGFloat(closestPoint) * step - nextPointAmount = pointAmount + step - if closestPoint == points.count { - foundPoint = true - } - continue - } - if accurateDistance < point.distance { - closestPoint = closestPoint - 1 - if closestPoint < 0 { - foundPoint = true - continue - } - point = points[closestPoint] - pointAmount = CGFloat(closestPoint) * step - nextPointAmount = pointAmount + step - continue - } - - /// Now we are certain the point is the closest point under the distance - let pointDiff = nextPoint.distance - point.distance - let proposedPointAmount = ((accurateDistance - point.distance) / pointDiff).remap(fromLow: 0, fromHigh: 1, toLow: pointAmount, toHigh: nextPointAmount) - - let newPoint = self.pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: proposedPointAmount) - let newDistance = point.distance + point.point.distanceTo(newPoint) - pointAmount = proposedPointAmount - point = (point: newPoint, distance: newDistance) - if accurateDistance - newDistance <= accuracy || - newDistance - accurateDistance <= accuracy { - foundPoint = true - } - - if refineIterations == maxIterations { - foundPoint = true - } - } - return point.point - } - - func pointOnPath(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat) -> CGPoint { - let a = self.interpolate(outTangent, amount: amount) - let b = outTangent.interpolate(inTangent, amount: amount) - let c = inTangent.interpolate(to, amount: amount) - let d = a.interpolate(b, amount: amount) - let e = b.interpolate(c, amount: amount) - let f = d.interpolate(e, amount: amount) - return f - } - - func colinear(_ a: CGPoint, _ b: CGPoint) -> Bool { - let area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); - let accuracy: CGFloat = 0.05 - if area < accuracy && area > -accuracy { - return true - } - return false - } - - func interpolateTo(_ to: CGPoint, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CGPoint { - guard let outTan = spatialOutTangent, - let inTan = spatialInTangent else { - return interpolate(to, amount: amount) - } - let cp1 = self + outTan - let cp2 = to + inTan - - return interpolate(to, outTangent: cp1, inTangent: cp2, amount: amount) - } - - /// Subtracts the given point from the receiving point. - func subtract(_ point: CGPoint) -> CGPoint { - return CGPoint(x: x - point.x, - y: y - point.y) - } - - /// Adds the given point from the receiving point. - func add(_ point: CGPoint) -> CGPoint { - return CGPoint(x: x + point.x, - y: y + point.y) - } - - var isZero: Bool { - return (x == 0 && y == 0) - } - - /// Operator convenience to divide points with / - static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { - return CGPoint(x: lhs.x / CGFloat(rhs), y: lhs.y / CGFloat(rhs)) - } - - /// Operator convenience to multiply points with * - static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { - return CGPoint(x: lhs.x * CGFloat(rhs), y: lhs.y * CGFloat(rhs)) - } - - /// Operator convenience to add points with + - static func +(left: CGPoint, right: CGPoint) -> CGPoint { - return left.add(right) - } - - /// Operator convenience to subtract points with - - static func -(left: CGPoint, right: CGPoint) -> CGPoint { - return left.subtract(right) - } - - static func +|(left: CGPoint, right: CGFloat) -> CGPoint { - return CGPoint(x: left.x, y: left.y + right) - } - - static func +-(left: CGPoint, right: CGFloat) -> CGPoint { - return CGPoint(x: left.x + right, y: left.y) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/StringExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/StringExtensions.swift deleted file mode 100644 index 99dd16e780..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/StringExtensions.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// StringExtensions.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/25/19. -// - -import Foundation -import CoreGraphics - -extension String { - - func hexColorComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat) { - - var cString:String = trimmingCharacters(in: .whitespacesAndNewlines).uppercased() - - if (cString.hasPrefix("#")) { - cString.remove(at: cString.startIndex) - } - - if ((cString.count) != 6) { - return (red: 0, green: 0, blue: 0) - } - - var rgbValue:UInt64 = 0 - Scanner(string: cString).scanHexInt64(&rgbValue) - - return (red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, - green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, - blue: CGFloat(rgbValue & 0x0000FF) / 255.0) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/Interpolatable.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/Interpolatable.swift deleted file mode 100644 index 061e03c4a7..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/Interpolatable.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Interpolatable.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -protocol Interpolatable { - - func interpolateTo(_ to: Self, - amount: CGFloat, - spatialOutTangent: CGPoint?, - spatialInTangent: CGPoint?) -> Self - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/InterpolatableExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/InterpolatableExtensions.swift deleted file mode 100644 index fd5d6e5c23..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/InterpolatableExtensions.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// InterpolatableExtensions.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -extension Vector1D: Interpolatable { - func interpolateTo(_ to: Vector1D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector1D { - return value.interpolateTo(to.value, amount: amount).vectorValue - } -} - -extension Vector2D: Interpolatable { - func interpolateTo(_ to: Vector2D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector2D { - return pointValue.interpolateTo(to.pointValue, amount: CGFloat(amount), spatialOutTangent: spatialOutTangent, spatialInTangent: spatialInTangent).vector2dValue - } - -} - -extension Vector3D: Interpolatable { - func interpolateTo(_ to: Vector3D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector3D { - if spatialInTangent != nil || spatialOutTangent != nil { - // TODO Support third dimension spatial interpolation - let point = pointValue.interpolateTo(to.pointValue, amount: amount, spatialOutTangent: spatialOutTangent, spatialInTangent: spatialInTangent) - return Vector3D(x: point.x, - y: point.y, - z: CGFloat(z.interpolateTo(to.z, amount: amount))) - } - return Vector3D(x: x.interpolateTo(to.x, amount: amount), - y: y.interpolateTo(to.y, amount: amount), - z: z.interpolateTo(to.z, amount: amount)) - } -} - -extension Color: Interpolatable { - - /// Initialize a new color with Hue Saturation and Value - init(h: Double, s: Double, v: Double, a: Double) { - - let i = floor(h * 6) - let f = h * 6 - i - let p = v * (1 - s); - let q = v * (1 - f * s) - let t = v * (1 - (1 - f) * s) - - switch (i.truncatingRemainder(dividingBy: 6)) { - case 0: - self.r = v - self.g = t - self.b = p - case 1: - self.r = q - self.g = v - self.b = p - case 2: - self.r = p - self.g = v - self.b = t - case 3: - self.r = p - self.g = q - self.b = v - case 4: - self.r = t - self.g = p - self.b = v - case 5: - self.r = v - self.g = p - self.b = q - default: - self.r = 0 - self.g = 0 - self.b = 0 - } - self.a = a - } - - /// Hue Saturation Value of the color. - var hsva: (h: Double, s: Double, v: Double, a: Double) { - let maxValue = max(r, g, b) - let minValue = min(r, g, b) - - var h: Double, s: Double, v: Double = maxValue - - let d = maxValue - minValue - s = maxValue == 0 ? 0 : d / maxValue; - - if (maxValue == minValue) { - h = 0; // achromatic - } else { - switch (maxValue) { - case r: h = (g - b) / d + (g < b ? 6 : 0) - case g: h = (b - r) / d + 2 - case b: h = (r - g) / d + 4 - default: h = maxValue - } - h = h / 6 - } - return (h: h, s: s, v: v, a: a) - } - - init(y: Double, u: Double, v: Double, a: Double) { - // From https://www.fourcc.org/fccyvrgb.php - self.r = y + 1.403 * v - self.g = y - 0.344 * u - self.b = y + 1.770 * u - self.a = a - } - - var yuv: (y: Double, u: Double, v: Double, a: Double) { - /// From https://www.fourcc.org/fccyvrgb.php - let y = 0.299 * r + 0.587 * g + 0.114 * b - let u = -0.14713 * r - 0.28886 * g + 0.436 * b - let v = 0.615 * r - 0.51499 * g - 0.10001 * b - return (y: y, u: u, v: v, a: a) - } - - func interpolateTo(_ to: Color, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Color { - return Color(r: r.interpolateTo(to.r, amount: amount), - g: g.interpolateTo(to.g, amount: amount), - b: b.interpolateTo(to.b, amount: amount), - a: a.interpolateTo(to.a, amount: amount)) - } -} - -extension CurveVertex: Interpolatable { - func interpolateTo(_ to: CurveVertex, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CurveVertex { - return CurveVertex(point: point.interpolate(to.point, amount: amount), - inTangent: inTangent.interpolate(to.inTangent, amount: amount), - outTangent: outTangent.interpolate(to.outTangent, amount: amount)) - } -} - -extension BezierPath: Interpolatable { - func interpolateTo(_ to: BezierPath, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> BezierPath { - var newPath = BezierPath() - for i in 0.. TextDocument { - if amount == 1 { - return to - } - return self - } -} - -extension Array: Interpolatable where Element == Double { - func interpolateTo(_ to: Array, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Array { - var returnArray = [Double]() - for i in 0.. CGFloat { - let startTime = time - let endTime = to.time - if keyTime <= startTime { - return 0 - } - if endTime <= keyTime { - return 1 - } - - if isHold { - return 0 - } - - let outTanPoint = outTangent?.pointValue ?? .zero - let inTanPoint = to.inTangent?.pointValue ?? CGPoint(x: 1, y: 1) - var progress: CGFloat = keyTime.remap(fromLow: startTime, fromHigh: endTime, toLow: 0, toHigh: 1) - if !outTanPoint.isZero || !inTanPoint.equalTo(CGPoint(x: 1, y: 1)) { - /// Cubic interpolation - progress = progress.cubicBezierInterpolate(.zero, outTanPoint, inTanPoint, CGPoint(x: 1, y: 1)) - } - return progress - } - - /// Interpolates the keyframes' by a progress from 0-1 - func interpolate(_ to: Keyframe, progress: CGFloat) -> T { - return value.interpolateTo(to.value, amount: progress, spatialOutTangent: spatialOutTangent?.pointValue, spatialInTangent: to.spatialInTangent?.pointValue) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/BezierPath.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/BezierPath.swift deleted file mode 100644 index f5f60831b0..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/BezierPath.swift +++ /dev/null @@ -1,402 +0,0 @@ -// -// Shape.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation -import CoreGraphics - -/// A container that holds instructions for creating a single, unbroken Bezier Path. -struct BezierPath { - - /// The elements of the path - fileprivate(set) var elements: [PathElement] - - /// If the path is closed or not. - fileprivate(set) var closed: Bool - - /// The total length of the path. - fileprivate(set) var length: CGFloat - - /// Initializes a new Bezier Path. - init(startPoint: CurveVertex) { - self.elements = [PathElement(vertex: startPoint)] - self.length = 0 - self.closed = false - } - - init() { - self.elements = [] - self.length = 0 - self.closed = false - } - - mutating func moveToStartPoint(_ vertex: CurveVertex) { - self.elements = [PathElement(vertex: vertex)] - self.length = 0 - } - - mutating func addVertex(_ vertex: CurveVertex) { - guard let previous = elements.last else { - addElement(PathElement(vertex: vertex)) - return - } - addElement(previous.pathElementTo(vertex)) - } - - mutating func addCurve(toPoint: CGPoint, outTangent: CGPoint, inTangent: CGPoint) { - guard let previous = elements.last else { return } - let newVertex = CurveVertex(inTangent, toPoint, toPoint) - updateVertex(CurveVertex(previous.vertex.inTangent, previous.vertex.point, outTangent), atIndex: elements.endIndex - 1, remeasure: false) - addVertex(newVertex) - } - - mutating func addLine(toPoint: CGPoint) { - guard let previous = elements.last else { return } - let newVertex = CurveVertex(point: toPoint, inTangentRelative: .zero, outTangentRelative: .zero) - updateVertex(CurveVertex(previous.vertex.inTangent, previous.vertex.point, previous.vertex.point), atIndex: elements.endIndex - 1, remeasure: false) - addVertex(newVertex) - } - - mutating func close() { - self.closed = true - } - - mutating func addElement(_ pathElement: PathElement) { - elements.append(pathElement) - length = length + pathElement.length - } - - mutating func updateVertex(_ vertex: CurveVertex, atIndex: Int, remeasure: Bool) { - if remeasure { - var newElement: PathElement - if atIndex > 0 { - let previousElement = elements[atIndex-1] - newElement = previousElement.pathElementTo(vertex) - } else { - newElement = PathElement(vertex: vertex) - } - elements[atIndex] = newElement - - if atIndex + 1 < elements.count{ - let nextElement = elements[atIndex + 1] - elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex) - } - - } else { - let oldElement = elements[atIndex] - elements[atIndex] = oldElement.updateVertex(newVertex: vertex) - } - } - - /** - Trims a path fromLength toLength with an offset. - - Length and offset are defined in the length coordinate space. - If any argument is outside the range of this path, then it will be looped over the path from finish to start. - - Cutting the curve when fromLength is less than toLength - x x x x - ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- - |Offset |fromLength toLength| | - - Cutting the curve when from Length is greater than toLength - x x x x x - oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo - | toLength| |Offset |fromLength | - - */ - func trim(fromLength: CGFloat, toLength: CGFloat, offsetLength: CGFloat) -> [BezierPath] { - guard elements.count > 1 else { - return [] - } - - if fromLength == toLength { - return [] - } - - /// Normalize lengths to the curve length. - var start = (fromLength+offsetLength).truncatingRemainder(dividingBy: length) - var end = (toLength+offsetLength).truncatingRemainder(dividingBy: length) - - if start < 0 { - start = length + start - } - - if end < 0 { - end = length + end - } - - if start == length { - start = 0 - } - if end == 0 { - end = length - } - - if start == 0 && end == length || - start == end || - start == length && end == 0 { - /// The trim encompasses the entire path. Return. - return [self] - } - - if start > end { - // Start is greater than end. Two paths are returned. - return trimPathAtLengths(positions: [(start: 0, end: end), (start: start, end: length)]) - } - - return trimPathAtLengths(positions: [(start: start, end: end)]) - } - - // MARK: File Private - - /// Trims a path by a list of positions and returns the sub paths - fileprivate func trimPathAtLengths(positions: [(start: CGFloat, end: CGFloat)]) -> [BezierPath] { - guard positions.count > 0 else { - return [] - } - var remainingPositions = positions - - var trim = remainingPositions.remove(at: 0) - - var paths = [BezierPath]() - - var runningLength: CGFloat = 0 - var finishedTrimming: Bool = false - var pathElements = elements - - var currentPath = BezierPath() - var i: Int = 0 - - while !finishedTrimming { - if pathElements.count <= i { - /// Do this for rounding errors - paths.append(currentPath) - finishedTrimming = true - continue - } - /// Loop through and add elements within start->end range. - /// Get current element - let element = pathElements[i] - - /// Calculate new running length. - let newLength = runningLength + element.length - - if newLength < trim.start { - /// Element is not included in the trim, continue. - runningLength = newLength - i = i + 1 - /// Increment index, we are done with this element. - continue - } - - if newLength == trim.start { - /// Current element IS the start element. - /// For start we want to add a zero length element. - currentPath.moveToStartPoint(element.vertex) - runningLength = newLength - i = i + 1 - /// Increment index, we are done with this element. - continue - } - - if runningLength < trim.start, trim.start < newLength, currentPath.elements.count == 0 { - /// The start of the trim is between this element and the previous, trim. - /// Get previous element. - let previousElement = pathElements[i-1] - /// Trim it - let trimLength = trim.start - runningLength - let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) - /// Add the right span start. - currentPath.moveToStartPoint(trimResults.rightSpan.start.vertex) - - pathElements[i] = trimResults.rightSpan.end - pathElements[i-1] = trimResults.rightSpan.start - runningLength = runningLength + trimResults.leftSpan.end.length - /// Dont increment index or the current length, the end of this path can be within this span. - continue - } - - if trim.start < newLength, newLength < trim.end { - /// Element lies within the trim span. - currentPath.addElement(element) - runningLength = newLength - i = i + 1 - continue - } - - if newLength == trim.end { - /// Element is the end element. - /// The element could have a new length if it's added right after the start node. - currentPath.addElement(element) - /// We are done with this span. - runningLength = newLength - i = i + 1 - /// Allow the path to be finalized. - /// Fall through to finalize path and move to next position - } - - if runningLength < trim.end, trim.end < newLength { - /// New element must be cut for end. - /// Get previous element. - let previousElement = pathElements[i-1] - /// Trim it - let trimLength = trim.end - runningLength - let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) - /// Add the left span end. - - currentPath.updateVertex(trimResults.leftSpan.start.vertex, atIndex: currentPath.elements.count - 1, remeasure: false) - currentPath.addElement(trimResults.leftSpan.end) - - pathElements[i] = trimResults.rightSpan.end - pathElements[i-1] = trimResults.rightSpan.start - runningLength = runningLength + trimResults.leftSpan.end.length - /// Dont increment index or the current length, the start of the next path can be within this span. - /// We are done with this span. - /// Allow the path to be finalized. - /// Fall through to finalize path and move to next position - } - - paths.append(currentPath) - currentPath = BezierPath() - if remainingPositions.count > 0 { - trim = remainingPositions.remove(at: 0) - } else { - finishedTrimming = true - } - } - return paths - } - -} - -extension BezierPath: Codable { - - /** - The BezierPath container is encoded and decoded from the JSON format - that defines points for a lottie animation. - - { - "c" = Bool - "i" = [[Double]], - "o" = [[Double]], - "v" = [[Double]] - } - - */ - - enum CodingKeys : String, CodingKey { - case closed = "c" - case inPoints = "i" - case outPoints = "o" - case vertices = "v" - } - - init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer - - if let keyedContainer = try? decoder.container(keyedBy: BezierPath.CodingKeys.self) { - container = keyedContainer - } else { - var unkeyedContainer = try decoder.unkeyedContainer() - container = try unkeyedContainer.nestedContainer(keyedBy: BezierPath.CodingKeys.self) - } - - self.closed = try container.decodeIfPresent(Bool.self, forKey: .closed) ?? true - - var vertexContainer = try container.nestedUnkeyedContainer(forKey: .vertices) - var inPointsContainer = try container.nestedUnkeyedContainer(forKey: .inPoints) - var outPointsContainer = try container.nestedUnkeyedContainer(forKey: .outPoints) - - guard vertexContainer.count == inPointsContainer.count, inPointsContainer.count == outPointsContainer.count else { - /// Will throw an error if vertex, inpoints, and outpoints are not the same length. - /// This error is to be expected. - throw DecodingError.dataCorruptedError(forKey: CodingKeys.vertices, - in: container, - debugDescription: "Vertex data does not match In Tangents and Out Tangents") - } - - guard let count = vertexContainer.count, count > 0 else { - self.length = 0 - self.elements = [] - return - } - - var decodedElements = [PathElement]() - - /// Create first point - let firstVertex = CurveVertex(point: try vertexContainer.decode(CGPoint.self), - inTangentRelative: try inPointsContainer.decode(CGPoint.self), - outTangentRelative: try outPointsContainer.decode(CGPoint.self)) - var previousElement = PathElement(vertex: firstVertex) - decodedElements.append(previousElement) - - var totalLength: CGFloat = 0 - while !vertexContainer.isAtEnd { - /// Get the next vertex data. - let vertex = CurveVertex(point: try vertexContainer.decode(CGPoint.self), - inTangentRelative: try inPointsContainer.decode(CGPoint.self), - outTangentRelative: try outPointsContainer.decode(CGPoint.self)) - let pathElement = previousElement.pathElementTo(vertex) - decodedElements.append(pathElement) - previousElement = pathElement - totalLength = totalLength + pathElement.length - } - if closed { - let closeElement = previousElement.pathElementTo(firstVertex) - decodedElements.append(closeElement) - totalLength = totalLength + closeElement.length - } - self.length = totalLength - self.elements = decodedElements - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: BezierPath.CodingKeys.self) - try container.encode(closed, forKey: .closed) - - var vertexContainer = container.nestedUnkeyedContainer(forKey: .vertices) - var inPointsContainer = container.nestedUnkeyedContainer(forKey: .inPoints) - var outPointsContainer = container.nestedUnkeyedContainer(forKey: .outPoints) - - /// If closed path, ignore the final element. - let finalIndex = closed ? self.elements.endIndex - 1 : self.elements.endIndex - for i in 0.. CGPath { - let cgPath = CGMutablePath() - - var previousElement: PathElement? - for element in elements { - if let previous = previousElement { - if previous.vertex.outTangentRelative.isZero && element.vertex.inTangentRelative.isZero { - cgPath.addLine(to: element.vertex.point) - } else { - //cgPath.addLine(to: element.vertex.point) - cgPath.addCurve(to: element.vertex.point, control1: previous.vertex.outTangent, control2: element.vertex.inTangent) - } - } else { - cgPath.move(to: element.vertex.point) - } - previousElement = element - } - if self.closed { - cgPath.closeSubpath() - } - return cgPath - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/ColorExtension.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/ColorExtension.swift deleted file mode 100644 index 309031a2a4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/ColorExtension.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// Color.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -extension Color: Codable { - - public init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - - var r1: Double - if !container.isAtEnd { - r1 = try container.decode(Double.self) - } else { - r1 = 0 - } - - var g1: Double - if !container.isAtEnd { - g1 = try container.decode(Double.self) - } else { - g1 = 0 - } - - var b1: Double - if !container.isAtEnd { - b1 = try container.decode(Double.self) - } else { - b1 = 0 - } - - var a1: Double - if !container.isAtEnd { - a1 = try container.decode(Double.self) - } else { - a1 = 1 - } - if r1 > 1, g1 > 1, b1 > 1, a1 > 1 { - r1 = r1 / 255 - g1 = g1 / 255 - b1 = b1 / 255 - a1 = a1 / 255 - } - self.r = r1 - self.g = g1 - self.b = b1 - self.a = a1 - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - try container.encode(r) - try container.encode(g) - try container.encode(b) - try container.encode(a) - } - -} - -extension Color { - - static var clearColor: CGColor { - return CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 0])! - } - - var cgColorValue: CGColor { - // TODO: Fix color spaces - let colorspace = CGColorSpaceCreateDeviceRGB() - return CGColor(colorSpace: colorspace, components: [CGFloat(r), CGFloat(g), CGFloat(b), CGFloat(a)]) ?? Color.clearColor - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.swift deleted file mode 100644 index 068a13dd63..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.swift +++ /dev/null @@ -1,158 +0,0 @@ -// -// CompoundBezierPath.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -/** - A collection of BezierPath objects that can be trimmed and added. - - */ -struct CompoundBezierPath { - - let paths: [BezierPath] - - let length: CGFloat - - init() { - paths = [] - length = 0 - } - - init(path: BezierPath) { - self.paths = [path] - self.length = path.length - } - - init(paths: [BezierPath], length: CGFloat) { - self.paths = paths - self.length = length - } - - init(paths: [BezierPath]) { - self.paths = paths - var l: CGFloat = 0 - for path in paths { - l = l + path.length - } - self.length = l - } - - func addPath(path: BezierPath) -> CompoundBezierPath { - var newPaths = paths - newPaths.append(path) - return CompoundBezierPath(paths: newPaths, length: length + path.length) - } - - func combine(_ compoundBezier: CompoundBezierPath) -> CompoundBezierPath { - var newPaths = paths - newPaths.append(contentsOf: compoundBezier.paths) - return CompoundBezierPath(paths: newPaths, length: length + compoundBezier.length) - } - - func trim(fromPosition: CGFloat, toPosition: CGFloat, offset: CGFloat, trimSimultaneously: Bool) -> CompoundBezierPath { - if fromPosition == toPosition { - return CompoundBezierPath() - } - - if trimSimultaneously { - /// Trim each path individually. - var newPaths = [BezierPath]() - for path in paths { - newPaths.append(contentsOf: path.trim(fromLength: fromPosition * path.length, - toLength: toPosition * path.length, - offsetLength: offset * path.length)) - } - return CompoundBezierPath(paths: newPaths) - } - - /// Normalize lengths to the curve length. - var startPosition = (fromPosition+offset).truncatingRemainder(dividingBy: 1) - var endPosition = (toPosition+offset).truncatingRemainder(dividingBy: 1) - - if startPosition < 0 { - startPosition = 1 + startPosition - } - - if endPosition < 0 { - endPosition = 1 + endPosition - } - - if startPosition == 1 { - startPosition = 0 - } - if endPosition == 0 { - endPosition = 1 - } - - if startPosition == 0 && endPosition == 1 || - startPosition == endPosition || - startPosition == 1 && endPosition == 0 { - /// The trim encompasses the entire path. Return. - return self - } - - var positions: [(start: CGFloat, end: CGFloat)] - if endPosition < startPosition { - positions = [(start: 0, end: endPosition * length), - (start: startPosition * length, end: length)] - } else { - positions = [(start: startPosition * length, end: endPosition * length)] - } - - var compoundPath = CompoundBezierPath() - var trim = positions.remove(at: 0) - var pathStartPosition: CGFloat = 0 - - var finishedTrimming: Bool = false - var i: Int = 0 - - while !finishedTrimming { - if paths.count <= i { - /// Rounding errors - finishedTrimming = true - continue - } - let path = paths[i] - - let pathEndPosition = pathStartPosition + path.length - - if pathEndPosition < trim.start { - /// Path is not included in the trim, continue. - pathStartPosition = pathEndPosition - i = i + 1 - continue - - } else if trim.start <= pathStartPosition, pathEndPosition <= trim.end { - /// Full Path is inside of trim. Add full path. - compoundPath = compoundPath.addPath(path: path) - } else { - if let trimPath = path.trim(fromLength: trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, - toLength: trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length, - offsetLength: 0).first { - compoundPath = compoundPath.addPath(path: trimPath) - } - } - - - if trim.end <= pathEndPosition { - /// We are done with the current trim. - /// Advance trim but remain on the same path in case the next trim overlaps it. - if positions.count > 0 { - trim = positions.remove(at: 0) - } else { - finishedTrimming = true - } - } else { - pathStartPosition = pathEndPosition - i = i + 1 - } - } - return compoundPath - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CurveVertex.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CurveVertex.swift deleted file mode 100644 index d4b5eb18f0..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CurveVertex.swift +++ /dev/null @@ -1,177 +0,0 @@ -// -// CurveVertex.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/11/19. -// - -import Foundation -import CoreGraphics - -/// A single vertex with an in and out tangent -struct CurveVertex { - - let point: CGPoint - - let inTangent: CGPoint - let outTangent: CGPoint - - /// Initializes a curve point with absolute values - init(_ inTangent: CGPoint, _ point: CGPoint, _ outTangent: CGPoint) { - self.point = point - self.inTangent = inTangent - self.outTangent = outTangent - } - - /// Initializes a curve point with relative values - init(point: CGPoint, inTangentRelative: CGPoint, outTangentRelative: CGPoint) { - self.point = point - self.inTangent = point.add(inTangentRelative) - self.outTangent = point.add(outTangentRelative) - } - - /// Initializes a curve point with absolute values - init(point: CGPoint, inTangent: CGPoint, outTangent: CGPoint) { - self.point = point - self.inTangent = inTangent - self.outTangent = outTangent - } - - var inTangentRelative: CGPoint { - return inTangent.subtract(point) - } - - var outTangentRelative: CGPoint { - return outTangent.subtract(point) - } - - func reversed() -> CurveVertex { - return CurveVertex(point: point, inTangent: outTangent, outTangent: inTangent) - } - - func translated(_ translation: CGPoint) -> CurveVertex { - return CurveVertex(point: point + translation, inTangent: inTangent + translation, outTangent: outTangent + translation) - } - - /** - Trims a path defined by two Vertices at a specific position, from 0 to 1 - - The path can be visualized below. - - F is fromVertex. - V is the vertex of the receiver. - P is the position from 0-1. - O is the outTangent of fromVertex. - F====O=========P=======I====V - - After trimming the curve can be visualized below. - - S is the returned Start vertex. - E is the returned End vertex. - T is the trim point. - TI and TO are the new tangents for the trimPoint - NO and NI are the new tangents for the startPoint and endPoints - S==NO=========TI==T==TO=======NI==E - */ - func splitCurve(toVertex: CurveVertex, position: CGFloat) -> - (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) { - - /// If position is less than or equal to 0, trim at start. - if position <= 0 { - return (start: CurveVertex(point: point, inTangentRelative: inTangentRelative, outTangentRelative: .zero), - trimPoint: CurveVertex(point: point, inTangentRelative: .zero, outTangentRelative: outTangentRelative), - end: toVertex) - } - - /// If position is greater than or equal to 1, trim at end. - if position >= 1 { - return (start: self, - trimPoint: CurveVertex(point: toVertex.point, inTangentRelative: toVertex.inTangentRelative, outTangentRelative: .zero), - end: CurveVertex(point: toVertex.point, inTangentRelative: .zero, outTangentRelative: toVertex.outTangentRelative)) - } - - if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { - /// If both tangents are zero, then span to be trimmed is a straight line. - let trimPoint = point.interpolate(toVertex.point, amount: position) - return (start: self, - trimPoint: CurveVertex(point: trimPoint, inTangentRelative: .zero, outTangentRelative: .zero), - end: toVertex) - } - /// Cutting by amount gives incorrect length.... - /// One option is to cut by a stride until it gets close then edge it down. - /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. - /// This is where the historical trim path bugs come from. - let a = point.interpolate(outTangent, amount: position) - let b = outTangent.interpolate(toVertex.inTangent, amount: position) - let c = toVertex.inTangent.interpolate(toVertex.point, amount: position) - let d = a.interpolate(b, amount: position) - let e = b.interpolate(c, amount: position) - let f = d.interpolate(e, amount: position) - return (start: CurveVertex(point: point, inTangent: inTangent, outTangent: a), - trimPoint: CurveVertex(point: f, inTangent: d, outTangent: e), - end: CurveVertex(point: toVertex.point, inTangent: c, outTangent: toVertex.outTangent)) - } - - /** - Trims a curve of a known length to a specific length and returns the points. - - There is not a performant yet accurate way to cut a curve to a specific length. - This calls splitCurve(toVertex: position:) to split the curve and then measures - the length of the new curve. The function then iterates through the samples, - adjusting the position of the cut for a more precise cut. - Usually a single iteration is enough to get within 0.5 points of the desired - length. - - This function should probably live in PathElement, since it deals with curve - lengths. - */ - func trimCurve(toVertex: CurveVertex, atLength: CGFloat, curveLength: CGFloat, maxSamples: Int, accuracy: CGFloat = 1) -> - (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) { - var currentPosition = atLength / curveLength - var results = splitCurve(toVertex: toVertex, position: currentPosition) - - if maxSamples == 0 { - return results - } - - for _ in 1...maxSamples { - let length = results.start.distanceTo(results.trimPoint) - let lengthDiff = atLength - length - /// Check if length is correct. - if lengthDiff < accuracy { - return results - } - let diffPosition = max(min(((currentPosition / length) * lengthDiff), currentPosition * 0.5), currentPosition * -0.5) - currentPosition = diffPosition + currentPosition - results = splitCurve(toVertex: toVertex, position: currentPosition) - } - return results - } - - - /** - The distance from the receiver to the provided vertex. - - For lines (zeroed tangents) the distance between the two points is measured. - For curves the curve is iterated over by sample count and the points are measured. - This is ~99% accurate at a sample count of 30 - */ - func distanceTo(_ toVertex: CurveVertex, sampleCount: Int = 25) -> CGFloat { - - if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { - /// Return a linear distance. - return point.distanceTo(toVertex.point) - } - - var distance: CGFloat = 0 - - var previousPoint = point - for i in 0.. PathElement { - return PathElement(length: vertex.distanceTo(toVertex), vertex: toVertex) - } - - /// Initializes a new path with length of 0 - init(vertex: CurveVertex) { - self.length = 0 - self.vertex = vertex - } - - /// Initializes a new path with length - fileprivate init(length: CGFloat, vertex: CurveVertex) { - self.length = length - self.vertex = vertex - } - - func updateVertex(newVertex: CurveVertex) -> PathElement { - return PathElement(length: length, vertex: newVertex) - } - - /// Splits an element span defined by the receiver and fromElement to a position 0-1 - func splitElementAtPosition(fromElement: PathElement, atLength: CGFloat) -> - (leftSpan: (start: PathElement, end: PathElement), rightSpan: (start: PathElement, end: PathElement)) { - /// Trim the span. Start and trim go into the first, trim and end go into second. - let trimResults = fromElement.vertex.trimCurve(toVertex: vertex, atLength: atLength, curveLength: length, maxSamples: 3) - - /// Create the elements for the break - let spanAStart = PathElement(length: fromElement.length, - vertex: CurveVertex(point: fromElement.vertex.point, - inTangent: fromElement.vertex.inTangent, - outTangent: trimResults.start.outTangent)) - /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. - let spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint) - - let spanBStart = PathElement(vertex: trimResults.trimPoint) - let spanBEnd = spanBStart.pathElementTo(trimResults.end) - return (leftSpan: (start: spanAStart, end: spanAEnd), - rightSpan: (start: spanBStart, end: spanBEnd)) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/VectorsExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/VectorsExtensions.swift deleted file mode 100644 index 5b0e23f66e..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/VectorsExtensions.swift +++ /dev/null @@ -1,218 +0,0 @@ -// -// Vector.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -/** - Single value container. Needed because lottie sometimes wraps a Double in an array. - */ -extension Vector1D: Codable { - - public init(from decoder: Decoder) throws { - /// Try to decode an array of doubles - do { - var container = try decoder.unkeyedContainer() - self.value = try container.decode(Double.self) - } catch { - self.value = try decoder.singleValueContainer().decode(Double.self) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(value) - } - - var cgFloatValue: CGFloat { - return CGFloat(value) - } - -} - -extension Double { - var vectorValue: Vector1D { - return Vector1D(self) - } -} - -/** - Needed for decoding json {x: y:} to a CGPoint - */ -struct Vector2D: Codable { - - var x: Double - var y: Double - - init(x: Double, y: Double) { - self.x = x - self.y = y - } - - private enum CodingKeys : String, CodingKey { - case x = "x" - case y = "y" - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Vector2D.CodingKeys.self) - - do { - let xValue: [Double] = try container.decode([Double].self, forKey: .x) - self.x = xValue[0] - } catch { - self.x = try container.decode(Double.self, forKey: .x) - } - - do { - let yValue: [Double] = try container.decode([Double].self, forKey: .y) - self.y = yValue[0] - } catch { - self.y = try container.decode(Double.self, forKey: .y) - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: Vector2D.CodingKeys.self) - try container.encode(x, forKey: .x) - try container.encode(y, forKey: .y) - } - - var pointValue: CGPoint { - return CGPoint(x: x, y: y) - } -} - -extension Vector2D { - -} - -extension CGPoint { - var vector2dValue: Vector2D { - return Vector2D(x: Double(x), y: Double(y)) - } -} - -/** - A three dimensional vector. - These vectors are encoded and decoded from [Double] - */ - -extension Vector3D: Codable { - - init(x: CGFloat, y: CGFloat, z: CGFloat) { - self.x = Double(x) - self.y = Double(y) - self.z = Double(z) - } - - public init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - - if !container.isAtEnd { - self.x = try container.decode(Double.self) - } else { - self.x = 0 - } - - if !container.isAtEnd { - self.y = try container.decode(Double.self) - } else { - self.y = 0 - } - - if !container.isAtEnd { - self.z = try container.decode(Double.self) - } else { - self.z = 0 - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - try container.encode(x) - try container.encode(y) - try container.encode(z) - } - -} - -public extension Vector3D { - var pointValue: CGPoint { - return CGPoint(x: x, y: y) - } - - var sizeValue: CGSize { - return CGSize(width: x, height: y) - } -} - -extension CGPoint { - var vector3dValue: Vector3D { - return Vector3D(x: x, y: y, z: 0) - } -} - -extension CGSize { - var vector3dValue: Vector3D { - return Vector3D(x: width, y: height, z: 1) - } -} - -extension CATransform3D { - - func rotated(_ degrees: CGFloat) -> CATransform3D { - return CATransform3DRotate(self, degrees.toRadians(), 0, 0, 1) - } - - func translated(_ translation: CGPoint) -> CATransform3D { - return CATransform3DTranslate(self, translation.x, translation.y, 0) - } - - func scaled(_ scale: CGSize) -> CATransform3D { - return CATransform3DScale(self, scale.width, scale.height, 1) - } - - func skewed(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { - return CATransform3DConcat(CATransform3D.makeSkew(skew: skew, skewAxis: skewAxis), self) - } - - static func makeSkew(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { - let mCos = cos(skewAxis.toRadians()) - let mSin = sin(skewAxis.toRadians()) - let aTan = tan(skew.toRadians()) - - let transform1 = CATransform3D(m11: mCos, m12: mSin, m13: 0, m14: 0, - m21: -mSin, m22: mCos, m23: 0, m24: 0, - m31: 0, m32: 0, m33: 1, m34: 0, - m41: 0, m42: 0, m43: 0, m44: 1) - - let transform2 = CATransform3D(m11: 1, m12: 0, m13: 0, m14: 0, - m21: aTan, m22: 1, m23: 0, m24: 0, - m31: 0, m32: 0, m33: 1, m34: 0, - m41: 0, m42: 0, m43: 0, m44: 1) - - let transform3 = CATransform3D(m11: mCos, m12: -mSin, m13: 0, m14: 0, - m21: mSin, m22: mCos, m23: 0, m24: 0, - m31: 0, m32: 0, m33: 1, m34: 0, - m41: 0, m42: 0, m43: 0, m44: 1) - return CATransform3DConcat(transform3, CATransform3DConcat(transform2, transform1)) - } - - static func makeTransform(anchor: CGPoint, - position: CGPoint, - scale: CGSize, - rotation: CGFloat, - skew: CGFloat?, - skewAxis: CGFloat?) -> CATransform3D { - if let skew = skew, skew != 0.0, let skewAxis = skewAxis { - return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).skewed(skew: -skew, skewAxis: skewAxis).scaled(scale * 0.01).translated(anchor * -1) - } - return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).scaled(scale * 0.01).translated(anchor * -1) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.swift deleted file mode 100644 index e9ece1b4da..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// AnimationKeypath.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation - -/** - `AnimationKeypath` is an object that describes a keypath search for nodes in the - animation JSON. `AnimationKeypath` matches views and properties inside of `AnimationView` - to their backing `Animation` model by name. - - A keypath can be used to set properties on an existing animation, or can be validated - with an existing `Animation`. - - `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching - of objects. Acceptable wildcards are either "*" (star) or "**" (double star). - Single star will search a single depth for the next object. - Double star will search any depth. - - Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties - - EG: - @"Layer.Shape Group.Stroke 1.Color" - Represents a specific color node on a specific stroke. - - @"**.Stroke 1.Color" - Represents the color node for every Stroke named "Stroke 1" in the animation. - */ -public struct AnimationKeypath { - - /// Creates a keypath from a dot separated string. The string is separated by "." - public init(keypath: String) { - self.keys = keypath.components(separatedBy: ".") - } - - /// Creates a keypath from a list of strings. - public init(keys: [String]) { - self.keys = keys - } - - let keys: [String] - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.swift deleted file mode 100644 index 1684cd90ab..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// AnyValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/** - `AnyValueProvider` is a protocol that return animation data for a property at a - given time. Every fame an `AnimationView` queries all of its properties and asks - if their ValueProvider has an update. If it does the AnimationView will read the - property and update that portion of the animation. - - Value Providers can be used to dynamically set animation properties at run time. - */ -public protocol AnyValueProvider { - - /// The Type of the value provider - var valueType: Any.Type { get } - - /// Asks the provider if it has an update for the given frame. - func hasUpdate(frame: AnimationFrameTime) -> Bool - - /// Asks the provider to update the container with its value for the frame. - func value(frame: AnimationFrameTime) -> Any -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift deleted file mode 100644 index 4d594d2f0a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// ColorValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a CGColor Value -public final class ColorValueProvider: AnyValueProvider { - - /// Returns a Color for a CGColor(Frame Time) - public typealias ColorValueBlock = (CGFloat) -> Color - - /// The color value of the provider. - public var color: Color { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping ColorValueBlock) { - self.block = block - self.color = Color(r: 0, g: 0, b: 0, a: 1) - } - - /// Initializes with a single color. - public init(_ color: Color) { - self.color = color - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Color.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newColor: Color - if let block = block { - newColor = block(frame) - } else { - newColor = color - } - return newColor - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: ColorValueBlock? -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift deleted file mode 100644 index ecd43e12df..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// DoubleValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a CGFloat Value -public final class FloatValueProvider: AnyValueProvider { - - /// Returns a CGFloat for a CGFloat(Frame Time) - public typealias CGFloatValueBlock = (CGFloat) -> CGFloat - - public var float: CGFloat { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping CGFloatValueBlock) { - self.block = block - self.float = 0 - } - - /// Initializes with a single float. - public init(_ float: CGFloat) { - self.float = float - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Vector1D.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newCGFloat: CGFloat - if let block = block { - newCGFloat = block(frame) - } else { - newCGFloat = float - } - return Vector1D(Double(newCGFloat)) - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: CGFloatValueBlock? -} - diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift deleted file mode 100644 index d19bfc46a4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// GradientValueProvider.swift -// lottie-swift -// -// Created by Enrique Bermúdez on 10/27/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a Gradient Color Value. -public final class GradientValueProvider: AnyValueProvider { - - /// Returns a [Color] for a CGFloat(Frame Time). - public typealias ColorsValueBlock = (CGFloat) -> [Color] - /// Returns a [Double](Color locations) for a CGFloat(Frame Time). - public typealias ColorLocationsBlock = (CGFloat) -> [Double] - - /// The colors values of the provider. - public var colors: [Color] { - didSet { - updateValueArray() - hasUpdate = true - } - } - - /// The color location values of the provider. - public var locations: [Double] { - didSet { - updateValueArray() - hasUpdate = true - } - } - - /// Initializes with a block provider. - public init(block: @escaping ColorsValueBlock, - locations: ColorLocationsBlock? = nil) { - self.block = block - self.locationsBlock = locations - self.colors = [] - self.locations = [] - } - - /// Initializes with an array of colors. - public init(_ colors: [Color], - locations: [Double] = []) { - self.colors = colors - self.locations = locations - updateValueArray() - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return [Double].self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil || locationsBlock != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - - if let block = block { - let newColors = block(frame) - let newLocations = locationsBlock?(frame) ?? [] - value = value(from: newColors, locations: newLocations) - } - - return value - } - - // MARK: Private - - private func value(from colors: [Color], locations: [Double]) -> [Double] { - - var colorValues = [Double]() - var alphaValues = [Double]() - var shouldAddAlphaValues = false - - for i in 0.. i ? locations[i] : - (Double(i) / Double(colors.count - 1)) - - colorValues.append(location) - colorValues.append(colors[i].r) - colorValues.append(colors[i].g) - colorValues.append(colors[i].b) - - alphaValues.append(location) - alphaValues.append(colors[i].a) - } - - return colorValues + (shouldAddAlphaValues ? alphaValues : []) - } - - private func updateValueArray() { - value = value(from: colors, locations: locations) - } - - private var hasUpdate: Bool = true - - private var block: ColorsValueBlock? - private var locationsBlock: ColorLocationsBlock? - private var value: [Double] = [] -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/PointValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/PointValueProvider.swift deleted file mode 100644 index 83579fba0c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// PointValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics -/// A `ValueProvider` that returns a CGPoint Value -public final class PointValueProvider: AnyValueProvider { - - /// Returns a CGPoint for a CGFloat(Frame Time) - public typealias PointValueBlock = (CGFloat) -> CGPoint - - public var point: CGPoint { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping PointValueBlock) { - self.block = block - self.point = .zero - } - - /// Initializes with a single point. - public init(_ point: CGPoint) { - self.point = point - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Vector3D.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newPoint: CGPoint - if let block = block { - newPoint = block(frame) - } else { - newPoint = point - } - return newPoint.vector3dValue - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: PointValueBlock? -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift deleted file mode 100644 index 4e893202bd..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// SizeValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a CGSize Value -public final class SizeValueProvider: AnyValueProvider { - - /// Returns a CGSize for a CGFloat(Frame Time) - public typealias SizeValueBlock = (CGFloat) -> CGSize - - public var size: CGSize { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping SizeValueBlock) { - self.block = block - self.size = .zero - } - - /// Initializes with a single size. - public init(_ size: CGSize) { - self.size = size - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Vector3D.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newSize: CGSize - if let block = block { - newSize = block(frame) - } else { - newSize = size - } - return newSize.vector3dValue - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: SizeValueBlock? -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/AnimationTime.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/AnimationTime.swift deleted file mode 100644 index b765c50ab8..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/AnimationTime.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// AnimationTime.swift -// lottie-swift-iOS -// -// Created by Brandon Withrow on 2/6/19. -// - -import Foundation -import CoreGraphics - -/// Defines animation time in Frames (Seconds * Framerate). -public typealias AnimationFrameTime = CGFloat - -/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) -public typealias AnimationProgressTime = CGFloat diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Color.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Color.swift deleted file mode 100644 index 4550f4cef4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Color.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Color.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation - -public enum ColorFormatDenominator { - case One - case OneHundred - case TwoFiftyFive - - var value: Double { - switch self { - case .One: - return 1.0 - case .OneHundred: - return 100.0 - case .TwoFiftyFive: - return 255.0 - } - } -} - -public struct Color { - - public var r: Double - public var g: Double - public var b: Double - public var a: Double - - public init(r: Double, g: Double, b: Double, a: Double, denominator: ColorFormatDenominator = .One) { - self.r = r / denominator.value - self.g = g / denominator.value - self.b = b / denominator.value - self.a = a / denominator.value - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Vectors.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Vectors.swift deleted file mode 100644 index bdf9381e56..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Vectors.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Vectors.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation - -public struct Vector1D { - - public init(_ value: Double) { - self.value = value - } - - public let value: Double - -} - - -/** - A three dimensional vector. - These vectors are encoded and decoded from [Double] - */ -public struct Vector3D { - - public let x: Double - public let y: Double - public let z: Double - - public init(x: Double, y: Double, z: Double) { - self.x = x - self.y = y - self.z = z - } - -} diff --git a/submodules/LottieMeshSwift/Sources/MeshAnimation.swift b/submodules/LottieMeshSwift/Sources/MeshAnimation.swift deleted file mode 100644 index d1236bdf23..0000000000 --- a/submodules/LottieMeshSwift/Sources/MeshAnimation.swift +++ /dev/null @@ -1,693 +0,0 @@ -import Foundation -import Metal -import MetalKit -import LottieMeshBinding -import Postbox -import ManagedFile - -enum TriangleFill { - struct Color { - var r: Float - var g: Float - var b: Float - var a: Float - } - - struct Gradient { - var colors: [Color] - var colorLocations: [Float] - var start: CGPoint - var end: CGPoint - var isRadial: Bool - - static func read(buffer: MeshReadBuffer) -> Gradient { - var colors: [Color] = [] - var colorLocations: [Float] = [] - - let numColors = buffer.readInt8() - for _ in 0 ..< numColors { - colors.append(Color(argb: UInt32(bitPattern: buffer.readInt32()))) - } - for _ in 0 ..< numColors { - colorLocations.append(buffer.readFloat()) - } - - return Gradient( - colors: colors, - colorLocations: colorLocations, - start: CGPoint(x: CGFloat(buffer.readFloat()), y: CGFloat(buffer.readFloat())), - end: CGPoint(x: CGFloat(buffer.readFloat()), y: CGFloat(buffer.readFloat())), - isRadial: buffer.readInt8() != 0 - ) - } - - func write(buffer: MeshWriteBuffer) { - buffer.writeInt8(Int8(self.colors.count)) - for color in self.colors { - buffer.writeInt32(Int32(bitPattern: color.argb)) - } - for location in self.colorLocations { - buffer.writeFloat(location) - } - buffer.writeFloat(Float(self.start.x)) - buffer.writeFloat(Float(self.start.y)) - buffer.writeFloat(Float(self.end.x)) - buffer.writeFloat(Float(self.end.y)) - buffer.writeInt8(self.isRadial ? 1 : 0) - } - } - - case color(Color) - case gradient(Gradient) - - static func read(buffer: MeshReadBuffer) -> TriangleFill { - let key = buffer.readInt8() - if key == 0 { - return .color(Color(argb: UInt32(bitPattern: buffer.readInt32()))) - } else { - return .gradient(Gradient.read(buffer: buffer)) - } - } - - func write(buffer: MeshWriteBuffer) { - switch self { - case let .color(color): - buffer.writeInt8(0) - buffer.writeInt32(Int32(bitPattern: color.argb)) - case let .gradient(gradient): - buffer.writeInt8(1) - gradient.write(buffer: buffer) - } - } -} - -extension TriangleFill.Color { - init(_ color: UIColor) { - var r: CGFloat = 0.0 - var g: CGFloat = 0.0 - var b: CGFloat = 0.0 - var a: CGFloat = 0.0 - color.getRed(&r, green: &g, blue: &b, alpha: &a) - self.init(r: Float(r), g: Float(g), b: Float(b), a: Float(a)) - } - - init(argb: UInt32) { - self.init(r: Float((argb >> 16) & 0xff) / 255.0, g: Float((argb >> 8) & 0xff) / 255.0, b: Float(argb & 0xff) / 255.0, a: Float((argb >> 24) & 0xff) / 255.0) - } - - var argb: UInt32 { - return (UInt32(self.a * 255.0) << 24) | (UInt32(max(0.0, self.r) * 255.0) << 16) | (UInt32(max(0.0, self.g) * 255.0) << 8) | (UInt32(max(0.0, self.b) * 255.0)) - } - - func multiplied(alpha: Float) -> TriangleFill.Color { - var color = self - color.a *= alpha - return color - } -} - -enum MeshOption { - case fill(rule: CGPathFillRule) - case stroke(lineWidth: CGFloat, miterLimit: CGFloat, lineJoin: CGLineJoin, lineCap: CGLineCap) -} - -final class DataRange { - let data: Data - let range: Range - - init(data: Data, range: Range) { - self.data = data - self.range = range - } - - var count: Int { - return self.range.upperBound - self.range.lowerBound - } -} - -public final class MeshAnimation { - final class Frame { - final class Segment { - let vertices: DataRange - let triangles: DataRange - let fill: TriangleFill - let transform: CGAffineTransform - - init(vertices: DataRange, triangles: DataRange, fill: TriangleFill, transform: CGAffineTransform) { - self.vertices = vertices - self.triangles = triangles - self.fill = fill - self.transform = transform - } - - static func read(buffer: MeshReadBuffer) -> Segment { - let vertCount = Int(buffer.readInt32()) - let vertices = buffer.readDataRange(count: vertCount) - - let triCount = Int(buffer.readInt32()) - let triangles = buffer.readDataRange(count: triCount) - - return Segment(vertices: vertices, triangles: triangles, fill: TriangleFill.read(buffer: buffer), transform: CGAffineTransform(a: CGFloat(buffer.readFloat()), b: CGFloat(buffer.readFloat()), c: CGFloat(buffer.readFloat()), d: CGFloat(buffer.readFloat()), tx: CGFloat(buffer.readFloat()), ty: CGFloat(buffer.readFloat()))) - } - - func write(buffer: MeshWriteBuffer) { - buffer.writeInt32(Int32(self.vertices.count)) - buffer.write(self.vertices) - buffer.writeInt32(Int32(self.triangles.count)) - buffer.write(self.triangles) - - self.fill.write(buffer: buffer) - buffer.writeFloat(Float(self.transform.a)) - buffer.writeFloat(Float(self.transform.b)) - buffer.writeFloat(Float(self.transform.c)) - buffer.writeFloat(Float(self.transform.d)) - buffer.writeFloat(Float(self.transform.tx)) - buffer.writeFloat(Float(self.transform.ty)) - } - } - - let segments: [Segment] - - init(segments: [Segment]) { - self.segments = segments - } - - static func read(buffer: MeshReadBuffer) -> Frame { - var segments: [Segment] = [] - let count = buffer.readInt32() - for _ in 0 ..< count { - segments.append(Segment.read(buffer: buffer)) - } - return Frame(segments: segments) - } - - /*func write(buffer: MeshWriteBuffer) { - buffer.writeInt32(Int32(self.segments.count)) - for segment in self.segments { - segment.write(buffer: buffer) - } - }*/ - } - - let frames: [Frame] - - init(frames: [Frame]) { - self.frames = frames - } - - public static func read(buffer: MeshReadBuffer) -> MeshAnimation { - var frames: [Frame] = [] - let count = buffer.readInt32() - for _ in 0 ..< count { - frames.append(Frame.read(buffer: buffer)) - } - return MeshAnimation(frames: frames) - } - - /*public func write(buffer: MeshWriteBuffer) { - buffer.writeInt32(Int32(self.frames.count)) - for frame in self.frames { - frame.write(buffer: buffer) - } - }*/ -} - -@available(iOS 13.0, *) -public final class MeshRenderer: MTKView { - private final class RenderingMesh { - let mesh: MeshAnimation - let offset: CGPoint - let loop: Bool - var currentFrame: Int = 0 - let vertexBuffer: MTLBuffer - let indexBuffer: MTLBuffer - let transformBuffer: MTLBuffer - let maxVertices: Int - let maxTriangles: Int - - init(device: MTLDevice, mesh: MeshAnimation, offset: CGPoint, loop: Bool) { - self.mesh = mesh - self.offset = offset - self.loop = loop - - var maxTriangles = 0 - var maxVertices = 0 - for i in 0 ..< mesh.frames.count { - var frameTriangles = 0 - var frameVertices = 0 - for segment in mesh.frames[i].segments { - frameTriangles += segment.triangles.count / (4 * 3) - frameVertices += segment.vertices.count / (4 * 2) - } - maxTriangles = max(maxTriangles, frameTriangles) - maxVertices = max(maxVertices, frameVertices) - } - - self.maxVertices = maxVertices - self.maxTriangles = maxTriangles - - let vertexBufferArray = Array(repeating: 0.0, count: self.maxVertices * 2) - guard let vertexBuffer = device.makeBuffer(bytes: vertexBufferArray, length: vertexBufferArray.count * MemoryLayout.size(ofValue: vertexBufferArray[0]), options: [.cpuCacheModeWriteCombined]) else { - preconditionFailure() - } - self.vertexBuffer = vertexBuffer - - let indexBufferArray = Array(repeating: 0, count: self.maxTriangles * 3) - guard let indexBuffer = device.makeBuffer(bytes: indexBufferArray, length: indexBufferArray.count * MemoryLayout.size(ofValue: indexBufferArray[0]), options: [.cpuCacheModeWriteCombined]) else { - preconditionFailure() - } - self.indexBuffer = indexBuffer - - let transformBufferArray = Array(repeating: 0.0, count: 2) - guard let transformBuffer = device.makeBuffer(bytes: transformBufferArray, length: transformBufferArray.count * MemoryLayout.size(ofValue: transformBufferArray[0]), options: [.cpuCacheModeWriteCombined]) else { - preconditionFailure() - } - self.transformBuffer = transformBuffer - } - } - - private let wireframe: Bool - private let commandQueue: MTLCommandQueue - private let drawPassthroughPipelineState: MTLRenderPipelineState - private let drawRadialGradientPipelineStates: [Int: MTLRenderPipelineState] - - private var displayLink: CADisplayLink? - - private var metalLayer: CAMetalLayer { - return self.layer as! CAMetalLayer - } - - private var meshes: [RenderingMesh] = [] - public var animationCount: Int { - return self.meshes.count - } - - public var allAnimationsCompleted: (() -> Void)? - - public init?(wireframe: Bool = false) { - self.wireframe = wireframe - - let mainBundle = Bundle(for: MeshRenderer.self) - - guard let path = mainBundle.path(forResource: "LottieMeshSwiftBundle", ofType: "bundle") else { - return nil - } - guard let bundle = Bundle(path: path) else { - return nil - } - - guard let device = MTLCreateSystemDefaultDevice() else { - return nil - } - - guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: bundle) else { - return nil - } - - guard let commandQueue = device.makeCommandQueue() else { - return nil - } - self.commandQueue = commandQueue - - guard let loadedVertexProgram = defaultLibrary.makeFunction(name: "vertexPassthrough") else { - return nil - } - - func makeDescriptor(fragmentProgram: String) -> MTLRenderPipelineDescriptor { - guard let loadedFragmentProgram = defaultLibrary.makeFunction(name: fragmentProgram) else { - preconditionFailure() - } - - let pipelineStateDescriptor = MTLRenderPipelineDescriptor() - pipelineStateDescriptor.vertexFunction = loadedVertexProgram - pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram - pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm - pipelineStateDescriptor.colorAttachments[0].isBlendingEnabled = true - pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha - pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha - pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha - pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha - pipelineStateDescriptor.sampleCount = 4 - return pipelineStateDescriptor - } - - self.drawPassthroughPipelineState = try! device.makeRenderPipelineState(descriptor: makeDescriptor(fragmentProgram: "fragmentPassthrough")) - var drawRadialGradientPipelineStates: [Int: MTLRenderPipelineState] = [:] - - for i in 2 ... 10 { - drawRadialGradientPipelineStates[i] = try! device.makeRenderPipelineState(descriptor: makeDescriptor(fragmentProgram: "fragmentRadialGradient\(i)")) - } - - self.drawRadialGradientPipelineStates = drawRadialGradientPipelineStates - - super.init(frame: CGRect(), device: device) - - self.sampleCount = 4 - - self.isOpaque = false - self.backgroundColor = .clear - - //self.metalLayer.device = self.device - //self.pixelFormat = .bgra8Unorm - self.framebufferOnly = true - //self.metalLayer.framebufferOnly = true - //if #available(iOS 11.0, *) { - self.metalLayer.allowsNextDrawableTimeout = true - //} - //self.metalLayer.contentsScale = 2.0 - - class DisplayLinkProxy: NSObject { - weak var target: MeshRenderer? - init(target: MeshRenderer) { - self.target = target - } - - @objc func displayLinkEvent() { - self.target?.displayLinkEvent() - } - } - - self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) - if #available(iOS 15.0, *) { - self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 60.0, preferred: 60.0) - //self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 10.0, maximum: 60.0, preferred: 10.0) - } - self.displayLink?.add(to: .main, forMode: .common) - self.displayLink?.isPaused = false - - self.isPaused = true - } - - required public init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.displayLink?.invalidate() - } - - public func add(mesh: MeshAnimation, offset: CGPoint, loop: Bool = false) { - self.meshes.append(RenderingMesh(device: self.device!, mesh: mesh, offset: offset, loop: loop)) - } - - @objc private func displayLinkEvent() { - self.draw() - } - - override public func draw(_ rect: CGRect) { - self.redraw(drawable: self.currentDrawable!) - } - - private func redraw(drawable: MTLDrawable) { - guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { - return - } - - var removeMeshes: [Int] = [] - - let renderPassDescriptor = self.currentRenderPassDescriptor! - renderPassDescriptor.colorAttachments[0].loadAction = .clear - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) - - guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { - return - } - - renderEncoder.setCullMode(.none) - if self.wireframe { - renderEncoder.setTriangleFillMode(.lines) - } - - func addTriangle(vertexData: UnsafeMutablePointer, maxVertices: Int, nextVertexIndex: inout Int, vertices: Data, triangles: Data, triangleIndex: Int) { - assert(nextVertexIndex + 3 <= maxVertices) - vertices.withUnsafeBytes { vertices in - let verticesPointer = vertices.baseAddress!.assumingMemoryBound(to: Float.self) - - triangles.withUnsafeBytes { triangles in - let trianglesPointer = triangles.baseAddress!.assumingMemoryBound(to: Int32.self) - - for i in 0 ..< 3 { - let vertexBase = vertexData.advanced(by: nextVertexIndex * 2) - - let vertexIndex = Int(trianglesPointer.advanced(by: triangleIndex * 3 + i).pointee) - let vertex = verticesPointer.advanced(by: vertexIndex * 2) - - vertexBase.advanced(by: 0).pointee = vertex.advanced(by: 0).pointee - vertexBase.advanced(by: 1).pointee = vertex.advanced(by: 1).pointee - - nextVertexIndex += 1 - } - } - } - } - - for i in 0 ..< self.meshes.count { - let mesh = self.meshes[i] - - let transformData = mesh.transformBuffer.contents().assumingMemoryBound(to: Float.self) - transformData.advanced(by: 0).pointee = Float(mesh.offset.x) - transformData.advanced(by: 1).pointee = Float(mesh.offset.y) - - renderEncoder.setVertexBuffer(mesh.transformBuffer, offset: 0, index: 1) - - var colorBytes: [Float] = [1.0, 0.0, 1.0, 1.0] - - var segmentVertexData: [Int: (vStart: Int, vCount: Int, iStart: Int, iCount: Int)] = [:] - - let vertexData = mesh.vertexBuffer.contents().assumingMemoryBound(to: Float.self) - let indexData = mesh.indexBuffer.contents().assumingMemoryBound(to: Int32.self) - var nextVertexIndex = 0 - var nextIndexIndex = 0 - - for i in 0 ..< mesh.mesh.frames[mesh.currentFrame].segments.count { - let segment = mesh.mesh.frames[mesh.currentFrame].segments[i] - let startVertexIndex = nextVertexIndex - let startIndexIndex = nextIndexIndex - - segment.vertices.data.withUnsafeBytes { vertices in - let _ = memcpy(vertexData.advanced(by: nextVertexIndex * 2), vertices.baseAddress!.advanced(by: segment.vertices.range.lowerBound), segment.vertices.count) - } - nextVertexIndex += segment.vertices.count / (4 * 2) - - let baseVertexIndex = Int32(startVertexIndex) - - segment.triangles.data.withUnsafeBytes { triangles in - let _ = memcpy(indexData.advanced(by: nextIndexIndex), triangles.baseAddress!.advanced(by: segment.triangles.range.lowerBound), segment.triangles.count) - } - nextIndexIndex += segment.triangles.count / 4 - - segmentVertexData[i] = (startVertexIndex, nextVertexIndex - startVertexIndex, startIndexIndex, nextIndexIndex - startIndexIndex) - - let (_, _, iStart, iCount) = segmentVertexData[i]! - - switch segment.fill { - case let .color(color): - renderEncoder.setRenderPipelineState(self.drawPassthroughPipelineState) - - colorBytes[0] = color.r - colorBytes[1] = color.g - colorBytes[2] = color.b - colorBytes[3] = color.a - - renderEncoder.setFragmentBytes(&colorBytes, length: 4 * 4, index: 1) - case let .gradient(gradient): - renderEncoder.setRenderPipelineState(self.drawRadialGradientPipelineStates[gradient.colors.count]!) - - var startBytes: [Float] = [ - Float(gradient.start.x), - Float(gradient.start.y) - ] - var endBytes: [Float] = [ - Float(gradient.end.x), - Float(gradient.end.y) - ] - renderEncoder.setFragmentBytes(&startBytes, length: startBytes.count * 4, index: 1) - renderEncoder.setFragmentBytes(&endBytes, length: endBytes.count * 4, index: 2) - - var colors: [Float] = [] - for color in gradient.colors { - colors.append(color.r) - colors.append(color.g) - colors.append(color.b) - colors.append(color.a) - } - renderEncoder.setFragmentBytes(&colors, length: colors.count * 4, index: 3) - - var steps: [Float] = gradient.colorLocations - renderEncoder.setFragmentBytes(&steps, length: colors.count * 4, index: 4) - } - - var transformBytes = Array(repeating: 0.0, count: 4 * 4) - let transform = CATransform3DMakeAffineTransform(segment.transform) - transformBytes[0] = Float(transform.m11) - transformBytes[1] = Float(transform.m12) - transformBytes[2] = Float(transform.m13) - transformBytes[3] = Float(transform.m14) - transformBytes[4] = Float(transform.m21) - transformBytes[5] = Float(transform.m22) - transformBytes[6] = Float(transform.m23) - transformBytes[7] = Float(transform.m24) - transformBytes[8] = Float(transform.m31) - transformBytes[9] = Float(transform.m32) - transformBytes[10] = Float(transform.m33) - transformBytes[11] = Float(transform.m34) - transformBytes[12] = Float(transform.m41) - transformBytes[13] = Float(transform.m42) - transformBytes[14] = Float(transform.m43) - transformBytes[15] = Float(transform.m44) - - renderEncoder.setVertexBytes(&transformBytes, length: transformBytes.count * 4, index: 2) - var baseVertexIndexBytes: Int32 = Int32(baseVertexIndex) - renderEncoder.setVertexBytes(&baseVertexIndexBytes, length: 4, index: 3) - - renderEncoder.setVertexBuffer(mesh.vertexBuffer, offset: 0, index: 0) - renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: iCount, indexType: .uint32, indexBuffer: mesh.indexBuffer, indexBufferOffset: iStart * 4) - } - - let nextFrame = mesh.currentFrame + 1 - if nextFrame >= mesh.mesh.frames.count { - if mesh.loop { - mesh.currentFrame = 0 - } else { - removeMeshes.append(i) - } - } else { - mesh.currentFrame = nextFrame - } - } - - renderEncoder.endEncoding() - - commandBuffer.present(drawable) - commandBuffer.commit() - - if !removeMeshes.isEmpty { - for i in removeMeshes.reversed() { - self.meshes.remove(at: i) - } - if self.meshes.isEmpty { - self.allAnimationsCompleted?() - } - } - } -} - -private func generateSegments(writeBuffer: MeshWriteBuffer, segmentCount: inout Int, geometry: CapturedGeometryNode, superAlpha: CGFloat, superTransform: CGAffineTransform) { - if geometry.isHidden || geometry.alpha.isZero { - return - } - - for i in 0 ..< geometry.subnodes.count { - generateSegments(writeBuffer: writeBuffer, segmentCount: &segmentCount, geometry: geometry.subnodes[i], superAlpha: superAlpha * geometry.alpha, superTransform: CATransform3DGetAffineTransform(geometry.transform).concatenating(superTransform)) - } - - if let displayItem = geometry.displayItem { - var meshData: LottieMeshData? - let triangleFill: TriangleFill - - switch displayItem.display { - case let .fill(fill): - switch fill.style { - case let .color(color, alpha): - triangleFill = .color(TriangleFill.Color(color).multiplied(alpha: Float(geometry.alpha * alpha * superAlpha))) - case let .gradient(colors, positions, start, end, type): - triangleFill = .gradient(TriangleFill.Gradient(colors: colors.map { TriangleFill.Color($0).multiplied(alpha: Float(geometry.alpha * superAlpha)) }, colorLocations: positions.map(Float.init), start: start, end: end, isRadial: type == .radial)) - } - - let mappedFillRule: LottieMeshFillRule - switch fill.fillRule { - case .evenOdd: - mappedFillRule = .evenOdd - case .winding: - mappedFillRule = .nonZero - default: - mappedFillRule = .evenOdd - } - - meshData = LottieMeshData.generate(with: UIBezierPath(cgPath: displayItem.path), fill: LottieMeshFill(fillRule: mappedFillRule), stroke: nil) - case let .stroke(stroke): - switch stroke.style { - case let .color(color, alpha): - triangleFill = .color(TriangleFill.Color(color).multiplied(alpha: Float(geometry.alpha * alpha * superAlpha))) - case let .gradient(colors, positions, start, end, type): - triangleFill = .gradient(TriangleFill.Gradient(colors: colors.map(TriangleFill.Color.init), colorLocations: positions.map(Float.init), start: start, end: end, isRadial: type == .radial)) - } - - meshData = LottieMeshData.generate(with: UIBezierPath(cgPath: displayItem.path), fill: nil, stroke: LottieMeshStroke(lineWidth: stroke.lineWidth, lineJoin: stroke.lineJoin, lineCap: stroke.lineCap, miterLimit: stroke.miterLimit)) - } - if let meshData = meshData, meshData.triangleCount() != 0 { - let mappedVertices = WriteBuffer() - for i in 0 ..< meshData.vertexCount() { - var x: Float = 0.0 - var y: Float = 0.0 - meshData.getVertexAt(i, x: &x, y: &y) - mappedVertices.writeFloat(x) - mappedVertices.writeFloat(y) - } - - let trianglesData = Data(bytes: meshData.getTriangles(), count: meshData.triangleCount() * 3 * 4) - - let verticesData = mappedVertices.makeData() - - let segment = MeshAnimation.Frame.Segment(vertices: DataRange(data: verticesData, range: 0 ..< verticesData.count), triangles: DataRange(data: trianglesData, range: 0 ..< trianglesData.count), fill: triangleFill, transform: CATransform3DGetAffineTransform(geometry.transform).concatenating(superTransform)) - - segment.write(buffer: writeBuffer) - segmentCount += 1 - } - } -} - -public func generateMeshAnimation(data: Data) -> TempBoxFile? { - guard let animation = try? JSONDecoder().decode(Animation.self, from: data) else { - return nil - } - let container = MyAnimationContainer(animation: animation) - - let tempFile = TempBox.shared.tempFile(fileName: "data") - guard let file = ManagedFile(queue: nil, path: tempFile.path, mode: .readwrite) else { - return nil - } - let writeBuffer = MeshWriteBuffer(file: file) - - let frameCountOffset = writeBuffer.offset - writeBuffer.writeInt32(0) - - var frameCount: Int = 0 - - for i in 0 ..< Int(animation.endFrame) { - container.setFrame(frame: CGFloat(i)) - //#if DEBUG - print("Frame \(i) / \(Int(animation.endFrame))") - //#endif - - let segmentCountOffset = writeBuffer.offset - writeBuffer.writeInt32(0) - var segmentCount: Int = 0 - - let geometry = container.captureGeometry() - geometry.transform = CATransform3DMakeTranslation(256.0, 256.0, 0.0) - - generateSegments(writeBuffer: writeBuffer, segmentCount: &segmentCount, geometry: geometry, superAlpha: 1.0, superTransform: .identity) - - let currentOffset = writeBuffer.offset - writeBuffer.seek(offset: segmentCountOffset) - writeBuffer.writeInt32(Int32(segmentCount)) - - writeBuffer.seek(offset: currentOffset) - - frameCount += 1 - } - - let currentOffset = writeBuffer.offset - writeBuffer.seek(offset: frameCountOffset) - writeBuffer.writeInt32(Int32(frameCount)) - writeBuffer.seek(offset: currentOffset) - - return tempFile -} - -public final class MeshRenderingContext { - -} diff --git a/submodules/LottieMeshSwift/Sources/Render.swift b/submodules/LottieMeshSwift/Sources/Render.swift deleted file mode 100644 index 78d5989904..0000000000 --- a/submodules/LottieMeshSwift/Sources/Render.swift +++ /dev/null @@ -1,138 +0,0 @@ -import Foundation -import UIKit - -func initializeCompositionLayers( - layers: [LayerModel], - assetLibrary: AssetLibrary?, - frameRate: CGFloat -) -> [MyCompositionLayer] { - var compositionLayers = [MyCompositionLayer]() - var layerMap = [Int : MyCompositionLayer]() - - /// Organize the assets into a dictionary of [ID : ImageAsset] - var childLayers = [LayerModel]() - - for layer in layers { - if layer.hidden == true { - let genericLayer = MyNullCompositionLayer(layer: layer) - compositionLayers.append(genericLayer) - layerMap[layer.index] = genericLayer - } else if let shapeLayer = layer as? ShapeLayerModel { - let shapeContainer = MyShapeCompositionLayer(shapeLayer: shapeLayer) - compositionLayers.append(shapeContainer) - layerMap[layer.index] = shapeContainer - } else if let solidLayer = layer as? SolidLayerModel { - let solidContainer = MySolidCompositionLayer(solid: solidLayer) - compositionLayers.append(solidContainer) - layerMap[layer.index] = solidContainer - } else if let precompLayer = layer as? PreCompLayerModel, - let assetLibrary = assetLibrary, - let precompAsset = assetLibrary.precompAssets[precompLayer.referenceID] { - let precompContainer = MyPreCompositionLayer(precomp: precompLayer, - asset: precompAsset, - assetLibrary: assetLibrary, - frameRate: frameRate) - compositionLayers.append(precompContainer) - layerMap[layer.index] = precompContainer - } else if let imageLayer = layer as? ImageLayerModel, - let assetLibrary = assetLibrary, - let imageAsset = assetLibrary.imageAssets[imageLayer.referenceID] { - let imageContainer = MyImageCompositionLayer(imageLayer: imageLayer, size: CGSize(width: imageAsset.width, height: imageAsset.height)) - compositionLayers.append(imageContainer) - layerMap[layer.index] = imageContainer - } else if let _ = layer as? TextLayerModel { - let genericLayer = MyNullCompositionLayer(layer: layer) - compositionLayers.append(genericLayer) - layerMap[layer.index] = genericLayer - /*let textContainer = TextCompositionLayer(textLayer: textLayer, textProvider: textProvider, fontProvider: fontProvider) - compositionLayers.append(textContainer) - layerMap[layer.index] = textContainer*/ - } else { - let genericLayer = MyNullCompositionLayer(layer: layer) - compositionLayers.append(genericLayer) - layerMap[layer.index] = genericLayer - } - if layer.parent != nil { - childLayers.append(layer) - } - } - - /// Now link children with their parents - for layerModel in childLayers { - if let parentID = layerModel.parent { - let childLayer = layerMap[layerModel.index] - let parentLayer = layerMap[parentID] - childLayer?.transformNode.parentNode = parentLayer?.transformNode - } - } - - return compositionLayers -} - -final class MyAnimationContainer { - let bounds: CGRect - - var currentFrame: CGFloat = 0.0 - - /// Forces the view to update its drawing. - func forceDisplayUpdate() { - animationLayers.forEach( { $0.displayWithFrame(frame: currentFrame, forceUpdates: true) }) - } - - var animationLayers: [MyCompositionLayer] - - init(animation: Animation) { - self.animationLayers = [] - - self.bounds = CGRect(origin: CGPoint(), size: CGSize(width: animation.width, height: animation.height)) - let layers = initializeCompositionLayers(layers: animation.layers, assetLibrary: animation.assetLibrary, frameRate: CGFloat(animation.framerate)) - - var imageLayers = [MyImageCompositionLayer]() - - var mattedLayer: MyCompositionLayer? = nil - - for layer in layers.reversed() { - layer.bounds = bounds - animationLayers.append(layer) - if let imageLayer = layer as? MyImageCompositionLayer { - imageLayers.append(imageLayer) - } - if let matte = mattedLayer { - /// The previous layer requires this layer to be its matte - matte.matteLayer = layer - mattedLayer = nil - continue - } - if let matte = layer.matteType, - (matte == .add || matte == .invert) { - /// We have a layer that requires a matte. - mattedLayer = layer - } - - //NOTE - //addSublayer(layer) - } - } - - func setFrame(frame: CGFloat) { - self.currentFrame = frame - for animationLayer in self.animationLayers { - animationLayer.displayWithFrame(frame: frame, forceUpdates: false) - } - } - - func captureGeometry() -> CapturedGeometryNode { - var subnodes: [CapturedGeometryNode] = [] - for animationLayer in self.animationLayers { - let capturedSubnode = animationLayer.captureGeometry() - subnodes.append(capturedSubnode) - } - return CapturedGeometryNode( - transform: CATransform3DIdentity, - alpha: 1.0, - isHidden: false, - displayItem: nil, - subnodes: subnodes - ) - } -} diff --git a/submodules/LottieMeshSwift/libtess2/Include/tesselator.h b/submodules/LottieMeshSwift/libtess2/Include/tesselator.h deleted file mode 100755 index 3d431559a1..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Include/tesselator.h +++ /dev/null @@ -1,242 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Mikko Mononen, July 2009. -*/ - -#ifndef TESSELATOR_H -#define TESSELATOR_H - -#ifdef __cplusplus -extern "C" { -#endif - -// See OpenGL Red Book for description of the winding rules -// http://www.glprogramming.com/red/chapter11.html -enum TessWindingRule -{ - TESS_WINDING_ODD, - TESS_WINDING_NONZERO, - TESS_WINDING_POSITIVE, - TESS_WINDING_NEGATIVE, - TESS_WINDING_ABS_GEQ_TWO, -}; - -// The contents of the tessGetElements() depends on element type being passed to tessTesselate(). -// Tesselation result element types: -// TESS_POLYGONS -// Each element in the element array is polygon defined as 'polySize' number of vertex indices. -// If a polygon has than 'polySize' vertices, the remaining indices are stored as TESS_UNDEF. -// Example, drawing a polygon: -// const int nelems = tessGetElementCount(tess); -// const TESSindex* elems = tessGetElements(tess); -// for (int i = 0; i < nelems; i++) { -// const TESSindex* poly = &elems[i * polySize]; -// glBegin(GL_POLYGON); -// for (int j = 0; j < polySize; j++) { -// if (poly[j] == TESS_UNDEF) break; -// glVertex2fv(&verts[poly[j]*vertexSize]); -// } -// glEnd(); -// } -// -// TESS_CONNECTED_POLYGONS -// Each element in the element array is polygon defined as 'polySize' number of vertex indices, -// followed by 'polySize' indices to neighour polygons, that is each element is 'polySize' * 2 indices. -// If a polygon has than 'polySize' vertices, the remaining indices are stored as TESS_UNDEF. -// If a polygon edge is a boundary, that is, not connected to another polygon, the neighbour index is TESS_UNDEF. -// Example, flood fill based on seed polygon: -// const int nelems = tessGetElementCount(tess); -// const TESSindex* elems = tessGetElements(tess); -// unsigned char* visited = (unsigned char*)calloc(nelems); -// TESSindex stack[50]; -// int nstack = 0; -// stack[nstack++] = seedPoly; -// visited[startPoly] = 1; -// while (nstack > 0) { -// TESSindex idx = stack[--nstack]; -// const TESSindex* poly = &elems[idx * polySize * 2]; -// const TESSindex* nei = &poly[polySize]; -// for (int i = 0; i < polySize; i++) { -// if (poly[i] == TESS_UNDEF) break; -// if (nei[i] != TESS_UNDEF && !visited[nei[i]]) -// stack[nstack++] = nei[i]; -// visited[nei[i]] = 1; -// } -// } -// } -// -// TESS_BOUNDARY_CONTOURS -// Each element in the element array is [base index, count] pair defining a range of vertices for a contour. -// The first value is index to first vertex in contour and the second value is number of vertices in the contour. -// Example, drawing contours: -// const int nelems = tessGetElementCount(tess); -// const TESSindex* elems = tessGetElements(tess); -// for (int i = 0; i < nelems; i++) { -// const TESSindex base = elems[i * 2]; -// const TESSindex count = elems[i * 2 + 1]; -// glBegin(GL_LINE_LOOP); -// for (int j = 0; j < count; j++) { -// glVertex2fv(&verts[(base+j) * vertexSize]); -// } -// glEnd(); -// } - -enum TessElementType -{ - TESS_POLYGONS, - TESS_CONNECTED_POLYGONS, - TESS_BOUNDARY_CONTOURS, -}; - - -// TESS_CONSTRAINED_DELAUNAY_TRIANGULATION -// If enabled, the initial triagulation is improved with non-robust Constrained Delayney triangulation. -// Disable by default. -// -// TESS_REVERSE_CONTOURS -// If enabled, tessAddContour() will treat CW contours as CCW and vice versa -// Disabled by default. - -enum TessOption -{ - TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, - TESS_REVERSE_CONTOURS -}; - -typedef float TESSreal; -typedef int TESSindex; -typedef struct TESStesselator TESStesselator; -typedef struct TESSalloc TESSalloc; - -#define TESS_UNDEF (~(TESSindex)0) - -#define TESS_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) - -// Custom memory allocator interface. -// The internal memory allocator allocates mesh edges, vertices and faces -// as well as dictionary nodes and active regions in buckets and uses simple -// freelist to speed up the allocation. The bucket size should roughly match your -// expected input data. For example if you process only hundreds of vertices, -// a bucket size of 128 might be ok, where as when processing thousands of vertices -// bucket size of 1024 might be approproate. The bucket size is a compromise between -// how often to allocate memory from the system versus how much extra space the system -// should allocate. Reasonable defaults are show in commects below, they will be used if -// the bucket sizes are zero. -// -// The use may left the memrealloc to be null. In that case, the tesselator will not try to -// dynamically grow int's internal arrays. The tesselator only needs the reallocation when it -// has found intersecting segments and needs to add new vertex. This defency can be cured by -// allocating some extra vertices beforehand. The 'extraVertices' variable allows to specify -// number of expected extra vertices. -struct TESSalloc -{ - void *(*memalloc)( void *userData, unsigned int size ); - void *(*memrealloc)( void *userData, void* ptr, unsigned int size ); - void (*memfree)( void *userData, void *ptr ); - void* userData; // User data passed to the allocator functions. - int meshEdgeBucketSize; // 512 - int meshVertexBucketSize; // 512 - int meshFaceBucketSize; // 256 - int dictNodeBucketSize; // 512 - int regionBucketSize; // 256 - int extraVertices; // Number of extra vertices allocated for the priority queue. -}; - - -// -// Example use: -// -// -// -// - -// tessNewTess() - Creates a new tesselator. -// Use tessDeleteTess() to delete the tesselator. -// Parameters: -// alloc - pointer to a filled TESSalloc struct or NULL to use default malloc based allocator. -// Returns: -// new tesselator object. -TESStesselator* tessNewTess( TESSalloc* alloc ); - -// tessDeleteTess() - Deletes a tesselator. -// Parameters: -// tess - pointer to tesselator object to be deleted. -void tessDeleteTess( TESStesselator *tess ); - -// tessAddContour() - Adds a contour to be tesselated. -// The type of the vertex coordinates is assumed to be TESSreal. -// Parameters: -// tess - pointer to tesselator object. -// size - number of coordinates per vertex. Must be 2 or 3. -// pointer - pointer to the first coordinate of the first vertex in the array. -// stride - defines offset in bytes between consecutive vertices. -// count - number of vertices in contour. -void tessAddContour( TESStesselator *tess, int size, const void* pointer, int stride, int count ); - -// tessSetOption() - Toggles optional tessellation parameters -// Parameters: -// option - one of TessOption -// value - 1 if enabled, 0 if disabled. -void tessSetOption( TESStesselator *tess, int option, int value ); - -// tessTesselate() - tesselate contours. -// Parameters: -// tess - pointer to tesselator object. -// windingRule - winding rules used for tesselation, must be one of TessWindingRule. -// elementType - defines the tesselation result element type, must be one of TessElementType. -// polySize - defines maximum vertices per polygons if output is polygons. -// vertexSize - defines the number of coordinates in tesselation result vertex, must be 2 or 3. -// normal - defines the normal of the input contours, of null the normal is calculated automatically. -// Returns: -// 1 if succeed, 0 if failed. -int tessTesselate( TESStesselator *tess, int windingRule, int elementType, int polySize, int vertexSize, const TESSreal* normal ); - -// tessGetVertexCount() - Returns number of vertices in the tesselated output. -int tessGetVertexCount( TESStesselator *tess ); - -// tessGetVertices() - Returns pointer to first coordinate of first vertex. -const TESSreal* tessGetVertices( TESStesselator *tess ); - -// tessGetVertexIndices() - Returns pointer to first vertex index. -// Vertex indices can be used to map the generated vertices to the original vertices. -// Every point added using tessAddContour() will get a new index starting at 0. -// New vertices generated at the intersections of segments are assigned value TESS_UNDEF. -const TESSindex* tessGetVertexIndices( TESStesselator *tess ); - -// tessGetElementCount() - Returns number of elements in the the tesselated output. -int tessGetElementCount( TESStesselator *tess ); - -// tessGetElements() - Returns pointer to the first element. -const TESSindex* tessGetElements( TESStesselator *tess ); - -#ifdef __cplusplus -}; -#endif - -#endif // TESSELATOR_H diff --git a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.c b/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.c deleted file mode 100755 index 420ebab5ea..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.c +++ /dev/null @@ -1,191 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Mikko Mononen, July 2009. -*/ - -#include -#include -#include "../Include/tesselator.h" - -//#define CHECK_BOUNDS - -typedef struct BucketAlloc BucketAlloc; -typedef struct Bucket Bucket; - -struct Bucket -{ - Bucket *next; -}; - -struct BucketAlloc -{ - void *freelist; - Bucket *buckets; - unsigned int itemSize; - unsigned int bucketSize; - const char *name; - TESSalloc* alloc; -}; - -static int CreateBucket( struct BucketAlloc* ba ) -{ - size_t size; - Bucket* bucket; - void* freelist; - unsigned char* head; - unsigned char* it; - - // Allocate memory for the bucket - size = sizeof(Bucket) + ba->itemSize * ba->bucketSize; - bucket = (Bucket*)ba->alloc->memalloc( ba->alloc->userData, size ); - if ( !bucket ) - return 0; - bucket->next = 0; - - // Add the bucket into the list of buckets. - bucket->next = ba->buckets; - ba->buckets = bucket; - - // Add new items to the free list. - freelist = ba->freelist; - head = (unsigned char*)bucket + sizeof(Bucket); - it = head + ba->itemSize * ba->bucketSize; - do - { - it -= ba->itemSize; - // Store pointer to next free item. - *((void**)it) = freelist; - // Pointer to next location containing a free item. - freelist = (void*)it; - } - while ( it != head ); - // Update pointer to next location containing a free item. - ba->freelist = (void*)it; - - return 1; -} - -static void *NextFreeItem( struct BucketAlloc *ba ) -{ - return *(void**)ba->freelist; -} - -struct BucketAlloc* createBucketAlloc( TESSalloc* alloc, const char* name, - unsigned int itemSize, unsigned int bucketSize ) -{ - BucketAlloc* ba = (BucketAlloc*)alloc->memalloc( alloc->userData, sizeof(BucketAlloc) ); - - ba->alloc = alloc; - ba->name = name; - ba->itemSize = itemSize; - if ( ba->itemSize < sizeof(void*) ) - ba->itemSize = sizeof(void*); - ba->bucketSize = bucketSize; - ba->freelist = 0; - ba->buckets = 0; - - if ( !CreateBucket( ba ) ) - { - alloc->memfree( alloc->userData, ba ); - return 0; - } - - return ba; -} - -void* bucketAlloc( struct BucketAlloc *ba ) -{ - void *it; - - // If running out of memory, allocate new bucket and update the freelist. - if ( !ba->freelist || !NextFreeItem( ba ) ) - { - if ( !CreateBucket( ba ) ) - return 0; - } - - // Pop item from in front of the free list. - it = ba->freelist; - ba->freelist = NextFreeItem( ba ); - - return it; -} - -void bucketFree( struct BucketAlloc *ba, void *ptr ) -{ -#ifdef CHECK_BOUNDS - int inBounds = 0; - Bucket *bucket; - - // Check that the pointer is allocated with this allocator. - bucket = ba->buckets; - while ( bucket ) - { - void *bucketMin = (void*)((unsigned char*)bucket + sizeof(Bucket)); - void *bucketMax = (void*)((unsigned char*)bucket + sizeof(Bucket) + ba->itemSize * ba->bucketSize); - if ( ptr >= bucketMin && ptr < bucketMax ) - { - inBounds = 1; - break; - } - bucket = bucket->next; - } - - if ( inBounds ) - { - // Add the node in front of the free list. - *(void**)ptr = ba->freelist; - ba->freelist = ptr; - } - else - { - printf("ERROR! pointer 0x%p does not belong to allocator '%s'\n", ba->name); - } -#else - // Add the node in front of the free list. - *(void**)ptr = ba->freelist; - ba->freelist = ptr; -#endif -} - -void deleteBucketAlloc( struct BucketAlloc *ba ) -{ - TESSalloc* alloc = ba->alloc; - Bucket *bucket = ba->buckets; - Bucket *next; - while ( bucket ) - { - next = bucket->next; - alloc->memfree( alloc->userData, bucket ); - bucket = next; - } - ba->freelist = 0; - ba->buckets = 0; - alloc->memfree( alloc->userData, ba ); -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.h b/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.h deleted file mode 100755 index c540951ea3..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.h +++ /dev/null @@ -1,51 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Mikko Mononen, July 2009. -*/ - -#ifndef MEMALLOC_H -#define MEMALLOC_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "tesselator.h" - -struct BucketAlloc *createBucketAlloc( TESSalloc* alloc, const char *name, - unsigned int itemSize, unsigned int bucketSize ); -void *bucketAlloc( struct BucketAlloc *ba); -void bucketFree( struct BucketAlloc *ba, void *ptr ); -void deleteBucketAlloc( struct BucketAlloc *ba ); - -#ifdef __cplusplus -}; -#endif - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/dict.c b/submodules/LottieMeshSwift/libtess2/Sources/dict.c deleted file mode 100755 index 650adda21d..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/dict.c +++ /dev/null @@ -1,109 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#include -#include "../Include/tesselator.h" -#include "bucketalloc.h" -#include "dict.h" - -/* really tessDictListNewDict */ -Dict *dictNewDict( TESSalloc* alloc, void *frame, int (*leq)(void *frame, DictKey key1, DictKey key2) ) -{ - Dict *dict = (Dict *)alloc->memalloc( alloc->userData, sizeof( Dict )); - DictNode *head; - - if (dict == NULL) return NULL; - - head = &dict->head; - - head->key = NULL; - head->next = head; - head->prev = head; - - dict->frame = frame; - dict->leq = leq; - - if (alloc->dictNodeBucketSize < 16) - alloc->dictNodeBucketSize = 16; - if (alloc->dictNodeBucketSize > 4096) - alloc->dictNodeBucketSize = 4096; - dict->nodePool = createBucketAlloc( alloc, "Dict", sizeof(DictNode), alloc->dictNodeBucketSize ); - - return dict; -} - -/* really tessDictListDeleteDict */ -void dictDeleteDict( TESSalloc* alloc, Dict *dict ) -{ - deleteBucketAlloc( dict->nodePool ); - alloc->memfree( alloc->userData, dict ); -} - -/* really tessDictListInsertBefore */ -DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key ) -{ - DictNode *newNode; - - do { - node = node->prev; - } while( node->key != NULL && ! (*dict->leq)(dict->frame, node->key, key)); - - newNode = (DictNode *)bucketAlloc( dict->nodePool ); - if (newNode == NULL) return NULL; - - newNode->key = key; - newNode->next = node->next; - node->next->prev = newNode; - newNode->prev = node; - node->next = newNode; - - return newNode; -} - -/* really tessDictListDelete */ -void dictDelete( Dict *dict, DictNode *node ) /*ARGSUSED*/ -{ - node->next->prev = node->prev; - node->prev->next = node->next; - bucketFree( dict->nodePool, node ); -} - -/* really tessDictListSearch */ -DictNode *dictSearch( Dict *dict, DictKey key ) -{ - DictNode *node = &dict->head; - - do { - node = node->next; - } while( node->key != NULL && ! (*dict->leq)(dict->frame, key, node->key)); - - return node; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/dict.h b/submodules/LottieMeshSwift/libtess2/Sources/dict.h deleted file mode 100755 index 4cf322657b..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/dict.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef DICT_LIST_H -#define DICT_LIST_H - -typedef void *DictKey; -typedef struct Dict Dict; -typedef struct DictNode DictNode; - -Dict *dictNewDict( TESSalloc* alloc, void *frame, int (*leq)(void *frame, DictKey key1, DictKey key2) ); - -void dictDeleteDict( TESSalloc* alloc, Dict *dict ); - -/* Search returns the node with the smallest key greater than or equal -* to the given key. If there is no such key, returns a node whose -* key is NULL. Similarly, Succ(Max(d)) has a NULL key, etc. -*/ -DictNode *dictSearch( Dict *dict, DictKey key ); -DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key ); -void dictDelete( Dict *dict, DictNode *node ); - -#define dictKey(n) ((n)->key) -#define dictSucc(n) ((n)->next) -#define dictPred(n) ((n)->prev) -#define dictMin(d) ((d)->head.next) -#define dictMax(d) ((d)->head.prev) -#define dictInsert(d,k) (dictInsertBefore((d),&(d)->head,(k))) - - -/*** Private data structures ***/ - -struct DictNode { - DictKey key; - DictNode *next; - DictNode *prev; -}; - -struct Dict { - DictNode head; - void *frame; - struct BucketAlloc *nodePool; - int (*leq)(void *frame, DictKey key1, DictKey key2); -}; - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/geom.c b/submodules/LottieMeshSwift/libtess2/Sources/geom.c deleted file mode 100755 index 66005540d4..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/geom.c +++ /dev/null @@ -1,293 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -//#include "tesos.h" -#include -#include "mesh.h" -#include "geom.h" -#include - -int tesvertLeq( TESSvertex *u, TESSvertex *v ) -{ - /* Returns TRUE if u is lexicographically <= v. */ - - return VertLeq( u, v ); -} - -TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w), - * evaluates the t-coord of the edge uw at the s-coord of the vertex v. - * Returns v->t - (uw)(v->s), ie. the signed distance from uw to v. - * If uw is vertical (and thus passes thru v), the result is zero. - * - * The calculation is extremely accurate and stable, even when v - * is very close to u or w. In particular if we set v->t = 0 and - * let r be the negated result (this evaluates (uw)(v->s)), then - * r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t). - */ - TESSreal gapL, gapR; - - assert( VertLeq( u, v ) && VertLeq( v, w )); - - gapL = v->s - u->s; - gapR = w->s - v->s; - - if( gapL + gapR > 0 ) { - if( gapL < gapR ) { - return (v->t - u->t) + (u->t - w->t) * (gapL / (gapL + gapR)); - } else { - return (v->t - w->t) + (w->t - u->t) * (gapR / (gapL + gapR)); - } - } - /* vertical line */ - return 0; -} - -TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Returns a number whose sign matches EdgeEval(u,v,w) but which - * is cheaper to evaluate. Returns > 0, == 0 , or < 0 - * as v is above, on, or below the edge uw. - */ - TESSreal gapL, gapR; - - assert( VertLeq( u, v ) && VertLeq( v, w )); - - gapL = v->s - u->s; - gapR = w->s - v->s; - - if( gapL + gapR > 0 ) { - return (v->t - w->t) * gapL + (v->t - u->t) * gapR; - } - /* vertical line */ - return 0; -} - - -/*********************************************************************** -* Define versions of EdgeSign, EdgeEval with s and t transposed. -*/ - -TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Given three vertices u,v,w such that TransLeq(u,v) && TransLeq(v,w), - * evaluates the t-coord of the edge uw at the s-coord of the vertex v. - * Returns v->s - (uw)(v->t), ie. the signed distance from uw to v. - * If uw is vertical (and thus passes thru v), the result is zero. - * - * The calculation is extremely accurate and stable, even when v - * is very close to u or w. In particular if we set v->s = 0 and - * let r be the negated result (this evaluates (uw)(v->t)), then - * r is guaranteed to satisfy MIN(u->s,w->s) <= r <= MAX(u->s,w->s). - */ - TESSreal gapL, gapR; - - assert( TransLeq( u, v ) && TransLeq( v, w )); - - gapL = v->t - u->t; - gapR = w->t - v->t; - - if( gapL + gapR > 0 ) { - if( gapL < gapR ) { - return (v->s - u->s) + (u->s - w->s) * (gapL / (gapL + gapR)); - } else { - return (v->s - w->s) + (w->s - u->s) * (gapR / (gapL + gapR)); - } - } - /* vertical line */ - return 0; -} - -TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Returns a number whose sign matches TransEval(u,v,w) but which - * is cheaper to evaluate. Returns > 0, == 0 , or < 0 - * as v is above, on, or below the edge uw. - */ - TESSreal gapL, gapR; - - assert( TransLeq( u, v ) && TransLeq( v, w )); - - gapL = v->t - u->t; - gapR = w->t - v->t; - - if( gapL + gapR > 0 ) { - return (v->s - w->s) * gapL + (v->s - u->s) * gapR; - } - /* vertical line */ - return 0; -} - - -int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* For almost-degenerate situations, the results are not reliable. - * Unless the floating-point arithmetic can be performed without - * rounding errors, *any* implementation will give incorrect results - * on some degenerate inputs, so the client must have some way to - * handle this situation. - */ - return (u->s*(v->t - w->t) + v->s*(w->t - u->t) + w->s*(u->t - v->t)) >= 0; -} - -/* Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b), -* or (x+y)/2 if a==b==0. It requires that a,b >= 0, and enforces -* this in the rare case that one argument is slightly negative. -* The implementation is extremely stable numerically. -* In particular it guarantees that the result r satisfies -* MIN(x,y) <= r <= MAX(x,y), and the results are very accurate -* even when a and b differ greatly in magnitude. -*/ -#define RealInterpolate(a,x,b,y) \ - (a = (a < 0) ? 0 : a, b = (b < 0) ? 0 : b, \ - ((a <= b) ? ((b == 0) ? ((x+y) / 2) \ - : (x + (y-x) * (a/(a+b)))) \ - : (y + (x-y) * (b/(a+b))))) - -#ifndef FOR_TRITE_TEST_PROGRAM -#define Interpolate(a,x,b,y) RealInterpolate(a,x,b,y) -#else - -/* Claim: the ONLY property the sweep algorithm relies on is that -* MIN(x,y) <= r <= MAX(x,y). This is a nasty way to test that. -*/ -#include -extern int RandomInterpolate; - -double Interpolate( double a, double x, double b, double y) -{ - printf("*********************%d\n",RandomInterpolate); - if( RandomInterpolate ) { - a = 1.2 * drand48() - 0.1; - a = (a < 0) ? 0 : ((a > 1) ? 1 : a); - b = 1.0 - a; - } - return RealInterpolate(a,x,b,y); -} - -#endif - -#define Swap(a,b) if (1) { TESSvertex *t = a; a = b; b = t; } else - -void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1, - TESSvertex *o2, TESSvertex *d2, - TESSvertex *v ) - /* Given edges (o1,d1) and (o2,d2), compute their point of intersection. - * The computed point is guaranteed to lie in the intersection of the - * bounding rectangles defined by each edge. - */ -{ - TESSreal z1, z2; - - /* This is certainly not the most efficient way to find the intersection - * of two line segments, but it is very numerically stable. - * - * Strategy: find the two middle vertices in the VertLeq ordering, - * and interpolate the intersection s-value from these. Then repeat - * using the TransLeq ordering to find the intersection t-value. - */ - - if( ! VertLeq( o1, d1 )) { Swap( o1, d1 ); } - if( ! VertLeq( o2, d2 )) { Swap( o2, d2 ); } - if( ! VertLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); } - - if( ! VertLeq( o2, d1 )) { - /* Technically, no intersection -- do our best */ - v->s = (o2->s + d1->s) / 2; - } else if( VertLeq( d1, d2 )) { - /* Interpolate between o2 and d1 */ - z1 = EdgeEval( o1, o2, d1 ); - z2 = EdgeEval( o2, d1, d2 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->s = Interpolate( z1, o2->s, z2, d1->s ); - } else { - /* Interpolate between o2 and d2 */ - z1 = EdgeSign( o1, o2, d1 ); - z2 = -EdgeSign( o1, d2, d1 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->s = Interpolate( z1, o2->s, z2, d2->s ); - } - - /* Now repeat the process for t */ - - if( ! TransLeq( o1, d1 )) { Swap( o1, d1 ); } - if( ! TransLeq( o2, d2 )) { Swap( o2, d2 ); } - if( ! TransLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); } - - if( ! TransLeq( o2, d1 )) { - /* Technically, no intersection -- do our best */ - v->t = (o2->t + d1->t) / 2; - } else if( TransLeq( d1, d2 )) { - /* Interpolate between o2 and d1 */ - z1 = TransEval( o1, o2, d1 ); - z2 = TransEval( o2, d1, d2 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->t = Interpolate( z1, o2->t, z2, d1->t ); - } else { - /* Interpolate between o2 and d2 */ - z1 = TransSign( o1, o2, d1 ); - z2 = -TransSign( o1, d2, d1 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->t = Interpolate( z1, o2->t, z2, d2->t ); - } -} - -TESSreal inCircle( TESSvertex *v, TESSvertex *v0, TESSvertex *v1, TESSvertex *v2 ) { - TESSreal adx, ady, bdx, bdy, cdx, cdy; - TESSreal abdet, bcdet, cadet; - TESSreal alift, blift, clift; - - adx = v0->s - v->s; - ady = v0->t - v->t; - bdx = v1->s - v->s; - bdy = v1->t - v->t; - cdx = v2->s - v->s; - cdy = v2->t - v->t; - - abdet = adx * bdy - bdx * ady; - bcdet = bdx * cdy - cdx * bdy; - cadet = cdx * ady - adx * cdy; - - alift = adx * adx + ady * ady; - blift = bdx * bdx + bdy * bdy; - clift = cdx * cdx + cdy * cdy; - - return alift * bcdet + blift * cadet + clift * abdet; -} - -/* - Returns 1 is edge is locally delaunay - */ -int tesedgeIsLocallyDelaunay( TESShalfEdge *e ) -{ - return inCircle(e->Sym->Lnext->Lnext->Org, e->Lnext->Org, e->Lnext->Lnext->Org, e->Org) < 0; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/geom.h b/submodules/LottieMeshSwift/libtess2/Sources/geom.h deleted file mode 100755 index c29a932685..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/geom.h +++ /dev/null @@ -1,78 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef GEOM_H -#define GEOM_H - -#include "mesh.h" - -#ifdef NO_BRANCH_CONDITIONS -/* MIPS architecture has special instructions to evaluate boolean -* conditions -- more efficient than branching, IF you can get the -* compiler to generate the right instructions (SGI compiler doesn't) -*/ -#define VertEq(u,v) (((u)->s == (v)->s) & ((u)->t == (v)->t)) -#define VertLeq(u,v) (((u)->s < (v)->s) | \ - ((u)->s == (v)->s & (u)->t <= (v)->t)) -#else -#define VertEq(u,v) ((u)->s == (v)->s && (u)->t == (v)->t) -#define VertLeq(u,v) (((u)->s < (v)->s) || ((u)->s == (v)->s && (u)->t <= (v)->t)) -#endif - -#define EdgeEval(u,v,w) tesedgeEval(u,v,w) -#define EdgeSign(u,v,w) tesedgeSign(u,v,w) - -/* Versions of VertLeq, EdgeSign, EdgeEval with s and t transposed. */ - -#define TransLeq(u,v) (((u)->t < (v)->t) || ((u)->t == (v)->t && (u)->s <= (v)->s)) -#define TransEval(u,v,w) testransEval(u,v,w) -#define TransSign(u,v,w) testransSign(u,v,w) - - -#define EdgeGoesLeft(e) VertLeq( (e)->Dst, (e)->Org ) -#define EdgeGoesRight(e) VertLeq( (e)->Org, (e)->Dst ) -#define EdgeIsInternal(e) e->Rface && e->Rface->inside - -#define ABS(x) ((x) < 0 ? -(x) : (x)) -#define VertL1dist(u,v) (ABS(u->s - v->s) + ABS(u->t - v->t)) - -#define VertCCW(u,v,w) tesvertCCW(u,v,w) - -int tesvertLeq( TESSvertex *u, TESSvertex *v ); -TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1, TESSvertex *o2, TESSvertex *d2, TESSvertex *v ); -int tesedgeIsLocallyDelaunay( TESShalfEdge *e ); - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/mesh.c b/submodules/LottieMeshSwift/libtess2/Sources/mesh.c deleted file mode 100755 index a0fa08e577..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/mesh.c +++ /dev/null @@ -1,917 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -//#include "tesos.h" -#include -#include -#include "mesh.h" -#include "geom.h" -#include "bucketalloc.h" - -#define TRUE 1 -#define FALSE 0 - -/************************ Utility Routines ************************/ - -/* Allocate and free half-edges in pairs for efficiency. -* The *only* place that should use this fact is allocation/free. -*/ -typedef struct { TESShalfEdge e, eSym; } EdgePair; - -/* MakeEdge creates a new pair of half-edges which form their own loop. -* No vertex or face structures are allocated, but these must be assigned -* before the current edge operation is completed. -*/ -static TESShalfEdge *MakeEdge( TESSmesh* mesh, TESShalfEdge *eNext ) -{ - TESShalfEdge *e; - TESShalfEdge *eSym; - TESShalfEdge *ePrev; - EdgePair *pair = (EdgePair *)bucketAlloc( mesh->edgeBucket ); - if (pair == NULL) return NULL; - - e = &pair->e; - eSym = &pair->eSym; - - /* Make sure eNext points to the first edge of the edge pair */ - if( eNext->Sym < eNext ) { eNext = eNext->Sym; } - - /* Insert in circular doubly-linked list before eNext. - * Note that the prev pointer is stored in Sym->next. - */ - ePrev = eNext->Sym->next; - eSym->next = ePrev; - ePrev->Sym->next = e; - e->next = eNext; - eNext->Sym->next = eSym; - - e->Sym = eSym; - e->Onext = e; - e->Lnext = eSym; - e->Org = NULL; - e->Lface = NULL; - e->winding = 0; - e->activeRegion = NULL; - e->mark = 0; - - eSym->Sym = e; - eSym->Onext = eSym; - eSym->Lnext = e; - eSym->Org = NULL; - eSym->Lface = NULL; - eSym->winding = 0; - eSym->activeRegion = NULL; - eSym->mark = 0; - - return e; -} - -/* Splice( a, b ) is best described by the Guibas/Stolfi paper or the -* CS348a notes (see mesh.h). Basically it modifies the mesh so that -* a->Onext and b->Onext are exchanged. This can have various effects -* depending on whether a and b belong to different face or vertex rings. -* For more explanation see tessMeshSplice() below. -*/ -static void Splice( TESShalfEdge *a, TESShalfEdge *b ) -{ - TESShalfEdge *aOnext = a->Onext; - TESShalfEdge *bOnext = b->Onext; - - aOnext->Sym->Lnext = b; - bOnext->Sym->Lnext = a; - a->Onext = bOnext; - b->Onext = aOnext; -} - -/* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the -* origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives -* a place to insert the new vertex in the global vertex list. We insert -* the new vertex *before* vNext so that algorithms which walk the vertex -* list will not see the newly created vertices. -*/ -static void MakeVertex( TESSvertex *newVertex, - TESShalfEdge *eOrig, TESSvertex *vNext ) -{ - TESShalfEdge *e; - TESSvertex *vPrev; - TESSvertex *vNew = newVertex; - - assert(vNew != NULL); - - /* insert in circular doubly-linked list before vNext */ - vPrev = vNext->prev; - vNew->prev = vPrev; - vPrev->next = vNew; - vNew->next = vNext; - vNext->prev = vNew; - - vNew->anEdge = eOrig; - /* leave coords, s, t undefined */ - - /* fix other edges on this vertex loop */ - e = eOrig; - do { - e->Org = vNew; - e = e->Onext; - } while( e != eOrig ); -} - -/* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left -* face of all edges in the face loop to which eOrig belongs. "fNext" gives -* a place to insert the new face in the global face list. We insert -* the new face *before* fNext so that algorithms which walk the face -* list will not see the newly created faces. -*/ -static void MakeFace( TESSface *newFace, TESShalfEdge *eOrig, TESSface *fNext ) -{ - TESShalfEdge *e; - TESSface *fPrev; - TESSface *fNew = newFace; - - assert(fNew != NULL); - - /* insert in circular doubly-linked list before fNext */ - fPrev = fNext->prev; - fNew->prev = fPrev; - fPrev->next = fNew; - fNew->next = fNext; - fNext->prev = fNew; - - fNew->anEdge = eOrig; - fNew->trail = NULL; - fNew->marked = FALSE; - - /* The new face is marked "inside" if the old one was. This is a - * convenience for the common case where a face has been split in two. - */ - fNew->inside = fNext->inside; - - /* fix other edges on this face loop */ - e = eOrig; - do { - e->Lface = fNew; - e = e->Lnext; - } while( e != eOrig ); -} - -/* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym), -* and removes from the global edge list. -*/ -static void KillEdge( TESSmesh *mesh, TESShalfEdge *eDel ) -{ - TESShalfEdge *ePrev, *eNext; - - /* Half-edges are allocated in pairs, see EdgePair above */ - if( eDel->Sym < eDel ) { eDel = eDel->Sym; } - - /* delete from circular doubly-linked list */ - eNext = eDel->next; - ePrev = eDel->Sym->next; - eNext->Sym->next = ePrev; - ePrev->Sym->next = eNext; - - bucketFree( mesh->edgeBucket, eDel ); -} - - -/* KillVertex( vDel ) destroys a vertex and removes it from the global -* vertex list. It updates the vertex loop to point to a given new vertex. -*/ -static void KillVertex( TESSmesh *mesh, TESSvertex *vDel, TESSvertex *newOrg ) -{ - TESShalfEdge *e, *eStart = vDel->anEdge; - TESSvertex *vPrev, *vNext; - - /* change the origin of all affected edges */ - e = eStart; - do { - e->Org = newOrg; - e = e->Onext; - } while( e != eStart ); - - /* delete from circular doubly-linked list */ - vPrev = vDel->prev; - vNext = vDel->next; - vNext->prev = vPrev; - vPrev->next = vNext; - - bucketFree( mesh->vertexBucket, vDel ); -} - -/* KillFace( fDel ) destroys a face and removes it from the global face -* list. It updates the face loop to point to a given new face. -*/ -static void KillFace( TESSmesh *mesh, TESSface *fDel, TESSface *newLface ) -{ - TESShalfEdge *e, *eStart = fDel->anEdge; - TESSface *fPrev, *fNext; - - /* change the left face of all affected edges */ - e = eStart; - do { - e->Lface = newLface; - e = e->Lnext; - } while( e != eStart ); - - /* delete from circular doubly-linked list */ - fPrev = fDel->prev; - fNext = fDel->next; - fNext->prev = fPrev; - fPrev->next = fNext; - - bucketFree( mesh->faceBucket, fDel ); -} - - -/****************** Basic Edge Operations **********************/ - -/* tessMeshMakeEdge creates one edge, two vertices, and a loop (face). -* The loop consists of the two new half-edges. -*/ -TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh ) -{ - TESSvertex *newVertex1 = (TESSvertex*)bucketAlloc(mesh->vertexBucket); - TESSvertex *newVertex2 = (TESSvertex*)bucketAlloc(mesh->vertexBucket); - TESSface *newFace = (TESSface*)bucketAlloc(mesh->faceBucket); - TESShalfEdge *e; - - /* if any one is null then all get freed */ - if (newVertex1 == NULL || newVertex2 == NULL || newFace == NULL) { - if (newVertex1 != NULL) bucketFree( mesh->vertexBucket, newVertex1 ); - if (newVertex2 != NULL) bucketFree( mesh->vertexBucket, newVertex2 ); - if (newFace != NULL) bucketFree( mesh->faceBucket, newFace ); - return NULL; - } - - e = MakeEdge( mesh, &mesh->eHead ); - if (e == NULL) return NULL; - - MakeVertex( newVertex1, e, &mesh->vHead ); - MakeVertex( newVertex2, e->Sym, &mesh->vHead ); - MakeFace( newFace, e, &mesh->fHead ); - return e; -} - - -/* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the -* mesh connectivity and topology. It changes the mesh so that -* eOrg->Onext <- OLD( eDst->Onext ) -* eDst->Onext <- OLD( eOrg->Onext ) -* where OLD(...) means the value before the meshSplice operation. -* -* This can have two effects on the vertex structure: -* - if eOrg->Org != eDst->Org, the two vertices are merged together -* - if eOrg->Org == eDst->Org, the origin is split into two vertices -* In both cases, eDst->Org is changed and eOrg->Org is untouched. -* -* Similarly (and independently) for the face structure, -* - if eOrg->Lface == eDst->Lface, one loop is split into two -* - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one -* In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. -* -* Some special cases: -* If eDst == eOrg, the operation has no effect. -* If eDst == eOrg->Lnext, the new face will have a single edge. -* If eDst == eOrg->Lprev, the old face will have a single edge. -* If eDst == eOrg->Onext, the new vertex will have a single edge. -* If eDst == eOrg->Oprev, the old vertex will have a single edge. -*/ -int tessMeshSplice( TESSmesh* mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ) -{ - int joiningLoops = FALSE; - int joiningVertices = FALSE; - - if( eOrg == eDst ) return 1; - - if( eDst->Org != eOrg->Org ) { - /* We are merging two disjoint vertices -- destroy eDst->Org */ - joiningVertices = TRUE; - KillVertex( mesh, eDst->Org, eOrg->Org ); - } - if( eDst->Lface != eOrg->Lface ) { - /* We are connecting two disjoint loops -- destroy eDst->Lface */ - joiningLoops = TRUE; - KillFace( mesh, eDst->Lface, eOrg->Lface ); - } - - /* Change the edge structure */ - Splice( eDst, eOrg ); - - if( ! joiningVertices ) { - TESSvertex *newVertex = (TESSvertex*)bucketAlloc( mesh->vertexBucket ); - if (newVertex == NULL) return 0; - - /* We split one vertex into two -- the new vertex is eDst->Org. - * Make sure the old vertex points to a valid half-edge. - */ - MakeVertex( newVertex, eDst, eOrg->Org ); - eOrg->Org->anEdge = eOrg; - } - if( ! joiningLoops ) { - TESSface *newFace = (TESSface*)bucketAlloc( mesh->faceBucket ); - if (newFace == NULL) return 0; - - /* We split one loop into two -- the new loop is eDst->Lface. - * Make sure the old face points to a valid half-edge. - */ - MakeFace( newFace, eDst, eOrg->Lface ); - eOrg->Lface->anEdge = eOrg; - } - - return 1; -} - - -/* tessMeshDelete( eDel ) removes the edge eDel. There are several cases: -* if (eDel->Lface != eDel->Rface), we join two loops into one; the loop -* eDel->Lface is deleted. Otherwise, we are splitting one loop into two; -* the newly created loop will contain eDel->Dst. If the deletion of eDel -* would create isolated vertices, those are deleted as well. -* -* This function could be implemented as two calls to tessMeshSplice -* plus a few calls to memFree, but this would allocate and delete -* unnecessary vertices and faces. -*/ -int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel ) -{ - TESShalfEdge *eDelSym = eDel->Sym; - int joiningLoops = FALSE; - - /* First step: disconnect the origin vertex eDel->Org. We make all - * changes to get a consistent mesh in this "intermediate" state. - */ - if( eDel->Lface != eDel->Rface ) { - /* We are joining two loops into one -- remove the left face */ - joiningLoops = TRUE; - KillFace( mesh, eDel->Lface, eDel->Rface ); - } - - if( eDel->Onext == eDel ) { - KillVertex( mesh, eDel->Org, NULL ); - } else { - /* Make sure that eDel->Org and eDel->Rface point to valid half-edges */ - eDel->Rface->anEdge = eDel->Oprev; - eDel->Org->anEdge = eDel->Onext; - - Splice( eDel, eDel->Oprev ); - if( ! joiningLoops ) { - TESSface *newFace= (TESSface*)bucketAlloc( mesh->faceBucket ); - if (newFace == NULL) return 0; - - /* We are splitting one loop into two -- create a new loop for eDel. */ - MakeFace( newFace, eDel, eDel->Lface ); - } - } - - /* Claim: the mesh is now in a consistent state, except that eDel->Org - * may have been deleted. Now we disconnect eDel->Dst. - */ - if( eDelSym->Onext == eDelSym ) { - KillVertex( mesh, eDelSym->Org, NULL ); - KillFace( mesh, eDelSym->Lface, NULL ); - } else { - /* Make sure that eDel->Dst and eDel->Lface point to valid half-edges */ - eDel->Lface->anEdge = eDelSym->Oprev; - eDelSym->Org->anEdge = eDelSym->Onext; - Splice( eDelSym, eDelSym->Oprev ); - } - - /* Any isolated vertices or faces have already been freed. */ - KillEdge( mesh, eDel ); - - return 1; -} - - -/******************** Other Edge Operations **********************/ - -/* All these routines can be implemented with the basic edge -* operations above. They are provided for convenience and efficiency. -*/ - - -/* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that -* eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. -* eOrg and eNew will have the same left face. -*/ -TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg ) -{ - TESShalfEdge *eNewSym; - TESShalfEdge *eNew = MakeEdge( mesh, eOrg ); - if (eNew == NULL) return NULL; - - eNewSym = eNew->Sym; - - /* Connect the new edge appropriately */ - Splice( eNew, eOrg->Lnext ); - - /* Set the vertex and face information */ - eNew->Org = eOrg->Dst; - { - TESSvertex *newVertex= (TESSvertex*)bucketAlloc( mesh->vertexBucket ); - if (newVertex == NULL) return NULL; - - MakeVertex( newVertex, eNewSym, eNew->Org ); - } - eNew->Lface = eNewSym->Lface = eOrg->Lface; - - return eNew; -} - - -/* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, -* such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. -* eOrg and eNew will have the same left face. -*/ -TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg ) -{ - TESShalfEdge *eNew; - TESShalfEdge *tempHalfEdge= tessMeshAddEdgeVertex( mesh, eOrg ); - if (tempHalfEdge == NULL) return NULL; - - eNew = tempHalfEdge->Sym; - - /* Disconnect eOrg from eOrg->Dst and connect it to eNew->Org */ - Splice( eOrg->Sym, eOrg->Sym->Oprev ); - Splice( eOrg->Sym, eNew ); - - /* Set the vertex and face information */ - eOrg->Dst = eNew->Org; - eNew->Dst->anEdge = eNew->Sym; /* may have pointed to eOrg->Sym */ - eNew->Rface = eOrg->Rface; - eNew->winding = eOrg->winding; /* copy old winding information */ - eNew->Sym->winding = eOrg->Sym->winding; - - return eNew; -} - - -/* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst -* to eDst->Org, and returns the corresponding half-edge eNew. -* If eOrg->Lface == eDst->Lface, this splits one loop into two, -* and the newly created loop is eNew->Lface. Otherwise, two disjoint -* loops are merged into one, and the loop eDst->Lface is destroyed. -* -* If (eOrg == eDst), the new face will have only two edges. -* If (eOrg->Lnext == eDst), the old face is reduced to a single edge. -* If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges. -*/ -TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ) -{ - TESShalfEdge *eNewSym; - int joiningLoops = FALSE; - TESShalfEdge *eNew = MakeEdge( mesh, eOrg ); - if (eNew == NULL) return NULL; - - eNewSym = eNew->Sym; - - if( eDst->Lface != eOrg->Lface ) { - /* We are connecting two disjoint loops -- destroy eDst->Lface */ - joiningLoops = TRUE; - KillFace( mesh, eDst->Lface, eOrg->Lface ); - } - - /* Connect the new edge appropriately */ - Splice( eNew, eOrg->Lnext ); - Splice( eNewSym, eDst ); - - /* Set the vertex and face information */ - eNew->Org = eOrg->Dst; - eNewSym->Org = eDst->Org; - eNew->Lface = eNewSym->Lface = eOrg->Lface; - - /* Make sure the old face points to a valid half-edge */ - eOrg->Lface->anEdge = eNewSym; - - if( ! joiningLoops ) { - TESSface *newFace= (TESSface*)bucketAlloc( mesh->faceBucket ); - if (newFace == NULL) return NULL; - - /* We split one loop into two -- the new loop is eNew->Lface */ - MakeFace( newFace, eNew, eOrg->Lface ); - } - return eNew; -} - - -/******************** Other Operations **********************/ - -/* tessMeshZapFace( fZap ) destroys a face and removes it from the -* global face list. All edges of fZap will have a NULL pointer as their -* left face. Any edges which also have a NULL pointer as their right face -* are deleted entirely (along with any isolated vertices this produces). -* An entire mesh can be deleted by zapping its faces, one at a time, -* in any order. Zapped faces cannot be used in further mesh operations! -*/ -void tessMeshZapFace( TESSmesh *mesh, TESSface *fZap ) -{ - TESShalfEdge *eStart = fZap->anEdge; - TESShalfEdge *e, *eNext, *eSym; - TESSface *fPrev, *fNext; - - /* walk around face, deleting edges whose right face is also NULL */ - eNext = eStart->Lnext; - do { - e = eNext; - eNext = e->Lnext; - - e->Lface = NULL; - if( e->Rface == NULL ) { - /* delete the edge -- see TESSmeshDelete above */ - - if( e->Onext == e ) { - KillVertex( mesh, e->Org, NULL ); - } else { - /* Make sure that e->Org points to a valid half-edge */ - e->Org->anEdge = e->Onext; - Splice( e, e->Oprev ); - } - eSym = e->Sym; - if( eSym->Onext == eSym ) { - KillVertex( mesh, eSym->Org, NULL ); - } else { - /* Make sure that eSym->Org points to a valid half-edge */ - eSym->Org->anEdge = eSym->Onext; - Splice( eSym, eSym->Oprev ); - } - KillEdge( mesh, e ); - } - } while( e != eStart ); - - /* delete from circular doubly-linked list */ - fPrev = fZap->prev; - fNext = fZap->next; - fNext->prev = fPrev; - fPrev->next = fNext; - - bucketFree( mesh->faceBucket, fZap ); -} - - -/* tessMeshNewMesh() creates a new mesh with no edges, no vertices, -* and no loops (what we usually call a "face"). -*/ -TESSmesh *tessMeshNewMesh( TESSalloc* alloc ) -{ - TESSvertex *v; - TESSface *f; - TESShalfEdge *e; - TESShalfEdge *eSym; - TESSmesh *mesh = (TESSmesh *)alloc->memalloc( alloc->userData, sizeof( TESSmesh )); - if (mesh == NULL) { - return NULL; - } - - if (alloc->meshEdgeBucketSize < 16) - alloc->meshEdgeBucketSize = 16; - if (alloc->meshEdgeBucketSize > 4096) - alloc->meshEdgeBucketSize = 4096; - - if (alloc->meshVertexBucketSize < 16) - alloc->meshVertexBucketSize = 16; - if (alloc->meshVertexBucketSize > 4096) - alloc->meshVertexBucketSize = 4096; - - if (alloc->meshFaceBucketSize < 16) - alloc->meshFaceBucketSize = 16; - if (alloc->meshFaceBucketSize > 4096) - alloc->meshFaceBucketSize = 4096; - - mesh->edgeBucket = createBucketAlloc( alloc, "Mesh Edges", sizeof(EdgePair), alloc->meshEdgeBucketSize ); - mesh->vertexBucket = createBucketAlloc( alloc, "Mesh Vertices", sizeof(TESSvertex), alloc->meshVertexBucketSize ); - mesh->faceBucket = createBucketAlloc( alloc, "Mesh Faces", sizeof(TESSface), alloc->meshFaceBucketSize ); - - v = &mesh->vHead; - f = &mesh->fHead; - e = &mesh->eHead; - eSym = &mesh->eHeadSym; - - v->next = v->prev = v; - v->anEdge = NULL; - - f->next = f->prev = f; - f->anEdge = NULL; - f->trail = NULL; - f->marked = FALSE; - f->inside = FALSE; - - e->next = e; - e->Sym = eSym; - e->Onext = NULL; - e->Lnext = NULL; - e->Org = NULL; - e->Lface = NULL; - e->winding = 0; - e->activeRegion = NULL; - - eSym->next = eSym; - eSym->Sym = e; - eSym->Onext = NULL; - eSym->Lnext = NULL; - eSym->Org = NULL; - eSym->Lface = NULL; - eSym->winding = 0; - eSym->activeRegion = NULL; - - return mesh; -} - - -/* tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in -* both meshes, and returns the new mesh (the old meshes are destroyed). -*/ -TESSmesh *tessMeshUnion( TESSalloc* alloc, TESSmesh *mesh1, TESSmesh *mesh2 ) -{ - TESSface *f1 = &mesh1->fHead; - TESSvertex *v1 = &mesh1->vHead; - TESShalfEdge *e1 = &mesh1->eHead; - TESSface *f2 = &mesh2->fHead; - TESSvertex *v2 = &mesh2->vHead; - TESShalfEdge *e2 = &mesh2->eHead; - - /* Add the faces, vertices, and edges of mesh2 to those of mesh1 */ - if( f2->next != f2 ) { - f1->prev->next = f2->next; - f2->next->prev = f1->prev; - f2->prev->next = f1; - f1->prev = f2->prev; - } - - if( v2->next != v2 ) { - v1->prev->next = v2->next; - v2->next->prev = v1->prev; - v2->prev->next = v1; - v1->prev = v2->prev; - } - - if( e2->next != e2 ) { - e1->Sym->next->Sym->next = e2->next; - e2->next->Sym->next = e1->Sym->next; - e2->Sym->next->Sym->next = e1; - e1->Sym->next = e2->Sym->next; - } - - alloc->memfree( alloc->userData, mesh2 ); - return mesh1; -} - - -static int CountFaceVerts( TESSface *f ) -{ - TESShalfEdge *eCur = f->anEdge; - int n = 0; - do - { - n++; - eCur = eCur->Lnext; - } - while (eCur != f->anEdge); - return n; -} - -int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace ) -{ - TESShalfEdge *e, *eNext, *eSym; - TESShalfEdge *eHead = &mesh->eHead; - TESSvertex *va, *vb, *vc, *vd, *ve, *vf; - int leftNv, rightNv; - - for( e = eHead->next; e != eHead; e = eNext ) - { - eNext = e->next; - eSym = e->Sym; - if( !eSym ) - continue; - - // Both faces must be inside - if( !e->Lface || !e->Lface->inside ) - continue; - if( !eSym->Lface || !eSym->Lface->inside ) - continue; - - leftNv = CountFaceVerts( e->Lface ); - rightNv = CountFaceVerts( eSym->Lface ); - if( (leftNv+rightNv-2) > maxVertsPerFace ) - continue; - - // Merge if the resulting poly is convex. - // - // vf--ve--vd - // ^| - // left e || right - // |v - // va--vb--vc - - va = e->Lprev->Org; - vb = e->Org; - vc = e->Sym->Lnext->Dst; - - vd = e->Sym->Lprev->Org; - ve = e->Sym->Org; - vf = e->Lnext->Dst; - - if( VertCCW( va, vb, vc ) && VertCCW( vd, ve, vf ) ) { - if( e == eNext || e == eNext->Sym ) { eNext = eNext->next; } - if( !tessMeshDelete( mesh, e ) ) - return 0; - } - } - - return 1; -} - -void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge ) -{ - TESShalfEdge *a0 = edge; - TESShalfEdge *a1 = a0->Lnext; - TESShalfEdge *a2 = a1->Lnext; - TESShalfEdge *b0 = edge->Sym; - TESShalfEdge *b1 = b0->Lnext; - TESShalfEdge *b2 = b1->Lnext; - - TESSvertex *aOrg = a0->Org; - TESSvertex *aOpp = a2->Org; - TESSvertex *bOrg = b0->Org; - TESSvertex *bOpp = b2->Org; - - TESSface *fa = a0->Lface; - TESSface *fb = b0->Lface; - - assert(EdgeIsInternal(edge)); - assert(a2->Lnext == a0); - assert(b2->Lnext == b0); - - a0->Org = bOpp; - a0->Onext = b1->Sym; - b0->Org = aOpp; - b0->Onext = a1->Sym; - a2->Onext = b0; - b2->Onext = a0; - b1->Onext = a2->Sym; - a1->Onext = b2->Sym; - - a0->Lnext = a2; - a2->Lnext = b1; - b1->Lnext = a0; - - b0->Lnext = b2; - b2->Lnext = a1; - a1->Lnext = b0; - - a1->Lface = fb; - b1->Lface = fa; - - fa->anEdge = a0; - fb->anEdge = b0; - - if (aOrg->anEdge == a0) aOrg->anEdge = b1; - if (bOrg->anEdge == b0) bOrg->anEdge = a1; - - assert( a0->Lnext->Onext->Sym == a0 ); - assert( a0->Onext->Sym->Lnext == a0 ); - assert( a0->Org->anEdge->Org == a0->Org ); - - - assert( a1->Lnext->Onext->Sym == a1 ); - assert( a1->Onext->Sym->Lnext == a1 ); - assert( a1->Org->anEdge->Org == a1->Org ); - - assert( a2->Lnext->Onext->Sym == a2 ); - assert( a2->Onext->Sym->Lnext == a2 ); - assert( a2->Org->anEdge->Org == a2->Org ); - - assert( b0->Lnext->Onext->Sym == b0 ); - assert( b0->Onext->Sym->Lnext == b0 ); - assert( b0->Org->anEdge->Org == b0->Org ); - - assert( b1->Lnext->Onext->Sym == b1 ); - assert( b1->Onext->Sym->Lnext == b1 ); - assert( b1->Org->anEdge->Org == b1->Org ); - - assert( b2->Lnext->Onext->Sym == b2 ); - assert( b2->Onext->Sym->Lnext == b2 ); - assert( b2->Org->anEdge->Org == b2->Org ); - - assert(aOrg->anEdge->Org == aOrg); - assert(bOrg->anEdge->Org == bOrg); - - assert(a0->Oprev->Onext->Org == a0->Org); -} - -#ifdef DELETE_BY_ZAPPING - -/* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh. -*/ -void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh ) -{ - TESSface *fHead = &mesh->fHead; - - while( fHead->next != fHead ) { - tessMeshZapFace( fHead->next ); - } - assert( mesh->vHead.next == &mesh->vHead ); - - alloc->memfree( alloc->userData, mesh ); -} - -#else - -/* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh. -*/ -void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh ) -{ - deleteBucketAlloc(mesh->edgeBucket); - deleteBucketAlloc(mesh->vertexBucket); - deleteBucketAlloc(mesh->faceBucket); - - alloc->memfree( alloc->userData, mesh ); -} - -#endif - -#ifndef NDEBUG - -/* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency. -*/ -void tessMeshCheckMesh( TESSmesh *mesh ) -{ - TESSface *fHead = &mesh->fHead; - TESSvertex *vHead = &mesh->vHead; - TESShalfEdge *eHead = &mesh->eHead; - TESSface *f, *fPrev; - TESSvertex *v, *vPrev; - TESShalfEdge *e, *ePrev; - - for( fPrev = fHead ; (f = fPrev->next) != fHead; fPrev = f) { - assert( f->prev == fPrev ); - e = f->anEdge; - do { - assert( e->Sym != e ); - assert( e->Sym->Sym == e ); - assert( e->Lnext->Onext->Sym == e ); - assert( e->Onext->Sym->Lnext == e ); - assert( e->Lface == f ); - e = e->Lnext; - } while( e != f->anEdge ); - } - assert( f->prev == fPrev && f->anEdge == NULL ); - - for( vPrev = vHead ; (v = vPrev->next) != vHead; vPrev = v) { - assert( v->prev == vPrev ); - e = v->anEdge; - do { - assert( e->Sym != e ); - assert( e->Sym->Sym == e ); - assert( e->Lnext->Onext->Sym == e ); - assert( e->Onext->Sym->Lnext == e ); - assert( e->Org == v ); - e = e->Onext; - } while( e != v->anEdge ); - } - assert( v->prev == vPrev && v->anEdge == NULL ); - - for( ePrev = eHead ; (e = ePrev->next) != eHead; ePrev = e) { - assert( e->Sym->next == ePrev->Sym ); - assert( e->Sym != e ); - assert( e->Sym->Sym == e ); - assert( e->Org != NULL ); - assert( e->Dst != NULL ); - assert( e->Lnext->Onext->Sym == e ); - assert( e->Onext->Sym->Lnext == e ); - } - assert( e->Sym->next == ePrev->Sym - && e->Sym == &mesh->eHeadSym - && e->Sym->Sym == e - && e->Org == NULL && e->Dst == NULL - && e->Lface == NULL && e->Rface == NULL ); -} - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/mesh.h b/submodules/LottieMeshSwift/libtess2/Sources/mesh.h deleted file mode 100755 index 479c66f369..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/mesh.h +++ /dev/null @@ -1,269 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef MESH_H -#define MESH_H - -#include "../Include/tesselator.h" - -typedef struct TESSmesh TESSmesh; -typedef struct TESSvertex TESSvertex; -typedef struct TESSface TESSface; -typedef struct TESShalfEdge TESShalfEdge; -typedef struct ActiveRegion ActiveRegion; - -/* The mesh structure is similar in spirit, notation, and operations -* to the "quad-edge" structure (see L. Guibas and J. Stolfi, Primitives -* for the manipulation of general subdivisions and the computation of -* Voronoi diagrams, ACM Transactions on Graphics, 4(2):74-123, April 1985). -* For a simplified description, see the course notes for CS348a, -* "Mathematical Foundations of Computer Graphics", available at the -* Stanford bookstore (and taught during the fall quarter). -* The implementation also borrows a tiny subset of the graph-based approach -* use in Mantyla's Geometric Work Bench (see M. Mantyla, An Introduction -* to Sold Modeling, Computer Science Press, Rockville, Maryland, 1988). -* -* The fundamental data structure is the "half-edge". Two half-edges -* go together to make an edge, but they point in opposite directions. -* Each half-edge has a pointer to its mate (the "symmetric" half-edge Sym), -* its origin vertex (Org), the face on its left side (Lface), and the -* adjacent half-edges in the CCW direction around the origin vertex -* (Onext) and around the left face (Lnext). There is also a "next" -* pointer for the global edge list (see below). -* -* The notation used for mesh navigation: -* Sym = the mate of a half-edge (same edge, but opposite direction) -* Onext = edge CCW around origin vertex (keep same origin) -* Dnext = edge CCW around destination vertex (keep same dest) -* Lnext = edge CCW around left face (dest becomes new origin) -* Rnext = edge CCW around right face (origin becomes new dest) -* -* "prev" means to substitute CW for CCW in the definitions above. -* -* The mesh keeps global lists of all vertices, faces, and edges, -* stored as doubly-linked circular lists with a dummy header node. -* The mesh stores pointers to these dummy headers (vHead, fHead, eHead). -* -* The circular edge list is special; since half-edges always occur -* in pairs (e and e->Sym), each half-edge stores a pointer in only -* one direction. Starting at eHead and following the e->next pointers -* will visit each *edge* once (ie. e or e->Sym, but not both). -* e->Sym stores a pointer in the opposite direction, thus it is -* always true that e->Sym->next->Sym->next == e. -* -* Each vertex has a pointer to next and previous vertices in the -* circular list, and a pointer to a half-edge with this vertex as -* the origin (NULL if this is the dummy header). There is also a -* field "data" for client data. -* -* Each face has a pointer to the next and previous faces in the -* circular list, and a pointer to a half-edge with this face as -* the left face (NULL if this is the dummy header). There is also -* a field "data" for client data. -* -* Note that what we call a "face" is really a loop; faces may consist -* of more than one loop (ie. not simply connected), but there is no -* record of this in the data structure. The mesh may consist of -* several disconnected regions, so it may not be possible to visit -* the entire mesh by starting at a half-edge and traversing the edge -* structure. -* -* The mesh does NOT support isolated vertices; a vertex is deleted along -* with its last edge. Similarly when two faces are merged, one of the -* faces is deleted (see tessMeshDelete below). For mesh operations, -* all face (loop) and vertex pointers must not be NULL. However, once -* mesh manipulation is finished, TESSmeshZapFace can be used to delete -* faces of the mesh, one at a time. All external faces can be "zapped" -* before the mesh is returned to the client; then a NULL face indicates -* a region which is not part of the output polygon. -*/ - -struct TESSvertex { - TESSvertex *next; /* next vertex (never NULL) */ - TESSvertex *prev; /* previous vertex (never NULL) */ - TESShalfEdge *anEdge; /* a half-edge with this origin */ - - /* Internal data (keep hidden) */ - TESSreal coords[3]; /* vertex location in 3D */ - TESSreal s, t; /* projection onto the sweep plane */ - int pqHandle; /* to allow deletion from priority queue */ - TESSindex n; /* to allow identify unique vertices */ - TESSindex idx; /* to allow map result to original verts */ -}; - -struct TESSface { - TESSface *next; /* next face (never NULL) */ - TESSface *prev; /* previous face (never NULL) */ - TESShalfEdge *anEdge; /* a half edge with this left face */ - - /* Internal data (keep hidden) */ - TESSface *trail; /* "stack" for conversion to strips */ - TESSindex n; /* to allow identiy unique faces */ - char marked; /* flag for conversion to strips */ - char inside; /* this face is in the polygon interior */ -}; - -struct TESShalfEdge { - TESShalfEdge *next; /* doubly-linked list (prev==Sym->next) */ - TESShalfEdge *Sym; /* same edge, opposite direction */ - TESShalfEdge *Onext; /* next edge CCW around origin */ - TESShalfEdge *Lnext; /* next edge CCW around left face */ - TESSvertex *Org; /* origin vertex (Overtex too long) */ - TESSface *Lface; /* left face */ - - /* Internal data (keep hidden) */ - ActiveRegion *activeRegion; /* a region with this upper edge (sweep.c) */ - int winding; /* change in winding number when crossing - from the right face to the left face */ - int mark; /* Used by the Edge Flip algorithm */ -}; - -#define Rface Sym->Lface -#define Dst Sym->Org - -#define Oprev Sym->Lnext -#define Lprev Onext->Sym -#define Dprev Lnext->Sym -#define Rprev Sym->Onext -#define Dnext Rprev->Sym /* 3 pointers */ -#define Rnext Oprev->Sym /* 3 pointers */ - -struct TESSmesh { - TESSvertex vHead; /* dummy header for vertex list */ - TESSface fHead; /* dummy header for face list */ - TESShalfEdge eHead; /* dummy header for edge list */ - TESShalfEdge eHeadSym; /* and its symmetric counterpart */ - - struct BucketAlloc* edgeBucket; - struct BucketAlloc* vertexBucket; - struct BucketAlloc* faceBucket; -}; - -/* The mesh operations below have three motivations: completeness, -* convenience, and efficiency. The basic mesh operations are MakeEdge, -* Splice, and Delete. All the other edge operations can be implemented -* in terms of these. The other operations are provided for convenience -* and/or efficiency. -* -* When a face is split or a vertex is added, they are inserted into the -* global list *before* the existing vertex or face (ie. e->Org or e->Lface). -* This makes it easier to process all vertices or faces in the global lists -* without worrying about processing the same data twice. As a convenience, -* when a face is split, the "inside" flag is copied from the old face. -* Other internal data (v->data, v->activeRegion, f->data, f->marked, -* f->trail, e->winding) is set to zero. -* -* ********************** Basic Edge Operations ************************** -* -* tessMeshMakeEdge( mesh ) creates one edge, two vertices, and a loop. -* The loop (face) consists of the two new half-edges. -* -* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the -* mesh connectivity and topology. It changes the mesh so that -* eOrg->Onext <- OLD( eDst->Onext ) -* eDst->Onext <- OLD( eOrg->Onext ) -* where OLD(...) means the value before the meshSplice operation. -* -* This can have two effects on the vertex structure: -* - if eOrg->Org != eDst->Org, the two vertices are merged together -* - if eOrg->Org == eDst->Org, the origin is split into two vertices -* In both cases, eDst->Org is changed and eOrg->Org is untouched. -* -* Similarly (and independently) for the face structure, -* - if eOrg->Lface == eDst->Lface, one loop is split into two -* - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one -* In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. -* -* tessMeshDelete( eDel ) removes the edge eDel. There are several cases: -* if (eDel->Lface != eDel->Rface), we join two loops into one; the loop -* eDel->Lface is deleted. Otherwise, we are splitting one loop into two; -* the newly created loop will contain eDel->Dst. If the deletion of eDel -* would create isolated vertices, those are deleted as well. -* -* ********************** Other Edge Operations ************************** -* -* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that -* eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. -* eOrg and eNew will have the same left face. -* -* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, -* such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. -* eOrg and eNew will have the same left face. -* -* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst -* to eDst->Org, and returns the corresponding half-edge eNew. -* If eOrg->Lface == eDst->Lface, this splits one loop into two, -* and the newly created loop is eNew->Lface. Otherwise, two disjoint -* loops are merged into one, and the loop eDst->Lface is destroyed. -* -* ************************ Other Operations ***************************** -* -* tessMeshNewMesh() creates a new mesh with no edges, no vertices, -* and no loops (what we usually call a "face"). -* -* tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in -* both meshes, and returns the new mesh (the old meshes are destroyed). -* -* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh. -* -* tessMeshZapFace( fZap ) destroys a face and removes it from the -* global face list. All edges of fZap will have a NULL pointer as their -* left face. Any edges which also have a NULL pointer as their right face -* are deleted entirely (along with any isolated vertices this produces). -* An entire mesh can be deleted by zapping its faces, one at a time, -* in any order. Zapped faces cannot be used in further mesh operations! -* -* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency. -*/ - -TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh ); -int tessMeshSplice( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ); -int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel ); - -TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg ); -TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg ); -TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ); - -TESSmesh *tessMeshNewMesh( TESSalloc* alloc ); -TESSmesh *tessMeshUnion( TESSalloc* alloc, TESSmesh *mesh1, TESSmesh *mesh2 ); -int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace ); -void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh ); -void tessMeshZapFace( TESSmesh *mesh, TESSface *fZap ); - -void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge ); - -#ifdef NDEBUG -#define tessMeshCheckMesh( mesh ) -#else -void tessMeshCheckMesh( TESSmesh *mesh ); -#endif - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.c b/submodules/LottieMeshSwift/libtess2/Sources/priorityq.c deleted file mode 100755 index 62a6654535..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.c +++ /dev/null @@ -1,514 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -//#include "tesos.h" -#include -#include -#include "../Include/tesselator.h" -#include "priorityq.h" - - -#define INIT_SIZE 32 - -#define TRUE 1 -#define FALSE 0 - -#ifdef FOR_TRITE_TEST_PROGRAM -#define LEQ(x,y) (*pq->leq)(x,y) -#else -/* Violates modularity, but a little faster */ -#include "geom.h" -#define LEQ(x,y) VertLeq((TESSvertex *)x, (TESSvertex *)y) -#endif - - -/* Include all the code for the regular heap-based queue here. */ - -/* The basic operations are insertion of a new key (pqInsert), -* and examination/extraction of a key whose value is minimum -* (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete); -* for this purpose pqInsert returns a "handle" which is supplied -* as the argument. -* -* An initial heap may be created efficiently by calling pqInsert -* repeatedly, then calling pqInit. In any case pqInit must be called -* before any operations other than pqInsert are used. -* -* If the heap is empty, pqMinimum/pqExtractMin will return a NULL key. -* This may also be tested with pqIsEmpty. -*/ - - -/* Since we support deletion the data structure is a little more -* complicated than an ordinary heap. "nodes" is the heap itself; -* active nodes are stored in the range 1..pq->size. When the -* heap exceeds its allocated size (pq->max), its size doubles. -* The children of node i are nodes 2i and 2i+1. -* -* Each node stores an index into an array "handles". Each handle -* stores a key, plus a pointer back to the node which currently -* represents that key (ie. nodes[handles[i].node].handle == i). -*/ - - -#define pqHeapMinimum(pq) ((pq)->handles[(pq)->nodes[1].handle].key) -#define pqHeapIsEmpty(pq) ((pq)->size == 0) - - - -/* really pqHeapNewPriorityQHeap */ -PriorityQHeap *pqHeapNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) ) -{ - PriorityQHeap *pq = (PriorityQHeap *)alloc->memalloc( alloc->userData, sizeof( PriorityQHeap )); - if (pq == NULL) return NULL; - - pq->size = 0; - pq->max = size; - pq->nodes = (PQnode *)alloc->memalloc( alloc->userData, (size + 1) * sizeof(pq->nodes[0]) ); - if (pq->nodes == NULL) { - alloc->memfree( alloc->userData, pq ); - return NULL; - } - - pq->handles = (PQhandleElem *)alloc->memalloc( alloc->userData, (size + 1) * sizeof(pq->handles[0]) ); - if (pq->handles == NULL) { - alloc->memfree( alloc->userData, pq->nodes ); - alloc->memfree( alloc->userData, pq ); - return NULL; - } - - pq->initialized = FALSE; - pq->freeList = 0; - pq->leq = leq; - - pq->nodes[1].handle = 1; /* so that Minimum() returns NULL */ - pq->handles[1].key = NULL; - return pq; -} - -/* really pqHeapDeletePriorityQHeap */ -void pqHeapDeletePriorityQ( TESSalloc* alloc, PriorityQHeap *pq ) -{ - alloc->memfree( alloc->userData, pq->handles ); - alloc->memfree( alloc->userData, pq->nodes ); - alloc->memfree( alloc->userData, pq ); -} - - -static void FloatDown( PriorityQHeap *pq, int curr ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - PQhandle hCurr, hChild; - int child; - - hCurr = n[curr].handle; - for( ;; ) { - child = curr << 1; - if( child < pq->size && LEQ( h[n[child+1].handle].key, - h[n[child].handle].key )) { - ++child; - } - - assert(child <= pq->max); - - hChild = n[child].handle; - if( child > pq->size || LEQ( h[hCurr].key, h[hChild].key )) { - n[curr].handle = hCurr; - h[hCurr].node = curr; - break; - } - n[curr].handle = hChild; - h[hChild].node = curr; - curr = child; - } -} - - -static void FloatUp( PriorityQHeap *pq, int curr ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - PQhandle hCurr, hParent; - int parent; - - hCurr = n[curr].handle; - for( ;; ) { - parent = curr >> 1; - hParent = n[parent].handle; - if( parent == 0 || LEQ( h[hParent].key, h[hCurr].key )) { - n[curr].handle = hCurr; - h[hCurr].node = curr; - break; - } - n[curr].handle = hParent; - h[hParent].node = curr; - curr = parent; - } -} - -/* really pqHeapInit */ -void pqHeapInit( PriorityQHeap *pq ) -{ - int i; - - /* This method of building a heap is O(n), rather than O(n lg n). */ - - for( i = pq->size; i >= 1; --i ) { - FloatDown( pq, i ); - } - pq->initialized = TRUE; -} - -/* really pqHeapInsert */ -/* returns INV_HANDLE iff out of memory */ -PQhandle pqHeapInsert( TESSalloc* alloc, PriorityQHeap *pq, PQkey keyNew ) -{ - int curr; - PQhandle free; - - curr = ++ pq->size; - if( (curr*2) > pq->max ) { - if (!alloc->memrealloc) - { - return INV_HANDLE; - } - else - { - PQnode *saveNodes= pq->nodes; - PQhandleElem *saveHandles= pq->handles; - - // If the heap overflows, double its size. - pq->max <<= 1; - pq->nodes = (PQnode *)alloc->memrealloc( alloc->userData, pq->nodes, - (size_t)((pq->max + 1) * sizeof( pq->nodes[0] ))); - if (pq->nodes == NULL) { - pq->nodes = saveNodes; // restore ptr to free upon return - return INV_HANDLE; - } - pq->handles = (PQhandleElem *)alloc->memrealloc( alloc->userData, pq->handles, - (size_t) ((pq->max + 1) * sizeof( pq->handles[0] ))); - if (pq->handles == NULL) { - pq->handles = saveHandles; // restore ptr to free upon return - return INV_HANDLE; - } - } - } - - if( pq->freeList == 0 ) { - free = curr; - } else { - free = pq->freeList; - pq->freeList = pq->handles[free].node; - } - - pq->nodes[curr].handle = free; - pq->handles[free].node = curr; - pq->handles[free].key = keyNew; - - if( pq->initialized ) { - FloatUp( pq, curr ); - } - assert(free != INV_HANDLE); - return free; -} - -/* really pqHeapExtractMin */ -PQkey pqHeapExtractMin( PriorityQHeap *pq ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - PQhandle hMin = n[1].handle; - PQkey min = h[hMin].key; - - if( pq->size > 0 ) { - n[1].handle = n[pq->size].handle; - h[n[1].handle].node = 1; - - h[hMin].key = NULL; - h[hMin].node = pq->freeList; - pq->freeList = hMin; - - if( -- pq->size > 0 ) { - FloatDown( pq, 1 ); - } - } - return min; -} - -/* really pqHeapDelete */ -void pqHeapDelete( PriorityQHeap *pq, PQhandle hCurr ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - int curr; - - assert( hCurr >= 1 && hCurr <= pq->max && h[hCurr].key != NULL ); - - curr = h[hCurr].node; - n[curr].handle = n[pq->size].handle; - h[n[curr].handle].node = curr; - - if( curr <= -- pq->size ) { - if( curr <= 1 || LEQ( h[n[curr>>1].handle].key, h[n[curr].handle].key )) { - FloatDown( pq, curr ); - } else { - FloatUp( pq, curr ); - } - } - h[hCurr].key = NULL; - h[hCurr].node = pq->freeList; - pq->freeList = hCurr; -} - - - -/* Now redefine all the function names to map to their "Sort" versions. */ - -/* really tessPqSortNewPriorityQ */ -PriorityQ *pqNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) ) -{ - PriorityQ *pq = (PriorityQ *)alloc->memalloc( alloc->userData, sizeof( PriorityQ )); - if (pq == NULL) return NULL; - - pq->heap = pqHeapNewPriorityQ( alloc, size, leq ); - if (pq->heap == NULL) { - alloc->memfree( alloc->userData, pq ); - return NULL; - } - -// pq->keys = (PQkey *)memAlloc( INIT_SIZE * sizeof(pq->keys[0]) ); - pq->keys = (PQkey *)alloc->memalloc( alloc->userData, size * sizeof(pq->keys[0]) ); - if (pq->keys == NULL) { - pqHeapDeletePriorityQ( alloc, pq->heap ); - alloc->memfree( alloc->userData, pq ); - return NULL; - } - - pq->size = 0; - pq->max = size; //INIT_SIZE; - pq->initialized = FALSE; - pq->leq = leq; - - return pq; -} - -/* really tessPqSortDeletePriorityQ */ -void pqDeletePriorityQ( TESSalloc* alloc, PriorityQ *pq ) -{ - assert(pq != NULL); - if (pq->heap != NULL) pqHeapDeletePriorityQ( alloc, pq->heap ); - if (pq->order != NULL) alloc->memfree( alloc->userData, pq->order ); - if (pq->keys != NULL) alloc->memfree( alloc->userData, pq->keys ); - alloc->memfree( alloc->userData, pq ); -} - - -#define LT(x,y) (! LEQ(y,x)) -#define GT(x,y) (! LEQ(x,y)) -#define Swap(a,b) if(1){PQkey *tmp = *a; *a = *b; *b = tmp;}else - -/* really tessPqSortInit */ -int pqInit( TESSalloc* alloc, PriorityQ *pq ) -{ - PQkey **p, **r, **i, **j, *piv; - struct { PQkey **p, **r; } Stack[50], *top = Stack; - unsigned int seed = 2016473283; - - /* Create an array of indirect pointers to the keys, so that we - * the handles we have returned are still valid. - */ - /* - pq->order = (PQkey **)memAlloc( (size_t) - (pq->size * sizeof(pq->order[0])) ); - */ - pq->order = (PQkey **)alloc->memalloc( alloc->userData, - (size_t)((pq->size+1) * sizeof(pq->order[0])) ); - /* the previous line is a patch to compensate for the fact that IBM */ - /* machines return a null on a malloc of zero bytes (unlike SGI), */ - /* so we have to put in this defense to guard against a memory */ - /* fault four lines down. from fossum@austin.ibm.com. */ - if (pq->order == NULL) return 0; - - p = pq->order; - r = p + pq->size - 1; - for( piv = pq->keys, i = p; i <= r; ++piv, ++i ) { - *i = piv; - } - - /* Sort the indirect pointers in descending order, - * using randomized Quicksort - */ - top->p = p; top->r = r; ++top; - while( --top >= Stack ) { - p = top->p; - r = top->r; - while( r > p + 10 ) { - seed = seed * 1539415821 + 1; - i = p + seed % (r - p + 1); - piv = *i; - *i = *p; - *p = piv; - i = p - 1; - j = r + 1; - do { - do { ++i; } while( GT( **i, *piv )); - do { --j; } while( LT( **j, *piv )); - Swap( i, j ); - } while( i < j ); - Swap( i, j ); /* Undo last swap */ - if( i - p < r - j ) { - top->p = j+1; top->r = r; ++top; - r = i-1; - } else { - top->p = p; top->r = i-1; ++top; - p = j+1; - } - } - /* Insertion sort small lists */ - for( i = p+1; i <= r; ++i ) { - piv = *i; - for( j = i; j > p && LT( **(j-1), *piv ); --j ) { - *j = *(j-1); - } - *j = piv; - } - } - pq->max = pq->size; - pq->initialized = TRUE; - pqHeapInit( pq->heap ); /* always succeeds */ - -#ifndef NDEBUG - p = pq->order; - r = p + pq->size - 1; - for( i = p; i < r; ++i ) { - assert( LEQ( **(i+1), **i )); - } -#endif - - return 1; -} - -/* really tessPqSortInsert */ -/* returns INV_HANDLE iff out of memory */ -PQhandle pqInsert( TESSalloc* alloc, PriorityQ *pq, PQkey keyNew ) -{ - int curr; - - if( pq->initialized ) { - return pqHeapInsert( alloc, pq->heap, keyNew ); - } - curr = pq->size; - if( ++ pq->size >= pq->max ) { - if (!alloc->memrealloc) - { - return INV_HANDLE; - } - else - { - PQkey *saveKey= pq->keys; - // If the heap overflows, double its size. - pq->max <<= 1; - pq->keys = (PQkey *)alloc->memrealloc( alloc->userData, pq->keys, - (size_t)(pq->max * sizeof( pq->keys[0] ))); - if (pq->keys == NULL) { - pq->keys = saveKey; // restore ptr to free upon return - return INV_HANDLE; - } - } - } - assert(curr != INV_HANDLE); - pq->keys[curr] = keyNew; - - /* Negative handles index the sorted array. */ - return -(curr+1); -} - -/* really tessPqSortExtractMin */ -PQkey pqExtractMin( PriorityQ *pq ) -{ - PQkey sortMin, heapMin; - - if( pq->size == 0 ) { - return pqHeapExtractMin( pq->heap ); - } - sortMin = *(pq->order[pq->size-1]); - if( ! pqHeapIsEmpty( pq->heap )) { - heapMin = pqHeapMinimum( pq->heap ); - if( LEQ( heapMin, sortMin )) { - return pqHeapExtractMin( pq->heap ); - } - } - do { - -- pq->size; - } while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ); - return sortMin; -} - -/* really tessPqSortMinimum */ -PQkey pqMinimum( PriorityQ *pq ) -{ - PQkey sortMin, heapMin; - - if( pq->size == 0 ) { - return pqHeapMinimum( pq->heap ); - } - sortMin = *(pq->order[pq->size-1]); - if( ! pqHeapIsEmpty( pq->heap )) { - heapMin = pqHeapMinimum( pq->heap ); - if( LEQ( heapMin, sortMin )) { - return heapMin; - } - } - return sortMin; -} - -/* really tessPqSortIsEmpty */ -int pqIsEmpty( PriorityQ *pq ) -{ - return (pq->size == 0) && pqHeapIsEmpty( pq->heap ); -} - -/* really tessPqSortDelete */ -void pqDelete( PriorityQ *pq, PQhandle curr ) -{ - if( curr >= 0 ) { - pqHeapDelete( pq->heap, curr ); - return; - } - curr = -(curr+1); - assert( curr < pq->max && pq->keys[curr] != NULL ); - - pq->keys[curr] = NULL; - while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ) { - -- pq->size; - } -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.h b/submodules/LottieMeshSwift/libtess2/Sources/priorityq.h deleted file mode 100755 index 6ef1c05f56..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.h +++ /dev/null @@ -1,104 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef PRIORITYQ_H -#define PRIORITYQ_H - -/* The basic operations are insertion of a new key (pqInsert), -* and examination/extraction of a key whose value is minimum -* (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete); -* for this purpose pqInsert returns a "handle" which is supplied -* as the argument. -* -* An initial heap may be created efficiently by calling pqInsert -* repeatedly, then calling pqInit. In any case pqInit must be called -* before any operations other than pqInsert are used. -* -* If the heap is empty, pqMinimum/pqExtractMin will return a NULL key. -* This may also be tested with pqIsEmpty. -*/ - -/* Since we support deletion the data structure is a little more -* complicated than an ordinary heap. "nodes" is the heap itself; -* active nodes are stored in the range 1..pq->size. When the -* heap exceeds its allocated size (pq->max), its size doubles. -* The children of node i are nodes 2i and 2i+1. -* -* Each node stores an index into an array "handles". Each handle -* stores a key, plus a pointer back to the node which currently -* represents that key (ie. nodes[handles[i].node].handle == i). -*/ - -typedef void *PQkey; -typedef int PQhandle; -typedef struct PriorityQHeap PriorityQHeap; - -#define INV_HANDLE 0x0fffffff - -typedef struct { PQhandle handle; } PQnode; -typedef struct { PQkey key; PQhandle node; } PQhandleElem; - -struct PriorityQHeap { - - PQnode *nodes; - PQhandleElem *handles; - int size, max; - PQhandle freeList; - int initialized; - - int (*leq)(PQkey key1, PQkey key2); -}; - -typedef struct PriorityQ PriorityQ; - -struct PriorityQ { - PriorityQHeap *heap; - - PQkey *keys; - PQkey **order; - PQhandle size, max; - int initialized; - - int (*leq)(PQkey key1, PQkey key2); -}; - -PriorityQ *pqNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) ); -void pqDeletePriorityQ( TESSalloc* alloc, PriorityQ *pq ); - -int pqInit( TESSalloc* alloc, PriorityQ *pq ); -PQhandle pqInsert( TESSalloc* alloc, PriorityQ *pq, PQkey key ); -PQkey pqExtractMin( PriorityQ *pq ); -void pqDelete( PriorityQ *pq, PQhandle handle ); - -PQkey pqMinimum( PriorityQ *pq ); -int pqIsEmpty( PriorityQ *pq ); - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/sweep.c b/submodules/LottieMeshSwift/libtess2/Sources/sweep.c deleted file mode 100755 index 32a56bf406..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/sweep.c +++ /dev/null @@ -1,1324 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#include -#include -#include /* longjmp */ - -#include "mesh.h" -#include "geom.h" -#include "tess.h" -#include "dict.h" -#include "priorityq.h" -#include "bucketalloc.h" -#include "sweep.h" - -#define TRUE 1 -#define FALSE 0 - -#ifdef FOR_TRITE_TEST_PROGRAM -extern void DebugEvent( TESStesselator *tess ); -#else -#define DebugEvent( tess ) -#endif - -/* -* Invariants for the Edge Dictionary. -* - each pair of adjacent edges e2=Succ(e1) satisfies EdgeLeq(e1,e2) -* at any valid location of the sweep event -* - if EdgeLeq(e2,e1) as well (at any valid sweep event), then e1 and e2 -* share a common endpoint -* - for each e, e->Dst has been processed, but not e->Org -* - each edge e satisfies VertLeq(e->Dst,event) && VertLeq(event,e->Org) -* where "event" is the current sweep line event. -* - no edge e has zero length -* -* Invariants for the Mesh (the processed portion). -* - the portion of the mesh left of the sweep line is a planar graph, -* ie. there is *some* way to embed it in the plane -* - no processed edge has zero length -* - no two processed vertices have identical coordinates -* - each "inside" region is monotone, ie. can be broken into two chains -* of monotonically increasing vertices according to VertLeq(v1,v2) -* - a non-invariant: these chains may intersect (very slightly) -* -* Invariants for the Sweep. -* - if none of the edges incident to the event vertex have an activeRegion -* (ie. none of these edges are in the edge dictionary), then the vertex -* has only right-going edges. -* - if an edge is marked "fixUpperEdge" (it is a temporary edge introduced -* by ConnectRightVertex), then it is the only right-going edge from -* its associated vertex. (This says that these edges exist only -* when it is necessary.) -*/ - -#define MAX(x,y) ((x) >= (y) ? (x) : (y)) -#define MIN(x,y) ((x) <= (y) ? (x) : (y)) - -/* When we merge two edges into one, we need to compute the combined -* winding of the new edge. -*/ -#define AddWinding(eDst,eSrc) (eDst->winding += eSrc->winding, \ - eDst->Sym->winding += eSrc->Sym->winding) - -static void SweepEvent( TESStesselator *tess, TESSvertex *vEvent ); -static void WalkDirtyRegions( TESStesselator *tess, ActiveRegion *regUp ); -static int CheckForRightSplice( TESStesselator *tess, ActiveRegion *regUp ); - -static int EdgeLeq( TESStesselator *tess, ActiveRegion *reg1, ActiveRegion *reg2 ) -/* -* Both edges must be directed from right to left (this is the canonical -* direction for the upper edge of each region). -* -* The strategy is to evaluate a "t" value for each edge at the -* current sweep line position, given by tess->event. The calculations -* are designed to be very stable, but of course they are not perfect. -* -* Special case: if both edge destinations are at the sweep event, -* we sort the edges by slope (they would otherwise compare equally). -*/ -{ - TESSvertex *event = tess->event; - TESShalfEdge *e1, *e2; - TESSreal t1, t2; - - e1 = reg1->eUp; - e2 = reg2->eUp; - - if( e1->Dst == event ) { - if( e2->Dst == event ) { - /* Two edges right of the sweep line which meet at the sweep event. - * Sort them by slope. - */ - if( VertLeq( e1->Org, e2->Org )) { - return EdgeSign( e2->Dst, e1->Org, e2->Org ) <= 0; - } - return EdgeSign( e1->Dst, e2->Org, e1->Org ) >= 0; - } - return EdgeSign( e2->Dst, event, e2->Org ) <= 0; - } - if( e2->Dst == event ) { - return EdgeSign( e1->Dst, event, e1->Org ) >= 0; - } - - /* General case - compute signed distance *from* e1, e2 to event */ - t1 = EdgeEval( e1->Dst, event, e1->Org ); - t2 = EdgeEval( e2->Dst, event, e2->Org ); - return (t1 >= t2); -} - - -static void DeleteRegion( TESStesselator *tess, ActiveRegion *reg ) -{ - if( reg->fixUpperEdge ) { - /* It was created with zero winding number, so it better be - * deleted with zero winding number (ie. it better not get merged - * with a real edge). - */ - assert( reg->eUp->winding == 0 ); - } - reg->eUp->activeRegion = NULL; - dictDelete( tess->dict, reg->nodeUp ); - bucketFree( tess->regionPool, reg ); -} - - -static int FixUpperEdge( TESStesselator *tess, ActiveRegion *reg, TESShalfEdge *newEdge ) -/* -* Replace an upper edge which needs fixing (see ConnectRightVertex). -*/ -{ - assert( reg->fixUpperEdge ); - if ( !tessMeshDelete( tess->mesh, reg->eUp ) ) return 0; - reg->fixUpperEdge = FALSE; - reg->eUp = newEdge; - newEdge->activeRegion = reg; - - return 1; -} - -static ActiveRegion *TopLeftRegion( TESStesselator *tess, ActiveRegion *reg ) -{ - TESSvertex *org = reg->eUp->Org; - TESShalfEdge *e; - - /* Find the region above the uppermost edge with the same origin */ - do { - reg = RegionAbove( reg ); - } while( reg->eUp->Org == org ); - - /* If the edge above was a temporary edge introduced by ConnectRightVertex, - * now is the time to fix it. - */ - if( reg->fixUpperEdge ) { - e = tessMeshConnect( tess->mesh, RegionBelow(reg)->eUp->Sym, reg->eUp->Lnext ); - if (e == NULL) return NULL; - if ( !FixUpperEdge( tess, reg, e ) ) return NULL; - reg = RegionAbove( reg ); - } - return reg; -} - -static ActiveRegion *TopRightRegion( ActiveRegion *reg ) -{ - TESSvertex *dst = reg->eUp->Dst; - - /* Find the region above the uppermost edge with the same destination */ - do { - reg = RegionAbove( reg ); - } while( reg->eUp->Dst == dst ); - return reg; -} - -static ActiveRegion *AddRegionBelow( TESStesselator *tess, - ActiveRegion *regAbove, - TESShalfEdge *eNewUp ) -/* -* Add a new active region to the sweep line, *somewhere* below "regAbove" -* (according to where the new edge belongs in the sweep-line dictionary). -* The upper edge of the new region will be "eNewUp". -* Winding number and "inside" flag are not updated. -*/ -{ - ActiveRegion *regNew = (ActiveRegion *)bucketAlloc( tess->regionPool ); - if (regNew == NULL) longjmp(tess->env,1); - - regNew->eUp = eNewUp; - regNew->nodeUp = dictInsertBefore( tess->dict, regAbove->nodeUp, regNew ); - if (regNew->nodeUp == NULL) longjmp(tess->env,1); - regNew->fixUpperEdge = FALSE; - regNew->sentinel = FALSE; - regNew->dirty = FALSE; - - eNewUp->activeRegion = regNew; - return regNew; -} - -static int IsWindingInside( TESStesselator *tess, int n ) -{ - switch( tess->windingRule ) { - case TESS_WINDING_ODD: - return (n & 1); - case TESS_WINDING_NONZERO: - return (n != 0); - case TESS_WINDING_POSITIVE: - return (n > 0); - case TESS_WINDING_NEGATIVE: - return (n < 0); - case TESS_WINDING_ABS_GEQ_TWO: - return (n >= 2) || (n <= -2); - } - /*LINTED*/ - assert( FALSE ); - /*NOTREACHED*/ - - return( FALSE ); -} - - -static void ComputeWinding( TESStesselator *tess, ActiveRegion *reg ) -{ - reg->windingNumber = RegionAbove(reg)->windingNumber + reg->eUp->winding; - reg->inside = IsWindingInside( tess, reg->windingNumber ); -} - - -static void FinishRegion( TESStesselator *tess, ActiveRegion *reg ) -/* -* Delete a region from the sweep line. This happens when the upper -* and lower chains of a region meet (at a vertex on the sweep line). -* The "inside" flag is copied to the appropriate mesh face (we could -* not do this before -- since the structure of the mesh is always -* changing, this face may not have even existed until now). -*/ -{ - TESShalfEdge *e = reg->eUp; - TESSface *f = e->Lface; - - f->inside = reg->inside; - f->anEdge = e; /* optimization for tessMeshTessellateMonoRegion() */ - DeleteRegion( tess, reg ); -} - - -static TESShalfEdge *FinishLeftRegions( TESStesselator *tess, - ActiveRegion *regFirst, ActiveRegion *regLast ) -/* -* We are given a vertex with one or more left-going edges. All affected -* edges should be in the edge dictionary. Starting at regFirst->eUp, -* we walk down deleting all regions where both edges have the same -* origin vOrg. At the same time we copy the "inside" flag from the -* active region to the face, since at this point each face will belong -* to at most one region (this was not necessarily true until this point -* in the sweep). The walk stops at the region above regLast; if regLast -* is NULL we walk as far as possible. At the same time we relink the -* mesh if necessary, so that the ordering of edges around vOrg is the -* same as in the dictionary. -*/ -{ - ActiveRegion *reg, *regPrev; - TESShalfEdge *e, *ePrev; - - regPrev = regFirst; - ePrev = regFirst->eUp; - while( regPrev != regLast ) { - regPrev->fixUpperEdge = FALSE; /* placement was OK */ - reg = RegionBelow( regPrev ); - e = reg->eUp; - if( e->Org != ePrev->Org ) { - if( ! reg->fixUpperEdge ) { - /* Remove the last left-going edge. Even though there are no further - * edges in the dictionary with this origin, there may be further - * such edges in the mesh (if we are adding left edges to a vertex - * that has already been processed). Thus it is important to call - * FinishRegion rather than just DeleteRegion. - */ - FinishRegion( tess, regPrev ); - break; - } - /* If the edge below was a temporary edge introduced by - * ConnectRightVertex, now is the time to fix it. - */ - e = tessMeshConnect( tess->mesh, ePrev->Lprev, e->Sym ); - if (e == NULL) longjmp(tess->env,1); - if ( !FixUpperEdge( tess, reg, e ) ) longjmp(tess->env,1); - } - - /* Relink edges so that ePrev->Onext == e */ - if( ePrev->Onext != e ) { - if ( !tessMeshSplice( tess->mesh, e->Oprev, e ) ) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, ePrev, e ) ) longjmp(tess->env,1); - } - FinishRegion( tess, regPrev ); /* may change reg->eUp */ - ePrev = reg->eUp; - regPrev = reg; - } - return ePrev; -} - - -static void AddRightEdges( TESStesselator *tess, ActiveRegion *regUp, - TESShalfEdge *eFirst, TESShalfEdge *eLast, TESShalfEdge *eTopLeft, - int cleanUp ) -/* -* Purpose: insert right-going edges into the edge dictionary, and update -* winding numbers and mesh connectivity appropriately. All right-going -* edges share a common origin vOrg. Edges are inserted CCW starting at -* eFirst; the last edge inserted is eLast->Oprev. If vOrg has any -* left-going edges already processed, then eTopLeft must be the edge -* such that an imaginary upward vertical segment from vOrg would be -* contained between eTopLeft->Oprev and eTopLeft; otherwise eTopLeft -* should be NULL. -*/ -{ - ActiveRegion *reg, *regPrev; - TESShalfEdge *e, *ePrev; - int firstTime = TRUE; - - /* Insert the new right-going edges in the dictionary */ - e = eFirst; - do { - assert( VertLeq( e->Org, e->Dst )); - AddRegionBelow( tess, regUp, e->Sym ); - e = e->Onext; - } while ( e != eLast ); - - /* Walk *all* right-going edges from e->Org, in the dictionary order, - * updating the winding numbers of each region, and re-linking the mesh - * edges to match the dictionary ordering (if necessary). - */ - if( eTopLeft == NULL ) { - eTopLeft = RegionBelow( regUp )->eUp->Rprev; - } - regPrev = regUp; - ePrev = eTopLeft; - for( ;; ) { - reg = RegionBelow( regPrev ); - e = reg->eUp->Sym; - if( e->Org != ePrev->Org ) break; - - if( e->Onext != ePrev ) { - /* Unlink e from its current position, and relink below ePrev */ - if ( !tessMeshSplice( tess->mesh, e->Oprev, e ) ) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, ePrev->Oprev, e ) ) longjmp(tess->env,1); - } - /* Compute the winding number and "inside" flag for the new regions */ - reg->windingNumber = regPrev->windingNumber - e->winding; - reg->inside = IsWindingInside( tess, reg->windingNumber ); - - /* Check for two outgoing edges with same slope -- process these - * before any intersection tests (see example in tessComputeInterior). - */ - regPrev->dirty = TRUE; - if( ! firstTime && CheckForRightSplice( tess, regPrev )) { - AddWinding( e, ePrev ); - DeleteRegion( tess, regPrev ); - if ( !tessMeshDelete( tess->mesh, ePrev ) ) longjmp(tess->env,1); - } - firstTime = FALSE; - regPrev = reg; - ePrev = e; - } - regPrev->dirty = TRUE; - assert( regPrev->windingNumber - e->winding == reg->windingNumber ); - - if( cleanUp ) { - /* Check for intersections between newly adjacent edges. */ - WalkDirtyRegions( tess, regPrev ); - } -} - - -static void SpliceMergeVertices( TESStesselator *tess, TESShalfEdge *e1, - TESShalfEdge *e2 ) -/* -* Two vertices with idential coordinates are combined into one. -* e1->Org is kept, while e2->Org is discarded. -*/ -{ - if ( !tessMeshSplice( tess->mesh, e1, e2 ) ) longjmp(tess->env,1); -} - -static void VertexWeights( TESSvertex *isect, TESSvertex *org, TESSvertex *dst, - TESSreal *weights ) -/* -* Find some weights which describe how the intersection vertex is -* a linear combination of "org" and "dest". Each of the two edges -* which generated "isect" is allocated 50% of the weight; each edge -* splits the weight between its org and dst according to the -* relative distance to "isect". -*/ -{ - TESSreal t1 = VertL1dist( org, isect ); - TESSreal t2 = VertL1dist( dst, isect ); - - weights[0] = (TESSreal)0.5 * t2 / (t1 + t2); - weights[1] = (TESSreal)0.5 * t1 / (t1 + t2); - isect->coords[0] += weights[0]*org->coords[0] + weights[1]*dst->coords[0]; - isect->coords[1] += weights[0]*org->coords[1] + weights[1]*dst->coords[1]; - isect->coords[2] += weights[0]*org->coords[2] + weights[1]*dst->coords[2]; -} - - -static void GetIntersectData( TESStesselator *tess, TESSvertex *isect, - TESSvertex *orgUp, TESSvertex *dstUp, - TESSvertex *orgLo, TESSvertex *dstLo ) - /* - * We've computed a new intersection point, now we need a "data" pointer - * from the user so that we can refer to this new vertex in the - * rendering callbacks. - */ -{ - TESSreal weights[4]; - TESS_NOTUSED( tess ); - - isect->coords[0] = isect->coords[1] = isect->coords[2] = 0; - isect->idx = TESS_UNDEF; - VertexWeights( isect, orgUp, dstUp, &weights[0] ); - VertexWeights( isect, orgLo, dstLo, &weights[2] ); -} - -static int CheckForRightSplice( TESStesselator *tess, ActiveRegion *regUp ) -/* -* Check the upper and lower edge of "regUp", to make sure that the -* eUp->Org is above eLo, or eLo->Org is below eUp (depending on which -* origin is leftmost). -* -* The main purpose is to splice right-going edges with the same -* dest vertex and nearly identical slopes (ie. we can't distinguish -* the slopes numerically). However the splicing can also help us -* to recover from numerical errors. For example, suppose at one -* point we checked eUp and eLo, and decided that eUp->Org is barely -* above eLo. Then later, we split eLo into two edges (eg. from -* a splice operation like this one). This can change the result of -* our test so that now eUp->Org is incident to eLo, or barely below it. -* We must correct this condition to maintain the dictionary invariants. -* -* One possibility is to check these edges for intersection again -* (ie. CheckForIntersect). This is what we do if possible. However -* CheckForIntersect requires that tess->event lies between eUp and eLo, -* so that it has something to fall back on when the intersection -* calculation gives us an unusable answer. So, for those cases where -* we can't check for intersection, this routine fixes the problem -* by just splicing the offending vertex into the other edge. -* This is a guaranteed solution, no matter how degenerate things get. -* Basically this is a combinatorial solution to a numerical problem. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - - if( VertLeq( eUp->Org, eLo->Org )) { - if( EdgeSign( eLo->Dst, eUp->Org, eLo->Org ) > 0 ) return FALSE; - - /* eUp->Org appears to be below eLo */ - if( ! VertEq( eUp->Org, eLo->Org )) { - /* Splice eUp->Org into eLo */ - if ( tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eUp, eLo->Oprev ) ) longjmp(tess->env,1); - regUp->dirty = regLo->dirty = TRUE; - - } else if( eUp->Org != eLo->Org ) { - /* merge the two vertices, discarding eUp->Org */ - pqDelete( tess->pq, eUp->Org->pqHandle ); - SpliceMergeVertices( tess, eLo->Oprev, eUp ); - } - } else { - if( EdgeSign( eUp->Dst, eLo->Org, eUp->Org ) <= 0 ) return FALSE; - - /* eLo->Org appears to be above eUp, so splice eLo->Org into eUp */ - RegionAbove(regUp)->dirty = regUp->dirty = TRUE; - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Oprev, eUp ) ) longjmp(tess->env,1); - } - return TRUE; -} - -static int CheckForLeftSplice( TESStesselator *tess, ActiveRegion *regUp ) -/* -* Check the upper and lower edge of "regUp", to make sure that the -* eUp->Dst is above eLo, or eLo->Dst is below eUp (depending on which -* destination is rightmost). -* -* Theoretically, this should always be true. However, splitting an edge -* into two pieces can change the results of previous tests. For example, -* suppose at one point we checked eUp and eLo, and decided that eUp->Dst -* is barely above eLo. Then later, we split eLo into two edges (eg. from -* a splice operation like this one). This can change the result of -* the test so that now eUp->Dst is incident to eLo, or barely below it. -* We must correct this condition to maintain the dictionary invariants -* (otherwise new edges might get inserted in the wrong place in the -* dictionary, and bad stuff will happen). -* -* We fix the problem by just splicing the offending vertex into the -* other edge. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - TESShalfEdge *e; - - assert( ! VertEq( eUp->Dst, eLo->Dst )); - - if( VertLeq( eUp->Dst, eLo->Dst )) { - if( EdgeSign( eUp->Dst, eLo->Dst, eUp->Org ) < 0 ) return FALSE; - - /* eLo->Dst is above eUp, so splice eLo->Dst into eUp */ - RegionAbove(regUp)->dirty = regUp->dirty = TRUE; - e = tessMeshSplitEdge( tess->mesh, eUp ); - if (e == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Sym, e ) ) longjmp(tess->env,1); - e->Lface->inside = regUp->inside; - } else { - if( EdgeSign( eLo->Dst, eUp->Dst, eLo->Org ) > 0 ) return FALSE; - - /* eUp->Dst is below eLo, so splice eUp->Dst into eLo */ - regUp->dirty = regLo->dirty = TRUE; - e = tessMeshSplitEdge( tess->mesh, eLo ); - if (e == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eUp->Lnext, eLo->Sym ) ) longjmp(tess->env,1); - e->Rface->inside = regUp->inside; - } - return TRUE; -} - - -static int CheckForIntersect( TESStesselator *tess, ActiveRegion *regUp ) -/* -* Check the upper and lower edges of the given region to see if -* they intersect. If so, create the intersection and add it -* to the data structures. -* -* Returns TRUE if adding the new intersection resulted in a recursive -* call to AddRightEdges(); in this case all "dirty" regions have been -* checked for intersections, and possibly regUp has been deleted. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - TESSvertex *orgUp = eUp->Org; - TESSvertex *orgLo = eLo->Org; - TESSvertex *dstUp = eUp->Dst; - TESSvertex *dstLo = eLo->Dst; - TESSreal tMinUp, tMaxLo; - TESSvertex isect, *orgMin; - TESShalfEdge *e; - - assert( ! VertEq( dstLo, dstUp )); - assert( EdgeSign( dstUp, tess->event, orgUp ) <= 0 ); - assert( EdgeSign( dstLo, tess->event, orgLo ) >= 0 ); - assert( orgUp != tess->event && orgLo != tess->event ); - assert( ! regUp->fixUpperEdge && ! regLo->fixUpperEdge ); - - if( orgUp == orgLo ) return FALSE; /* right endpoints are the same */ - - tMinUp = MIN( orgUp->t, dstUp->t ); - tMaxLo = MAX( orgLo->t, dstLo->t ); - if( tMinUp > tMaxLo ) return FALSE; /* t ranges do not overlap */ - - if( VertLeq( orgUp, orgLo )) { - if( EdgeSign( dstLo, orgUp, orgLo ) > 0 ) return FALSE; - } else { - if( EdgeSign( dstUp, orgLo, orgUp ) < 0 ) return FALSE; - } - - /* At this point the edges intersect, at least marginally */ - DebugEvent( tess ); - - tesedgeIntersect( dstUp, orgUp, dstLo, orgLo, &isect ); - /* The following properties are guaranteed: */ - assert( MIN( orgUp->t, dstUp->t ) <= isect.t ); - assert( isect.t <= MAX( orgLo->t, dstLo->t )); - assert( MIN( dstLo->s, dstUp->s ) <= isect.s ); - assert( isect.s <= MAX( orgLo->s, orgUp->s )); - - if( VertLeq( &isect, tess->event )) { - /* The intersection point lies slightly to the left of the sweep line, - * so move it until it''s slightly to the right of the sweep line. - * (If we had perfect numerical precision, this would never happen - * in the first place). The easiest and safest thing to do is - * replace the intersection by tess->event. - */ - isect.s = tess->event->s; - isect.t = tess->event->t; - } - /* Similarly, if the computed intersection lies to the right of the - * rightmost origin (which should rarely happen), it can cause - * unbelievable inefficiency on sufficiently degenerate inputs. - * (If you have the test program, try running test54.d with the - * "X zoom" option turned on). - */ - orgMin = VertLeq( orgUp, orgLo ) ? orgUp : orgLo; - if( VertLeq( orgMin, &isect )) { - isect.s = orgMin->s; - isect.t = orgMin->t; - } - - if( VertEq( &isect, orgUp ) || VertEq( &isect, orgLo )) { - /* Easy case -- intersection at one of the right endpoints */ - (void) CheckForRightSplice( tess, regUp ); - return FALSE; - } - - if( (! VertEq( dstUp, tess->event ) - && EdgeSign( dstUp, tess->event, &isect ) >= 0) - || (! VertEq( dstLo, tess->event ) - && EdgeSign( dstLo, tess->event, &isect ) <= 0 )) - { - /* Very unusual -- the new upper or lower edge would pass on the - * wrong side of the sweep event, or through it. This can happen - * due to very small numerical errors in the intersection calculation. - */ - if( dstLo == tess->event ) { - /* Splice dstLo into eUp, and process the new region(s) */ - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Sym, eUp ) ) longjmp(tess->env,1); - regUp = TopLeftRegion( tess, regUp ); - if (regUp == NULL) longjmp(tess->env,1); - eUp = RegionBelow(regUp)->eUp; - FinishLeftRegions( tess, RegionBelow(regUp), regLo ); - AddRightEdges( tess, regUp, eUp->Oprev, eUp, eUp, TRUE ); - return TRUE; - } - if( dstUp == tess->event ) { - /* Splice dstUp into eLo, and process the new region(s) */ - if (tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eUp->Lnext, eLo->Oprev ) ) longjmp(tess->env,1); - regLo = regUp; - regUp = TopRightRegion( regUp ); - e = RegionBelow(regUp)->eUp->Rprev; - regLo->eUp = eLo->Oprev; - eLo = FinishLeftRegions( tess, regLo, NULL ); - AddRightEdges( tess, regUp, eLo->Onext, eUp->Rprev, e, TRUE ); - return TRUE; - } - /* Special case: called from ConnectRightVertex. If either - * edge passes on the wrong side of tess->event, split it - * (and wait for ConnectRightVertex to splice it appropriately). - */ - if( EdgeSign( dstUp, tess->event, &isect ) >= 0 ) { - RegionAbove(regUp)->dirty = regUp->dirty = TRUE; - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - eUp->Org->s = tess->event->s; - eUp->Org->t = tess->event->t; - } - if( EdgeSign( dstLo, tess->event, &isect ) <= 0 ) { - regUp->dirty = regLo->dirty = TRUE; - if (tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - eLo->Org->s = tess->event->s; - eLo->Org->t = tess->event->t; - } - /* leave the rest for ConnectRightVertex */ - return FALSE; - } - - /* General case -- split both edges, splice into new vertex. - * When we do the splice operation, the order of the arguments is - * arbitrary as far as correctness goes. However, when the operation - * creates a new face, the work done is proportional to the size of - * the new face. We expect the faces in the processed part of - * the mesh (ie. eUp->Lface) to be smaller than the faces in the - * unprocessed original contours (which will be eLo->Oprev->Lface). - */ - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - if (tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Oprev, eUp ) ) longjmp(tess->env,1); - eUp->Org->s = isect.s; - eUp->Org->t = isect.t; - eUp->Org->pqHandle = pqInsert( &tess->alloc, tess->pq, eUp->Org ); - if (eUp->Org->pqHandle == INV_HANDLE) { - pqDeletePriorityQ( &tess->alloc, tess->pq ); - tess->pq = NULL; - longjmp(tess->env,1); - } - GetIntersectData( tess, eUp->Org, orgUp, dstUp, orgLo, dstLo ); - RegionAbove(regUp)->dirty = regUp->dirty = regLo->dirty = TRUE; - return FALSE; -} - -static void WalkDirtyRegions( TESStesselator *tess, ActiveRegion *regUp ) -/* -* When the upper or lower edge of any region changes, the region is -* marked "dirty". This routine walks through all the dirty regions -* and makes sure that the dictionary invariants are satisfied -* (see the comments at the beginning of this file). Of course -* new dirty regions can be created as we make changes to restore -* the invariants. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp, *eLo; - - for( ;; ) { - /* Find the lowest dirty region (we walk from the bottom up). */ - while( regLo->dirty ) { - regUp = regLo; - regLo = RegionBelow(regLo); - } - if( ! regUp->dirty ) { - regLo = regUp; - regUp = RegionAbove( regUp ); - if( regUp == NULL || ! regUp->dirty ) { - /* We've walked all the dirty regions */ - return; - } - } - regUp->dirty = FALSE; - eUp = regUp->eUp; - eLo = regLo->eUp; - - if( eUp->Dst != eLo->Dst ) { - /* Check that the edge ordering is obeyed at the Dst vertices. */ - if( CheckForLeftSplice( tess, regUp )) { - - /* If the upper or lower edge was marked fixUpperEdge, then - * we no longer need it (since these edges are needed only for - * vertices which otherwise have no right-going edges). - */ - if( regLo->fixUpperEdge ) { - DeleteRegion( tess, regLo ); - if ( !tessMeshDelete( tess->mesh, eLo ) ) longjmp(tess->env,1); - regLo = RegionBelow( regUp ); - eLo = regLo->eUp; - } else if( regUp->fixUpperEdge ) { - DeleteRegion( tess, regUp ); - if ( !tessMeshDelete( tess->mesh, eUp ) ) longjmp(tess->env,1); - regUp = RegionAbove( regLo ); - eUp = regUp->eUp; - } - } - } - if( eUp->Org != eLo->Org ) { - if( eUp->Dst != eLo->Dst - && ! regUp->fixUpperEdge && ! regLo->fixUpperEdge - && (eUp->Dst == tess->event || eLo->Dst == tess->event) ) - { - /* When all else fails in CheckForIntersect(), it uses tess->event - * as the intersection location. To make this possible, it requires - * that tess->event lie between the upper and lower edges, and also - * that neither of these is marked fixUpperEdge (since in the worst - * case it might splice one of these edges into tess->event, and - * violate the invariant that fixable edges are the only right-going - * edge from their associated vertex). - */ - if( CheckForIntersect( tess, regUp )) { - /* WalkDirtyRegions() was called recursively; we're done */ - return; - } - } else { - /* Even though we can't use CheckForIntersect(), the Org vertices - * may violate the dictionary edge ordering. Check and correct this. - */ - (void) CheckForRightSplice( tess, regUp ); - } - } - if( eUp->Org == eLo->Org && eUp->Dst == eLo->Dst ) { - /* A degenerate loop consisting of only two edges -- delete it. */ - AddWinding( eLo, eUp ); - DeleteRegion( tess, regUp ); - if ( !tessMeshDelete( tess->mesh, eUp ) ) longjmp(tess->env,1); - regUp = RegionAbove( regLo ); - } - } -} - - -static void ConnectRightVertex( TESStesselator *tess, ActiveRegion *regUp, - TESShalfEdge *eBottomLeft ) -/* -* Purpose: connect a "right" vertex vEvent (one where all edges go left) -* to the unprocessed portion of the mesh. Since there are no right-going -* edges, two regions (one above vEvent and one below) are being merged -* into one. "regUp" is the upper of these two regions. -* -* There are two reasons for doing this (adding a right-going edge): -* - if the two regions being merged are "inside", we must add an edge -* to keep them separated (the combined region would not be monotone). -* - in any case, we must leave some record of vEvent in the dictionary, -* so that we can merge vEvent with features that we have not seen yet. -* For example, maybe there is a vertical edge which passes just to -* the right of vEvent; we would like to splice vEvent into this edge. -* -* However, we don't want to connect vEvent to just any vertex. We don''t -* want the new edge to cross any other edges; otherwise we will create -* intersection vertices even when the input data had no self-intersections. -* (This is a bad thing; if the user's input data has no intersections, -* we don't want to generate any false intersections ourselves.) -* -* Our eventual goal is to connect vEvent to the leftmost unprocessed -* vertex of the combined region (the union of regUp and regLo). -* But because of unseen vertices with all right-going edges, and also -* new vertices which may be created by edge intersections, we don''t -* know where that leftmost unprocessed vertex is. In the meantime, we -* connect vEvent to the closest vertex of either chain, and mark the region -* as "fixUpperEdge". This flag says to delete and reconnect this edge -* to the next processed vertex on the boundary of the combined region. -* Quite possibly the vertex we connected to will turn out to be the -* closest one, in which case we won''t need to make any changes. -*/ -{ - TESShalfEdge *eNew; - TESShalfEdge *eTopLeft = eBottomLeft->Onext; - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - int degenerate = FALSE; - - if( eUp->Dst != eLo->Dst ) { - (void) CheckForIntersect( tess, regUp ); - } - - /* Possible new degeneracies: upper or lower edge of regUp may pass - * through vEvent, or may coincide with new intersection vertex - */ - if( VertEq( eUp->Org, tess->event )) { - if ( !tessMeshSplice( tess->mesh, eTopLeft->Oprev, eUp ) ) longjmp(tess->env,1); - regUp = TopLeftRegion( tess, regUp ); - if (regUp == NULL) longjmp(tess->env,1); - eTopLeft = RegionBelow( regUp )->eUp; - FinishLeftRegions( tess, RegionBelow(regUp), regLo ); - degenerate = TRUE; - } - if( VertEq( eLo->Org, tess->event )) { - if ( !tessMeshSplice( tess->mesh, eBottomLeft, eLo->Oprev ) ) longjmp(tess->env,1); - eBottomLeft = FinishLeftRegions( tess, regLo, NULL ); - degenerate = TRUE; - } - if( degenerate ) { - AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE ); - return; - } - - /* Non-degenerate situation -- need to add a temporary, fixable edge. - * Connect to the closer of eLo->Org, eUp->Org. - */ - if( VertLeq( eLo->Org, eUp->Org )) { - eNew = eLo->Oprev; - } else { - eNew = eUp; - } - eNew = tessMeshConnect( tess->mesh, eBottomLeft->Lprev, eNew ); - if (eNew == NULL) longjmp(tess->env,1); - - /* Prevent cleanup, otherwise eNew might disappear before we've even - * had a chance to mark it as a temporary edge. - */ - AddRightEdges( tess, regUp, eNew, eNew->Onext, eNew->Onext, FALSE ); - eNew->Sym->activeRegion->fixUpperEdge = TRUE; - WalkDirtyRegions( tess, regUp ); -} - -/* Because vertices at exactly the same location are merged together -* before we process the sweep event, some degenerate cases can't occur. -* However if someone eventually makes the modifications required to -* merge features which are close together, the cases below marked -* TOLERANCE_NONZERO will be useful. They were debugged before the -* code to merge identical vertices in the main loop was added. -*/ -#define TOLERANCE_NONZERO FALSE - -static void ConnectLeftDegenerate( TESStesselator *tess, - ActiveRegion *regUp, TESSvertex *vEvent ) -/* -* The event vertex lies exacty on an already-processed edge or vertex. -* Adding the new vertex involves splicing it into the already-processed -* part of the mesh. -*/ -{ - TESShalfEdge *e, *eTopLeft, *eTopRight, *eLast; - ActiveRegion *reg; - - e = regUp->eUp; - if( VertEq( e->Org, vEvent )) { - /* e->Org is an unprocessed vertex - just combine them, and wait - * for e->Org to be pulled from the queue - */ - assert( TOLERANCE_NONZERO ); - SpliceMergeVertices( tess, e, vEvent->anEdge ); - return; - } - - if( ! VertEq( e->Dst, vEvent )) { - /* General case -- splice vEvent into edge e which passes through it */ - if (tessMeshSplitEdge( tess->mesh, e->Sym ) == NULL) longjmp(tess->env,1); - if( regUp->fixUpperEdge ) { - /* This edge was fixable -- delete unused portion of original edge */ - if ( !tessMeshDelete( tess->mesh, e->Onext ) ) longjmp(tess->env,1); - regUp->fixUpperEdge = FALSE; - } - if ( !tessMeshSplice( tess->mesh, vEvent->anEdge, e ) ) longjmp(tess->env,1); - SweepEvent( tess, vEvent ); /* recurse */ - return; - } - - /* vEvent coincides with e->Dst, which has already been processed. - * Splice in the additional right-going edges. - */ - assert( TOLERANCE_NONZERO ); - regUp = TopRightRegion( regUp ); - reg = RegionBelow( regUp ); - eTopRight = reg->eUp->Sym; - eTopLeft = eLast = eTopRight->Onext; - if( reg->fixUpperEdge ) { - /* Here e->Dst has only a single fixable edge going right. - * We can delete it since now we have some real right-going edges. - */ - assert( eTopLeft != eTopRight ); /* there are some left edges too */ - DeleteRegion( tess, reg ); - if ( !tessMeshDelete( tess->mesh, eTopRight ) ) longjmp(tess->env,1); - eTopRight = eTopLeft->Oprev; - } - if ( !tessMeshSplice( tess->mesh, vEvent->anEdge, eTopRight ) ) longjmp(tess->env,1); - if( ! EdgeGoesLeft( eTopLeft )) { - /* e->Dst had no left-going edges -- indicate this to AddRightEdges() */ - eTopLeft = NULL; - } - AddRightEdges( tess, regUp, eTopRight->Onext, eLast, eTopLeft, TRUE ); -} - - -static void ConnectLeftVertex( TESStesselator *tess, TESSvertex *vEvent ) -/* -* Purpose: connect a "left" vertex (one where both edges go right) -* to the processed portion of the mesh. Let R be the active region -* containing vEvent, and let U and L be the upper and lower edge -* chains of R. There are two possibilities: -* -* - the normal case: split R into two regions, by connecting vEvent to -* the rightmost vertex of U or L lying to the left of the sweep line -* -* - the degenerate case: if vEvent is close enough to U or L, we -* merge vEvent into that edge chain. The subcases are: -* - merging with the rightmost vertex of U or L -* - merging with the active edge of U or L -* - merging with an already-processed portion of U or L -*/ -{ - ActiveRegion *regUp, *regLo, *reg; - TESShalfEdge *eUp, *eLo, *eNew; - ActiveRegion tmp; - - /* assert( vEvent->anEdge->Onext->Onext == vEvent->anEdge ); */ - - /* Get a pointer to the active region containing vEvent */ - tmp.eUp = vEvent->anEdge->Sym; - /* __GL_DICTLISTKEY */ /* tessDictListSearch */ - regUp = (ActiveRegion *)dictKey( dictSearch( tess->dict, &tmp )); - regLo = RegionBelow( regUp ); - if( !regLo ) { - // This may happen if the input polygon is coplanar. - return; - } - eUp = regUp->eUp; - eLo = regLo->eUp; - - /* Try merging with U or L first */ - if( EdgeSign( eUp->Dst, vEvent, eUp->Org ) == 0 ) { - ConnectLeftDegenerate( tess, regUp, vEvent ); - return; - } - - /* Connect vEvent to rightmost processed vertex of either chain. - * e->Dst is the vertex that we will connect to vEvent. - */ - reg = VertLeq( eLo->Dst, eUp->Dst ) ? regUp : regLo; - - if( regUp->inside || reg->fixUpperEdge) { - if( reg == regUp ) { - eNew = tessMeshConnect( tess->mesh, vEvent->anEdge->Sym, eUp->Lnext ); - if (eNew == NULL) longjmp(tess->env,1); - } else { - TESShalfEdge *tempHalfEdge= tessMeshConnect( tess->mesh, eLo->Dnext, vEvent->anEdge); - if (tempHalfEdge == NULL) longjmp(tess->env,1); - - eNew = tempHalfEdge->Sym; - } - if( reg->fixUpperEdge ) { - if ( !FixUpperEdge( tess, reg, eNew ) ) longjmp(tess->env,1); - } else { - ComputeWinding( tess, AddRegionBelow( tess, regUp, eNew )); - } - SweepEvent( tess, vEvent ); - } else { - /* The new vertex is in a region which does not belong to the polygon. - * We don''t need to connect this vertex to the rest of the mesh. - */ - AddRightEdges( tess, regUp, vEvent->anEdge, vEvent->anEdge, NULL, TRUE ); - } -} - - -static void SweepEvent( TESStesselator *tess, TESSvertex *vEvent ) -/* -* Does everything necessary when the sweep line crosses a vertex. -* Updates the mesh and the edge dictionary. -*/ -{ - ActiveRegion *regUp, *reg; - TESShalfEdge *e, *eTopLeft, *eBottomLeft; - - tess->event = vEvent; /* for access in EdgeLeq() */ - DebugEvent( tess ); - - /* Check if this vertex is the right endpoint of an edge that is - * already in the dictionary. In this case we don't need to waste - * time searching for the location to insert new edges. - */ - e = vEvent->anEdge; - while( e->activeRegion == NULL ) { - e = e->Onext; - if( e == vEvent->anEdge ) { - /* All edges go right -- not incident to any processed edges */ - ConnectLeftVertex( tess, vEvent ); - return; - } - } - - /* Processing consists of two phases: first we "finish" all the - * active regions where both the upper and lower edges terminate - * at vEvent (ie. vEvent is closing off these regions). - * We mark these faces "inside" or "outside" the polygon according - * to their winding number, and delete the edges from the dictionary. - * This takes care of all the left-going edges from vEvent. - */ - regUp = TopLeftRegion( tess, e->activeRegion ); - if (regUp == NULL) longjmp(tess->env,1); - reg = RegionBelow( regUp ); - eTopLeft = reg->eUp; - eBottomLeft = FinishLeftRegions( tess, reg, NULL ); - - /* Next we process all the right-going edges from vEvent. This - * involves adding the edges to the dictionary, and creating the - * associated "active regions" which record information about the - * regions between adjacent dictionary edges. - */ - if( eBottomLeft->Onext == eTopLeft ) { - /* No right-going edges -- add a temporary "fixable" edge */ - ConnectRightVertex( tess, regUp, eBottomLeft ); - } else { - AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE ); - } -} - - -/* Make the sentinel coordinates big enough that they will never be -* merged with real input features. -*/ - -static void AddSentinel( TESStesselator *tess, TESSreal smin, TESSreal smax, TESSreal t ) -/* -* We add two sentinel edges above and below all other edges, -* to avoid special cases at the top and bottom. -*/ -{ - TESShalfEdge *e; - ActiveRegion *reg = (ActiveRegion *)bucketAlloc( tess->regionPool ); - if (reg == NULL) longjmp(tess->env,1); - - e = tessMeshMakeEdge( tess->mesh ); - if (e == NULL) longjmp(tess->env,1); - - e->Org->s = smax; - e->Org->t = t; - e->Dst->s = smin; - e->Dst->t = t; - tess->event = e->Dst; /* initialize it */ - - reg->eUp = e; - reg->windingNumber = 0; - reg->inside = FALSE; - reg->fixUpperEdge = FALSE; - reg->sentinel = TRUE; - reg->dirty = FALSE; - reg->nodeUp = dictInsert( tess->dict, reg ); - if (reg->nodeUp == NULL) longjmp(tess->env,1); -} - - -static void InitEdgeDict( TESStesselator *tess ) -/* -* We maintain an ordering of edge intersections with the sweep line. -* This order is maintained in a dynamic dictionary. -*/ -{ - TESSreal w, h; - TESSreal smin, smax, tmin, tmax; - - tess->dict = dictNewDict( &tess->alloc, tess, (int (*)(void *, DictKey, DictKey)) EdgeLeq ); - if (tess->dict == NULL) longjmp(tess->env,1); - - /* If the bbox is empty, ensure that sentinels are not coincident by slightly enlarging it. */ - w = (tess->bmax[0] - tess->bmin[0]) + (TESSreal)0.01; - h = (tess->bmax[1] - tess->bmin[1]) + (TESSreal)0.01; - - smin = tess->bmin[0] - w; - smax = tess->bmax[0] + w; - tmin = tess->bmin[1] - h; - tmax = tess->bmax[1] + h; - - AddSentinel( tess, smin, smax, tmin ); - AddSentinel( tess, smin, smax, tmax ); -} - - -static void DoneEdgeDict( TESStesselator *tess ) -{ - ActiveRegion *reg; - int fixedEdges = 0; - - while( (reg = (ActiveRegion *)dictKey( dictMin( tess->dict ))) != NULL ) { - /* - * At the end of all processing, the dictionary should contain - * only the two sentinel edges, plus at most one "fixable" edge - * created by ConnectRightVertex(). - */ - if( ! reg->sentinel ) { - assert( reg->fixUpperEdge ); - assert( ++fixedEdges == 1 ); - } - assert( reg->windingNumber == 0 ); - DeleteRegion( tess, reg ); - /* tessMeshDelete( reg->eUp );*/ - } - dictDeleteDict( &tess->alloc, tess->dict ); -} - - -static void RemoveDegenerateEdges( TESStesselator *tess ) -/* -* Remove zero-length edges, and contours with fewer than 3 vertices. -*/ -{ - TESShalfEdge *e, *eNext, *eLnext; - TESShalfEdge *eHead = &tess->mesh->eHead; - - /*LINTED*/ - for( e = eHead->next; e != eHead; e = eNext ) { - eNext = e->next; - eLnext = e->Lnext; - - if( VertEq( e->Org, e->Dst ) && e->Lnext->Lnext != e ) { - /* Zero-length edge, contour has at least 3 edges */ - - SpliceMergeVertices( tess, eLnext, e ); /* deletes e->Org */ - if ( !tessMeshDelete( tess->mesh, e ) ) longjmp(tess->env,1); /* e is a self-loop */ - e = eLnext; - eLnext = e->Lnext; - } - if( eLnext->Lnext == e ) { - /* Degenerate contour (one or two edges) */ - - if( eLnext != e ) { - if( eLnext == eNext || eLnext == eNext->Sym ) { eNext = eNext->next; } - if ( !tessMeshDelete( tess->mesh, eLnext ) ) longjmp(tess->env,1); - } - if( e == eNext || e == eNext->Sym ) { eNext = eNext->next; } - if ( !tessMeshDelete( tess->mesh, e ) ) longjmp(tess->env,1); - } - } -} - -static int InitPriorityQ( TESStesselator *tess ) -/* -* Insert all vertices into the priority queue which determines the -* order in which vertices cross the sweep line. -*/ -{ - PriorityQ *pq; - TESSvertex *v, *vHead; - int vertexCount = 0; - - vHead = &tess->mesh->vHead; - for( v = vHead->next; v != vHead; v = v->next ) { - vertexCount++; - } - /* Make sure there is enough space for sentinels. */ - vertexCount += MAX( 8, tess->alloc.extraVertices ); - - pq = tess->pq = pqNewPriorityQ( &tess->alloc, vertexCount, (int (*)(PQkey, PQkey)) tesvertLeq ); - if (pq == NULL) return 0; - - vHead = &tess->mesh->vHead; - for( v = vHead->next; v != vHead; v = v->next ) { - v->pqHandle = pqInsert( &tess->alloc, pq, v ); - if (v->pqHandle == INV_HANDLE) - break; - } - if (v != vHead || !pqInit( &tess->alloc, pq ) ) { - pqDeletePriorityQ( &tess->alloc, tess->pq ); - tess->pq = NULL; - return 0; - } - - return 1; -} - - -static void DonePriorityQ( TESStesselator *tess ) -{ - pqDeletePriorityQ( &tess->alloc, tess->pq ); -} - - -static int RemoveDegenerateFaces( TESStesselator *tess, TESSmesh *mesh ) -/* -* Delete any degenerate faces with only two edges. WalkDirtyRegions() -* will catch almost all of these, but it won't catch degenerate faces -* produced by splice operations on already-processed edges. -* The two places this can happen are in FinishLeftRegions(), when -* we splice in a "temporary" edge produced by ConnectRightVertex(), -* and in CheckForLeftSplice(), where we splice already-processed -* edges to ensure that our dictionary invariants are not violated -* by numerical errors. -* -* In both these cases it is *very* dangerous to delete the offending -* edge at the time, since one of the routines further up the stack -* will sometimes be keeping a pointer to that edge. -*/ -{ - TESSface *f, *fNext; - TESShalfEdge *e; - - /*LINTED*/ - for( f = mesh->fHead.next; f != &mesh->fHead; f = fNext ) { - fNext = f->next; - e = f->anEdge; - assert( e->Lnext != e ); - - if( e->Lnext->Lnext == e ) { - /* A face with only two edges */ - AddWinding( e->Onext, e ); - if ( !tessMeshDelete( tess->mesh, e ) ) return 0; - } - } - return 1; -} - -int tessComputeInterior( TESStesselator *tess ) -/* -* tessComputeInterior( tess ) computes the planar arrangement specified -* by the given contours, and further subdivides this arrangement -* into regions. Each region is marked "inside" if it belongs -* to the polygon, according to the rule given by tess->windingRule. -* Each interior region is guaranteed be monotone. -*/ -{ - TESSvertex *v, *vNext; - - /* Each vertex defines an event for our sweep line. Start by inserting - * all the vertices in a priority queue. Events are processed in - * lexicographic order, ie. - * - * e1 < e2 iff e1.x < e2.x || (e1.x == e2.x && e1.y < e2.y) - */ - RemoveDegenerateEdges( tess ); - if ( !InitPriorityQ( tess ) ) return 0; /* if error */ - InitEdgeDict( tess ); - - while( (v = (TESSvertex *)pqExtractMin( tess->pq )) != NULL ) { - for( ;; ) { - vNext = (TESSvertex *)pqMinimum( tess->pq ); - if( vNext == NULL || ! VertEq( vNext, v )) break; - - /* Merge together all vertices at exactly the same location. - * This is more efficient than processing them one at a time, - * simplifies the code (see ConnectLeftDegenerate), and is also - * important for correct handling of certain degenerate cases. - * For example, suppose there are two identical edges A and B - * that belong to different contours (so without this code they would - * be processed by separate sweep events). Suppose another edge C - * crosses A and B from above. When A is processed, we split it - * at its intersection point with C. However this also splits C, - * so when we insert B we may compute a slightly different - * intersection point. This might leave two edges with a small - * gap between them. This kind of error is especially obvious - * when using boundary extraction (TESS_BOUNDARY_ONLY). - */ - vNext = (TESSvertex *)pqExtractMin( tess->pq ); - SpliceMergeVertices( tess, v->anEdge, vNext->anEdge ); - } - SweepEvent( tess, v ); - } - - /* Set tess->event for debugging purposes */ - tess->event = ((ActiveRegion *) dictKey( dictMin( tess->dict )))->eUp->Org; - DebugEvent( tess ); - DoneEdgeDict( tess ); - DonePriorityQ( tess ); - - if ( !RemoveDegenerateFaces( tess, tess->mesh ) ) return 0; - tessMeshCheckMesh( tess->mesh ); - - return 1; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/sweep.h b/submodules/LottieMeshSwift/libtess2/Sources/sweep.h deleted file mode 100755 index 32f0f86dca..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/sweep.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef SWEEP_H -#define SWEEP_H - -#include "mesh.h" - -/* tessComputeInterior( tess ) computes the planar arrangement specified -* by the given contours, and further subdivides this arrangement -* into regions. Each region is marked "inside" if it belongs -* to the polygon, according to the rule given by tess->windingRule. -* Each interior region is guaranteed be monotone. -*/ -int tessComputeInterior( TESStesselator *tess ); - - -/* The following is here *only* for access by debugging routines */ - -#include "dict.h" - -/* For each pair of adjacent edges crossing the sweep line, there is -* an ActiveRegion to represent the region between them. The active -* regions are kept in sorted order in a dynamic dictionary. As the -* sweep line crosses each vertex, we update the affected regions. -*/ - -struct ActiveRegion { - TESShalfEdge *eUp; /* upper edge, directed right to left */ - DictNode *nodeUp; /* dictionary node corresponding to eUp */ - int windingNumber; /* used to determine which regions are - * inside the polygon */ - int inside; /* is this region inside the polygon? */ - int sentinel; /* marks fake edges at t = +/-infinity */ - int dirty; /* marks regions where the upper or lower - * edge has changed, but we haven't checked - * whether they intersect yet */ - int fixUpperEdge; /* marks temporary edges introduced when - * we process a "right vertex" (one without - * any edges leaving to the right) */ -}; - -#define RegionBelow(r) ((ActiveRegion *) dictKey(dictPred((r)->nodeUp))) -#define RegionAbove(r) ((ActiveRegion *) dictKey(dictSucc((r)->nodeUp))) - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/tess.c b/submodules/LottieMeshSwift/libtess2/Sources/tess.c deleted file mode 100755 index 5f47f8de06..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/tess.c +++ /dev/null @@ -1,1114 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#include -#include -#include -#include "bucketalloc.h" -#include "tess.h" -#include "mesh.h" -#include "sweep.h" -#include "geom.h" -#include -#include -#include - -#define TRUE 1 -#define FALSE 0 - -#define Dot(u,v) (u[0]*v[0] + u[1]*v[1] + u[2]*v[2]) - -#if defined(FOR_TRITE_TEST_PROGRAM) || defined(TRUE_PROJECT) -static void Normalize( TESSreal v[3] ) -{ - TESSreal len = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; - - assert( len > 0 ); - len = sqrtf( len ); - v[0] /= len; - v[1] /= len; - v[2] /= len; -} -#endif - -#define ABS(x) ((x) < 0 ? -(x) : (x)) - -static int LongAxis( TESSreal v[3] ) -{ - int i = 0; - - if( ABS(v[1]) > ABS(v[0]) ) { i = 1; } - if( ABS(v[2]) > ABS(v[i]) ) { i = 2; } - return i; -} - -static int ShortAxis( TESSreal v[3] ) -{ - int i = 0; - - if( ABS(v[1]) < ABS(v[0]) ) { i = 1; } - if( ABS(v[2]) < ABS(v[i]) ) { i = 2; } - return i; -} - -static void ComputeNormal( TESStesselator *tess, TESSreal norm[3] ) -{ - TESSvertex *v, *v1, *v2; - TESSreal c, tLen2, maxLen2; - TESSreal maxVal[3], minVal[3], d1[3], d2[3], tNorm[3]; - TESSvertex *maxVert[3], *minVert[3]; - TESSvertex *vHead = &tess->mesh->vHead; - int i; - - v = vHead->next; - for( i = 0; i < 3; ++i ) { - c = v->coords[i]; - minVal[i] = c; - minVert[i] = v; - maxVal[i] = c; - maxVert[i] = v; - } - - for( v = vHead->next; v != vHead; v = v->next ) { - for( i = 0; i < 3; ++i ) { - c = v->coords[i]; - if( c < minVal[i] ) { minVal[i] = c; minVert[i] = v; } - if( c > maxVal[i] ) { maxVal[i] = c; maxVert[i] = v; } - } - } - - /* Find two vertices separated by at least 1/sqrt(3) of the maximum - * distance between any two vertices - */ - i = 0; - if( maxVal[1] - minVal[1] > maxVal[0] - minVal[0] ) { i = 1; } - if( maxVal[2] - minVal[2] > maxVal[i] - minVal[i] ) { i = 2; } - if( minVal[i] >= maxVal[i] ) { - /* All vertices are the same -- normal doesn't matter */ - norm[0] = 0; norm[1] = 0; norm[2] = 1; - return; - } - - /* Look for a third vertex which forms the triangle with maximum area - * (Length of normal == twice the triangle area) - */ - maxLen2 = 0; - v1 = minVert[i]; - v2 = maxVert[i]; - d1[0] = v1->coords[0] - v2->coords[0]; - d1[1] = v1->coords[1] - v2->coords[1]; - d1[2] = v1->coords[2] - v2->coords[2]; - for( v = vHead->next; v != vHead; v = v->next ) { - d2[0] = v->coords[0] - v2->coords[0]; - d2[1] = v->coords[1] - v2->coords[1]; - d2[2] = v->coords[2] - v2->coords[2]; - tNorm[0] = d1[1]*d2[2] - d1[2]*d2[1]; - tNorm[1] = d1[2]*d2[0] - d1[0]*d2[2]; - tNorm[2] = d1[0]*d2[1] - d1[1]*d2[0]; - tLen2 = tNorm[0]*tNorm[0] + tNorm[1]*tNorm[1] + tNorm[2]*tNorm[2]; - if( tLen2 > maxLen2 ) { - maxLen2 = tLen2; - norm[0] = tNorm[0]; - norm[1] = tNorm[1]; - norm[2] = tNorm[2]; - } - } - - if( maxLen2 <= 0 ) { - /* All points lie on a single line -- any decent normal will do */ - norm[0] = norm[1] = norm[2] = 0; - norm[ShortAxis(d1)] = 1; - } -} - - -static void CheckOrientation( TESStesselator *tess ) -{ - TESSreal area; - TESSface *f, *fHead = &tess->mesh->fHead; - TESSvertex *v, *vHead = &tess->mesh->vHead; - TESShalfEdge *e; - - /* When we compute the normal automatically, we choose the orientation - * so that the the sum of the signed areas of all contours is non-negative. - */ - area = 0; - for( f = fHead->next; f != fHead; f = f->next ) { - e = f->anEdge; - if( e->winding <= 0 ) continue; - do { - area += (e->Org->s - e->Dst->s) * (e->Org->t + e->Dst->t); - e = e->Lnext; - } while( e != f->anEdge ); - } - if( area < 0 ) { - /* Reverse the orientation by flipping all the t-coordinates */ - for( v = vHead->next; v != vHead; v = v->next ) { - v->t = - v->t; - } - tess->tUnit[0] = - tess->tUnit[0]; - tess->tUnit[1] = - tess->tUnit[1]; - tess->tUnit[2] = - tess->tUnit[2]; - } -} - -#ifdef FOR_TRITE_TEST_PROGRAM -#include -extern int RandomSweep; -#define S_UNIT_X (RandomSweep ? (2*drand48()-1) : 1.0) -#define S_UNIT_Y (RandomSweep ? (2*drand48()-1) : 0.0) -#else -#if defined(SLANTED_SWEEP) -/* The "feature merging" is not intended to be complete. There are -* special cases where edges are nearly parallel to the sweep line -* which are not implemented. The algorithm should still behave -* robustly (ie. produce a reasonable tesselation) in the presence -* of such edges, however it may miss features which could have been -* merged. We could minimize this effect by choosing the sweep line -* direction to be something unusual (ie. not parallel to one of the -* coordinate axes). -*/ -#define S_UNIT_X (TESSreal)0.50941539564955385 /* Pre-normalized */ -#define S_UNIT_Y (TESSreal)0.86052074622010633 -#else -#define S_UNIT_X (TESSreal)1.0 -#define S_UNIT_Y (TESSreal)0.0 -#endif -#endif - -/* Determine the polygon normal and project vertices onto the plane -* of the polygon. -*/ -void tessProjectPolygon( TESStesselator *tess ) -{ - TESSvertex *v, *vHead = &tess->mesh->vHead; - TESSreal norm[3]; - TESSreal *sUnit, *tUnit; - int i, first, computedNormal = FALSE; - - norm[0] = tess->normal[0]; - norm[1] = tess->normal[1]; - norm[2] = tess->normal[2]; - if( norm[0] == 0 && norm[1] == 0 && norm[2] == 0 ) { - ComputeNormal( tess, norm ); - computedNormal = TRUE; - } - sUnit = tess->sUnit; - tUnit = tess->tUnit; - i = LongAxis( norm ); - -#if defined(FOR_TRITE_TEST_PROGRAM) || defined(TRUE_PROJECT) - /* Choose the initial sUnit vector to be approximately perpendicular - * to the normal. - */ - Normalize( norm ); - - sUnit[i] = 0; - sUnit[(i+1)%3] = S_UNIT_X; - sUnit[(i+2)%3] = S_UNIT_Y; - - /* Now make it exactly perpendicular */ - w = Dot( sUnit, norm ); - sUnit[0] -= w * norm[0]; - sUnit[1] -= w * norm[1]; - sUnit[2] -= w * norm[2]; - Normalize( sUnit ); - - /* Choose tUnit so that (sUnit,tUnit,norm) form a right-handed frame */ - tUnit[0] = norm[1]*sUnit[2] - norm[2]*sUnit[1]; - tUnit[1] = norm[2]*sUnit[0] - norm[0]*sUnit[2]; - tUnit[2] = norm[0]*sUnit[1] - norm[1]*sUnit[0]; - Normalize( tUnit ); -#else - /* Project perpendicular to a coordinate axis -- better numerically */ - sUnit[i] = 0; - sUnit[(i+1)%3] = S_UNIT_X; - sUnit[(i+2)%3] = S_UNIT_Y; - - tUnit[i] = 0; - tUnit[(i+1)%3] = (norm[i] > 0) ? -S_UNIT_Y : S_UNIT_Y; - tUnit[(i+2)%3] = (norm[i] > 0) ? S_UNIT_X : -S_UNIT_X; -#endif - - /* Project the vertices onto the sweep plane */ - for( v = vHead->next; v != vHead; v = v->next ) - { - v->s = Dot( v->coords, sUnit ); - v->t = Dot( v->coords, tUnit ); - } - if( computedNormal ) { - CheckOrientation( tess ); - } - - /* Compute ST bounds. */ - first = 1; - for( v = vHead->next; v != vHead; v = v->next ) - { - if (first) - { - tess->bmin[0] = tess->bmax[0] = v->s; - tess->bmin[1] = tess->bmax[1] = v->t; - first = 0; - } - else - { - if (v->s < tess->bmin[0]) tess->bmin[0] = v->s; - if (v->s > tess->bmax[0]) tess->bmax[0] = v->s; - if (v->t < tess->bmin[1]) tess->bmin[1] = v->t; - if (v->t > tess->bmax[1]) tess->bmax[1] = v->t; - } - } -} - -#define AddWinding(eDst,eSrc) (eDst->winding += eSrc->winding, \ - eDst->Sym->winding += eSrc->Sym->winding) - -/* tessMeshTessellateMonoRegion( face ) tessellates a monotone region -* (what else would it do??) The region must consist of a single -* loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this -* case means that any vertical line intersects the interior of the -* region in a single interval. -* -* Tessellation consists of adding interior edges (actually pairs of -* half-edges), to split the region into non-overlapping triangles. -* -* The basic idea is explained in Preparata and Shamos (which I don''t -* have handy right now), although their implementation is more -* complicated than this one. The are two edge chains, an upper chain -* and a lower chain. We process all vertices from both chains in order, -* from right to left. -* -* The algorithm ensures that the following invariant holds after each -* vertex is processed: the untessellated region consists of two -* chains, where one chain (say the upper) is a single edge, and -* the other chain is concave. The left vertex of the single edge -* is always to the left of all vertices in the concave chain. -* -* Each step consists of adding the rightmost unprocessed vertex to one -* of the two chains, and forming a fan of triangles from the rightmost -* of two chain endpoints. Determining whether we can add each triangle -* to the fan is a simple orientation test. By making the fan as large -* as possible, we restore the invariant (check it yourself). -*/ -int tessMeshTessellateMonoRegion( TESSmesh *mesh, TESSface *face ) -{ - TESShalfEdge *up, *lo; - - /* All edges are oriented CCW around the boundary of the region. - * First, find the half-edge whose origin vertex is rightmost. - * Since the sweep goes from left to right, face->anEdge should - * be close to the edge we want. - */ - up = face->anEdge; - assert( up->Lnext != up && up->Lnext->Lnext != up ); - - for( ; VertLeq( up->Dst, up->Org ); up = up->Lprev ) - ; - for( ; VertLeq( up->Org, up->Dst ); up = up->Lnext ) - ; - lo = up->Lprev; - - while( up->Lnext != lo ) { - if( VertLeq( up->Dst, lo->Org )) { - /* up->Dst is on the left. It is safe to form triangles from lo->Org. - * The EdgeGoesLeft test guarantees progress even when some triangles - * are CW, given that the upper and lower chains are truly monotone. - */ - while( lo->Lnext != up && (EdgeGoesLeft( lo->Lnext ) - || EdgeSign( lo->Org, lo->Dst, lo->Lnext->Dst ) <= 0 )) { - TESShalfEdge *tempHalfEdge= tessMeshConnect( mesh, lo->Lnext, lo ); - if (tempHalfEdge == NULL) return 0; - lo = tempHalfEdge->Sym; - } - lo = lo->Lprev; - } else { - /* lo->Org is on the left. We can make CCW triangles from up->Dst. */ - while( lo->Lnext != up && (EdgeGoesRight( up->Lprev ) - || EdgeSign( up->Dst, up->Org, up->Lprev->Org ) >= 0 )) { - TESShalfEdge *tempHalfEdge= tessMeshConnect( mesh, up, up->Lprev ); - if (tempHalfEdge == NULL) return 0; - up = tempHalfEdge->Sym; - } - up = up->Lnext; - } - } - - /* Now lo->Org == up->Dst == the leftmost vertex. The remaining region - * can be tessellated in a fan from this leftmost vertex. - */ - assert( lo->Lnext != up ); - while( lo->Lnext->Lnext != up ) { - TESShalfEdge *tempHalfEdge= tessMeshConnect( mesh, lo->Lnext, lo ); - if (tempHalfEdge == NULL) return 0; - lo = tempHalfEdge->Sym; - } - - return 1; -} - -/* tessMeshTessellateInterior( mesh ) tessellates each region of -* the mesh which is marked "inside" the polygon. Each such region -* must be monotone. -*/ -int tessMeshTessellateInterior( TESSmesh *mesh ) -{ - TESSface *f, *next; - - /*LINTED*/ - for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) { - /* Make sure we don''t try to tessellate the new triangles. */ - next = f->next; - if( f->inside ) { - if ( !tessMeshTessellateMonoRegion( mesh, f ) ) return 0; - } - } - return 1; -} - - -typedef struct EdgeStackNode EdgeStackNode; -typedef struct EdgeStack EdgeStack; - -struct EdgeStackNode { - TESShalfEdge *edge; - EdgeStackNode *next; -}; - -struct EdgeStack { - EdgeStackNode *top; - struct BucketAlloc *nodeBucket; -}; - -int stackInit( EdgeStack *stack, TESSalloc *alloc ) -{ - stack->top = NULL; - stack->nodeBucket = createBucketAlloc( alloc, "CDT nodes", sizeof(EdgeStackNode), 512 ); - return stack->nodeBucket != NULL; -} - -void stackDelete( EdgeStack *stack ) -{ - deleteBucketAlloc( stack->nodeBucket ); -} - -int stackEmpty( EdgeStack *stack ) -{ - return stack->top == NULL; -} - -void stackPush( EdgeStack *stack, TESShalfEdge *e ) -{ - EdgeStackNode *node = (EdgeStackNode *)bucketAlloc( stack->nodeBucket ); - if ( ! node ) return; - node->edge = e; - node->next = stack->top; - stack->top = node; -} - -TESShalfEdge *stackPop( EdgeStack *stack ) -{ - TESShalfEdge *e = NULL; - EdgeStackNode *node = stack->top; - if (node) { - stack->top = node->next; - e = node->edge; - bucketFree( stack->nodeBucket, node ); - } - return e; -} - - -// Starting with a valid triangulation, uses the Edge Flip algorithm to -// refine the triangulation into a Constrained Delaunay Triangulation. -void tessMeshRefineDelaunay( TESSmesh *mesh, TESSalloc *alloc ) -{ - // At this point, we have a valid, but not optimal, triangulation. - // We refine the triangulation using the Edge Flip algorithm - // - // 1) Find all internal edges - // 2) Mark all dual edges - // 3) insert all dual edges into a queue - - TESSface *f; - EdgeStack stack; - TESShalfEdge *e; - int maxFaces = 0, maxIter = 0, iter = 0; - - stackInit(&stack, alloc); - - for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) { - if ( f->inside) { - e = f->anEdge; - do { - e->mark = EdgeIsInternal(e); // Mark internal edges - if (e->mark && !e->Sym->mark) stackPush(&stack, e); // Insert into queue - e = e->Lnext; - } while (e != f->anEdge); - maxFaces++; - } - } - - // The algorithm should converge on O(n^2), since the predicate is not robust, - // we'll save guard against infinite loop. - maxIter = maxFaces * maxFaces; - - // Pop stack until we find a reversed edge - // Flip the reversed edge, and insert any of the four opposite edges - // which are internal and not already in the stack (!marked) - while (!stackEmpty(&stack) && iter < maxIter) { - e = stackPop(&stack); - e->mark = e->Sym->mark = 0; - if (!tesedgeIsLocallyDelaunay(e)) { - TESShalfEdge *edges[4]; - int i; - tessMeshFlipEdge(mesh, e); - // for each opposite edge - edges[0] = e->Lnext; - edges[1] = e->Lprev; - edges[2] = e->Sym->Lnext; - edges[3] = e->Sym->Lprev; - for (i = 0; i < 4; i++) { - if (!edges[i]->mark && EdgeIsInternal(edges[i])) { - edges[i]->mark = edges[i]->Sym->mark = 1; - stackPush(&stack, edges[i]); - } - } - } - iter++; - } - - stackDelete(&stack); -} - - -/* tessMeshDiscardExterior( mesh ) zaps (ie. sets to NULL) all faces -* which are not marked "inside" the polygon. Since further mesh operations -* on NULL faces are not allowed, the main purpose is to clean up the -* mesh so that exterior loops are not represented in the data structure. -*/ -void tessMeshDiscardExterior( TESSmesh *mesh ) -{ - TESSface *f, *next; - - /*LINTED*/ - for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) { - /* Since f will be destroyed, save its next pointer. */ - next = f->next; - if( ! f->inside ) { - tessMeshZapFace( mesh, f ); - } - } -} - -/* tessMeshSetWindingNumber( mesh, value, keepOnlyBoundary ) resets the -* winding numbers on all edges so that regions marked "inside" the -* polygon have a winding number of "value", and regions outside -* have a winding number of 0. -* -* If keepOnlyBoundary is TRUE, it also deletes all edges which do not -* separate an interior region from an exterior one. -*/ -int tessMeshSetWindingNumber( TESSmesh *mesh, int value, - int keepOnlyBoundary ) -{ - TESShalfEdge *e, *eNext; - - for( e = mesh->eHead.next; e != &mesh->eHead; e = eNext ) { - eNext = e->next; - if( e->Rface->inside != e->Lface->inside ) { - - /* This is a boundary edge (one side is interior, one is exterior). */ - e->winding = (e->Lface->inside) ? value : -value; - } else { - - /* Both regions are interior, or both are exterior. */ - if( ! keepOnlyBoundary ) { - e->winding = 0; - } else { - if ( !tessMeshDelete( mesh, e ) ) return 0; - } - } - } - return 1; -} - -void* heapAlloc( void* userData, unsigned int size ) -{ - TESS_NOTUSED( userData ); - return malloc( size ); -} - -void* heapRealloc( void *userData, void* ptr, unsigned int size ) -{ - TESS_NOTUSED( userData ); - return realloc( ptr, size ); -} - -void heapFree( void* userData, void* ptr ) -{ - TESS_NOTUSED( userData ); - free( ptr ); -} - -static TESSalloc defaulAlloc = -{ - heapAlloc, - heapRealloc, - heapFree, - 0, - 0, - 0, - 0, - 0, - 0, - 0, -}; - -TESStesselator* tessNewTess( TESSalloc* alloc ) -{ - TESStesselator* tess; - - if (alloc == NULL) - alloc = &defaulAlloc; - - /* Only initialize fields which can be changed by the api. Other fields - * are initialized where they are used. - */ - - tess = (TESStesselator *)alloc->memalloc( alloc->userData, sizeof( TESStesselator )); - if ( tess == NULL ) { - return 0; /* out of memory */ - } - tess->alloc = *alloc; - /* Check and set defaults. */ - if (tess->alloc.meshEdgeBucketSize == 0) - tess->alloc.meshEdgeBucketSize = 512; - if (tess->alloc.meshVertexBucketSize == 0) - tess->alloc.meshVertexBucketSize = 512; - if (tess->alloc.meshFaceBucketSize == 0) - tess->alloc.meshFaceBucketSize = 256; - if (tess->alloc.dictNodeBucketSize == 0) - tess->alloc.dictNodeBucketSize = 512; - if (tess->alloc.regionBucketSize == 0) - tess->alloc.regionBucketSize = 256; - - tess->normal[0] = 0; - tess->normal[1] = 0; - tess->normal[2] = 0; - - tess->bmin[0] = 0; - tess->bmin[1] = 0; - tess->bmax[0] = 0; - tess->bmax[1] = 0; - - tess->reverseContours = 0; - - tess->windingRule = TESS_WINDING_ODD; - tess->processCDT = 0; - - if (tess->alloc.regionBucketSize < 16) - tess->alloc.regionBucketSize = 16; - if (tess->alloc.regionBucketSize > 4096) - tess->alloc.regionBucketSize = 4096; - tess->regionPool = createBucketAlloc( &tess->alloc, "Regions", - sizeof(ActiveRegion), tess->alloc.regionBucketSize ); - - // Initialize to begin polygon. - tess->mesh = NULL; - - tess->outOfMemory = 0; - tess->vertexIndexCounter = 0; - - tess->vertices = 0; - tess->vertexIndices = 0; - tess->vertexCount = 0; - tess->elements = 0; - tess->elementCount = 0; - - return tess; -} - -void tessDeleteTess( TESStesselator *tess ) -{ - - struct TESSalloc alloc = tess->alloc; - - deleteBucketAlloc( tess->regionPool ); - - if( tess->mesh != NULL ) { - tessMeshDeleteMesh( &alloc, tess->mesh ); - tess->mesh = NULL; - } - if (tess->vertices != NULL) { - alloc.memfree( alloc.userData, tess->vertices ); - tess->vertices = 0; - } - if (tess->vertexIndices != NULL) { - alloc.memfree( alloc.userData, tess->vertexIndices ); - tess->vertexIndices = 0; - } - if (tess->elements != NULL) { - alloc.memfree( alloc.userData, tess->elements ); - tess->elements = 0; - } - - alloc.memfree( alloc.userData, tess ); -} - - -static TESSindex GetNeighbourFace(TESShalfEdge* edge) -{ - if (!edge->Rface) - return TESS_UNDEF; - if (!edge->Rface->inside) - return TESS_UNDEF; - return edge->Rface->n; -} - -void OutputPolymesh( TESStesselator *tess, TESSmesh *mesh, int elementType, int polySize, int vertexSize ) -{ - TESSvertex* v = 0; - TESSface* f = 0; - TESShalfEdge* edge = 0; - int maxFaceCount = 0; - int maxVertexCount = 0; - int faceVerts, i; - TESSindex *elements = 0; - TESSreal *vert; - - // Assume that the input data is triangles now. - // Try to merge as many polygons as possible - if (polySize > 3) - { - if (!tessMeshMergeConvexFaces( mesh, polySize )) - { - tess->outOfMemory = 1; - return; - } - } - - // Mark unused - for ( v = mesh->vHead.next; v != &mesh->vHead; v = v->next ) - v->n = TESS_UNDEF; - - // Create unique IDs for all vertices and faces. - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - f->n = TESS_UNDEF; - if( !f->inside ) continue; - - edge = f->anEdge; - faceVerts = 0; - do - { - v = edge->Org; - if ( v->n == TESS_UNDEF ) - { - v->n = maxVertexCount; - maxVertexCount++; - } - faceVerts++; - edge = edge->Lnext; - } - while (edge != f->anEdge); - - assert( faceVerts <= polySize ); - - f->n = maxFaceCount; - ++maxFaceCount; - } - - tess->elementCount = maxFaceCount; - if (elementType == TESS_CONNECTED_POLYGONS) - maxFaceCount *= 2; - tess->elements = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * maxFaceCount * polySize ); - if (!tess->elements) - { - tess->outOfMemory = 1; - return; - } - - tess->vertexCount = maxVertexCount; - tess->vertices = (TESSreal*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSreal) * tess->vertexCount * vertexSize ); - if (!tess->vertices) - { - tess->outOfMemory = 1; - return; - } - - tess->vertexIndices = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * tess->vertexCount ); - if (!tess->vertexIndices) - { - tess->outOfMemory = 1; - return; - } - - // Output vertices. - for ( v = mesh->vHead.next; v != &mesh->vHead; v = v->next ) - { - if ( v->n != TESS_UNDEF ) - { - // Store coordinate - vert = &tess->vertices[v->n*vertexSize]; - vert[0] = v->coords[0]; - vert[1] = v->coords[1]; - if ( vertexSize > 2 ) - vert[2] = v->coords[2]; - // Store vertex index. - tess->vertexIndices[v->n] = v->idx; - } - } - - // Output indices. - elements = tess->elements; - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - if ( !f->inside ) continue; - - // Store polygon - edge = f->anEdge; - faceVerts = 0; - do - { - v = edge->Org; - *elements++ = v->n; - faceVerts++; - edge = edge->Lnext; - } - while (edge != f->anEdge); - // Fill unused. - for (i = faceVerts; i < polySize; ++i) - *elements++ = TESS_UNDEF; - - // Store polygon connectivity - if ( elementType == TESS_CONNECTED_POLYGONS ) - { - edge = f->anEdge; - do - { - *elements++ = GetNeighbourFace( edge ); - edge = edge->Lnext; - } - while (edge != f->anEdge); - // Fill unused. - for (i = faceVerts; i < polySize; ++i) - *elements++ = TESS_UNDEF; - } - } -} - -void OutputContours( TESStesselator *tess, TESSmesh *mesh, int vertexSize ) -{ - TESSface *f = 0; - TESShalfEdge *edge = 0; - TESShalfEdge *start = 0; - TESSreal *verts = 0; - TESSindex *elements = 0; - TESSindex *vertInds = 0; - int startVert = 0; - int vertCount = 0; - - tess->vertexCount = 0; - tess->elementCount = 0; - - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - if ( !f->inside ) continue; - - start = edge = f->anEdge; - do - { - ++tess->vertexCount; - edge = edge->Lnext; - } - while ( edge != start ); - - ++tess->elementCount; - } - - tess->elements = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * tess->elementCount * 2 ); - if (!tess->elements) - { - tess->outOfMemory = 1; - return; - } - - tess->vertices = (TESSreal*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSreal) * tess->vertexCount * vertexSize ); - if (!tess->vertices) - { - tess->outOfMemory = 1; - return; - } - - tess->vertexIndices = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * tess->vertexCount ); - if (!tess->vertexIndices) - { - tess->outOfMemory = 1; - return; - } - - verts = tess->vertices; - elements = tess->elements; - vertInds = tess->vertexIndices; - - startVert = 0; - - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - if ( !f->inside ) continue; - - vertCount = 0; - start = edge = f->anEdge; - do - { - *verts++ = edge->Org->coords[0]; - *verts++ = edge->Org->coords[1]; - if ( vertexSize > 2 ) - *verts++ = edge->Org->coords[2]; - *vertInds++ = edge->Org->idx; - ++vertCount; - edge = edge->Lnext; - } - while ( edge != start ); - - elements[0] = startVert; - elements[1] = vertCount; - elements += 2; - - startVert += vertCount; - } -} - -void tessAddContour( TESStesselator *tess, int size, const void* vertices, - int stride, int numVertices ) -{ - const unsigned char *src = (const unsigned char*)vertices; - TESShalfEdge *e; - int i; - - if ( tess->mesh == NULL ) - tess->mesh = tessMeshNewMesh( &tess->alloc ); - if ( tess->mesh == NULL ) { - tess->outOfMemory = 1; - return; - } - - if ( size < 2 ) - size = 2; - if ( size > 3 ) - size = 3; - - e = NULL; - - for( i = 0; i < numVertices; ++i ) - { - const TESSreal* coords = (const TESSreal*)src; - src += stride; - - if( e == NULL ) { - /* Make a self-loop (one vertex, one edge). */ - e = tessMeshMakeEdge( tess->mesh ); - if ( e == NULL ) { - tess->outOfMemory = 1; - return; - } - if ( !tessMeshSplice( tess->mesh, e, e->Sym ) ) { - tess->outOfMemory = 1; - return; - } - } else { - /* Create a new vertex and edge which immediately follow e - * in the ordering around the left face. - */ - if ( tessMeshSplitEdge( tess->mesh, e ) == NULL ) { - tess->outOfMemory = 1; - return; - } - e = e->Lnext; - } - - /* The new vertex is now e->Org. */ - e->Org->coords[0] = coords[0]; - e->Org->coords[1] = coords[1]; - if ( size > 2 ) - e->Org->coords[2] = coords[2]; - else - e->Org->coords[2] = 0; - /* Store the insertion number so that the vertex can be later recognized. */ - e->Org->idx = tess->vertexIndexCounter++; - - /* The winding of an edge says how the winding number changes as we - * cross from the edge''s right face to its left face. We add the - * vertices in such an order that a CCW contour will add +1 to - * the winding number of the region inside the contour. - */ - e->winding = tess->reverseContours ? -1 : 1; - e->Sym->winding = tess->reverseContours ? 1 : -1; - } -} - -void tessSetOption( TESStesselator *tess, int option, int value ) -{ - switch(option) - { - case TESS_CONSTRAINED_DELAUNAY_TRIANGULATION: - tess->processCDT = value > 0 ? 1 : 0; - break; - case TESS_REVERSE_CONTOURS: - tess->reverseContours = value > 0 ? 1 : 0; - break; - } -} - - -int tessTesselate( TESStesselator *tess, int windingRule, int elementType, - int polySize, int vertexSize, const TESSreal* normal ) -{ - TESSmesh *mesh; - int rc = 1; - - if (tess->vertices != NULL) { - tess->alloc.memfree( tess->alloc.userData, tess->vertices ); - tess->vertices = 0; - } - if (tess->elements != NULL) { - tess->alloc.memfree( tess->alloc.userData, tess->elements ); - tess->elements = 0; - } - if (tess->vertexIndices != NULL) { - tess->alloc.memfree( tess->alloc.userData, tess->vertexIndices ); - tess->vertexIndices = 0; - } - - tess->vertexIndexCounter = 0; - - if (normal) - { - tess->normal[0] = normal[0]; - tess->normal[1] = normal[1]; - tess->normal[2] = normal[2]; - } - - tess->windingRule = windingRule; - - if (vertexSize < 2) - vertexSize = 2; - if (vertexSize > 3) - vertexSize = 3; - - if (setjmp(tess->env) != 0) { - /* come back here if out of memory */ - return 0; - } - - if (!tess->mesh) - { - return 0; - } - - /* Determine the polygon normal and project vertices onto the plane - * of the polygon. - */ - tessProjectPolygon( tess ); - - /* tessComputeInterior( tess ) computes the planar arrangement specified - * by the given contours, and further subdivides this arrangement - * into regions. Each region is marked "inside" if it belongs - * to the polygon, according to the rule given by tess->windingRule. - * Each interior region is guaranteed be monotone. - */ - if ( !tessComputeInterior( tess ) ) { - longjmp(tess->env,1); /* could've used a label */ - } - - mesh = tess->mesh; - - /* If the user wants only the boundary contours, we throw away all edges - * except those which separate the interior from the exterior. - * Otherwise we tessellate all the regions marked "inside". - */ - if (elementType == TESS_BOUNDARY_CONTOURS) { - rc = tessMeshSetWindingNumber( mesh, 1, TRUE ); - } else { - rc = tessMeshTessellateInterior( mesh ); - if (rc != 0 && tess->processCDT != 0) - tessMeshRefineDelaunay( mesh, &tess->alloc ); - } - if (rc == 0) longjmp(tess->env,1); /* could've used a label */ - - tessMeshCheckMesh( mesh ); - - if (elementType == TESS_BOUNDARY_CONTOURS) { - OutputContours( tess, mesh, vertexSize ); /* output contours */ - } - else - { - OutputPolymesh( tess, mesh, elementType, polySize, vertexSize ); /* output polygons */ - } - - tessMeshDeleteMesh( &tess->alloc, mesh ); - tess->mesh = NULL; - - if (tess->outOfMemory) - return 0; - return 1; -} - -int tessGetVertexCount( TESStesselator *tess ) -{ - return tess->vertexCount; -} - -const TESSreal* tessGetVertices( TESStesselator *tess ) -{ - return tess->vertices; -} - -const TESSindex* tessGetVertexIndices( TESStesselator *tess ) -{ - return tess->vertexIndices; -} - -int tessGetElementCount( TESStesselator *tess ) -{ - return tess->elementCount; -} - -const int* tessGetElements( TESStesselator *tess ) -{ - return tess->elements; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/tess.h b/submodules/LottieMeshSwift/libtess2/Sources/tess.h deleted file mode 100755 index 30fda27bc2..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/tess.h +++ /dev/null @@ -1,93 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef TESS_H -#define TESS_H - -#include -#include "bucketalloc.h" -#include "mesh.h" -#include "dict.h" -#include "priorityq.h" -#include "../Include/tesselator.h" - -#ifdef __cplusplus -extern "C" { -#endif - -//typedef struct TESStesselator TESStesselator; - -struct TESStesselator { - - /*** state needed for collecting the input data ***/ - TESSmesh *mesh; /* stores the input contours, and eventually - the tessellation itself */ - int outOfMemory; - - /*** state needed for projecting onto the sweep plane ***/ - - TESSreal normal[3]; /* user-specified normal (if provided) */ - TESSreal sUnit[3]; /* unit vector in s-direction (debugging) */ - TESSreal tUnit[3]; /* unit vector in t-direction (debugging) */ - - TESSreal bmin[2]; - TESSreal bmax[2]; - - int processCDT; /* option to run Constrained Delayney pass. */ - int reverseContours; /* tessAddContour() will treat CCW contours as CW and vice versa */ - - /*** state needed for the line sweep ***/ - int windingRule; /* rule for determining polygon interior */ - - Dict *dict; /* edge dictionary for sweep line */ - PriorityQ *pq; /* priority queue of vertex events */ - TESSvertex *event; /* current sweep event being processed */ - - struct BucketAlloc* regionPool; - - TESSindex vertexIndexCounter; - - TESSreal *vertices; - TESSindex *vertexIndices; - int vertexCount; - TESSindex *elements; - int elementCount; - - TESSalloc alloc; - - jmp_buf env; /* place to jump to when memAllocs fail */ -}; - -#ifdef __cplusplus -}; -#endif - -#endif diff --git a/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift b/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift index 99360ec901..69ec0dcfc3 100644 --- a/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift +++ b/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences @@ -29,8 +28,8 @@ public final class MediaPlaybackStoredState: Codable { } } -public func mediaPlaybackStoredState(engine: TelegramEngine, messageId: MessageId) -> Signal { - let key = ValueBoxKey(length: 20) +public func mediaPlaybackStoredState(engine: TelegramEngine, messageId: EngineMessage.Id) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) @@ -42,8 +41,8 @@ public func mediaPlaybackStoredState(engine: TelegramEngine, messageId: MessageI } } -public func updateMediaPlaybackStoredStateInteractively(engine: TelegramEngine, messageId: MessageId, state: MediaPlaybackStoredState?) -> Signal { - let key = ValueBoxKey(length: 20) +public func updateMediaPlaybackStoredStateInteractively(engine: TelegramEngine, messageId: EngineMessage.Id, state: MediaPlaybackStoredState?) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) diff --git a/submodules/MeshAnimationCache/BUILD b/submodules/MeshAnimationCache/BUILD deleted file mode 100644 index e3c8ebcdef..0000000000 --- a/submodules/MeshAnimationCache/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "MeshAnimationCache", - module_name = "MeshAnimationCache", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/LottieMeshSwift:LottieMeshSwift", - "//submodules/Postbox:Postbox", - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/GZip:GZip", - "//submodules/AppBundle:AppBundle", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift b/submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift deleted file mode 100644 index 403bf31727..0000000000 --- a/submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift +++ /dev/null @@ -1,182 +0,0 @@ -import Foundation -import LottieMeshSwift -import Postbox -import SwiftSignalKit -import GZip -import AppBundle - -public final class MeshAnimationCache { - private final class Item { - var isPending: Bool = false - let disposable = MetaDisposable() - var readyPath: String? - var animation: MeshAnimation? - } - - private let mediaBox: MediaBox - - private var items: [String: Item] = [:] - - public init(mediaBox: MediaBox) { - self.mediaBox = mediaBox - } - - public func get(resource: MediaResource) -> MeshAnimation? { - if let item = self.items[resource.id.stringRepresentation] { - if let animation = item.animation { - return animation - } else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath), options: [.alwaysMapped]) { - let buffer = MeshReadBuffer(data: data) - let animation = MeshAnimation.read(buffer: buffer) - item.animation = animation - return animation - } else { - return nil - } - } else { - let item = Item() - self.items[resource.id.stringRepresentation] = item - - let path = self.mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general) - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - item.readyPath = path - item.animation = animation - return animation - } else { - self.cache(item: item, resource: resource) - return nil - } - } - } - - public func get(bundleName: String) -> MeshAnimation? { - if let item = self.items[bundleName] { - if let animation = item.animation { - return animation - } else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath), options: [.alwaysMapped]) { - let buffer = MeshReadBuffer(data: data) - let animation = MeshAnimation.read(buffer: buffer) - item.animation = animation - return animation - } else { - return nil - } - } else { - let item = Item() - self.items[bundleName] = item - - let path = self.mediaBox.cachedRepresentationPathForId(bundleName, representationId: "mesh-animation", keepDuration: .general) - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - item.readyPath = path - item.animation = animation - return animation - } else { - self.cache(item: item, bundleName: bundleName) - return nil - } - } - } - - private func cache(item: Item, resource: MediaResource) { - let mediaBox = self.mediaBox - item.isPending = true - item.disposable.set((self.mediaBox.resourceData(resource) - |> filter { data in - return data.complete - } - |> take(1) - |> mapToSignal { data -> Signal<(MeshAnimation, String)?, NoError> in - return Signal { subscriber in - guard let zippedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.alwaysMapped]) else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - let jsonData = TGGUnzipData(zippedData, 1 * 1024 * 1024) ?? zippedData - if let tempFile = generateMeshAnimation(data: jsonData) { - mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general, tempFile: tempFile, completion: { path in - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - subscriber.putNext((animation, path)) - subscriber.putCompletion() - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - } - }) - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - - return EmptyDisposable - } - |> runOn(Queue.concurrentBackgroundQueue()) - } - |> deliverOnMainQueue).start(next: { [weak self] animationAndPath in - guard let strongSelf = self else { - return - } - if let animationAndPath = animationAndPath { - if let item = strongSelf.items[resource.id.stringRepresentation] { - item.isPending = false - item.animation = animationAndPath.0 - item.readyPath = animationAndPath.1 - } - } - })) - } - - private func cache(item: Item, bundleName: String) { - let mediaBox = self.mediaBox - item.isPending = true - item.disposable.set((Signal<(MeshAnimation, String)?, NoError> { subscriber in - guard let path = getAppBundle().path(forResource: bundleName, ofType: "tgs") else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - - guard let zippedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - let jsonData = TGGUnzipData(zippedData, 1 * 1024 * 1024) ?? zippedData - if let tempFile = generateMeshAnimation(data: jsonData) { - mediaBox.storeCachedResourceRepresentation(bundleName, representationId: "mesh-animation", keepDuration: .general, tempFile: tempFile, completion: { path in - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - subscriber.putNext((animation, path)) - subscriber.putCompletion() - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - } - }) - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - - return EmptyDisposable - } - |> runOn(Queue.concurrentDefaultQueue()) - |> deliverOnMainQueue).start(next: { [weak self] animationAndPath in - guard let strongSelf = self else { - return - } - if let animationAndPath = animationAndPath { - if let item = strongSelf.items[bundleName] { - item.isPending = false - item.animation = animationAndPath.0 - item.readyPath = animationAndPath.1 - } - } - })) - } -} diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift index 85197ec7af..ea61bbd1a4 100644 --- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift +++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift @@ -13,7 +13,6 @@ import AppBundle import LegacyMediaPickerUI import AVFoundation import UndoUI -import Postbox private struct NotificationSoundSelectionArguments { let account: Account @@ -516,20 +515,20 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co let fileName = url.lastPathComponent var maybeUrl: URL? - let tempFile = TempBox.shared.tempFile(fileName: "file.mp3") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file.mp3") do { try FileManager.default.copyItem(at: url, to: URL(fileURLWithPath: tempFile.path)) maybeUrl = URL(fileURLWithPath: tempFile.path) } catch let e { Logger.shared.log("NotificationSoundSelection", "copy file error \(e)") - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) souceUrl.stopAccessingSecurityScopedResource() return } guard let url = maybeUrl else { Logger.shared.log("NotificationSoundSelection", "temp url is nil") - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) souceUrl.stopAccessingSecurityScopedResource() return } @@ -544,7 +543,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string, timeout: nil)) souceUrl.stopAccessingSecurityScopedResource() - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return } @@ -572,7 +571,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co Logger.shared.log("NotificationSoundSelection", "track is nil") url.stopAccessingSecurityScopedResource() - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return } @@ -583,12 +582,12 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co Logger.shared.log("NotificationSoundSelection", "duration is zero") souceUrl.stopAccessingSecurityScopedResource() - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return } - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) Queue.mainQueue().async { if duration > Double(settings.maxDuration) { diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift index 41f3518fab..9eedf8738b 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift index 113573f540..4f6e313750 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/PasscodeUI/Sources/PasscodeSetupController.swift b/submodules/PasscodeUI/Sources/PasscodeSetupController.swift index 6eb37915ec..a4592b8085 100644 --- a/submodules/PasscodeUI/Sources/PasscodeSetupController.swift +++ b/submodules/PasscodeUI/Sources/PasscodeSetupController.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext diff --git a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift index 4bb391d32d..f42594e4f0 100644 --- a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift +++ b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift index 33768b8952..9427aa0c22 100644 --- a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift +++ b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift index e3cda020c3..692d1e07cf 100644 --- a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift +++ b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 6a2e0c8a76..3be80120ed 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import QuickLook -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -17,7 +16,7 @@ import PresentationDataUtils public enum AvatarGalleryEntryId: Hashable { case topImage - case image(MediaId) + case image(EngineMedia.Id) case resource(String) } @@ -27,7 +26,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id guard let peer = peer else { return .single(nil) } - return initialAvatarGalleryEntries(account: context.account, engine: context.engine, peer: peer._asPeer()) + return initialAvatarGalleryEntries(account: context.account, engine: context.engine, peer: peer) } |> distinctUntilChanged |> mapToSignal { entries -> Signal<(Bool, [AvatarGalleryEntry])?, NoError> in @@ -51,7 +50,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id lastEntry = photo } } - return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer, firstEntry: firstEntry, secondEntry: secondEntry, lastEntry: lastEntry) + return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: EnginePeer(peer), firstEntry: firstEntry, secondEntry: secondEntry, lastEntry: lastEntry) |> map(Optional.init) } else { return .single(nil) @@ -76,7 +75,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id } } -public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: PeerId) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { +public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: EnginePeer.Id) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { return context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> map { items -> (Bool, [AvatarGalleryEntry]) in return items as? (Bool, [AvatarGalleryEntry]) ?? (true, []) @@ -84,10 +83,10 @@ public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: Peer } public enum AvatarGalleryEntry: Equatable { - case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, GalleryItemIndexData?, Data?, String?) - case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, Int32?, GalleryItemIndexData?, MessageId?, Data?, String?, Bool, TelegramMediaImage.EmojiMarkup?) + case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], EnginePeer?, GalleryItemIndexData?, Data?, String?) + case image(EngineMedia.Id, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], EnginePeer?, Int32?, GalleryItemIndexData?, EngineMessage.Id?, Data?, String?, Bool, TelegramMediaImage.EmojiMarkup?) - public init(representation: TelegramMediaImageRepresentation, peer: Peer) { + public init(representation: TelegramMediaImageRepresentation, peer: EnginePeer) { self = .topImage([ImageRepresentationWithReference(representation: representation, reference: MediaResourceReference.standalone(resource: representation.resource))], [], peer, nil, nil, nil) } @@ -106,7 +105,7 @@ public enum AvatarGalleryEntry: Equatable { } } - public var peer: Peer? { + public var peer: EnginePeer? { switch self { case let .topImage(_, _, peer, _, _, _): return peer @@ -163,13 +162,13 @@ public enum AvatarGalleryEntry: Equatable { public static func ==(lhs: AvatarGalleryEntry, rhs: AvatarGalleryEntry) -> Bool { switch lhs { case let .topImage(lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsIndexData, lhsImmediateThumbnailData, lhsCategory): - if case let .topImage(rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsIndexData, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsIndexData == rhsIndexData, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory { + if case let .topImage(rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsIndexData, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, lhsPeer == rhsPeer, lhsIndexData == rhsIndexData, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory { return true } else { return false } case let .image(lhsId, lhsImageReference, lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsDate, lhsIndexData, lhsMessageId, lhsImmediateThumbnailData, lhsCategory, lhsIsFallback, lhsEmojiMarkup): - if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId, rhsImmediateThumbnailData, rhsCategory, rhsIsFallback, rhsEmojiMarkup) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory, lhsIsFallback == rhsIsFallback, lhsEmojiMarkup == rhsEmojiMarkup { + if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId, rhsImmediateThumbnailData, rhsCategory, rhsIsFallback, rhsEmojiMarkup) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory, lhsIsFallback == rhsIsFallback, lhsEmojiMarkup == rhsEmojiMarkup { return true } else { return false @@ -204,40 +203,46 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE return updatedEntries } -public func initialAvatarGalleryEntries(account: Account, engine: TelegramEngine, peer: Peer) -> Signal<[AvatarGalleryEntry]?, NoError> { +public func initialAvatarGalleryEntries(account: Account, engine: TelegramEngine, peer: EnginePeer) -> Signal<[AvatarGalleryEntry]?, NoError> { var initialEntries: [AvatarGalleryEntry] = [] - if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) { + if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer._asPeer()) { initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), [], peer, nil, nil, nil)) } - if peer is TelegramChannel || peer is TelegramGroup, let peerReference = PeerReference(peer) { - return engine.data.get(TelegramEngine.EngineData.Item.Peer.Photo(id: peer.id)) - |> map { peerPhoto in - var initialPhoto: TelegramMediaImage? - if case let .known(value) = peerPhoto { - initialPhoto = value + guard let peerReference = PeerReference(peer._asPeer()) else { + return .single(initialEntries) + } + switch peer { + case .channel, .legacyGroup: + break + default: + return .single(initialEntries) + } + + return engine.data.get(TelegramEngine.EngineData.Item.Peer.Photo(id: peer.id)) + |> map { peerPhoto in + var initialPhoto: TelegramMediaImage? + if case let .known(value) = peerPhoto { + initialPhoto = value + } + + if let photo = initialPhoto { + var representations = photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }) + if photo.immediateThumbnailData == nil, let firstEntry = initialEntries.first, let firstRepresentation = firstEntry.representations.first { + representations.insert(firstRepresentation, at: 0) } - - if let photo = initialPhoto { - var representations = photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }) - if photo.immediateThumbnailData == nil, let firstEntry = initialEntries.first, let firstRepresentation = firstEntry.representations.first { - representations.insert(firstRepresentation, at: 0) - } - return [.image(photo.imageId, photo.reference, representations, photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil, photo.immediateThumbnailData, nil, false, photo.emojiMarkup)] + return [.image(photo.imageId, photo.reference, representations, photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil, photo.immediateThumbnailData, nil, false, photo.emojiMarkup)] + } else { + if case .known = peerPhoto { + return [] } else { - if case .known = peerPhoto { - return [] - } else { - return nil - } + return nil } } - } else { - return .single(initialEntries) } } -public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> { +public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: EnginePeer) -> Signal<[AvatarGalleryEntry], NoError> { return initialAvatarGalleryEntries(account: account, engine: engine, peer: peer) |> map { entries -> [AvatarGalleryEntry] in return entries ?? [] @@ -250,10 +255,10 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account var result: [AvatarGalleryEntry] = [] if photos.isEmpty { result = initialEntries - } else if let peerReference = PeerReference(peer) { + } else if let peerReference = PeerReference(peer._asPeer()) { var index: Int32 = 0 if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { - var initialMediaIds = Set() + var initialMediaIds = Set() for entry in initialEntries { if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _, _) = entry { initialMediaIds.insert(mediaId) @@ -296,7 +301,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account } } -public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer, firstEntry: AvatarGalleryEntry, secondEntry: TelegramMediaImage?, lastEntry: TelegramMediaImage?) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { +public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: EnginePeer, firstEntry: AvatarGalleryEntry, secondEntry: TelegramMediaImage?, lastEntry: TelegramMediaImage?) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { let initialEntries = [firstEntry] return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries)) |> then( @@ -306,11 +311,11 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account let initialEntries = [firstEntry] if photos.isEmpty { result = initialEntries - } else if let peerReference = PeerReference(peer) { + } else if let peerReference = PeerReference(peer._asPeer()) { var index: Int32 = 0 if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { - var initialMediaIds = Set() + var initialMediaIds = Set() for entry in initialEntries { if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _, _) = entry { initialMediaIds.insert(mediaId) @@ -386,7 +391,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } private let context: AccountContext - private let peer: Peer + private let peer: EnginePeer private let sourceCorners: SourceCorners private let isSuggested: Bool @@ -428,7 +433,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let editDisposable = MetaDisposable () - public init(context: AccountContext, peer: Peer, sourceCorners: SourceCorners = .round, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, isSuggested: Bool = false, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { + public init(context: AccountContext, peer: EnginePeer, sourceCorners: SourceCorners = .round, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, isSuggested: Bool = false, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { self.context = context self.peer = peer self.sourceCorners = sourceCorners @@ -759,14 +764,14 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr let canDelete: Bool if self.peer.id == self.context.account.peerId { canDelete = true - } else if let group = self.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = self.peer { switch group.role { case .creator, .admin: canDelete = true case .member: canDelete = false } - } else if let channel = self.peer as? TelegramChannel { + } else if case let .channel(channel) = self.peer { canDelete = channel.hasPermission(.changeInfo) } else { canDelete = false @@ -799,7 +804,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } else { } case let .image(_, reference, _, _, _, _, _, _, _, _, _, _): - if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) { + if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer._asPeer()) { if let reference = reference { let _ = (self.context.engine.accountData.updatePeerPhotoExisting(reference: reference) |> deliverOnMainQueue).start(next: { [weak self] photo in diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift index f744ae4558..ab03807855 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import Photos @@ -109,7 +108,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { switch entry { case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, isFallback, _): if date != 0 || isFallback { - nameText = peer.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" + nameText = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" } if let date = date, date != 0 { dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string @@ -126,7 +125,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { } if let peer = peer { - canShare = !peer.isCopyProtectionEnabled + canShare = !peer._asPeer().isCopyProtectionEnabled } default: break diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index ad32448449..85e5c5f39d 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -16,10 +15,10 @@ import UndoUI private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem { let account: Account - let peer: Peer + let peer: EnginePeer let content: [ImageRepresentationWithReference] - init(account: Account, peer: Peer, content: [ImageRepresentationWithReference]) { + init(account: Account, peer: EnginePeer, content: [ImageRepresentationWithReference]) { self.account = account self.peer = peer self.content = content @@ -48,7 +47,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { } let context: AccountContext - let peer: Peer + let peer: EnginePeer let presentationData: PresentationData let entry: AvatarGalleryEntry let sourceCorners: AvatarGalleryController.SourceCorners @@ -56,7 +55,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { let setMain: (() -> Void)? let edit: (() -> Void)? - init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) { + init(context: AccountContext, peer: EnginePeer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) { self.context = context self.peer = peer self.presentationData = presentationData @@ -129,7 +128,7 @@ private class PeerAvatarImageGalleryContentNode: ASDisplayNode { final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private let context: AccountContext private let presentationData: PresentationData - private let peer: Peer + private let peer: EnginePeer private let sourceCorners: AvatarGalleryController.SourceCorners private var entry: AvatarGalleryEntry? @@ -150,12 +149,12 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private let fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? private let playbackStatusDisposable = MetaDisposable() fileprivate var edit: (() -> Void)? - init(context: AccountContext, presentationData: PresentationData, peer: Peer, sourceCorners: AvatarGalleryController.SourceCorners) { + init(context: AccountContext, presentationData: PresentationData, peer: EnginePeer, sourceCorners: AvatarGalleryController.SourceCorners) { self.context = context self.presentationData = presentationData self.peer = peer @@ -187,8 +186,8 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if let strongSelf = self, let entry = strongSelf.entry, !entry.representations.isEmpty { let subject: ShareControllerSubject var actionCompletionText: String? - if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) { + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) subject = .media(videoFileReference.abstract) actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved } else { @@ -277,10 +276,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { id = id &+ resource.photoId } } - if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer) { + if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { if video != previousVideoRepresentations?.last { let mediaManager = self.context.sharedContext.mediaManager - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(id, category), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, useLargeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) videoNode.isUserInteractionEnabled = false diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index 8e2337c059..ad7b767433 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -94,16 +93,16 @@ public enum PeerInfoAvatarListItem: Equatable { case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?) case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?, Bool, TelegramMediaImage.EmojiMarkup?) - var id: MediaResourceId { + var id: EngineMediaResource.Id { switch self { case .custom: - return MediaResourceId(CustomListItemResourceId().uniqueId) + return EngineMediaResource.Id(CustomListItemResourceId().uniqueId) case let .topImage(representations, _, _): let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation - return representation.resource.id + return EngineMediaResource.Id(representation.resource.id) case let .image(_, representations, _, _, _, _): let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation - return representation.resource.id + return EngineMediaResource.Id(representation.resource.id) } } @@ -205,7 +204,7 @@ public enum PeerInfoAvatarListItem: Equatable { public final class PeerInfoAvatarListItemNode: ASDisplayNode { private let context: AccountContext - private let peer: Peer + private let peer: EnginePeer public let imageNode: TransformImageNode private var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? @@ -266,7 +265,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { } } - init(context: AccountContext, peer: Peer) { + init(context: AccountContext, peer: EnginePeer) { self.context = context self.peer = peer self.imageNode = TransformImageNode() @@ -511,8 +510,8 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { self.didSetReady = true self.isReady.set(.single(true)) } - } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { @@ -570,7 +569,7 @@ private let fadeWidth: CGFloat = 70.0 public final class PeerInfoAvatarListContainerNode: ASDisplayNode { private let context: AccountContext private let isSettings: Bool - public var peer: Peer? + public var peer: EnginePeer? public let controlsContainerNode: ASDisplayNode public let controlsClippingNode: ASDisplayNode @@ -590,7 +589,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { public private(set) var galleryEntries: [AvatarGalleryEntry] = [] private var items: [PeerInfoAvatarListItem] = [] - private var itemNodes: [MediaResourceId: PeerInfoAvatarListItemNode] = [:] + private var itemNodes: [EngineMediaResource.Id: PeerInfoAvatarListItemNode] = [:] private var stripNodes: [ASImageNode] = [] private var activeStripNode: ASImageNode private var loadingStripNode: PeerInfoAvatarListLoadingStripNode @@ -1118,16 +1117,20 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { index += 1 } - - if let peer = self.peer, peer is TelegramGroup || peer is TelegramChannel, deletedIndex == 0 { - self.galleryEntries = [] - self.items = [] - self.itemsUpdated?([]) - self.currentIndex = 0 - if let size = self.validLayout { - self.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: true) + switch self.peer { + case .legacyGroup, .channel: + if deletedIndex == 0 { + self.galleryEntries = [] + self.items = [] + self.itemsUpdated?([]) + self.currentIndex = 0 + if let size = self.validLayout { + self.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: true) + } + return true } - return true + default: + break } self.galleryEntries = normalizeEntries(entries) @@ -1146,7 +1149,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { } private var additionalEntryProgress: Signal? = nil - public func update(size: CGSize, peer: Peer?, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError> = .single(nil), isExpanded: Bool, transition: ContainedViewLayoutTransition) { + public func update(size: CGSize, peer: EnginePeer?, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError> = .single(nil), isExpanded: Bool, transition: ContainedViewLayoutTransition) { self.validLayout = size let previousExpanded = self.isExpanded self.isExpanded = isExpanded @@ -1304,7 +1307,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { public var updateCustomItemsOnlySynchronously = false private func updateItems(size: CGSize, update: Bool = false, transition: ContainedViewLayoutTransition, stripTransition: ContainedViewLayoutTransition, synchronous: Bool = false) { - var validIds: [MediaResourceId] = [] + var validIds: [EngineMediaResource.Id] = [] var addedItemNodesForAdditiveTransition: [PeerInfoAvatarListItemNode] = [] var additiveTransitionOffset: CGFloat = 0.0 var itemsAdded = false @@ -1371,7 +1374,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { photoTitle = representation.hasVideo ? presentationData.strings.UserInfo_PublicVideo : presentationData.strings.UserInfo_PublicPhoto hasLink = true if let peer = self.peer { - fallbackImageSignal = peerAvatarCompleteImage(account: self.context.account, peer: EnginePeer(peer), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)) + fallbackImageSignal = peerAvatarCompleteImage(account: self.context.account, peer: peer, forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)) } } @@ -1398,7 +1401,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { for itemNode in addedItemNodesForAdditiveTransition { transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: additiveTransitionOffset, y: 0.0)) } - var removeIds: [MediaResourceId] = [] + var removeIds: [EngineMediaResource.Id] = [] for (id, _) in self.itemNodes { if !validIds.contains(id) { removeIds.append(id) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index f0fc00b5b7..05fe16fdbb 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -483,8 +482,8 @@ private func rightDependencies(_ right: TelegramChatAdminRightsFlags) -> [Telegr } } -private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initialParticipant: ChannelParticipant?) -> Bool { - if let channel = channelPeer as? TelegramChannel { +private func canEditAdminRights(accountPeerId: EnginePeer.Id, channelPeer: EnginePeer, initialParticipant: ChannelParticipant?) -> Bool { + if case let .channel(channel) = channelPeer { if channel.flags.contains(.isCreator) { return true } else if let initialParticipant = initialParticipant { @@ -501,7 +500,7 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } else { return channel.hasPermission(.addAdmins) } - } else if let group = channelPeer as? TelegramGroup { + } else if case let .legacyGroup(group) = channelPeer { if case .creator = group.role { return true } else { @@ -512,8 +511,8 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } } -private func rightEnabledByDefault(channelPeer: Peer, right: TelegramChatAdminRightsFlags) -> Bool { - if let channel = channelPeer as? TelegramChannel { +private func rightEnabledByDefault(channelPeer: EnginePeer, right: TelegramChatAdminRightsFlags) -> Bool { + if case let .channel(channel) = channelPeer { guard let defaultBannedRights = channel.defaultBannedRights else { return false } @@ -533,7 +532,7 @@ private func areAllAdminRightsEnabled(_ flags: TelegramChatAdminRightsFlags, pee return TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except).intersection(flags) == TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except) } -private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: PeerId, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] { +private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: EnginePeer.Id, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] { var entries: [ChannelAdminEntry] = [] if case let .channel(channel) = channelPeer, let admin = adminPeer { @@ -630,7 +629,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s } } } else { - if case let .user(adminPeer) = adminPeer, adminPeer.botInfo != nil, case .group = channel.info, invite, let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer._asPeer(), initialParticipant: initialParticipant) { + if case let .user(adminPeer) = adminPeer, adminPeer.botInfo != nil, case .group = channel.info, invite, let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer, initialParticipant: initialParticipant) { if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _) = initialParticipant, adminInfo != nil { } else { @@ -641,7 +640,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s if !invite || state.adminRights { entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) - if let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer._asPeer(), initialParticipant: initialParticipant) { + if let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer, initialParticipant: initialParticipant) { let accountUserRightsFlags: TelegramChatAdminRightsFlags if channel.flags.contains(.isCreator) { accountUserRightsFlags = maskRightsFlags @@ -663,7 +662,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s var index = 0 for right in rightsOrder { if accountUserRightsFlags.contains(right) { - entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: channel, right: right))) + entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: .channel(channel), right: right))) index += 1 } } @@ -829,7 +828,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s return entries } -public func channelAdminController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, invite: Bool = false, initialAdminRights: TelegramChatAdminRightsFlags? = nil, updated: @escaping (TelegramChatAdminRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, transferedOwnership: @escaping (PeerId) -> Void) -> ViewController { +public func channelAdminController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, adminId: EnginePeer.Id, initialParticipant: ChannelParticipant?, invite: Bool = false, initialAdminRights: TelegramChatAdminRightsFlags? = nil, updated: @escaping (TelegramChatAdminRights?) -> Void, upgradedToSupergroup: @escaping (EnginePeer.Id, @escaping () -> Void) -> Void, transferedOwnership: @escaping (EnginePeer.Id) -> Void) -> ViewController { let statePromise = ValuePromise(ChannelAdminControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelAdminControllerState()) let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in @@ -857,8 +856,8 @@ public func channelAdminController(context: AccountContext, updatedPresentationD var errorImpl: (() -> Void)? var scrollToRankImpl: (() -> Void)? - let actualPeerId = Atomic(value: peerId) - let upgradedToSupergroupImpl: (PeerId, @escaping () -> Void) -> Void = { peerId, completion in + let actualPeerId = Atomic(value: peerId) + let upgradedToSupergroupImpl: (EnginePeer.Id, @escaping () -> Void) -> Void = { peerId, completion in let _ = actualPeerId.swap(peerId) upgradedToSupergroup(peerId, completion) } @@ -889,9 +888,9 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let presentationData = context.sharedContext.currentPresentationData.with { $0 } let text: String - if !canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: peer._asPeer(), initialParticipant: initialParticipant) { + if !canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: peer, initialParticipant: initialParticipant) { text = presentationData.strings.Channel_EditAdmin_CannotEdit - } else if rightEnabledByDefault(channelPeer: peer._asPeer(), right: right) { + } else if rightEnabledByDefault(channelPeer: peer, right: right) { text = presentationData.strings.Channel_EditAdmin_PermissionEnabledByDefault } else { text = presentationData.strings.Channel_EditAdmin_CannotEdit @@ -993,7 +992,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let channelPeer = peerInfoData.0.flatMap { $0 } let adminPeer = peerInfoData.1.flatMap { $0 } let adminPresence = peerInfoData.2 - let canEdit = canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: channelPeer!._asPeer(), initialParticipant: initialParticipant) + let canEdit = canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: channelPeer!, initialParticipant: initialParticipant) let leftNavigationButton: ItemListNavigationButton if canEdit { @@ -1308,7 +1307,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId) |> map(Optional.init) - |> `catch` { error -> Signal in + |> `catch` { error -> Signal in switch error { case .tooManyChannels: return .fail(.conversionTooManyChannels) @@ -1316,7 +1315,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD return .fail(.conversionFailed) } } - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in guard let upgradedPeerId = upgradedPeerId else { return .fail(.conversionFailed) } @@ -1324,7 +1323,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD |> mapError { error -> WrappedUpdateChannelAdminRightsError in return .direct(error) } - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(upgradedPeerId)) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index c1b8399f12..e00c4d8015 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -120,7 +120,7 @@ public final class ChannelMembersSearchController: ViewController { self?.deactivateSearch(animated: true) } self.controllerNode.requestOpenPeerFromSearch = { [weak self] peer, participant in - self?.openPeer(peer, participant) + self?.openPeer(peer._asPeer(), participant) } self.controllerNode.requestCopyInviteLink = { [weak self] in self?.copyInviteLink?() diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index e77c6778e6..b48c6fe0c2 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -18,11 +17,11 @@ import ContactListUI import ChatListSearchItemHeader private final class ChannelMembersSearchInteraction { - let openPeer: (Peer, RenderedChannelParticipant?) -> Void + let openPeer: (EnginePeer, RenderedChannelParticipant?) -> Void let copyInviteLink: () -> Void init( - openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, + openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void, copyInviteLink: @escaping () -> Void ) { self.openPeer = openPeer @@ -32,14 +31,14 @@ private final class ChannelMembersSearchInteraction { private enum ChannelMembersSearchEntryId: Hashable { case copyInviteLink - case peer(PeerId) - case contact(PeerId) + case peer(EnginePeer.Id) + case contact(EnginePeer.Id) } private enum ChannelMembersSearchEntry: Comparable, Identifiable { case copyInviteLink case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool, Bool, Bool) - case contact(Int, Peer, TelegramUserPresence?) + case contact(Int, EnginePeer, TelegramUserPresence?) var stableId: ChannelMembersSearchEntryId { switch self { @@ -71,7 +70,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { if lhsIndex != rhsIndex { return false } - if !lhsPeer.isEqual(rhsPeer) { + if lhsPeer != rhsPeer { return false } if lhsPresence != rhsPresence { @@ -146,7 +145,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { } return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(participant.peer), chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings), action: { _ in - interaction.openPeer(participant.peer, participant) + interaction.openPeer(EnginePeer(participant.peer), participant) }) case let .contact(_, peer, presence): let status: ContactsPeerItemStatus @@ -156,7 +155,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { status = .none } - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer), chatPeer: nil), status: status, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: presentationData.theme, strings: presentationData.strings), action: { _ in + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: nil), status: status, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: presentationData.theme, strings: presentationData.strings), action: { _ in interaction.openPeer(peer, nil) }) } @@ -182,7 +181,7 @@ private func preparedTransition(from fromEntries: [ChannelMembersSearchEntry]?, class ChannelMembersSearchControllerNode: ASDisplayNode { private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let mode: ChannelMembersSearchControllerMode private let filters: [ChannelMembersSearchFilter] let listNode: ListView @@ -196,7 +195,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { var requestActivateSearch: (() -> Void)? var requestDeactivateSearch: (() -> Void)? - var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)? + var requestOpenPeerFromSearch: ((EnginePeer, RenderedChannelParticipant?) -> Void)? var requestCopyInviteLink: (() -> Void)? var pushController: ((ViewController) -> Void)? @@ -206,7 +205,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { private var disposable: Disposable? private var listControl: PeerChannelMemberCategoryControl? - init(context: AccountContext, presentationData: PresentationData, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) { + init(context: AccountContext, presentationData: PresentationData, forceTheme: PresentationTheme?, peerId: EnginePeer.Id, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) { self.context = context self.listNode = ListView() self.peerId = peerId @@ -265,12 +264,12 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { guard let cachedData = peerView.cachedData as? CachedGroupData, let participants = cachedData.participants else { return } - var creatorPeer: Peer? + var creatorPeer: EnginePeer? for participant in participants.participants { if let peer = peerView.peers[participant.peerId] { switch participant { case .creator: - creatorPeer = peer + creatorPeer = EnginePeer(peer) default: break } @@ -397,14 +396,14 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { case .creator: renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer, presences: peerView.peerPresences) case .admin: - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] peers[creator.id] = creator - peers[peer.id] = peer - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers, presences: peerView.peerPresences) + peers[peer.id] = EnginePeer(peer) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences) case .member: - var peers: [PeerId: Peer] = [:] - peers[peer.id] = peer - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers, presences: peerView.peerPresences) + var peers: [EnginePeer.Id: EnginePeer] = [:] + peers[peer.id] = EnginePeer(peer) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences) } entries.append(.peer(index, renderedParticipant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, false, false)) @@ -419,7 +418,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } }) { for peer in contactsView.peers { - entries.append(ChannelMembersSearchEntry.contact(index, peer._asPeer(), contactsView.presences[peer.id]?._asPresence())) + entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.presences[peer.id]?._asPresence())) index += 1 } } @@ -477,7 +476,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } var index = 0 - var existingPeersIds = Set() + var existingPeersIds = Set() if case .inviteToCall = mode, canInviteByLink, !filters.contains(where: { filter in if case .excludeNonMembers = filter { return true @@ -596,7 +595,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } }) { for peer in contactsView.peers { - entries.append(ChannelMembersSearchEntry.contact(index, peer._asPeer(), contactsView.presences[peer.id]?._asPresence())) + entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.presences[peer.id]?._asPresence())) index += 1 } } @@ -677,7 +676,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: self.forceTheme, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, searchContext: nil, openPeer: { [weak self] peer, participant in - self?.requestOpenPeerFromSearch?(peer, participant) + self?.requestOpenPeerFromSearch?(EnginePeer(peer), participant) }, updateActivity: { value in }, pushController: { [weak self] c in diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index ff47699cce..69618abfd2 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1349,7 +1349,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } openAvatarImpl = { [weak controller] peer in - let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in + let avatarController = AvatarGalleryController(context: context, peer: EnginePeer(peer), replaceRootController: { _, _ in }) hiddenAvatarPromise.set( avatarController.hiddenMedia diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 99364741a6..2922a37a5c 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -682,9 +682,9 @@ public final class MediaBox { messageIdValue = messageId.id } - self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType.rawValue) + self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType) } else { - self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType.rawValue ?? 0) + self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType ?? 0) } guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else { @@ -857,9 +857,9 @@ public final class MediaBox { messageIdValue = messageId.id } - self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType.rawValue) + self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType) } else { - self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType.rawValue ?? 0) + self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType ?? 0) } if let _ = fileSize(paths.complete) { @@ -1387,7 +1387,7 @@ public final class MediaBox { } } - private func updateGeneralResourceIndex(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { + private func updateGeneralResourceIndex(otherResourceContentType: UInt8, lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { let basePath = self.basePath let storageBox = self.storageBox @@ -1456,7 +1456,7 @@ public final class MediaBox { size = value } return (resourceId.data(using: .utf8)!, size) - }, contentType: MediaResourceUserContentType.other.rawValue, completion: { addedCount in + }, contentType: otherResourceContentType, completion: { addedCount in if addedCount != 0 { postboxLog("UpdateResourceIndex: added \(addedCount) unreferenced ids") } @@ -1572,8 +1572,8 @@ public final class MediaBox { } }*/ - public func updateResourceIndex(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { - return self.updateGeneralResourceIndex(lowImpact: lowImpact, completion: { + public func updateResourceIndex(otherResourceContentType: UInt8, lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { + return self.updateGeneralResourceIndex(otherResourceContentType: otherResourceContentType, lowImpact: lowImpact, completion: { completion() }) } diff --git a/submodules/Postbox/Sources/MediaResource.swift b/submodules/Postbox/Sources/MediaResource.swift index 1811c565ed..aafeb87f9b 100644 --- a/submodules/Postbox/Sources/MediaResource.swift +++ b/submodules/Postbox/Sources/MediaResource.swift @@ -49,25 +49,14 @@ public final class MediaResourceStorageLocation { } } -public enum MediaResourceUserContentType: UInt8, Equatable { - case other = 0 - case image = 1 - case video = 2 - case audio = 3 - case file = 4 - case sticker = 6 - case avatar = 7 - case audioVideoMessage = 8 -} - public struct MediaResourceFetchParameters { public let tag: MediaResourceFetchTag? public let info: MediaResourceFetchInfo? public let location: MediaResourceStorageLocation? - public let contentType: MediaResourceUserContentType + public let contentType: UInt8 public let isRandomAccessAllowed: Bool - public init(tag: MediaResourceFetchTag?, info: MediaResourceFetchInfo?, location: MediaResourceStorageLocation?, contentType: MediaResourceUserContentType, isRandomAccessAllowed: Bool) { + public init(tag: MediaResourceFetchTag?, info: MediaResourceFetchInfo?, location: MediaResourceStorageLocation?, contentType: UInt8, isRandomAccessAllowed: Bool) { self.tag = tag self.info = info self.location = location diff --git a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift index fdbfa8d939..51be389a87 100644 --- a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift +++ b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift @@ -4,7 +4,6 @@ import SceneKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import ComponentFlow import AccountContext @@ -221,7 +220,7 @@ private final class PhoneView: UIView { self.contentContainerView.backgroundColor = .clear let videoContent = NativeVideoContent( - id: .message(1, MediaId(namespace: 0, id: Int64.random(in: 0.. Void - let removePeer: (PeerId) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let removePeer: (EnginePeer.Id) -> Void let addPeer: () -> Void let openPeer: (EnginePeer) -> Void let deleteAll: () -> Void - init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, deleteAll: @escaping () -> Void) { + init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, deleteAll: @escaping () -> Void) { self.context = context self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removePeer = removePeer @@ -39,7 +38,7 @@ private enum SelectivePrivacyPeersSection: Int32 { private enum SelectivePrivacyPeersEntryStableId: Hashable { case header case add - case peer(PeerId) + case peer(EnginePeer.Id) case delete } @@ -193,14 +192,14 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { private struct SelectivePrivacyPeersControllerState: Equatable { let editing: Bool - let peerIdWithRevealedOptions: PeerId? + let peerIdWithRevealedOptions: EnginePeer.Id? init() { self.editing = false self.peerIdWithRevealedOptions = nil } - init(editing: Bool, peerIdWithRevealedOptions: PeerId?) { + init(editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?) { self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions } @@ -219,7 +218,7 @@ private struct SelectivePrivacyPeersControllerState: Equatable { return SelectivePrivacyPeersControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions) } - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> SelectivePrivacyPeersControllerState { + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: EnginePeer.Id?) -> SelectivePrivacyPeersControllerState { return SelectivePrivacyPeersControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions) } } @@ -249,7 +248,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati return entries } -public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [PeerId: SelectivePrivacyPeer], updated: @escaping ([PeerId: SelectivePrivacyPeer]) -> Void) -> ViewController { +public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer]) -> Void) -> ViewController { let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: SelectivePrivacyPeersControllerState()) let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in @@ -293,7 +292,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri } peersPromise.set(.single(updatedPeers)) - var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:] + var updatedPeerDict: [EnginePeer.Id: SelectivePrivacyPeer] = [:] for peer in updatedPeers { updatedPeerDict[peer.peer.id] = peer } @@ -357,7 +356,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri |> mapToSignal { updatedPeers -> Signal in peersPromise.set(.single(updatedPeers)) - var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:] + var updatedPeerDict: [EnginePeer.Id: SelectivePrivacyPeer] = [:] for peer in updatedPeers { updatedPeerDict[peer.peer.id] = peer } diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift index 4e4db063d1..333125c412 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift index cb991de4aa..8c9e6c1639 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 7bbfd06c43..c353ba6286 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -1,10 +1,10 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore +import Postbox import TelegramPresentationData import TelegramUIPreferences import AccountContext diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift index 486d63080e..81ca956786 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramUIPreferences import TelegramPresentationData import TelegramCore diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index e35dbd8b14..acce3f33a9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import LegacyComponents diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift index 7b3b91c209..aae0ab87c4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import AccountContext import GridMessageSelectionNode diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift index 1981669ea4..9566b7797c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index c3eff1a4ba..3f493e9dd5 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -12,6 +11,7 @@ import ShareController import UndoUI import InviteLinksUI import TextFormat +import Postbox private final class UsernameSetupControllerArguments { let account: Account @@ -404,7 +404,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData, public enum UsernameSetupMode: Equatable { case account - case bot(PeerId) + case bot(EnginePeer.Id) } public func usernameSetupController(context: AccountContext, mode: UsernameSetupMode = .account) -> ViewController { @@ -426,7 +426,7 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup let updateAddressNameDisposable = MetaDisposable() actionsDisposable.add(updateAddressNameDisposable) - let peerId: PeerId + let peerId: EnginePeer.Id let domain: AddressNameDomain switch mode { case .account: diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 29c2df3ac3..07ca59222c 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -1042,7 +1042,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate |> take(1) |> deliverOnMainQueue).start(next: { peers in if let strongSelf = self { - let searchContentNode = ShareSearchContainerNode(sharedContext: strongSelf.sharedContext, context: context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction!, recentPeers: peers.filter({ $0.peer.peerId.namespace != Namespaces.Peer.SecretChat }).map({ $0.peer })) + let searchContentNode = ShareSearchContainerNode(sharedContext: strongSelf.sharedContext, context: context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction!, recentPeers: peers.filter({ $0.peer.peerId.namespace != Namespaces.Peer.SecretChat }).map({ EngineRenderedPeer($0.peer) })) searchContentNode.cancel = { if let strongSelf = self, let peersContentNode = strongSelf.peersContentNode { strongSelf.transitionToContentNode(peersContentNode) diff --git a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift index 0b93022e03..5195c536fe 100644 --- a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramPresentationData import TelegramStringFormatting import SelectablePeerNode @@ -14,7 +13,7 @@ import ShimmerEffect final class ShareControllerInteraction { var foundPeers: [EngineRenderedPeer] = [] - var selectedPeerIds = Set() + var selectedPeerIds = Set() var selectedPeers: [EngineRenderedPeer] = [] var selectedTopics: [EnginePeer.Id: (Int64, MessageHistoryThreadData)] = [:] diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index f3145f0398..900046237b 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit import Display -import Postbox import TelegramPresentationData import ActivityIndicator import RadialStatusNode @@ -14,6 +13,26 @@ import TelegramUniversalVideoContent import TelegramCore import AccountContext +private func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? { + if useTotalFileAllocatedSize { + let url = URL(fileURLWithPath: path) + if let values = (try? url.resourceValues(forKeys: Set([.isRegularFileKey, .totalFileAllocatedSizeKey]))) { + if values.isRegularFile ?? false { + if let fileSize = values.totalFileAllocatedSize { + return Int64(fileSize) + } + } + } + } + + var value = stat() + if stat(path, &value) == 0 { + return value.st_size + } else { + return nil + } +} + public enum ShareLoadingState { case preparing case progress(Float) @@ -71,7 +90,7 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain public func deactivate() { } - public func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + public func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { } public func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) { @@ -256,9 +275,9 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte if let account = account, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) - let videoContent = NativeVideoContent(id: .message(1, MediaId(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) + let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: account.postbox, audioSession: sharedContext.mediaManager.audioSession, manager: sharedContext.mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) videoNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)) @@ -285,7 +304,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte public func deactivate() { } - public func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + public func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { } public func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) { diff --git a/submodules/ShareController/Sources/SharePeersContainerNode.swift b/submodules/ShareController/Sources/SharePeersContainerNode.swift index a9f094be3a..b04eb7317e 100644 --- a/submodules/ShareController/Sources/SharePeersContainerNode.swift +++ b/submodules/ShareController/Sources/SharePeersContainerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -140,7 +139,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { var openShare: ((ASDisplayNode, ContextGesture?) -> Void)? var segmentedSelectedIndexUpdated: ((Int) -> Void)? - private var ensurePeerVisibleOnLayout: PeerId? + private var ensurePeerVisibleOnLayout: EnginePeer.Id? private var validLayout: (CGSize, CGFloat)? private var overrideGridOffsetTransition: ContainedViewLayoutTransition? @@ -175,7 +174,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { var entries: [SharePeerEntry] = [] var index: Int32 = 0 - var existingPeerIds: Set = Set() + var existingPeerIds: Set = Set() entries.append(SharePeerEntry(index: index, peer: EngineRenderedPeer(peer: accountPeer), presence: nil, threadId: nil, threadData: nil, theme: theme, strings: strings)) existingPeerIds.insert(accountPeer.id) index += 1 @@ -338,7 +337,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } } - func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { self.ensurePeerVisibleOnLayout = peerId } diff --git a/submodules/ShareController/Sources/ShareSearchContainerNode.swift b/submodules/ShareController/Sources/ShareSearchContainerNode.swift index f229d67ce0..3b166ed261 100644 --- a/submodules/ShareController/Sources/ShareSearchContainerNode.swift +++ b/submodules/ShareController/Sources/ShareSearchContainerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -14,7 +13,7 @@ private let subtitleFont = Font.regular(12.0) private enum ShareSearchRecentEntryStableId: Hashable { case topPeers - case peerId(PeerId) + case peerId(EnginePeer.Id) static func ==(lhs: ShareSearchRecentEntryStableId, rhs: ShareSearchRecentEntryStableId) -> Bool { switch lhs { @@ -36,7 +35,7 @@ private enum ShareSearchRecentEntryStableId: Hashable { private enum ShareSearchRecentEntry: Comparable, Identifiable { case topPeers(PresentationTheme, PresentationStrings) - case peer(index: Int, theme: PresentationTheme, peer: Peer, associatedPeer: Peer?, presence: EnginePeer.Presence?, PresentationStrings) + case peer(index: Int, theme: PresentationTheme, peer: EnginePeer, associatedPeer: EnginePeer?, presence: EnginePeer.Presence?, PresentationStrings) var stableId: ShareSearchRecentEntryStableId { switch self { @@ -62,7 +61,7 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable { return false } case let .peer(lhsIndex, lhsTheme, lhsPeer, lhsAssociatedPeer, lhsPresence, lhsStrings): - if case let .peer(rhsIndex, rhsTheme, rhsPeer, rhsAssociatedPeer, rhsPresence, rhsStrings) = rhs, lhsPeer.isEqual(rhsPeer) && arePeersEqual(lhsAssociatedPeer, rhsAssociatedPeer) && lhsIndex == rhsIndex && lhsStrings === rhsStrings && lhsTheme === rhsTheme && lhsPresence == rhsPresence { + if case let .peer(rhsIndex, rhsTheme, rhsPeer, rhsAssociatedPeer, rhsPresence, rhsStrings) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsStrings === rhsStrings && lhsTheme === rhsTheme && lhsPresence == rhsPresence { return true } else { return false @@ -89,11 +88,11 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable { case let .topPeers(theme, strings): return ShareControllerRecentPeersGridItem(context: context, theme: theme, strings: strings, controllerInteraction: interfaceInteraction) case let .peer(_, theme, peer, associatedPeer, presence, strings): - var peers: [PeerId: Peer] = [peer.id: peer] + var peers: [EnginePeer.Id: EnginePeer] = [peer.id: peer] if let associatedPeer = associatedPeer { peers[associatedPeer.id] = associatedPeer } - let peer = EngineRenderedPeer(RenderedPeer(peerId: peer.id, peers: SimpleDictionary(peers), associatedMedia: [:])) + let peer = EngineRenderedPeer(peerId: peer.id, peers: peers, associatedMedia: [:]) return ShareControllerPeerGridItem(context: context, theme: theme, strings: strings, peer: peer, presence: presence, topicId: nil, threadData: nil, controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent, search: true) } } @@ -189,7 +188,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { var cancel: (() -> Void)? - private var ensurePeerVisibleOnLayout: PeerId? + private var ensurePeerVisibleOnLayout: EnginePeer.Id? private var validLayout: (CGSize, CGFloat)? private var overrideGridOffsetTransition: ContainedViewLayoutTransition? @@ -198,7 +197,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { private let searchQuery = ValuePromise("", ignoreRepeated: true) private let searchDisposable = MetaDisposable() - init(sharedContext: SharedAccountContext, context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ShareControllerInteraction, recentPeers recentPeerList: [RenderedPeer]) { + init(sharedContext: SharedAccountContext, context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ShareControllerInteraction, recentPeers recentPeerList: [EngineRenderedPeer]) { self.sharedContext = sharedContext self.context = context self.theme = theme @@ -264,13 +263,13 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { var entries: [ShareSearchPeerEntry] = [] var index: Int32 = 0 - var existingPeerIds = Set() + var existingPeerIds = Set() let lowercasedQuery = query.lowercased() if strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) { if !existingPeerIds.contains(accountPeer.id) { existingPeerIds.insert(accountPeer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(RenderedPeer(peer: accountPeer)), presence: nil, theme: theme, strings: strings)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(accountPeer)), presence: nil, theme: theme, strings: strings)) index += 1 } } @@ -297,7 +296,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let peer = foundPeer.peer if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(RenderedPeer(peer: foundPeer.peer)), presence: nil, theme: theme, strings: strings)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(foundPeer.peer)), presence: nil, theme: theme, strings: strings)) index += 1 } } @@ -306,7 +305,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let peer = foundPeer.peer if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(RenderedPeer(peer: peer)), presence: nil, theme: theme, strings: strings)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, theme: theme, strings: strings)) index += 1 } } @@ -370,8 +369,8 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } var index = 0 for peer in recentPeerList { - if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer) { - recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer.associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, strings)) + if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer._asPeer()) { + recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer._asPeer().associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, strings)) index += 1 } } @@ -396,7 +395,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { self.recentDisposable.dispose() } - func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { self.ensurePeerVisibleOnLayout = peerId } diff --git a/submodules/ShareController/Sources/ShareTopicGridItem.swift b/submodules/ShareController/Sources/ShareTopicGridItem.swift index bac41acd22..b3a4af9703 100644 --- a/submodules/ShareController/Sources/ShareTopicGridItem.swift +++ b/submodules/ShareController/Sources/ShareTopicGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramPresentationData import TelegramStringFormatting import SelectablePeerNode diff --git a/submodules/ShareController/Sources/ShareTopicsContainerNode.swift b/submodules/ShareController/Sources/ShareTopicsContainerNode.swift index f4f6f076fc..bf68b640af 100644 --- a/submodules/ShareController/Sources/ShareTopicsContainerNode.swift +++ b/submodules/ShareController/Sources/ShareTopicsContainerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import Display diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index aea2de6d6f..600df725fb 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -23,17 +22,17 @@ private final class GroupStatsControllerArguments { let context: AccountContext let loadDetailedGraph: (StatsGraph, Int64) -> Signal let openPeer: (EnginePeer) -> Void - let openPeerHistory: (PeerId) -> Void - let openPeerAdminActions: (PeerId) -> Void - let promotePeer: (PeerId) -> Void + let openPeerHistory: (EnginePeer.Id) -> Void + let openPeerAdminActions: (EnginePeer.Id) -> Void + let promotePeer: (EnginePeer.Id) -> Void let expandTopPosters: () -> Void let expandTopAdmins: () -> Void let expandTopInviters: () -> Void - let setPostersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let setPostersPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let setAdminsPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let setInvitersPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPeer: @escaping (EnginePeer) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) { + init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPeer: @escaping (EnginePeer) -> Void, openPeerHistory: @escaping (EnginePeer.Id) -> Void, openPeerAdminActions: @escaping (EnginePeer.Id) -> Void, promotePeer: @escaping (EnginePeer.Id) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void) { self.context = context self.loadDetailedGraph = loadDetailedGraph self.openPeer = openPeer @@ -93,15 +92,15 @@ private enum StatsEntry: ItemListNodeEntry { case topWeekdaysGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case topPostersTitle(PresentationTheme, String, String) - case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool, Bool) + case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, GroupStatsTopPoster, Bool, Bool) case topPostersExpand(PresentationTheme, String) case topAdminsTitle(PresentationTheme, String, String) - case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool, Bool) + case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, GroupStatsTopAdmin, Bool, Bool) case topAdminsExpand(PresentationTheme, String) case topInvitersTitle(PresentationTheme, String, String) - case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool, Bool) + case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, GroupStatsTopInviter, Bool, Bool) case topInvitersExpand(PresentationTheme, String) var section: ItemListSectionId { @@ -309,7 +308,7 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed, lhsCanPromote): - if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { + if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { return true } else { return false @@ -327,7 +326,7 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed, lhsCanPromote): - if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { + if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { return true } else { return false @@ -345,7 +344,7 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed, lhsCanPromote): - if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { + if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { return true } else { return false @@ -411,8 +410,8 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(EnginePeer(peer)) + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -442,8 +441,8 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(EnginePeer(peer)) + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -465,8 +464,8 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(EnginePeer(peer)) + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -478,7 +477,7 @@ private enum StatsEntry: ItemListNodeEntry { } } -private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStatsState, data: GroupStats?, channelPeer: Peer, peers: [EnginePeer.Id: EnginePeer]?, presentationData: PresentationData) -> [StatsEntry] { +private func groupStatsControllerEntries(accountPeerId: EnginePeer.Id, state: GroupStatsState, data: GroupStats?, channelPeer: EnginePeer, peers: [EnginePeer.Id: EnginePeer]?, presentationData: PresentationData) -> [StatsEntry] { var entries: [StatsEntry] = [] if let data = data { @@ -545,7 +544,7 @@ private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStat for topPoster in topPosters { if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 { - entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions, canPromote)) + entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions, canPromote)) index += 1 } } @@ -568,7 +567,7 @@ private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStat for topAdmin in data.topAdmins { if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 { - entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions, canPromote)) + entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions, canPromote)) index += 1 } } @@ -591,7 +590,7 @@ private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStat for topInviter in data.topInviters { if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 { - entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions, canPromote)) + entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions, canPromote)) index += 1 } } @@ -610,9 +609,9 @@ private struct GroupStatsState: Equatable { let topPostersExpanded: Bool let topAdminsExpanded: Bool let topInvitersExpanded: Bool - let posterPeerIdWithRevealedOptions: PeerId? - let adminPeerIdWithRevealedOptions: PeerId? - let inviterPeerIdWithRevealedOptions: PeerId? + let posterPeerIdWithRevealedOptions: EnginePeer.Id? + let adminPeerIdWithRevealedOptions: EnginePeer.Id? + let inviterPeerIdWithRevealedOptions: EnginePeer.Id? init() { self.topPostersExpanded = false @@ -623,7 +622,7 @@ private struct GroupStatsState: Equatable { self.inviterPeerIdWithRevealedOptions = nil } - init(topPostersExpanded: Bool, topAdminsExpanded: Bool, topInvitersExpanded: Bool, posterPeerIdWithRevealedOptions: PeerId?, adminPeerIdWithRevealedOptions: PeerId?, inviterPeerIdWithRevealedOptions: PeerId?) { + init(topPostersExpanded: Bool, topAdminsExpanded: Bool, topInvitersExpanded: Bool, posterPeerIdWithRevealedOptions: EnginePeer.Id?, adminPeerIdWithRevealedOptions: EnginePeer.Id?, inviterPeerIdWithRevealedOptions: EnginePeer.Id?) { self.topPostersExpanded = topPostersExpanded self.topAdminsExpanded = topAdminsExpanded self.topInvitersExpanded = topInvitersExpanded @@ -666,21 +665,21 @@ private struct GroupStatsState: Equatable { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: topInvitersExpanded, posterPeerIdWithRevealedOptions: self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: self.inviterPeerIdWithRevealedOptions) } - func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { + func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: EnginePeer.Id?) -> GroupStatsState { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions) } - func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { + func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: EnginePeer.Id?) -> GroupStatsState { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions) } - func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { + func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: EnginePeer.Id?) -> GroupStatsState { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions) } } -private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initialParticipant: ChannelParticipant?) -> Bool { - if let channel = channelPeer as? TelegramChannel { +private func canEditAdminRights(accountPeerId: EnginePeer.Id, channelPeer: EnginePeer, initialParticipant: ChannelParticipant?) -> Bool { + if case let .channel(channel) = channelPeer { if channel.flags.contains(.isCreator) { return true } else if let initialParticipant = initialParticipant { @@ -697,7 +696,7 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } else { return channel.hasPermission(.addAdmins) } - } else if let group = channelPeer as? TelegramGroup { + } else if case let .legacyGroup(group) = channelPeer { if case .creator = group.role { return true } else { @@ -708,7 +707,7 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } } -public func groupStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, statsDatacenterId: Int32?) -> ViewController { +public func groupStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, statsDatacenterId: Int32?) -> ViewController { let statePromise = ValuePromise(GroupStatsState()) let stateValue = Atomic(value: GroupStatsState()) let updateState: ((GroupStatsState) -> GroupStatsState) -> Void = { f in @@ -722,12 +721,11 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat let datacenterId: Int32 = statsDatacenterId ?? 0 var openPeerImpl: ((EnginePeer) -> Void)? - var openPeerHistoryImpl: ((PeerId) -> Void)? - var openPeerAdminActionsImpl: ((PeerId) -> Void)? - var promotePeerImpl: ((PeerId) -> Void)? + var openPeerHistoryImpl: ((EnginePeer.Id) -> Void)? + var openPeerAdminActionsImpl: ((EnginePeer.Id) -> Void)? + var promotePeerImpl: ((EnginePeer.Id) -> Void)? - let peerView = Promise() - peerView.set(context.account.viewTracker.peerView(peerId, updateData: true)) + actionsDisposable.add(context.account.viewTracker.peerView(peerId, updateData: true).start()) let statsContext = GroupStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId) let dataSignal: Signal = statsContext.state @@ -844,7 +842,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: channelPeer, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: EnginePeer(channelPeer), peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index ce419b28e0..2a84ae83e6 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -18,9 +17,9 @@ import GraphUI private final class MessageStatsControllerArguments { let context: AccountContext let loadDetailedGraph: (StatsGraph, Int64) -> Signal - let openMessage: (MessageId) -> Void + let openMessage: (EngineMessage.Id) -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openMessage: @escaping (MessageId) -> Void) { + init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openMessage: @escaping (EngineMessage.Id) -> Void) { self.context = context self.loadDetailedGraph = loadDetailedGraph self.openMessage = openMessage @@ -41,7 +40,7 @@ private enum StatsEntry: ItemListNodeEntry { case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case publicForwardsTitle(PresentationTheme, String) - case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message) + case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EngineMessage) var section: ItemListSectionId { switch self { @@ -176,7 +175,7 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search entries.append(.publicForwardsTitle(presentationData.theme, presentationData.strings.Stats_MessagePublicForwardsTitle.uppercased())) var index: Int32 = 0 for message in messages.messages { - entries.append(.publicForward(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, message)) + entries.append(.publicForward(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, EngineMessage(message))) index += 1 } } @@ -185,8 +184,8 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search return entries } -public func messageStatsController(context: AccountContext, messageId: MessageId, statsDatacenterId: Int32?) -> ViewController { - var navigateToMessageImpl: ((MessageId) -> Void)? +public func messageStatsController(context: AccountContext, messageId: EngineMessage.Id, statsDatacenterId: Int32?) -> ViewController { + var navigateToMessageImpl: ((EngineMessage.Id) -> Void)? let actionsDisposable = DisposableSet() let dataPromise = Promise(nil) diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index c085b6ccec..ed3eeee0b1 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -965,7 +965,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } } - items.append(VoiceChatPeerActionSheetItem(context: context, peer: peer.peer, title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { + items.append(VoiceChatPeerActionSheetItem(context: context, peer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { dismissAction() completion(peer.peer.id) })) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 61283c8744..b2293dd85e 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -496,7 +496,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController text = .text(about, textIcon, .generic) } - return VoiceChatTileItem(account: context.account, peer: peerEntry.peer, videoEndpointId: videoEndpointId, videoReady: videoReady, videoTimeouted: videoTimeouted, isVideoLimit: false, videoLimit: 0, isPaused: videoIsPaused, isOwnScreencast: peerEntry.presentationEndpointId == videoEndpointId && peerEntry.isMyPeer, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, secondary: secondary, isTablet: isTablet, icon: showAsPresentation ? .presentation : icon, text: text, additionalText: additionalText, action: { + return VoiceChatTileItem(account: context.account, peer: EnginePeer(peerEntry.peer), videoEndpointId: videoEndpointId, videoReady: videoReady, videoTimeouted: videoTimeouted, isVideoLimit: false, videoLimit: 0, isPaused: videoIsPaused, isOwnScreencast: peerEntry.presentationEndpointId == videoEndpointId && peerEntry.isMyPeer, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, secondary: secondary, isTablet: isTablet, icon: showAsPresentation ? .presentation : icon, text: text, additionalText: additionalText, action: { interaction.switchToPeer(peer.id, videoEndpointId, !secondary) }, contextAction: { node, gesture in interaction.peerContextAction(peerEntry, node, gesture, false) @@ -2709,7 +2709,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController return } - let controller = VoiceChatRecordingSetupController(context: strongSelf.context, peer: peer, completion: { [weak self] videoOrientation in + let controller = VoiceChatRecordingSetupController(context: strongSelf.context, peer: EnginePeer(peer), completion: { [weak self] videoOrientation in if let strongSelf = self { let title: String let text: String @@ -5282,7 +5282,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController tileItems.removeAll() gridTileItems.removeAll() - tileItems.append(VoiceChatTileItem(account: self.context.account, peer: peer, videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, videoLimit: configuration.videoParticipantsMaxCount, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil)) + tileItems.append(VoiceChatTileItem(account: self.context.account, peer: EnginePeer(peer), videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, videoLimit: configuration.videoParticipantsMaxCount, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil)) } else if let callState = self.callState, !tileItems.isEmpty && callState.isVideoWatchersLimitReached && self.connectedOnce && (callState.canManageCall || callState.adminIds.contains(self.context.account.peerId)) { reachedLimit = true } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 7310677131..af8d621318 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -383,7 +383,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { if let videoNode = self.videoNode, videoNode.supernode == self.videoContainerNode, !videoNode.alpha.isZero { hasVideo = true } - let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: item.peer, text: item.text, customNode: hasVideo ? self.videoContainerNode : nil, additionalEntry: .single(nil), requestDismiss: { [weak self] in + let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: EnginePeer(item.peer), text: item.text, customNode: hasVideo ? self.videoContainerNode : nil, additionalEntry: .single(nil), requestDismiss: { [weak self] in self?.contextSourceNode.requestDismiss?() }) profileNode.frame = CGRect(origin: CGPoint(), size: extractedRect.size) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift index 4b0ea7ee4c..7e724bd582 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift @@ -8,7 +8,6 @@ import TelegramUIPreferences import TelegramVoip import TelegramAudio import AccountContext -import Postbox import TelegramCore import AppBundle import ContextUI diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index b989d5d491..8af9954d51 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -512,7 +512,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context) avatarListWrapperNode.contentNode.clipsToBounds = true avatarListNode.backgroundColor = .clear - avatarListNode.peer = item.peer + avatarListNode.peer = EnginePeer(item.peer) avatarListNode.firstFullSizeOnly = true avatarListNode.offsetLocation = true avatarListNode.customCenterTapAction = { [weak self] in @@ -528,7 +528,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode) avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode) - avatarListNode.update(size: targetRect.size, peer: item.peer, customNode: nil, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate) + avatarListNode.update(size: targetRect.size, peer: EnginePeer(item.peer), customNode: nil, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate) strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode) strongSelf.audioLevelView?.alpha = 0.0 diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift index bd1eb8bb1f..16e876cb8d 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift @@ -3,19 +3,18 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramPresentationData import AvatarNode import AccountContext public class VoiceChatPeerActionSheetItem: ActionSheetItem { public let context: AccountContext - public let peer: Peer + public let peer: EnginePeer public let title: String public let subtitle: String public let action: () -> Void - public init(context: AccountContext, peer: Peer, title: String, subtitle: String, action: @escaping () -> Void) { + public init(context: AccountContext, peer: EnginePeer, title: String, subtitle: String, action: @escaping () -> Void) { self.context = context self.peer = peer self.title = title @@ -120,7 +119,7 @@ public class VoiceChatPeerActionSheetItemNode: ActionSheetItemNode { self.subtitleNode.attributedText = NSAttributedString(string: item.subtitle, font: subtitleFont, textColor: self.theme.secondaryTextColor) let theme = item.context.sharedContext.currentPresentationData.with { $0 }.theme - self.avatarNode.setPeer(context: item.context, theme: theme, peer: EnginePeer(item.peer)) + self.avatarNode.setPeer(context: item.context, theme: theme, peer: item.peer) self.accessibilityArea.accessibilityTraits = [.button] self.accessibilityArea.accessibilityLabel = item.title diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift index 859b8302cc..9742a7f585 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -20,7 +19,7 @@ private let backgroundCornerRadius: CGFloat = 14.0 final class VoiceChatPeerProfileNode: ASDisplayNode { private let context: AccountContext private let size: CGSize - private var peer: Peer + private var peer: EnginePeer private var text: VoiceChatParticipantItem.ParticipantText private let customNode: ASDisplayNode? private let additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError> @@ -36,7 +35,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { private var appeared = false - init(context: AccountContext, size: CGSize, sourceSize: CGSize, peer: Peer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) { + init(context: AccountContext, size: CGSize, sourceSize: CGSize, peer: EnginePeer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) { self.context = context self.size = size self.peer = peer @@ -129,7 +128,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { let titleFont = Font.regular(17.0) let titleColor = UIColor.white var titleAttributedString: NSAttributedString? - if let user = self.peer as? TelegramUser { + if case let .user(user) = self.peer { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch presentationData.nameDisplayOrder { @@ -150,9 +149,9 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { } else { titleAttributedString = NSAttributedString(string: presentationData.strings.User_DeletedAccount, font: titleFont, textColor: titleColor) } - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor) - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor) } self.titleNode.attributedText = titleAttributedString diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift index b53b66f7a8..f03d96ef09 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -18,14 +17,14 @@ final class VoiceChatRecordingSetupController: ViewController { } private let context: AccountContext - private let peer: Peer + private let peer: EnginePeer private let completion: (Bool?) -> Void private var animatedIn = false private var presentationDataDisposable: Disposable? - init(context: AccountContext, peer: Peer, completion: @escaping (Bool?) -> Void) { + init(context: AccountContext, peer: EnginePeer, completion: @escaping (Bool?) -> Void) { self.context = context self.peer = peer self.completion = completion @@ -149,7 +148,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, var dismiss: (() -> Void)? var cancel: (() -> Void)? - init(controller: VoiceChatRecordingSetupController, context: AccountContext, peer: Peer) { + init(controller: VoiceChatRecordingSetupController, context: AccountContext, peer: EnginePeer) { self.controller = controller self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -183,7 +182,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, self.contentBackgroundNode.backgroundColor = backgroundColor let isLivestream: Bool - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isLivestream = true } else { isLivestream = false diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift index df22c4536f..41f6d0c6ef 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramUIPreferences @@ -23,7 +22,7 @@ final class VoiceChatTileItem: Equatable { } let account: Account - let peer: Peer + let peer: EnginePeer let videoEndpointId: String let videoReady: Bool let videoTimeouted: Bool @@ -48,7 +47,7 @@ final class VoiceChatTileItem: Equatable { return self.videoEndpointId } - init(account: Account, peer: Peer, videoEndpointId: String, videoReady: Bool, videoTimeouted: Bool, isVideoLimit: Bool, videoLimit: Int32, isPaused: Bool, isOwnScreencast: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, secondary: Bool, isTablet: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping (GroupVideoNode.Position) -> GroupVideoNode?, getAudioLevel: (() -> Signal)?) { + init(account: Account, peer: EnginePeer, videoEndpointId: String, videoReady: Bool, videoTimeouted: Bool, isVideoLimit: Bool, videoLimit: Int32, isPaused: Bool, isOwnScreencast: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, secondary: Bool, isTablet: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping (GroupVideoNode.Position) -> GroupVideoNode?, getAudioLevel: (() -> Signal)?) { self.account = account self.peer = peer self.videoEndpointId = videoEndpointId @@ -73,7 +72,7 @@ final class VoiceChatTileItem: Equatable { } static func == (lhs: VoiceChatTileItem, rhs: VoiceChatTileItem) -> Bool { - if !arePeersEqual(lhs.peer, rhs.peer) { + if lhs.peer != rhs.peer { return false } if lhs.videoEndpointId != rhs.videoEndpointId { @@ -451,7 +450,7 @@ final class VoiceChatTileItemNode: ASDisplayNode { var titleAttributedString: NSAttributedString? if item.isVideoLimit { titleAttributedString = nil - } else if let user = item.peer as? TelegramUser { + } else if case let .user(user) = item.peer { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch item.nameDisplayOrder { @@ -472,9 +471,9 @@ final class VoiceChatTileItemNode: ASDisplayNode { } else { titleAttributedString = NSAttributedString(string: item.strings.User_DeletedAccount, font: titleFont, textColor: titleColor) } - } else if let group = item.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = item.peer { titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor) - } else if let channel = item.peer as? TelegramChannel { + } else if case let .channel(channel) = item.peer { titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor) } @@ -910,7 +909,7 @@ private class VoiceChatTileShimmeringNode: ASDisplayNode { private var currentShimmering: Bool? private var currentSize: CGSize? - public init(account: Account, peer: Peer) { + public init(account: Account, peer: EnginePeer) { self.backgroundNode = ImageNode(enableHasImage: false, enableEmpty: false, enableAnimatedTransition: true) self.backgroundNode.displaysAsynchronously = false self.backgroundNode.contentMode = .scaleAspectFill @@ -930,7 +929,7 @@ private class VoiceChatTileShimmeringNode: ASDisplayNode { self.addSubnode(self.borderNode) self.borderNode.addSubnode(self.borderEffectNode) - self.backgroundNode.setSignal(peerAvatarCompleteImage(account: account, peer: EnginePeer(peer), size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true)) + self.backgroundNode.setSignal(peerAvatarCompleteImage(account: account, peer: peer, size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true)) } public override func didLoad() { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift index 4aee77ae3c..1ba276b3db 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index a478bb3e8a..ef4ca53c0f 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1282,7 +1282,7 @@ public class Account { return _internal_reindexCacheInBackground(account: self, lowImpact: lowImpact) |> then( Signal { subscriber in - return postbox.mediaBox.updateResourceIndex(lowImpact: lowImpact, completion: { + return postbox.mediaBox.updateResourceIndex(otherResourceContentType: MediaResourceUserContentType.other.rawValue, lowImpact: lowImpact, completion: { subscriber.putCompletion() }) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index d8c8d6d550..252e52681e 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -389,7 +389,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } } -public final class TelegramMediaAction: Media { +public final class TelegramMediaAction: Media, Equatable { public let id: MediaId? = nil public var peerIds: [PeerId] { return self.action.peerIds @@ -416,6 +416,10 @@ public final class TelegramMediaAction: Media { self.action.encode(encoder) } + public static func ==(lhs: TelegramMediaAction, rhs: TelegramMediaAction) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaAction { return self.action == other.action diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift index 184a367271..2a1fd8aeb0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift @@ -1,6 +1,6 @@ import Postbox -public final class TelegramMediaContact: Media { +public final class TelegramMediaContact: Media, Equatable { public let id: MediaId? = nil public let firstName: String public let lastName: String @@ -51,6 +51,10 @@ public final class TelegramMediaContact: Media { } } + public static func ==(lhs: TelegramMediaContact, rhs: TelegramMediaContact) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaContact { if self.id == other.id && self.firstName == other.firstName && self.lastName == other.lastName && self.phoneNumber == other.phoneNumber && self.peerId == other.peerId && self.vCardData == other.vCardData && self.peerIds == other.peerIds { @@ -63,4 +67,4 @@ public final class TelegramMediaContact: Media { public func isSemanticallyEqual(to other: Media) -> Bool { return self.isEqual(to: other) } -} \ No newline at end of file +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift index 5a569fa0dd..d142f4d89d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift @@ -1,6 +1,6 @@ import Postbox -public final class TelegramMediaDice: Media { +public final class TelegramMediaDice: Media, Equatable { public let emoji: String public let value: Int32? @@ -26,6 +26,10 @@ public final class TelegramMediaDice: Media { } } + public static func ==(lhs: TelegramMediaDice, rhs: TelegramMediaDice) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaDice { if self.emoji != other.emoji { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift index 17f650d664..cfa0f73ca6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift @@ -6,7 +6,7 @@ public enum TelegramMediaExpiredContentData: Int32 { case file } -public final class TelegramMediaExpiredContent: Media { +public final class TelegramMediaExpiredContent: Media, Equatable { public let data: TelegramMediaExpiredContentData public let id: MediaId? = nil @@ -24,6 +24,10 @@ public final class TelegramMediaExpiredContent: Media { encoder.encodeInt32(self.data.rawValue, forKey: "d") } + public static func ==(lhs: TelegramMediaExpiredContent, rhs: TelegramMediaExpiredContent) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaExpiredContent { return self.data == other.data diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift index 981a067c14..93fb7d8e03 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift @@ -1,6 +1,6 @@ import Postbox -public final class TelegramMediaGame: Media { +public final class TelegramMediaGame: Media, Equatable { public let gameId: Int64 public let accessHash: Int64 public let name: String @@ -52,6 +52,10 @@ public final class TelegramMediaGame: Media { } } + public static func ==(lhs: TelegramMediaGame, rhs: TelegramMediaGame) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { guard let other = other as? TelegramMediaGame else { return false diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift index b919f16065..3cb14d3ea0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift @@ -87,7 +87,7 @@ public enum TelegramExtendedMedia: PostboxCoding, Equatable { } } -public final class TelegramMediaInvoice: Media { +public final class TelegramMediaInvoice: Media, Equatable { public static let lastVersion: Int32 = 1 public var peerIds: [PeerId] = [] @@ -171,6 +171,10 @@ public final class TelegramMediaInvoice: Media { encoder.encodeInt32(self.version, forKey: "vrs") } + public static func ==(lhs: TelegramMediaInvoice, rhs: TelegramMediaInvoice) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { guard let other = other as? TelegramMediaInvoice else { return false diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift index 3e9d71321f..e4fa801154 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift @@ -130,7 +130,7 @@ public final class MapVenue: PostboxCoding, Equatable { } } -public final class TelegramMediaMap: Media { +public final class TelegramMediaMap: Media, Equatable { public let latitude: Double public let longitude: Double public let heading: Int32? @@ -200,6 +200,10 @@ public final class TelegramMediaMap: Media { } } + public static func ==(lhs: TelegramMediaMap, rhs: TelegramMediaMap) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaMap { if self.latitude != other.latitude || self.longitude != other.longitude { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift index 9995166213..63cbf795b8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift @@ -2,7 +2,7 @@ import Foundation import Postbox -public final class TelegramMediaUnsupported: Media { +public final class TelegramMediaUnsupported: Media, Equatable { public let id: MediaId? = nil public let peerIds: [PeerId] = [] @@ -15,6 +15,10 @@ public final class TelegramMediaUnsupported: Media { public func encode(_ encoder: PostboxEncoder) { } + public static func ==(lhs: TelegramMediaUnsupported, rhs: TelegramMediaUnsupported) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if other is TelegramMediaUnsupported { return true diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift index 54a7f7ecf7..a590cc269c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift @@ -1,6 +1,6 @@ import Postbox -public enum EngineMedia { +public enum EngineMedia: Equatable { public typealias Id = MediaId case image(TelegramMediaImage) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index 82a3ab83af..853acdacf7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -3,6 +3,17 @@ import SwiftSignalKit import Postbox import TelegramApi +public enum MediaResourceUserContentType: UInt8, Equatable { + case other = 0 + case image = 1 + case video = 2 + case audio = 3 + case file = 4 + case sticker = 6 + case avatar = 7 + case audioVideoMessage = 8 +} + public extension MediaResourceUserContentType { init(file: TelegramMediaFile) { if file.isInstantVideo || file.isVoice { @@ -25,6 +36,12 @@ public extension MediaResourceUserContentType { } } +public extension MediaResourceFetchParameters { + init(tag: MediaResourceFetchTag?, info: MediaResourceFetchInfo?, location: MediaResourceStorageLocation?, contentType: MediaResourceUserContentType, isRandomAccessAllowed: Bool) { + self.init(tag: tag, info: info, location: location, contentType: contentType.rawValue, isRandomAccessAllowed: isRandomAccessAllowed) + } +} + func bufferedFetch(_ signal: Signal) -> Signal { return Signal { subscriber in final class State { @@ -251,7 +268,7 @@ public extension TelegramEngine { return _internal_reindexCacheInBackground(account: self.account, lowImpact: lowImpact) |> then(Signal { subscriber in - return mediaBox.updateResourceIndex(lowImpact: lowImpact, completion: { + return mediaBox.updateResourceIndex(otherResourceContentType: MediaResourceUserContentType.other.rawValue, lowImpact: lowImpact, completion: { subscriber.putCompletion() }) }) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index 9e139f7c9a..bb0fbf6763 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -92,6 +92,28 @@ public struct ImportSticker { } } +public extension ImportSticker { + var stickerPackItem: StickerPackItem? { + guard let resource = self.resource as? TelegramMediaResource else { + return nil + } + var fileAttributes: [TelegramMediaFileAttribute] = [] + if self.mimeType == "video/webm" { + fileAttributes.append(.FileName(fileName: "sticker.webm")) + fileAttributes.append(.Animated) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + } else if self.mimeType == "application/x-tgsticker" { + fileAttributes.append(.FileName(fileName: "sticker.tgs")) + fileAttributes.append(.Animated) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + } else { + fileAttributes.append(.FileName(fileName: "sticker.webp")) + } + fileAttributes.append(.ImageSize(size: self.dimensions)) + return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: self.mimeType, size: nil, attributes: fileAttributes), indexKeys: []) + } +} + public enum CreateStickerSetStatus { case progress(Float, Int32, Int32) case complete(StickerPackCollectionInfo, [StickerPackItem]) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 52087d1c77..dbfea1dce2 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import AppBundle diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index aedf979642..7b5ead0a17 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -294,8 +294,6 @@ swift_library( "//submodules/AdUI:AdUI", "//submodules/SparseItemGrid:SparseItemGrid", "//submodules/CalendarMessageScreen:CalendarMessageScreen", - "//submodules/LottieMeshSwift:LottieMeshSwift", - "//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/DirectMediaImageCache:DirectMediaImageCache", "//submodules/CodeInputView:CodeInputView", "//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift index 9465e1d7c5..369dfc812d 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import ComponentDisplayAdapters import BundleIconComponent diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 9db9edbbb1..8bda5b9955 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -16,7 +16,6 @@ import TelegramCallsUI import TelegramBaseController import AsyncDisplayKit import PresentationDataUtils -import MeshAnimationCache import FetchManagerImpl import InAppPurchaseManager import AnimationCache @@ -168,7 +167,6 @@ public final class AccountContextImpl: AccountContext { private var experimentalUISettingsDisposable: Disposable? public let cachedGroupCallContexts: AccountGroupCallContextCache - public let meshAnimationCache: MeshAnimationCache public let animationCache: AnimationCache public let animationRenderer: MultiAnimationRenderer @@ -281,7 +279,6 @@ public final class AccountContextImpl: AccountContext { } self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl() - self.meshAnimationCache = MeshAnimationCache(mediaBox: account.postbox.mediaBox) let cacheStorageBox = self.account.postbox.mediaBox.cacheStorageBox self.animationCache = AnimationCacheImpl(basePath: self.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d1e479f861..1c434dca0a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -66,7 +66,6 @@ import WallpaperBackgroundNode import ChatListUI import CalendarMessageScreen import ReactionSelectionNode -import LottieMeshSwift import ReactionListContextMenuContent import AttachmentUI import AttachmentTextInputPanelNode @@ -3859,7 +3858,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let galleryController = AvatarGalleryController(context: context, peer: peer._asPeer(), remoteEntries: nil, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in }, synchronousLoad: true) galleryController.setHintWillBePresentedInPreviewingContext(true) @@ -4521,7 +4520,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil else { return } - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in }, synchronousLoad: true) galleryController.setHintWillBePresentedInPreviewingContext(true) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 5cf74c2ebc..c420091dd2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -25,7 +25,6 @@ import ShimmerEffect import WallpaperBackgroundNode import LocalMediaResources import AppBundle -import LottieMeshSwift import ChatPresentationInterfaceState import TextNodeWithEntities import ChatControllerInteraction @@ -2009,38 +2008,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId) - if #available(iOS 13.0, *), !"".isEmpty, item.context.sharedContext.immediateExperimentalUISettings.acceleratedStickers, let meshAnimation = item.context.meshAnimationCache.get(resource: resource) { - var overlayMeshAnimationNode: ChatMessageTransitionNode.DecorationItemNode? - if let current = self.overlayMeshAnimationNode { - overlayMeshAnimationNode = current - } else { - if let animationView = MeshRenderer() { - let animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height) - .offsetBy(dx: incomingMessage ? animationNodeFrame.width - 10.0 : -animationNodeFrame.width + 10.0, dy: 0.0) - animationView.frame = animationFrame - - animationView.allAnimationsCompleted = { [weak transitionNode, weak animationView, weak self] in - guard let strongSelf = self, let animationView = animationView else { - return - } - guard let overlayMeshAnimationNode = strongSelf.overlayMeshAnimationNode else { - return - } - if overlayMeshAnimationNode.contentView !== animationView { - return - } - strongSelf.overlayMeshAnimationNode = nil - transitionNode?.remove(decorationNode: overlayMeshAnimationNode) - } - - overlayMeshAnimationNode = transitionNode.add(decorationView: animationView, itemNode: self) - self.overlayMeshAnimationNode = overlayMeshAnimationNode - } - } - if let meshRenderer = overlayMeshAnimationNode?.contentView as? MeshRenderer { - meshRenderer.add(mesh: meshAnimation, offset: CGPoint(x: CGFloat.random(in: -30.0 ... 30.0), y: CGFloat.random(in: -30.0 ... 30.0))) - } - } else { + do { let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) let additionalAnimationNode = DefaultAnimatedStickerNodeImpl() additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 1.6), height: Int(animationSize.height * 1.6), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index c0a3cd4c3d..cb75a7ff71 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -335,7 +335,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } case .playbackStatus: - if let context = self.context, let message = self.message, let type = peerMessageMediaPlayerType(message) { + if let context = self.context, let message = self.message, let type = peerMessageMediaPlayerType(EngineMessage(message)) { context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type) } } @@ -543,19 +543,19 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { if statusUpdated { if arguments.message.flags.isSending { - updatedStatusSignal = combineLatest(messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions), messageMediaFileStatus(context: arguments.context, messageId: arguments.message.id, file: arguments.file)) + updatedStatusSignal = combineLatest(messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions), messageMediaFileStatus(context: arguments.context, messageId: arguments.message.id, file: arguments.file)) |> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, actualFetchStatus) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) } else { - updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions) + updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions) |> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, nil) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) } - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) } var isAudio = false @@ -1196,7 +1196,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { peak: audioWaveform?.peak ?? 0, status: strongSelf.playbackStatus.get(), seek: { timestamp in - if let strongSelf = self, let context = strongSelf.context, let message = strongSelf.message, let type = peerMessageMediaPlayerType(message) { + if let strongSelf = self, let context = strongSelf.context, let message = strongSelf.message, let type = peerMessageMediaPlayerType(EngineMessage(message)) { context.sharedContext.mediaManager.playlistControl(.seek(timestamp), type: type) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 2beb1ff859..f7492a9ec5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -416,7 +416,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { var updatedPlaybackStatus: Signal? if let updatedFile = updatedFile, updatedMedia || updatedMessageId { - updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: item.message, isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id) |> map { $0.0 }) + updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: EngineMessage(item.message), isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in if let pendingStatus = pendingStatus { var progress = pendingStatus.progress @@ -1200,7 +1200,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5) - let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false, isDownloadList: false) + let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: EngineMessage(item.message), isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false, isDownloadList: false) playbackStatusNode.status = status self.durationNode?.status = status |> map(Optional.init) diff --git a/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift b/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift index 4aa1bf13af..6b0550d34b 100644 --- a/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift +++ b/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 87f9351a43..b4555bbca1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2586,7 +2586,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.state = state self.peer = peer self.threadData = threadData - self.avatarListNode.listContainerNode.peer = peer + self.avatarListNode.listContainerNode.peer = peer.flatMap(EnginePeer.init) let isFirstTime = self.validLayout == nil self.validLayout = (width, deviceMetrics) @@ -3382,7 +3382,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.avatarListNode.containerNode.view.mask = nil } - self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition) + self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer.flatMap(EnginePeer.init), isExpanded: self.isAvatarExpanded, transition: transition) if self.avatarListNode.listContainerNode.isCollapsing && !self.ignoreCollapse { self.avatarListNode.avatarContainerNode.canAttachVideo = false } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index d7cda1b9b4..734ece2992 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3027,7 +3027,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } let entriesPromise = Promise<[AvatarGalleryEntry]>(entries) - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceCorners: .round, remoteEntries: entriesPromise, skipInitial: true, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), sourceCorners: .round, remoteEntries: entriesPromise, skipInitial: true, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in }) galleryController.openAvatarSetup = { [weak self] completion in self?.openAvatarForEditing(fromGallery: true, completion: { _ in @@ -3728,7 +3728,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate })) ] - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in }, synchronousLoad: true) galleryController.setHintWillBePresentedInPreviewingContext(true) diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index 60b69c7f6e..4bc128c4f0 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -29,7 +28,7 @@ private final class PrefetchManagerInnerImpl { private var listDisposable: Disposable? - private var contexts: [MediaId: PrefetchMediaContext] = [:] + private var contexts: [EngineMedia.Id: PrefetchMediaContext] = [:] private let preloadGreetingStickerDisposable = MetaDisposable() fileprivate let preloadedGreetingStickerPromise = Promise(nil) @@ -109,7 +108,7 @@ private final class PrefetchManagerInnerImpl { return } #endif - var validIds = Set() + var validIds = Set() var order: Int32 = 0 for mediaItem in items { switch mediaItem { @@ -136,15 +135,15 @@ private final class PrefetchManagerInnerImpl { } else { peerType = .otherPrivate } - var mediaResource: MediaResource? + var mediaResource: EngineMediaResource? if let telegramImage = mediaItem.media.media as? TelegramMediaImage { - mediaResource = largestRepresentationForPhoto(telegramImage)?.resource + mediaResource = (largestRepresentationForPhoto(telegramImage)?.resource).flatMap(EngineMediaResource.init) if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) { automaticDownload = .full } } else if let telegramFile = mediaItem.media.media as? TelegramMediaFile { - mediaResource = telegramFile.resource + mediaResource = EngineMediaResource(telegramFile.resource) if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) { automaticDownload = .full } else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) { @@ -173,7 +172,7 @@ private final class PrefetchManagerInnerImpl { if case .full = automaticDownload { if let image = media as? TelegramMediaImage { - context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerId: nil).start()) + context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource._asResource(), userInitiated: false, priority: priority, storeToDownloadsPeerId: nil).start()) } else if let _ = media as? TelegramMediaWebFile { //strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start()) } else if let file = media as? TelegramMediaFile { @@ -213,7 +212,7 @@ private final class PrefetchManagerInnerImpl { context = PrefetchMediaContext() self.contexts[id] = context - let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, threadId: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: order), timestamp: 0)) + let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, threadId: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: EngineMessage.Index(id: EngineMessage.Id(peerId: EnginePeer.Id(0), namespace: 0, id: order), timestamp: 0)) if case .full = automaticDownload { let fetchSignal = freeMediaFileInteractiveFetched(fetchManager: self.fetchManager, fileReference: .standalone(media: media), priority: priority) @@ -224,7 +223,7 @@ private final class PrefetchManagerInnerImpl { } } } - var removeIds: [MediaId] = [] + var removeIds: [EngineMedia.Id] = [] for key in self.contexts.keys { if !validIds.contains(key) { removeIds.append(key) diff --git a/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift b/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift index 0df5c16081..58b471333f 100644 --- a/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift +++ b/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift @@ -1,10 +1,10 @@ import Foundation import UIKit -import Postbox import Display +import TelegramCore -func prepareSecretThumbnailData(_ data: MediaResourceData) -> (CGSize, Data)? { - if data.complete, let image = UIImage(contentsOfFile: data.path) { +func prepareSecretThumbnailData(_ data: EngineMediaResource.ResourceData) -> (CGSize, Data)? { + if data.isComplete, let image = UIImage(contentsOfFile: data.path) { if image.size.width < 100 && image.size.height < 100 { if let resultData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { return (image.size, resultData) diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index 459906cc6a..6c8e75a175 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -106,7 +106,7 @@ func makeTelegramAccountAuxiliaryMethods(appDelegate: AppDelegate?) -> AccountAu } return .single(nil) }, prepareSecretThumbnailData: { data in - return prepareSecretThumbnailData(data).flatMap { size, data in + return prepareSecretThumbnailData(EngineMediaResource.ResourceData(data)).flatMap { size, data in return (PixelDimensions(size), data) } }, backgroundUpload: { postbox, _, resource in