mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Drawing improvements
This commit is contained in:
parent
afe3c2bd6b
commit
076129348b
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import CoreServices
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import LegacyComponents
|
||||
@ -921,7 +922,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
func addTextEntity() {
|
||||
let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white))
|
||||
let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, animation: .none, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white))
|
||||
self.insertEntity.invoke(textEntity)
|
||||
}
|
||||
|
||||
@ -934,7 +935,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
self?.updateEntitiesPlayback.invoke(true)
|
||||
|
||||
if let file = file {
|
||||
let stickerEntity = DrawingStickerEntity(file: file)
|
||||
let stickerEntity = DrawingStickerEntity(content: .file(file))
|
||||
self?.insertEntity.invoke(stickerEntity)
|
||||
} else {
|
||||
self?.updateCurrentMode(.drawing)
|
||||
@ -1085,6 +1086,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
component: TextSettingsComponent(
|
||||
color: nil,
|
||||
style: DrawingTextStyle(style: textEntity.style),
|
||||
animation: DrawingTextAnimation(animation: textEntity.animation),
|
||||
alignment: DrawingTextAlignment(alignment: textEntity.alignment),
|
||||
font: DrawingTextFont(font: textEntity.font),
|
||||
isEmojiKeyboard: false,
|
||||
@ -1111,6 +1113,27 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
}
|
||||
state?.updated(transition: .easeInOut(duration: 0.2))
|
||||
},
|
||||
toggleAnimation: { [weak state, weak textEntity] in
|
||||
guard let textEntity = textEntity else {
|
||||
return
|
||||
}
|
||||
var nextAnimation: DrawingTextEntity.Animation
|
||||
switch textEntity.animation {
|
||||
case .none:
|
||||
nextAnimation = .typing
|
||||
case .typing:
|
||||
nextAnimation = .wiggle
|
||||
case .wiggle:
|
||||
nextAnimation = .zoomIn
|
||||
case .zoomIn:
|
||||
nextAnimation = .none
|
||||
}
|
||||
textEntity.animation = nextAnimation
|
||||
if let entityView = textEntity.currentEntityView {
|
||||
entityView.update()
|
||||
}
|
||||
state?.updated(transition: .easeInOut(duration: 0.2))
|
||||
},
|
||||
toggleAlignment: { [weak state, weak textEntity] in
|
||||
guard let textEntity = textEntity else {
|
||||
return
|
||||
@ -1933,7 +1956,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, UIDropInteractionDelegate {
|
||||
fileprivate final class Node: ViewControllerTracingNode {
|
||||
private weak var controller: DrawingScreen?
|
||||
private let context: AccountContext
|
||||
@ -1942,7 +1965,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
private let performAction: ActionSlot<DrawingView.Action>
|
||||
private let updateToolState: ActionSlot<DrawingToolState>
|
||||
private let updateSelectedEntity: ActionSlot<DrawingEntity?>
|
||||
private let insertEntity: ActionSlot<DrawingEntity>
|
||||
fileprivate let insertEntity: ActionSlot<DrawingEntity>
|
||||
private let deselectEntity: ActionSlot<Void>
|
||||
private let updateEntitiesPlayback: ActionSlot<Bool>
|
||||
private let previewBrushSize: ActionSlot<CGFloat?>
|
||||
@ -2683,6 +2706,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
TextSettingsComponent(
|
||||
color: textEntity.color,
|
||||
style: DrawingTextStyle(style: textEntity.style),
|
||||
animation: DrawingTextAnimation(animation: textEntity.animation),
|
||||
alignment: DrawingTextAlignment(alignment: textEntity.alignment),
|
||||
font: DrawingTextFont(font: textEntity.font),
|
||||
isEmojiKeyboard: entityView.textView.inputView != nil,
|
||||
@ -2731,6 +2755,29 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, orientation: orientation, transition: .immediate)
|
||||
}
|
||||
},
|
||||
toggleAnimation: { [weak self] in
|
||||
self?.dismissFontPicker()
|
||||
guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else {
|
||||
return
|
||||
}
|
||||
var nextAnimation: DrawingTextEntity.Animation
|
||||
switch textEntity.animation {
|
||||
case .none:
|
||||
nextAnimation = .typing
|
||||
case .typing:
|
||||
nextAnimation = .wiggle
|
||||
case .wiggle:
|
||||
nextAnimation = .zoomIn
|
||||
case .zoomIn:
|
||||
nextAnimation = .none
|
||||
}
|
||||
textEntity.animation = nextAnimation
|
||||
entityView.update()
|
||||
|
||||
if let (layout, orientation) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, orientation: orientation, transition: .immediate)
|
||||
}
|
||||
},
|
||||
toggleAlignment: { [weak self] in
|
||||
self?.dismissFontPicker()
|
||||
guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else {
|
||||
@ -2893,6 +2940,9 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
self.displayNode = Node(controller: self, context: self.context)
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
|
||||
let dropInteraction = UIDropInteraction(delegate: self)
|
||||
self.view.addInteraction(dropInteraction)
|
||||
}
|
||||
|
||||
public func generateResultData() -> TGPaintingData? {
|
||||
@ -2944,15 +2994,17 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
|
||||
var stickers: [Any] = []
|
||||
for entity in self.entitiesView.entities {
|
||||
if let sticker = entity as? DrawingStickerEntity {
|
||||
if let sticker = entity as? DrawingStickerEntity, case let .file(file) = sticker.content {
|
||||
let coder = PostboxEncoder()
|
||||
coder.encodeRootObject(sticker.file)
|
||||
coder.encodeRootObject(file)
|
||||
stickers.append(coder.makeData())
|
||||
} else if let text = entity as? DrawingTextEntity, let subEntities = text.renderSubEntities {
|
||||
for sticker in subEntities {
|
||||
let coder = PostboxEncoder()
|
||||
coder.encodeRootObject(sticker.file)
|
||||
stickers.append(coder.makeData())
|
||||
if case let .file(file) = sticker.content {
|
||||
let coder = PostboxEncoder()
|
||||
coder.encodeRootObject(file)
|
||||
stickers.append(coder.makeData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3004,4 +3056,44 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController {
|
||||
self.orientation = orientation
|
||||
self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
|
||||
return session.hasItemsConforming(toTypeIdentifiers: [kUTTypeImage as String])
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
|
||||
//self.chatDisplayNode.updateDropInteraction(isActive: true)
|
||||
|
||||
let operation: UIDropOperation
|
||||
operation = .copy
|
||||
return UIDropProposal(operation: operation)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
|
||||
session.loadObjects(ofClass: UIImage.self) { [weak self] imageItems in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let images = imageItems as! [UIImage]
|
||||
|
||||
//strongSelf.chatDisplayNode.updateDropInteraction(isActive: false)
|
||||
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
||||
let entity = DrawingStickerEntity(content: .image(image))
|
||||
strongSelf.node.insertEntity.invoke(entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) {
|
||||
//self.chatDisplayNode.updateDropInteraction(isActive: false)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnd session: UIDropSession) {
|
||||
//self.chatDisplayNode.updateDropInteraction(isActive: false)
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,14 @@ import StickerResources
|
||||
import AccountContext
|
||||
|
||||
public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
public enum Content {
|
||||
case file(TelegramMediaFile)
|
||||
case image(UIImage)
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case uuid
|
||||
case isAnimated
|
||||
case file
|
||||
case image
|
||||
case referenceDrawingSize
|
||||
case position
|
||||
case scale
|
||||
@ -21,8 +25,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
}
|
||||
|
||||
public let uuid: UUID
|
||||
public let isAnimated: Bool
|
||||
public let file: TelegramMediaFile
|
||||
public let content: Content
|
||||
|
||||
public var referenceDrawingSize: CGSize
|
||||
public var position: CGPoint
|
||||
@ -38,15 +41,22 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
}
|
||||
|
||||
public var baseSize: CGSize {
|
||||
let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.4)
|
||||
let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.2)
|
||||
return CGSize(width: size, height: size)
|
||||
}
|
||||
|
||||
init(file: TelegramMediaFile) {
|
||||
public var isAnimated: Bool {
|
||||
switch self.content {
|
||||
case let .file(file):
|
||||
return file.isAnimatedSticker || file.isVideoSticker
|
||||
case .image:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
init(content: Content) {
|
||||
self.uuid = UUID()
|
||||
self.isAnimated = file.isAnimatedSticker || file.isVideoSticker
|
||||
|
||||
self.file = file
|
||||
self.content = content
|
||||
|
||||
self.referenceDrawingSize = .zero
|
||||
self.position = CGPoint()
|
||||
@ -58,8 +68,13 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
self.isAnimated = try container.decode(Bool.self, forKey: .isAnimated)
|
||||
self.file = try container.decode(TelegramMediaFile.self, forKey: .file)
|
||||
if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) {
|
||||
self.content = .file(file)
|
||||
} else if let imageData = try container.decodeIfPresent(Data.self, forKey: .image), let image = UIImage(data: imageData) {
|
||||
self.content = .image(image)
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
|
||||
self.position = try container.decode(CGPoint.self, forKey: .position)
|
||||
self.scale = try container.decode(CGFloat.self, forKey: .scale)
|
||||
@ -70,8 +85,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.uuid, forKey: .uuid)
|
||||
try container.encode(self.isAnimated, forKey: .isAnimated)
|
||||
try container.encode(self.file, forKey: .file)
|
||||
switch self.content {
|
||||
case let .file(file):
|
||||
try container.encode(file, forKey: .file)
|
||||
case let .image(image):
|
||||
try container.encodeIfPresent(image.pngData(), forKey: .image)
|
||||
}
|
||||
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
|
||||
try container.encode(self.position, forKey: .position)
|
||||
try container.encode(self.scale, forKey: .scale)
|
||||
@ -80,7 +99,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
}
|
||||
|
||||
public func duplicate() -> DrawingEntity {
|
||||
let newEntity = DrawingStickerEntity(file: self.file)
|
||||
let newEntity = DrawingStickerEntity(content: self.content)
|
||||
newEntity.referenceDrawingSize = self.referenceDrawingSize
|
||||
newEntity.position = self.position
|
||||
newEntity.scale = self.scale
|
||||
@ -108,7 +127,6 @@ final class DrawingStickerEntityView: DrawingEntityView {
|
||||
var started: ((Double) -> Void)?
|
||||
|
||||
private var currentSize: CGSize?
|
||||
private var dimensions: CGSize?
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
@ -139,46 +157,77 @@ final class DrawingStickerEntityView: DrawingEntityView {
|
||||
self.cachedDisposable.dispose()
|
||||
}
|
||||
|
||||
private var file: TelegramMediaFile {
|
||||
return (self.entity as! DrawingStickerEntity).file
|
||||
private var file: TelegramMediaFile? {
|
||||
if case let .file(file) = self.stickerEntity.content {
|
||||
return file
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var image: UIImage? {
|
||||
if case let .image(image) = self.stickerEntity.content {
|
||||
return image
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var dimensions: CGSize {
|
||||
switch self.stickerEntity.content {
|
||||
case let .file(file):
|
||||
return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
case let .image(image):
|
||||
return image.size
|
||||
}
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
if let dimensions = self.file.dimensions {
|
||||
if self.file.isAnimatedSticker || self.file.isVideoSticker || self.file.mimeType == "video/webm" {
|
||||
if self.animationNode == nil {
|
||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
animationNode.autoplay = false
|
||||
self.animationNode = animationNode
|
||||
animationNode.started = { [weak self, weak animationNode] in
|
||||
self?.imageNode.isHidden = true
|
||||
|
||||
if let animationNode = animationNode {
|
||||
let _ = (animationNode.status
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
self?.started?(status.duration)
|
||||
})
|
||||
if let file = self.file {
|
||||
if let dimensions = file.dimensions {
|
||||
if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" {
|
||||
if self.animationNode == nil {
|
||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
animationNode.autoplay = false
|
||||
self.animationNode = animationNode
|
||||
animationNode.started = { [weak self, weak animationNode] in
|
||||
self?.imageNode.isHidden = true
|
||||
|
||||
if let animationNode = animationNode {
|
||||
let _ = (animationNode.status
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
self?.started?(status.duration)
|
||||
})
|
||||
}
|
||||
}
|
||||
self.addSubnode(animationNode)
|
||||
}
|
||||
self.addSubnode(animationNode)
|
||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0))))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: file.resource).start())
|
||||
} else {
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.visibility = false
|
||||
self.animationNode = nil
|
||||
animationNode.removeFromSupernode()
|
||||
self.imageNode.isHidden = false
|
||||
self.didSetUpAnimationNode = false
|
||||
}
|
||||
self.imageNode.setSignal(chatMessageSticker(account: self.context.account, userLocation: .other, file: file, small: false, synchronousLoad: false))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: false)).start())
|
||||
}
|
||||
let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: self.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0))))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(self.file), resource: self.file.resource).start())
|
||||
} else {
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.visibility = false
|
||||
self.animationNode = nil
|
||||
animationNode.removeFromSupernode()
|
||||
self.imageNode.isHidden = false
|
||||
self.didSetUpAnimationNode = false
|
||||
}
|
||||
self.imageNode.setSignal(chatMessageSticker(account: self.context.account, userLocation: .other, file: self.file, small: false, synchronousLoad: false))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: stickerPackFileReference(self.file), resource: chatMessageStickerResource(file: self.file, small: false)).start())
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
self.dimensions = dimensions.cgSize
|
||||
} else if let image = self.image {
|
||||
self.imageNode.setSignal(.single({ arguments -> DrawingContext? in
|
||||
let context = DrawingContext(size: arguments.drawingSize, opaque: false, clear: true)
|
||||
context?.withFlippedContext({ ctx in
|
||||
if let cgImage = image.cgImage {
|
||||
ctx.draw(cgImage, in: CGRect(origin: .zero, size: arguments.drawingSize))
|
||||
}
|
||||
})
|
||||
return context
|
||||
}))
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
@ -215,15 +264,17 @@ final class DrawingStickerEntityView: DrawingEntityView {
|
||||
if self.isPlaying != isPlaying {
|
||||
self.isPlaying = isPlaying
|
||||
|
||||
if isPlaying && !self.didSetUpAnimationNode {
|
||||
self.didSetUpAnimationNode = true
|
||||
let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.file.resource, isVideo: self.file.isVideoSticker || self.file.mimeType == "video/webm")
|
||||
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||
|
||||
self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384)
|
||||
|> deliverOn(Queue.concurrentDefaultQueue())).start())
|
||||
if let file = self.file {
|
||||
if isPlaying && !self.didSetUpAnimationNode {
|
||||
self.didSetUpAnimationNode = true
|
||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm")
|
||||
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||
|
||||
self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384)
|
||||
|> deliverOn(Queue.concurrentDefaultQueue())).start())
|
||||
}
|
||||
}
|
||||
self.animationNode?.visibility = isPlaying
|
||||
}
|
||||
@ -241,33 +292,29 @@ final class DrawingStickerEntityView: DrawingEntityView {
|
||||
let sideSize: CGFloat = size.width
|
||||
let boundingSize = CGSize(width: sideSize, height: sideSize)
|
||||
|
||||
if let dimensions = self.dimensions {
|
||||
let imageSize = dimensions.aspectFitted(boundingSize)
|
||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
animationNode.updateLayout(size: imageSize)
|
||||
|
||||
if !self.didApplyVisibility {
|
||||
self.didApplyVisibility = true
|
||||
self.applyVisibility()
|
||||
}
|
||||
|
||||
let imageSize = self.dimensions.aspectFitted(boundingSize)
|
||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
animationNode.updateLayout(size: imageSize)
|
||||
|
||||
if !self.didApplyVisibility {
|
||||
self.didApplyVisibility = true
|
||||
self.applyVisibility()
|
||||
}
|
||||
self.update(animated: false)
|
||||
}
|
||||
self.update(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func update(animated: Bool) {
|
||||
guard let dimensions = self.stickerEntity.file.dimensions?.cgSize else {
|
||||
return
|
||||
}
|
||||
self.center = self.stickerEntity.position
|
||||
|
||||
let size = self.stickerEntity.baseSize
|
||||
|
||||
self.bounds = CGRect(origin: .zero, size: dimensions.aspectFitted(size))
|
||||
self.bounds = CGRect(origin: .zero, size: self.dimensions.aspectFitted(size))
|
||||
self.transform = CGAffineTransformScale(CGAffineTransformMakeRotation(self.stickerEntity.rotation), self.stickerEntity.scale, self.stickerEntity.scale)
|
||||
|
||||
var transform = CATransform3DIdentity
|
||||
@ -297,13 +344,13 @@ final class DrawingStickerEntityView: DrawingEntityView {
|
||||
self.pushIdentityTransformForMeasurement()
|
||||
|
||||
selectionView.transform = .identity
|
||||
let bounds = self.selectionBounds
|
||||
let center = bounds.center
|
||||
let maxSide = max(self.selectionBounds.width, self.selectionBounds.height)
|
||||
let center = self.selectionBounds.center
|
||||
|
||||
let scale = self.superview?.superview?.layer.value(forKeyPath: "transform.scale.x") as? CGFloat ?? 1.0
|
||||
selectionView.center = self.convert(center, to: selectionView.superview)
|
||||
|
||||
selectionView.bounds = CGRect(origin: .zero, size: CGSize(width: (bounds.width * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0, height: (bounds.height * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0))
|
||||
selectionView.bounds = CGRect(origin: .zero, size: CGSize(width: (maxSide * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0, height: (maxSide * self.stickerEntity.scale) * scale + selectionView.selectionInset * 2.0))
|
||||
selectionView.transform = CGAffineTransformMakeRotation(self.stickerEntity.rotation)
|
||||
|
||||
self.popIdentityTransformForMeasurement()
|
||||
|
@ -43,6 +43,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
case text
|
||||
case textAttributes
|
||||
case style
|
||||
case animation
|
||||
case font
|
||||
case alignment
|
||||
case fontSize
|
||||
@ -61,19 +62,13 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
case filled
|
||||
case semi
|
||||
case stroke
|
||||
|
||||
init(style: DrawingTextEntity.Style) {
|
||||
switch style {
|
||||
case .regular:
|
||||
self = .regular
|
||||
case .filled:
|
||||
self = .filled
|
||||
case .semi:
|
||||
self = .semi
|
||||
case .stroke:
|
||||
self = .stroke
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Animation: Codable {
|
||||
case none
|
||||
case typing
|
||||
case wiggle
|
||||
case zoomIn
|
||||
}
|
||||
|
||||
enum Font: Codable {
|
||||
@ -100,6 +95,9 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
|
||||
public var uuid: UUID
|
||||
public var isAnimated: Bool {
|
||||
if self.animation != .none {
|
||||
return true
|
||||
}
|
||||
var isAnimated = false
|
||||
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
|
||||
if let _ = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
|
||||
@ -111,6 +109,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
|
||||
var text: NSAttributedString
|
||||
var style: Style
|
||||
var animation: Animation
|
||||
var font: Font
|
||||
var alignment: Alignment
|
||||
var fontSize: CGFloat
|
||||
@ -130,11 +129,12 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
public var renderImage: UIImage?
|
||||
public var renderSubEntities: [DrawingStickerEntity]?
|
||||
|
||||
init(text: NSAttributedString, style: Style, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) {
|
||||
init(text: NSAttributedString, style: Style, animation: Animation, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) {
|
||||
self.uuid = UUID()
|
||||
|
||||
self.text = text
|
||||
self.style = style
|
||||
self.animation = animation
|
||||
self.font = font
|
||||
self.alignment = alignment
|
||||
self.fontSize = fontSize
|
||||
@ -160,6 +160,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
self.text = attributedString
|
||||
|
||||
self.style = try container.decode(Style.self, forKey: .style)
|
||||
self.animation = try container.decode(Animation.self, forKey: .animation)
|
||||
self.font = try container.decode(Font.self, forKey: .font)
|
||||
self.alignment = try container.decode(Alignment.self, forKey: .alignment)
|
||||
self.fontSize = try container.decode(CGFloat.self, forKey: .fontSize)
|
||||
@ -191,6 +192,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
try container.encode(textAttributes, forKey: .textAttributes)
|
||||
|
||||
try container.encode(self.style, forKey: .style)
|
||||
try container.encode(self.animation, forKey: .animation)
|
||||
try container.encode(self.font, forKey: .font)
|
||||
try container.encode(self.alignment, forKey: .alignment)
|
||||
try container.encode(self.fontSize, forKey: .fontSize)
|
||||
@ -210,7 +212,7 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
}
|
||||
|
||||
public func duplicate() -> DrawingEntity {
|
||||
let newEntity = DrawingTextEntity(text: self.text, style: self.style, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color)
|
||||
let newEntity = DrawingTextEntity(text: self.text, style: self.style, animation: self.animation, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color)
|
||||
newEntity.referenceDrawingSize = self.referenceDrawingSize
|
||||
newEntity.position = self.position
|
||||
newEntity.width = self.width
|
||||
@ -700,7 +702,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
||||
}
|
||||
let emojiTextPosition = emojiRect.center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0)
|
||||
|
||||
let entity = DrawingStickerEntity(file: file)
|
||||
let entity = DrawingStickerEntity(content: .file(file))
|
||||
entity.referenceDrawingSize = CGSize(width: itemSize * 2.5, height: itemSize * 2.5)
|
||||
entity.scale = scale
|
||||
entity.position = textPosition.offsetBy(
|
||||
|
@ -27,6 +27,26 @@ enum DrawingTextStyle: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
enum DrawingTextAnimation: Equatable {
|
||||
case none
|
||||
case typing
|
||||
case wiggle
|
||||
case zoomIn
|
||||
|
||||
init(animation: DrawingTextEntity.Animation) {
|
||||
switch animation {
|
||||
case .none:
|
||||
self = .none
|
||||
case .typing:
|
||||
self = .typing
|
||||
case .wiggle:
|
||||
self = .wiggle
|
||||
case .zoomIn:
|
||||
self = .zoomIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DrawingTextAlignment: Equatable {
|
||||
case left
|
||||
case center
|
||||
@ -266,6 +286,7 @@ final class TextFontComponent: Component {
|
||||
final class TextSettingsComponent: CombinedComponent {
|
||||
let color: DrawingColor?
|
||||
let style: DrawingTextStyle
|
||||
let animation: DrawingTextAnimation
|
||||
let alignment: DrawingTextAlignment
|
||||
let font: DrawingTextFont
|
||||
let isEmojiKeyboard: Bool
|
||||
@ -277,6 +298,7 @@ final class TextSettingsComponent: CombinedComponent {
|
||||
let updateFastColorPickerPan: (CGPoint) -> Void
|
||||
let dismissFastColorPicker: () -> Void
|
||||
let toggleStyle: () -> Void
|
||||
let toggleAnimation: () -> Void
|
||||
let toggleAlignment: () -> Void
|
||||
let presentFontPicker: () -> Void
|
||||
let toggleKeyboard: (() -> Void)?
|
||||
@ -284,6 +306,7 @@ final class TextSettingsComponent: CombinedComponent {
|
||||
init(
|
||||
color: DrawingColor?,
|
||||
style: DrawingTextStyle,
|
||||
animation: DrawingTextAnimation,
|
||||
alignment: DrawingTextAlignment,
|
||||
font: DrawingTextFont,
|
||||
isEmojiKeyboard: Bool,
|
||||
@ -294,12 +317,14 @@ final class TextSettingsComponent: CombinedComponent {
|
||||
updateFastColorPickerPan: @escaping (CGPoint) -> Void = { _ in },
|
||||
dismissFastColorPicker: @escaping () -> Void = {},
|
||||
toggleStyle: @escaping () -> Void,
|
||||
toggleAnimation: @escaping () -> Void,
|
||||
toggleAlignment: @escaping () -> Void,
|
||||
presentFontPicker: @escaping () -> Void,
|
||||
toggleKeyboard: (() -> Void)?
|
||||
) {
|
||||
self.color = color
|
||||
self.style = style
|
||||
self.animation = animation
|
||||
self.alignment = alignment
|
||||
self.font = font
|
||||
self.isEmojiKeyboard = isEmojiKeyboard
|
||||
@ -310,6 +335,7 @@ final class TextSettingsComponent: CombinedComponent {
|
||||
self.updateFastColorPickerPan = updateFastColorPickerPan
|
||||
self.dismissFastColorPicker = dismissFastColorPicker
|
||||
self.toggleStyle = toggleStyle
|
||||
self.toggleAnimation = toggleAnimation
|
||||
self.toggleAlignment = toggleAlignment
|
||||
self.presentFontPicker = presentFontPicker
|
||||
self.toggleKeyboard = toggleKeyboard
|
||||
@ -322,6 +348,9 @@ final class TextSettingsComponent: CombinedComponent {
|
||||
if lhs.style != rhs.style {
|
||||
return false
|
||||
}
|
||||
if lhs.animation != rhs.animation {
|
||||
return false
|
||||
}
|
||||
if lhs.alignment != rhs.alignment {
|
||||
return false
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
|
||||
}
|
||||
|
||||
let account: Account
|
||||
let file: TelegramMediaFile
|
||||
let file: TelegramMediaFile?
|
||||
let entity: DrawingStickerEntity
|
||||
let animated: Bool
|
||||
let durationPromise = Promise<Double>()
|
||||
@ -95,47 +95,53 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
|
||||
init(account: Account, entity: DrawingStickerEntity) {
|
||||
self.account = account
|
||||
self.entity = entity
|
||||
self.file = entity.file
|
||||
self.animated = file.isAnimatedSticker || file.isVideoSticker
|
||||
|
||||
if file.isAnimatedSticker || file.isVideoSticker {
|
||||
self.source = AnimatedStickerResourceSource(account: account, resource: file.resource, isVideo: file.isVideoSticker)
|
||||
if let source = self.source {
|
||||
let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384, height: 384))
|
||||
self.disposables.add((source.cachedDataPath(width: Int(fittedDimensions.width), height: Int(fittedDimensions.height))
|
||||
self.animated = entity.isAnimated
|
||||
|
||||
switch entity.content {
|
||||
case let .file(file):
|
||||
self.file = file
|
||||
if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" {
|
||||
self.source = AnimatedStickerResourceSource(account: account, resource: file.resource, isVideo: file.isVideoSticker)
|
||||
if let source = self.source {
|
||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384, height: 384))
|
||||
self.disposables.add((source.cachedDataPath(width: Int(fittedDimensions.width), height: Int(fittedDimensions.height))
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] path, complete in
|
||||
if let strongSelf = self, complete {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
||||
let queue = strongSelf.queue
|
||||
let frameSource = AnimatedStickerCachedFrameSource(queue: queue, data: data, complete: complete, notifyUpdated: {})!
|
||||
strongSelf.frameCount = frameSource.frameCount
|
||||
strongSelf.frameRate = frameSource.frameRate
|
||||
|
||||
let duration = Double(frameSource.frameCount) / Double(frameSource.frameRate)
|
||||
strongSelf.totalDuration = duration
|
||||
|
||||
strongSelf.durationPromise.set(.single(duration))
|
||||
|
||||
let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: {
|
||||
return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource)
|
||||
})
|
||||
strongSelf.frameQueue.set(.single(frameQueue))
|
||||
if let strongSelf = self, complete {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
||||
let queue = strongSelf.queue
|
||||
let frameSource = AnimatedStickerCachedFrameSource(queue: queue, data: data, complete: complete, notifyUpdated: {})!
|
||||
strongSelf.frameCount = frameSource.frameCount
|
||||
strongSelf.frameRate = frameSource.frameRate
|
||||
|
||||
let duration = Double(frameSource.frameCount) / Double(frameSource.frameRate)
|
||||
strongSelf.totalDuration = duration
|
||||
|
||||
strongSelf.durationPromise.set(.single(duration))
|
||||
|
||||
let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: {
|
||||
return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource)
|
||||
})
|
||||
strongSelf.frameQueue.set(.single(frameQueue))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
self.disposables.add((chatMessageSticker(account: self.account, userLocation: .other, file: file, small: false, fetched: true, onlyFullSize: true, thumbnail: false, synchronousLoad: false)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] generator in
|
||||
if let strongSelf = self {
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: entity.baseSize, boundingSize: entity.baseSize, intrinsicInsets: UIEdgeInsets()))
|
||||
let image = context?.generateImage()
|
||||
if let image = image {
|
||||
strongSelf.imagePromise.set(.single(image))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
self.disposables.add((chatMessageSticker(account: self.account, userLocation: .other, file: self.file, small: false, fetched: true, onlyFullSize: true, thumbnail: false, synchronousLoad: false)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] generator in
|
||||
if let strongSelf = self {
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: entity.baseSize, boundingSize: entity.baseSize, intrinsicInsets: UIEdgeInsets()))
|
||||
let image = context?.generateImage()
|
||||
if let image = image {
|
||||
strongSelf.imagePromise.set(.single(image))
|
||||
}
|
||||
}
|
||||
}))
|
||||
case let .image(image):
|
||||
self.file = nil
|
||||
self.imagePromise.set(.single(image))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user