Drawing improvements

This commit is contained in:
Ilya Laktyushin 2023-01-03 05:15:36 +04:00
parent afe3c2bd6b
commit 076129348b
5 changed files with 315 additions and 139 deletions

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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(

View File

@ -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
}

View File

@ -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))
}
}