From 1798554940988f2646d7088076912ebe76b92f3b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 22 Dec 2022 16:42:13 +0400 Subject: [PATCH] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../ContextControllerActionsStackNode.swift | 6 + submodules/DrawingUI/BUILD | 1 + submodules/DrawingUI/Resources/pencil.png | Bin 1071 -> 0 bytes .../DrawingUI/Sources/ColorPickerScreen.swift | 42 +- .../Sources/DrawingBubbleEntity.swift | 39 +- .../Sources/DrawingEntitiesView.swift | 32 +- .../DrawingUI/Sources/DrawingGesture.swift | 42 ++ .../DrawingUI/Sources/DrawingMetalView.swift | 58 +- .../DrawingUI/Sources/DrawingScreen.swift | 230 +++++--- .../Sources/DrawingSimpleShapeEntity.swift | 21 +- .../Sources/DrawingStickerEntity.swift | 21 +- .../DrawingUI/Sources/DrawingTextEntity.swift | 10 +- .../DrawingUI/Sources/DrawingTools.swift | 168 +----- .../Sources/DrawingVectorEntity.swift | 2 + .../DrawingUI/Sources/DrawingView.swift | 548 +++++++----------- .../DrawingUI/Sources/EyedropperView.swift | 10 + submodules/DrawingUI/Sources/PenTool.swift | 531 ++++++++++++----- .../Sources/StickerPickerScreen.swift | 20 +- .../Sources/TextSettingsComponent.swift | 2 + .../DrawingUI/Sources/ToolsComponent.swift | 100 +--- .../LegacyComponentsContext.h | 1 + .../TGPhotoPaintStickersContext.h | 3 +- .../Sources/TGMediaAssetsPickerController.m | 2 + .../Sources/TGMediaPickerGalleryModel.m | 2 + .../Sources/TGPhotoDrawingController.m | 13 +- .../Sources/LegacyPaintStickersContext.swift | 10 +- .../LegacyUI/Sources/LegacyController.swift | 7 + .../Sources/AvatarGalleryController.swift | 7 +- .../Sources/SegmentedControlNode.swift | 5 + .../SelectivePrivacySettingsController.swift | 7 +- .../Peers/PeerPhotoUpdater.swift | 16 + .../Sources/ChatEntityKeyboardInputNode.swift | 208 +++---- .../ToolArrowShadow.imageset/Arrow.png | Bin 0 -> 26873 bytes .../ToolArrowShadow.imageset/Contents.json | 21 + .../ToolBlurShadow.imageset/Blur.png | Bin 0 -> 24274 bytes .../ToolBlurShadow.imageset/Contents.json | 21 + .../ToolEraserShadow.imageset/Contents.json | 21 + .../ToolEraserShadow.imageset/Eraser.png | Bin 0 -> 25395 bytes .../ToolMarkerShadow.imageset/Brush.png | Bin 0 -> 24358 bytes .../ToolMarkerShadow.imageset/Contents.json | 21 + .../ToolNeonShadow.imageset/Contents.json | 21 + .../ToolNeonShadow.imageset/Neon.png | Bin 0 -> 24298 bytes .../ToolPenShadow.imageset/Contents.json | 21 + .../ToolPenShadow.imageset/Pen.png | Bin 0 -> 23540 bytes .../Sources/ChatAvatarNavigationNode.swift | 24 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 16 +- 47 files changed, 1389 insertions(+), 942 deletions(-) delete mode 100644 submodules/DrawingUI/Resources/pencil.png create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolArrowShadow.imageset/Arrow.png create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolArrowShadow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolBlurShadow.imageset/Blur.png create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolBlurShadow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolEraserShadow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolEraserShadow.imageset/Eraser.png create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolMarkerShadow.imageset/Brush.png create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolMarkerShadow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolNeonShadow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolNeonShadow.imageset/Neon.png create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolPenShadow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/ToolPenShadow.imageset/Pen.png diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 7550088fd0..97a82aa15a 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8518,6 +8518,7 @@ Sorry for the inconvenience."; "Privacy.ProfilePhoto.CustomOverrideInfo" = "You can add users or entire groups which will not see your profile photo."; "Privacy.ProfilePhoto.CustomOverrideAddInfo" = "Add users or entire groups which will still see your profile photo."; +"Privacy.ProfilePhoto.CustomOverrideBothInfo" = "You can add users or entire groups as exceptions that will override the settings above."; "WebApp.AddToAttachmentAllowMessages" = "Allow **%@** to send me messages"; diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 03dac59ab2..ce8367c994 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -127,6 +127,12 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin self.iconDisposable?.dispose() } + override func didLoad() { + super.didLoad() + + self.view.isExclusiveTouch = true + } + @objc private func pressed() { guard let controller = self.getController() else { return diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD index d0b9ea1261..6f8d77266d 100644 --- a/submodules/DrawingUI/BUILD +++ b/submodules/DrawingUI/BUILD @@ -91,6 +91,7 @@ swift_library( "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode:ChatEntityKeyboardInputNode", "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", "//submodules/TelegramNotices:TelegramNotices", + "//submodules/FastBlur:FastBlur", ], visibility = [ "//visibility:public", diff --git a/submodules/DrawingUI/Resources/pencil.png b/submodules/DrawingUI/Resources/pencil.png deleted file mode 100644 index 17c0a20afabb02e88068651d18d031880c3be4de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1071 zcmV+~1kn45P)T`U}*o zfoF_!Qe&*rZtjc>q)G^71=v)+2WHm40dn^>q6>VgO(`MeXTZ0jqhe;Qyq9Lyarcfu z$^t49G*^IM+`VOH{elwzDzF25q^KYS@Vx*J%mQEEDIp3jjZ@8qGOS< znig|TJte4qplJ58h}^ySw_YdjM~tuYZGzJGiCiL(+Hg1|Nsf5WI#E{( zGxlWH!!c37k5KWNyJuH(5BMXuX_>g~6|{wnc{l-A1F)t=H@8z+F-CSU^RAmVkGG_ZSNzB}(B)O!V^CyQ!(}%F9HQwdDO> z*|msTvRvI|1zOQO7Ff^B(n(GW#AS6V&+dt`^$EKQP&KpQJ3Ji#Z83tGwcWiq>mwz* z$U5?7Df^?*XmlATcjkwPnYGO<`)rc|x`& pMRExg|I6XG4tG!7J^8=S{S!|QkRFRF-WdP@002ovPDHLkV1l{H`AGl( diff --git a/submodules/DrawingUI/Sources/ColorPickerScreen.swift b/submodules/DrawingUI/Sources/ColorPickerScreen.swift index a8ddcdc0e8..0314c8ac07 100644 --- a/submodules/DrawingUI/Sources/ColorPickerScreen.swift +++ b/submodules/DrawingUI/Sources/ColorPickerScreen.swift @@ -277,7 +277,7 @@ private class ColorSliderComponent: Component { self.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))) if !self.transparencyLayer.isHidden { - self.transparencyLayer.contents = generateCheckeredImage(size: sliderSize, whiteColor: UIColor(rgb: 0xffffff, alpha: 0.1), blackColor: .clear, length: 12.0)?.cgImage + self.transparencyLayer.contents = generateCheckeredImage(size: sliderSize, whiteColor: UIColor(rgb: 0xffffff, alpha: 1.0), blackColor: .clear, length: 12.0)?.cgImage } self.knob.contents = generateKnobImage()?.cgImage @@ -437,6 +437,10 @@ private class ColorFieldComponent: Component { } } + func textFieldDidBeginEditing(_ textField: UITextField) { + textField.selectAll(nil) + } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { return false } @@ -1247,13 +1251,14 @@ private final class ColorSlidersComponent: CombinedComponent { type: .number, value: "\(Int(component.color.red * 255.0))", updated: { value in - if let intValue = Int(value) { - updateColor(currentColor.withUpdatedRed(CGFloat(intValue) / 255.0)) - } + let intValue = Int(value) ?? 0 + updateColor(currentColor.withUpdatedRed(CGFloat(intValue) / 255.0)) }, shouldUpdate: { value in if let intValue = Int(value), intValue >= 0 && intValue <= 255 { return true + } else if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return true } else { return false } @@ -1313,13 +1318,14 @@ private final class ColorSlidersComponent: CombinedComponent { type: .number, value: "\(Int(component.color.green * 255.0))", updated: { value in - if let intValue = Int(value) { - updateColor(currentColor.withUpdatedGreen(CGFloat(intValue) / 255.0)) - } + let intValue = Int(value) ?? 0 + updateColor(currentColor.withUpdatedGreen(CGFloat(intValue) / 255.0)) }, shouldUpdate: { value in if let intValue = Int(value), intValue >= 0 && intValue <= 255 { return true + } else if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return true } else { return false } @@ -1379,13 +1385,14 @@ private final class ColorSlidersComponent: CombinedComponent { type: .number, value: "\(Int(component.color.blue * 255.0))", updated: { value in - if let intValue = Int(value) { - updateColor(currentColor.withUpdatedBlue(CGFloat(intValue) / 255.0)) - } + let intValue = Int(value) ?? 0 + updateColor(currentColor.withUpdatedBlue(CGFloat(intValue) / 255.0)) }, shouldUpdate: { value in if let intValue = Int(value), intValue >= 0 && intValue <= 255 { return true + } else if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return true } else { return false } @@ -2198,8 +2205,19 @@ private final class ColorPickerContent: CombinedComponent { type: .number, value: "\(Int(state.selectedColor.alpha * 100.0))", suffix: "%", - updated: { _ in }, - shouldUpdate: { _ in return true } + updated: { value in + let intValue = Int(value) ?? 0 + state.updateAlpha(CGFloat(intValue) / 100.0) + }, + shouldUpdate: { value in + if let intValue = Int(value), intValue >= 0 && intValue <= 100 { + return true + } else if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return true + } else { + return false + } + } ), availableSize: CGSize(width: 77.0, height: 36.0), transition: .immediate diff --git a/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift b/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift index d221c5649b..0e71863519 100644 --- a/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift @@ -39,6 +39,8 @@ public final class DrawingBubbleEntity: DrawingEntity, Codable { return self.position } + public var scale: CGFloat = 1.0 + public var renderImage: UIImage? init(drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) { @@ -174,6 +176,11 @@ final class DrawingBubbleEntityView: DrawingEntityView { return max(10.0, max(self.bubbleEntity.referenceDrawingSize.width, self.bubbleEntity.referenceDrawingSize.height) * 0.1) } + fileprivate var minimumSize: CGSize { + let minSize = min(self.bubbleEntity.referenceDrawingSize.width, self.bubbleEntity.referenceDrawingSize.height) + return CGSize(width: minSize * 0.1, height: minSize * 0.1) + } + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { let lineWidth = self.maxLineWidth * 0.5 let expandedBounds = self.bounds.insetBy(dx: -lineWidth, dy: -lineWidth) @@ -285,7 +292,7 @@ final class DrawingBubbleEntititySelectionView: DrawingEntitySelectionView, UIGe self.snapTool.onSnapYUpdated = { [weak self] snapped in if let strongSelf = self, let entityView = strongSelf.entityView { - entityView.onSnapToXAxis(snapped) + entityView.onSnapToYAxis(snapped) } } } @@ -312,7 +319,7 @@ final class DrawingBubbleEntititySelectionView: DrawingEntitySelectionView, UIGe private var currentHandle: CALayer? @objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { - guard let entityView = self.entityView, let entity = entityView.entity as? DrawingBubbleEntity else { + guard let entityView = self.entityView as? DrawingBubbleEntityView, let entity = entityView.entity as? DrawingBubbleEntity else { return } let location = gestureRecognizer.location(in: self) @@ -338,37 +345,39 @@ final class DrawingBubbleEntititySelectionView: DrawingEntitySelectionView, UIGe var updatedPosition = entity.position var updatedTailPosition = entity.tailPosition + let minimumSize = entityView.minimumSize + if self.currentHandle === self.leftHandle { - updatedSize.width -= delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width - delta.x) updatedPosition.x -= delta.x * -0.5 } else if self.currentHandle === self.rightHandle { - updatedSize.width += delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width + delta.x) updatedPosition.x += delta.x * 0.5 } else if self.currentHandle === self.topHandle { - updatedSize.height -= delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height - delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.bottomHandle { - updatedSize.height += delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height + delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.topLeftHandle { - updatedSize.width -= delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width - delta.x) updatedPosition.x -= delta.x * -0.5 - updatedSize.height -= delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height - delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.topRightHandle { - updatedSize.width += delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width + delta.x) updatedPosition.x += delta.x * 0.5 - updatedSize.height -= delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height - delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.bottomLeftHandle { - updatedSize.width -= delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width - delta.x) updatedPosition.x -= delta.x * -0.5 - updatedSize.height += delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height + delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.bottomRightHandle { - updatedSize.width += delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width + delta.x) updatedPosition.x += delta.x * 0.5 - updatedSize.height += delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height + delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.tailHandle { updatedTailPosition = CGPoint(x: max(0.0, min(1.0, updatedTailPosition.x + delta.x / updatedSize.width)), y: max(0.0, min(updatedSize.height, updatedTailPosition.y + delta.y))) @@ -382,7 +391,7 @@ final class DrawingBubbleEntititySelectionView: DrawingEntitySelectionView, UIGe entity.size = updatedSize entity.position = updatedPosition entity.tailPosition = updatedTailPosition - entityView.update() + entityView.update(animated: false) gestureRecognizer.setTranslation(.zero, in: entityView) case .ended: diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 2d57b09f46..4705d1016f 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -4,6 +4,7 @@ import Display import LegacyComponents import AccountContext + public protocol DrawingEntity: AnyObject { var uuid: UUID { get } var isAnimated: Bool { get } @@ -11,7 +12,9 @@ public protocol DrawingEntity: AnyObject { var lineWidth: CGFloat { get set } var color: DrawingColor { get set } - + + var scale: CGFloat { get set } + func duplicate() -> DrawingEntity var currentEntityView: DrawingEntityView? { get } @@ -123,6 +126,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { private let context: AccountContext private let size: CGSize + weak var drawingView: DrawingView? weak var selectionContainerView: DrawingSelectionContainerView? private var tapGestureRecognizer: UITapGestureRecognizer! @@ -167,6 +171,10 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { fatalError("init(coder:) has not been implemented") } + deinit { + print() + } + public override func layoutSubviews() { super.layoutSubviews() @@ -188,9 +196,12 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { return entities } + private var initialEntitiesData: Data? public func setup(withEntitiesData entitiesData: Data!) { self.clear() + self.initialEntitiesData = entitiesData + if let entitiesData = entitiesData, let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: entitiesData) { let entities = codableEntities.map { $0.entity } for entity in entities { @@ -215,6 +226,15 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { } } + var hasChanges: Bool { + if let initialEntitiesData = self.initialEntitiesData { + let entitiesData = self.entitiesData + return entitiesData != initialEntitiesData + } else { + return !self.entities.isEmpty + } + } + private func startPosition(relativeTo entity: DrawingEntity?) -> CGPoint { let offsetLength = round(self.size.width * 0.1) let offset = CGPoint(x: offsetLength, y: offsetLength) @@ -244,14 +264,15 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { } private func newEntitySize() -> CGSize { - let width = round(self.size.width * 0.5) - + let zoomScale = 1.0 / (self.drawingView?.zoomScale ?? 1.0) + let width = round(self.size.width * 0.5) * zoomScale return CGSize(width: width, height: width) } func prepareNewEntity(_ entity: DrawingEntity, setup: Bool = true, relativeTo: DrawingEntity? = nil) { let center = self.startPosition(relativeTo: relativeTo) let rotation = self.getEntityInitialRotation() + let zoomScale = 1.0 / (self.drawingView?.zoomScale ?? 1.0) if let shape = entity as? DrawingSimpleShapeEntity { shape.position = center @@ -280,7 +301,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { sticker.rotation = rotation if setup { sticker.referenceDrawingSize = self.size - sticker.scale = 1.0 + sticker.scale = zoomScale } } else if let bubble = entity as? DrawingBubbleEntity { bubble.position = center @@ -298,6 +319,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { text.referenceDrawingSize = self.size text.width = floor(self.size.width * 0.9) text.fontSize = 0.3 + text.scale = zoomScale } } } @@ -368,7 +390,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { view?.removeFromSuperview() }) if !(view.entity is DrawingVectorEntity) { - view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) + view.layer.animateScale(from: view.entity.scale, to: 0.1, duration: 0.2, removeOnCompletion: false) } if let selectionView = view.selectionView { selectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak selectionView] _ in diff --git a/submodules/DrawingUI/Sources/DrawingGesture.swift b/submodules/DrawingUI/Sources/DrawingGesture.swift index affa06b605..8ad353cc98 100644 --- a/submodules/DrawingUI/Sources/DrawingGesture.swift +++ b/submodules/DrawingUI/Sources/DrawingGesture.swift @@ -255,7 +255,22 @@ class DrawingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate } class DrawingGesturePipeline { + struct Point { + let location: CGPoint + let velocity: CGFloat + let timestamp: Double + + var x: CGFloat { + return self.location.x + } + + var y: CGFloat { + return self.location.y + } + } + enum Mode { + case direct case location case smoothCurve case polyline @@ -269,6 +284,7 @@ class DrawingGesturePipeline { } enum DrawingResult { + case point(DrawingGesturePipeline.Point) case location(Polyline.Point) case smoothCurve(BezierPath) case polyline(Polyline) @@ -303,6 +319,7 @@ class DrawingGesturePipeline { view.addGestureRecognizer(gestureRecognizer) } + var previousPoint: Point? @objc private func handleGesture(_ gestureRecognizer: DrawingGestureRecognizer) { let state: DrawingGestureState switch gestureRecognizer.state { @@ -322,6 +339,29 @@ class DrawingGesturePipeline { state = .cancelled } + if case .direct = self.mode, let touch = self.pendingTouches.first { + if state == .began { + self.previousPoint = nil + } + + var velocity: Double = 0.0 + if let previousPoint = self.previousPoint { + let distance = touch.location.distance(to: previousPoint.location) + let elapsed = max(0.0, touch.timestamp - previousPoint.timestamp) + velocity = elapsed > 0.0 ? distance / elapsed : 0.0 + } else { + velocity = 0.0 + } + + let point = Point(location: touch.location, velocity: velocity, timestamp: touch.timestamp) + self.previousPoint = point + + self.onDrawing(state, .point(point)) + + self.pendingTouches.removeAll() + return + } + let touchDeltas = self.processTouchEvents(self.pendingTouches) let polylineDeltas = self.processTouchPaths(inputDeltas: touchDeltas) let simplifiedPolylineDeltas = self.simplifyPolylines(inputDeltas: polylineDeltas) @@ -339,6 +379,8 @@ class DrawingGesturePipeline { if let polyline = self.simplifiedPolylines.last { self.onDrawing(state, .polyline(polyline)) } + case .direct: + break } self.pendingTouches.removeAll() diff --git a/submodules/DrawingUI/Sources/DrawingMetalView.swift b/submodules/DrawingUI/Sources/DrawingMetalView.swift index 22ba6b4893..688c2226f1 100644 --- a/submodules/DrawingUI/Sources/DrawingMetalView.swift +++ b/submodules/DrawingUI/Sources/DrawingMetalView.swift @@ -22,7 +22,7 @@ final class DrawingMetalView: MTKView { init?(size: CGSize) { var size = size if Int(size.width) % 16 != 0 { - size.width = round(size.width / 16.0) * 16.0 + size.width = ceil(size.width / 16.0) * 16.0 } let mainBundle = Bundle(for: DrawingView.self) @@ -46,11 +46,14 @@ final class DrawingMetalView: MTKView { self.commandQueue = commandQueue self.size = size - + super.init(frame: CGRect(origin: .zero, size: size), device: device) + self.autoResizeDrawable = false self.isOpaque = false + self.contentScaleFactor = 1.0 self.drawableSize = self.size + //self.presentsWithTransaction = true self.setup() } @@ -87,7 +90,7 @@ final class DrawingMetalView: MTKView { private func setup() { self.drawable = Drawable(size: self.size, pixelFormat: self.colorPixelFormat, device: device) - + let size = self.size let w = size.width, h = size.height let vertices = [ @@ -97,32 +100,46 @@ final class DrawingMetalView: MTKView { Vertex(position: CGPoint(x: w , y: h), texCoord: CGPoint(x: 1, y: 1)), ] self.render_target_vertex = self.device?.makeBuffer(bytes: vertices, length: MemoryLayout.stride * vertices.count, options: .cpuCacheModeWriteCombined) - + let matrix = Matrix.identity matrix.scaling(x: 2.0 / Float(size.width), y: -2.0 / Float(size.height), z: 1) matrix.translation(x: -1, y: 1, z: 0) self.render_target_uniform = self.device?.makeBuffer(bytes: matrix.m, length: MemoryLayout.size * 16, options: []) - + let vertexFunction = self.library.makeFunction(name: "vertex_render_target") let fragmentFunction = self.library.makeFunction(name: "fragment_render_target") let pipelineDescription = MTLRenderPipelineDescriptor() pipelineDescription.vertexFunction = vertexFunction pipelineDescription.fragmentFunction = fragmentFunction pipelineDescription.colorAttachments[0].pixelFormat = colorPixelFormat - + do { self.pipelineState = try self.device?.makeRenderPipelineState(descriptor: pipelineDescription) } catch { fatalError(error.localizedDescription) } - + if let url = getAppBundle().url(forResource: "marker", withExtension: "png"), let data = try? Data(contentsOf: url) { self.markerBrush = Brush(texture: self.makeTexture(with: data), target: self, rotation: .fixed(-0.55)) } + + self.drawable?.clear() } - var clearOnce = false + + override var frame: CGRect { + get { + return super.frame + } set { + super.frame = newValue + self.drawableSize = self.size + } + } + override func draw(_ rect: CGRect) { + guard !self.isHidden || (self.drawable?.isClearing ?? false) else { + return + } super.draw(rect) guard let drawable = self.drawable, let texture = drawable.texture?.texture else { @@ -135,38 +152,35 @@ final class DrawingMetalView: MTKView { attachment?.texture = self.currentDrawable?.texture attachment?.loadAction = .clear attachment?.storeAction = .store - + guard let _ = attachment?.texture else { return } - + let commandBuffer = self.commandQueue.makeCommandBuffer() let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor) - + commandEncoder?.setRenderPipelineState(self.pipelineState) - + commandEncoder?.setVertexBuffer(self.render_target_vertex, offset: 0, index: 0) commandEncoder?.setVertexBuffer(self.render_target_uniform, offset: 0, index: 1) commandEncoder?.setFragmentTexture(texture, index: 0) commandEncoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) - + commandEncoder?.endEncoding() if let drawable = self.currentDrawable { commandBuffer?.present(drawable) } commandBuffer?.commit() } - + func clear() { guard let drawable = self.drawable else { return } - self.clearOnce = true drawable.updateBuffer(with: self.size) drawable.clear() - - drawable.commit(wait: true) } enum BrushType { @@ -215,7 +229,9 @@ private class Drawable { self.updateBuffer(with: size) } + var isClearing = false func clear() { + self.isClearing = true self.texture?.clear() self.commit(wait: true) } @@ -246,6 +262,10 @@ private class Drawable { self.commandBuffer?.waitUntilCompleted() } self.commandBuffer = nil + + if self.isClearing && wait { + self.isClearing = false + } } internal func makeTexture() -> Texture? { @@ -645,7 +665,7 @@ final class Texture { textureDescriptor.height = height textureDescriptor.usage = [.renderTarget, .shaderRead] textureDescriptor.storageMode = .shared - + guard let texture = device.makeTexture(descriptor: textureDescriptor) else { return nil } @@ -706,7 +726,7 @@ final class Texture { bitsPerComponent: 8, bitsPerPixel: 8 * 4, bytesPerRow: self.bytesPerRow, - space: DeviceGraphicsContextSettings.shared.colorSpace, + space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: DeviceGraphicsContextSettings.shared.transparentBitmapInfo, provider: dataProvider, decode: nil, diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index d736acdff4..2ff7d85923 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -18,6 +18,7 @@ import ContextUI import ChatEntityKeyboardInputNode import EntityKeyboard import TelegramUIPreferences +import FastBlur enum DrawingToolState: Equatable, Codable { private enum CodingKeys: String, CodingKey { @@ -31,8 +32,8 @@ enum DrawingToolState: Equatable, Codable { case arrow = 1 case marker = 2 case neon = 3 - case eraser = 4 - case blur = 5 + case blur = 4 + case eraser = 5 } struct BrushState: Equatable, Codable { @@ -100,8 +101,8 @@ enum DrawingToolState: Equatable, Codable { case arrow(BrushState) case marker(BrushState) case neon(BrushState) - case eraser(EraserState) case blur(EraserState) + case eraser(EraserState) func withUpdatedColor(_ color: DrawingColor) -> DrawingToolState { switch self { @@ -113,7 +114,7 @@ enum DrawingToolState: Equatable, Codable { return .marker(state.withUpdatedColor(color)) case let .neon(state): return .neon(state.withUpdatedColor(color)) - case .eraser, .blur: + case .blur, .eraser: return self } } @@ -128,10 +129,10 @@ enum DrawingToolState: Equatable, Codable { return .marker(state.withUpdatedSize(size)) case let .neon(state): return .neon(state.withUpdatedSize(size)) - case let .eraser(state): - return .eraser(state.withUpdatedSize(size)) case let .blur(state): return .blur(state.withUpdatedSize(size)) + case let .eraser(state): + return .eraser(state.withUpdatedSize(size)) } } @@ -148,7 +149,7 @@ enum DrawingToolState: Equatable, Codable { switch self { case let .pen(state), let .arrow(state), let .marker(state), let .neon(state): return state.size - case let .eraser(state), let .blur(state): + case let .blur(state), let .eraser(state): return state.size } } @@ -163,10 +164,10 @@ enum DrawingToolState: Equatable, Codable { return .marker case .neon: return .neon - case .eraser: - return .eraser case .blur: return .blur + case .eraser: + return .eraser } } @@ -183,10 +184,10 @@ enum DrawingToolState: Equatable, Codable { self = .marker(try container.decode(BrushState.self, forKey: .brushState)) case .neon: self = .neon(try container.decode(BrushState.self, forKey: .brushState)) - case .eraser: - self = .eraser(try container.decode(EraserState.self, forKey: .eraserState)) case .blur: self = .blur(try container.decode(EraserState.self, forKey: .eraserState)) + case .eraser: + self = .eraser(try container.decode(EraserState.self, forKey: .eraserState)) } } else { self = .pen(BrushState(color: DrawingColor(rgb: 0x000000), size: 0.5)) @@ -208,12 +209,12 @@ enum DrawingToolState: Equatable, Codable { case let .neon(state): try container.encode(DrawingToolState.Key.neon.rawValue, forKey: .type) try container.encode(state, forKey: .brushState) - case let .eraser(state): - try container.encode(DrawingToolState.Key.eraser.rawValue, forKey: .type) - try container.encode(state, forKey: .eraserState) case let .blur(state): try container.encode(DrawingToolState.Key.blur.rawValue, forKey: .type) try container.encode(state, forKey: .eraserState) + case let .eraser(state): + try container.encode(DrawingToolState.Key.eraser.rawValue, forKey: .type) + try container.encode(state, forKey: .eraserState) } } } @@ -285,11 +286,24 @@ struct DrawingState: Equatable { .arrow(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xff8a00), size: 0.23)), .marker(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xffd60a), size: 0.75)), .neon(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x34c759), size: 0.4)), - .eraser(DrawingToolState.EraserState(size: 0.5)), - .blur(DrawingToolState.EraserState(size: 0.5)) + .blur(DrawingToolState.EraserState(size: 0.5)), + .eraser(DrawingToolState.EraserState(size: 0.5)) ] ) } + + func forVideo() -> DrawingState { + return DrawingState( + selectedTool: self.selectedTool, + tools: self.tools.filter { tool in + if case .blur = tool { + return false + } else { + return true + } + } + ) + } } final class DrawingSettings: Codable, Equatable { @@ -365,6 +379,7 @@ private final class DrawingScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let isVideo: Bool let isAvatar: Bool let present: (ViewController) -> Void let updateState: ActionSlot @@ -374,8 +389,9 @@ private final class DrawingScreenComponent: CombinedComponent { let updateSelectedEntity: ActionSlot let insertEntity: ActionSlot let deselectEntity: ActionSlot - let updatePlayback: ActionSlot + let updateEntitiesPlayback: ActionSlot let previewBrushSize: ActionSlot + let dismissEyedropper: ActionSlot let apply: ActionSlot let dismiss: ActionSlot @@ -386,6 +402,7 @@ private final class DrawingScreenComponent: CombinedComponent { init( context: AccountContext, + isVideo: Bool, isAvatar: Bool, present: @escaping (ViewController) -> Void, updateState: ActionSlot, @@ -395,8 +412,9 @@ private final class DrawingScreenComponent: CombinedComponent { updateSelectedEntity: ActionSlot, insertEntity: ActionSlot, deselectEntity: ActionSlot, - updatePlayback: ActionSlot, + updateEntitiesPlayback: ActionSlot, previewBrushSize: ActionSlot, + dismissEyedropper: ActionSlot, apply: ActionSlot, dismiss: ActionSlot, presentColorPicker: @escaping (DrawingColor) -> Void, @@ -405,6 +423,7 @@ private final class DrawingScreenComponent: CombinedComponent { dismissFastColorPicker: @escaping () -> Void ) { self.context = context + self.isVideo = isVideo self.isAvatar = isAvatar self.present = present self.updateState = updateState @@ -414,8 +433,9 @@ private final class DrawingScreenComponent: CombinedComponent { self.updateSelectedEntity = updateSelectedEntity self.insertEntity = insertEntity self.deselectEntity = deselectEntity - self.updatePlayback = updatePlayback + self.updateEntitiesPlayback = updateEntitiesPlayback self.previewBrushSize = previewBrushSize + self.dismissEyedropper = dismissEyedropper self.apply = apply self.dismiss = dismiss self.presentColorPicker = presentColorPicker @@ -484,7 +504,8 @@ private final class DrawingScreenComponent: CombinedComponent { private let updateToolState: ActionSlot private let insertEntity: ActionSlot private let deselectEntity: ActionSlot - private let updatePlayback: ActionSlot + private let updateEntitiesPlayback: ActionSlot + private let dismissEyedropper: ActionSlot private let present: (ViewController) -> Void var currentMode: Mode @@ -497,12 +518,13 @@ private final class DrawingScreenComponent: CombinedComponent { private let stickerPickerInputData = Promise() - init(context: AccountContext, updateToolState: ActionSlot, insertEntity: ActionSlot, deselectEntity: ActionSlot, updatePlayback: ActionSlot, present: @escaping (ViewController) -> Void) { + init(context: AccountContext, updateToolState: ActionSlot, insertEntity: ActionSlot, deselectEntity: ActionSlot, updateEntitiesPlayback: ActionSlot, dismissEyedropper: ActionSlot, present: @escaping (ViewController) -> Void) { self.context = context self.updateToolState = updateToolState self.insertEntity = insertEntity self.deselectEntity = deselectEntity - self.updatePlayback = updatePlayback + self.updateEntitiesPlayback = updateEntitiesPlayback + self.dismissEyedropper = dismissEyedropper self.present = present self.currentMode = .drawing @@ -647,6 +669,8 @@ private final class DrawingScreenComponent: CombinedComponent { var skipSelectedEntityUpdate = false func updateSelectedEntity(_ entity: DrawingEntity?) { + self.dismissEyedropper.invoke(Void()) + self.selectedEntity = entity if let entity = entity { if !entity.color.isClear { @@ -758,10 +782,10 @@ private final class DrawingScreenComponent: CombinedComponent { func presentStickerPicker() { self.currentMode = .sticker - self.updatePlayback.invoke(false) + self.updateEntitiesPlayback.invoke(false) let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get()) controller.completion = { [weak self] file in - self?.updatePlayback.invoke(true) + self?.updateEntitiesPlayback.invoke(true) if let file = file { let stickerEntity = DrawingStickerEntity(file: file) @@ -776,7 +800,7 @@ private final class DrawingScreenComponent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, updateToolState: self.updateToolState, insertEntity: self.insertEntity, deselectEntity: self.deselectEntity, updatePlayback: self.updatePlayback, present: self.present) + return State(context: self.context, updateToolState: self.updateToolState, insertEntity: self.insertEntity, deselectEntity: self.deselectEntity, updateEntitiesPlayback: self.updateEntitiesPlayback, dismissEyedropper: self.dismissEyedropper, present: self.present) } static var body: Body { @@ -836,6 +860,8 @@ private final class DrawingScreenComponent: CombinedComponent { let previewBrushSize = component.previewBrushSize let performAction = component.performAction + let dismissEyedropper = component.dismissEyedropper + component.updateState.connect { [weak state] updatedState in state?.updateDrawingState(updatedState) } @@ -952,6 +978,7 @@ private final class DrawingScreenComponent: CombinedComponent { let delta: CGFloat = (rightButtonPosition - offsetX) / 7.0 let applySwatchColor: (DrawingColor) -> Void = { [weak state] color in + dismissEyedropper.invoke(Void()) if let state = state { if [.eraser, .blur].contains(state.drawingState.selectedTool) || state.selectedEntity is DrawingStickerEntity { state.updateSelectedTool(.pen, update: false) @@ -1186,15 +1213,17 @@ private final class DrawingScreenComponent: CombinedComponent { } else { let tools = tools.update( component: ToolsComponent( - state: state.drawingState, + state: component.isVideo ? state.drawingState.forVideo() : state.drawingState, isFocused: false, tag: toolsTag, toolPressed: { [weak state] tool in + dismissEyedropper.invoke(Void()) if let state = state { state.updateSelectedTool(tool) } }, toolResized: { [weak state] _, size in + dismissEyedropper.invoke(Void()) state?.updateBrushSize(size) if state?.selectedEntity == nil { previewBrushSize.invoke(size) @@ -1353,6 +1382,7 @@ private final class DrawingScreenComponent: CombinedComponent { ) ), action: { + dismissEyedropper.invoke(Void()) performAction.invoke(.zoomOut) } ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(zoomOutButtonTag), @@ -1379,6 +1409,7 @@ private final class DrawingScreenComponent: CombinedComponent { tag: sizeSliderTag, updated: { [weak state] size in if let state = state { + dismissEyedropper.invoke(Void()) state.updateBrushSize(size) if state.selectedEntity == nil { previewBrushSize.invoke(size) @@ -1402,6 +1433,7 @@ private final class DrawingScreenComponent: CombinedComponent { ), isEnabled: state.drawingViewState.canUndo, action: { + dismissEyedropper.invoke(Void()) performAction.invoke(.undo) } ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(undoButtonTag), @@ -1421,6 +1453,7 @@ private final class DrawingScreenComponent: CombinedComponent { Image(image: state.image(.redo)) ), action: { + dismissEyedropper.invoke(Void()) performAction.invoke(.redo) } ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(redoButtonTag), @@ -1440,6 +1473,7 @@ private final class DrawingScreenComponent: CombinedComponent { ), isEnabled: state.drawingViewState.canClear, action: { + dismissEyedropper.invoke(Void()) performAction.invoke(.clear) } ).tagged(clearAllButtonTag), @@ -1536,7 +1570,6 @@ private final class DrawingScreenComponent: CombinedComponent { .disappear(.default(scale: true)) ) - let modeRightInset: CGFloat = 57.0 let addButton = addButton.update( component: Button( @@ -1563,12 +1596,15 @@ private final class DrawingScreenComponent: CombinedComponent { } switch state.currentMode { case .drawing: + dismissEyedropper.invoke(Void()) if let buttonView = controller.node.componentHost.findTaggedView(tag: addButtonTag) as? Button.View { state.presentShapePicker(buttonView) } case .sticker: + dismissEyedropper.invoke(Void()) state.presentStickerPicker() case .text: + dismissEyedropper.invoke(Void()) state.addTextEntity() } } @@ -1588,6 +1624,7 @@ private final class DrawingScreenComponent: CombinedComponent { Image(image: state.image(.done)) ), action: { [weak state] in + dismissEyedropper.invoke(Void()) state?.saveToolState() apply.invoke(Void()) } @@ -1639,6 +1676,7 @@ private final class DrawingScreenComponent: CombinedComponent { tag: modeTag, selectedIndex: selectedIndex, selectionChanged: { [weak state] index in + dismissEyedropper.invoke(Void()) guard let state = state else { return } @@ -1653,6 +1691,7 @@ private final class DrawingScreenComponent: CombinedComponent { }, sizeUpdated: { [weak state] size in if let state = state { + dismissEyedropper.invoke(Void()) state.updateBrushSize(size) if state.selectedEntity == nil { previewBrushSize.invoke(size) @@ -1690,6 +1729,7 @@ private final class DrawingScreenComponent: CombinedComponent { ), action: { [weak state] in if let state = state { + dismissEyedropper.invoke(Void()) state.saveToolState() dismiss.invoke(Void()) } @@ -1718,8 +1758,9 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { private let updateSelectedEntity: ActionSlot private let insertEntity: ActionSlot private let deselectEntity: ActionSlot - private let updatePlayback: ActionSlot + private let updateEntitiesPlayback: ActionSlot private let previewBrushSize: ActionSlot + private let dismissEyedropper: ActionSlot private let apply: ActionSlot private let dismiss: ActionSlot @@ -1752,36 +1793,6 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { strongSelf.updateState.invoke(state) } } - self._drawingView?.requestMenu = { [weak self] elements, rect in - if let strongSelf = self, let drawingView = strongSelf._drawingView { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - var actions: [ContextMenuAction] = [] - actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_Delete, accessibilityLabel: presentationData.strings.Paint_Delete), action: { [weak self] in - if let strongSelf = self { - strongSelf._drawingView?.removeElements(elements) - } - })) - actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_Duplicate, accessibilityLabel: presentationData.strings.Paint_Duplicate), action: { [weak self] in - if let strongSelf = self { - strongSelf._drawingView?.removeElements(elements) - } - })) - let strokeFrame = drawingView.lassoView.convert(rect, to: strongSelf.view).offsetBy(dx: 0.0, dy: -6.0) - let controller = ContextMenuController(actions: actions) - strongSelf.currentMenuController = controller - strongSelf.controller?.present( - controller, - in: .window(.root), - with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in - if let strongSelf = self { - return (strongSelf, strokeFrame, strongSelf, strongSelf.bounds) - } else { - return nil - } - }) - ) - } - } self.performAction.connect { [weak self] action in if let strongSelf = self { if action == .clear { @@ -1816,6 +1827,11 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { strongSelf._drawingView?.setBrushSizePreview(size) } } + self.dismissEyedropper.connect { [weak self] in + if let strongSelf = self { + strongSelf.dismissCurrentEyedropper() + } + } } return self._drawingView! } @@ -1826,33 +1842,48 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { if self._entitiesView == nil, let controller = self.controller { self._entitiesView = DrawingEntitiesView(context: self.context, size: controller.size) self._drawingView?.entitiesView = self._entitiesView + self._entitiesView?.drawingView = self._drawingView self._entitiesView?.entityAdded = { [weak self] entity in self?._drawingView?.onEntityAdded(entity) } self._entitiesView?.entityRemoved = { [weak self] entity in self?._drawingView?.onEntityRemoved(entity) } - let entitiesLayer = self.entitiesView.layer - self._drawingView?.getFullImage = { [weak self, weak entitiesLayer] withDrawing in + self._drawingView?.getFullImage = { [weak self] in if let strongSelf = self, let controller = strongSelf.controller, let currentImage = controller.getCurrentImage() { - if withDrawing { - let image = generateImage(controller.size, contextGenerator: { size, context in + let size = controller.size.fitted(CGSize(width: 256.0, height: 256.0)) + + if let imageContext = DrawingContext(size: size, scale: 1.0, opaque: true, clear: false) { + imageContext.withFlippedContext { c in let bounds = CGRect(origin: .zero, size: size) if let cgImage = currentImage.cgImage { - context.draw(cgImage, in: bounds) + c.draw(cgImage, in: bounds) } if let cgImage = strongSelf.drawingView.drawingImage?.cgImage { - context.draw(cgImage, in: bounds) + c.draw(cgImage, in: bounds) } - context.translateBy(x: size.width / 2.0, y: size.height / 2.0) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - entitiesLayer?.render(in: context) - }, opaque: true, scale: 1.0) - return image + telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) + telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) + } + return imageContext.generateImage() } else { - return currentImage + return nil } + +// let image = generateImage(controller.size, contextGenerator: { size, context in +// let bounds = CGRect(origin: .zero, size: size) +// if let cgImage = currentImage.cgImage { +// context.draw(cgImage, in: bounds) +// } +// if let cgImage = strongSelf.drawingView.drawingImage?.cgImage { +// context.draw(cgImage, in: bounds) +// } +// telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) +// +// +// +// }, opaque: true, scale: 1.0) +// return image } else { return nil } @@ -1908,7 +1939,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in if let strongSelf = self { - return (strongSelf, entityFrame, strongSelf, strongSelf.bounds) + return (strongSelf, entityFrame, strongSelf, strongSelf.bounds.insetBy(dx: 0.0, dy: 160.0)) } else { return nil } @@ -1926,7 +1957,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { textEntityView.beginEditing(accessoryView: strongSelf.textEditAccessoryView) } else { entityView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - entityView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + entityView.layer.animateScale(from: 0.1, to: entity.scale, duration: 0.2) if let selectionView = entityView.selectionView { selectionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.2) @@ -1940,7 +1971,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { entitiesView.selectEntity(nil) } } - self.updatePlayback.connect { [weak self] play in + self.updateEntitiesPlayback.connect { [weak self] play in if let strongSelf = self, let entitiesView = strongSelf._entitiesView { if play { entitiesView.play() @@ -1979,8 +2010,9 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { self.updateSelectedEntity = ActionSlot() self.insertEntity = ActionSlot() self.deselectEntity = ActionSlot() - self.updatePlayback = ActionSlot() + self.updateEntitiesPlayback = ActionSlot() self.previewBrushSize = ActionSlot() + self.dismissEyedropper = ActionSlot() self.apply = ActionSlot() self.dismiss = ActionSlot() @@ -2000,7 +2032,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { } self.dismiss.connect { [weak self] _ in if let strongSelf = self { - if !strongSelf.drawingView.isEmpty { + if strongSelf.drawingView.canUndo || strongSelf.entitiesView.hasChanges { let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)) actionSheet.setItemGroups([ ActionSheetItemGroup(items: [ @@ -2031,13 +2063,30 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { self.view.disablesInteractiveTransitionGestureRecognizer = true } - func presentEyedropper(dismissed: @escaping () -> Void) { + private var currentEyedropperView: EyedropperView? + func presentEyedropper(retryLaterForVideo: Bool = true, dismissed: @escaping () -> Void) { guard let controller = self.controller else { return } self.entitiesView.pause() + if controller.isVideo && retryLaterForVideo { + controller.updateVideoPlayback(false) + Queue.mainQueue().after(0.1) { + self.presentEyedropper(retryLaterForVideo: false, dismissed: dismissed) + } + return + } + guard let currentImage = controller.getCurrentImage() else { +// if controller.isVideo && retryIfFailed { +// Queue.mainQueue().after(0.01) { +// self.presentEyedropper(retryIfFailed: false, dismissed: dismissed) +// } +// } else { + self.entitiesView.play() + controller.updateVideoPlayback(true) +// } return } @@ -2063,14 +2112,29 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { if let strongSelf = self, let controller = controller { strongSelf.updateColor.invoke(color) controller.entitiesView.play() + controller.updateVideoPlayback(true) dismissed() } } + eyedropperView.dismissed = { + controller.entitiesView.play() + controller.updateVideoPlayback(true) + } eyedropperView.frame = controller.contentWrapperView.convert(controller.contentWrapperView.bounds, to: controller.view) controller.view.addSubview(eyedropperView) + self.currentEyedropperView = eyedropperView + } + + func dismissCurrentEyedropper() { + if let currentEyedropperView = self.currentEyedropperView { + self.currentEyedropperView = nil + currentEyedropperView.dismiss() + } } func presentColorPicker(initialColor: DrawingColor, dismissed: @escaping () -> Void = {}) { + self.dismissCurrentEyedropper() + guard let controller = self.controller else { return } @@ -2087,6 +2151,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { private var fastColorPickerView: ColorSpectrumPickerView? func presentFastColorPicker(sourceView: UIView) { + self.dismissCurrentEyedropper() + guard self.fastColorPickerView == nil, let superview = sourceView.superview else { return } @@ -2275,6 +2341,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { component: AnyComponent( DrawingScreenComponent( context: self.context, + isVideo: controller.isVideo, isAvatar: controller.isAvatar, present: { [weak self] c in self?.controller?.present(c, in: .window(.root)) @@ -2286,8 +2353,9 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { updateSelectedEntity: self.updateSelectedEntity, insertEntity: self.insertEntity, deselectEntity: self.deselectEntity, - updatePlayback: self.updatePlayback, + updateEntitiesPlayback: self.updateEntitiesPlayback, previewBrushSize: self.previewBrushSize, + dismissEyedropper: self.dismissEyedropper, apply: self.apply, dismiss: self.dismiss, presentColorPicker: { [weak self] initialColor in @@ -2496,16 +2564,19 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { private let context: AccountContext private let size: CGSize private let originalSize: CGSize + private let isVideo: Bool private let isAvatar: Bool public var requestDismiss: (() -> Void)! public var requestApply: (() -> Void)! public var getCurrentImage: (() -> UIImage?)! + public var updateVideoPlayback: ((Bool) -> Void)! - public init(context: AccountContext, size: CGSize, originalSize: CGSize, isAvatar: Bool) { + public init(context: AccountContext, size: CGSize, originalSize: CGSize, isVideo: Bool, isAvatar: Bool ) { self.context = context self.size = size self.originalSize = originalSize + self.isVideo = isVideo self.isAvatar = isAvatar super.init(navigationBarPresentationData: nil) @@ -2585,7 +2656,6 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { } let drawingData = self.drawingView.drawingData - let entitiesData = self.entitiesView.entitiesData var stickers: [Any] = [] diff --git a/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift b/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift index bc381e977e..83a0d4f52b 100644 --- a/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift @@ -45,6 +45,8 @@ public final class DrawingSimpleShapeEntity: DrawingEntity, Codable { return self.position } + public var scale: CGFloat = 1.0 + public var renderImage: UIImage? init(shapeType: ShapeType, drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) { @@ -305,7 +307,7 @@ final class DrawingSimpleShapeEntititySelectionView: DrawingEntitySelectionView, self.snapTool.onSnapYUpdated = { [weak self] snapped in if let strongSelf = self, let entityView = strongSelf.entityView { - entityView.onSnapToXAxis(snapped) + entityView.onSnapToYAxis(snapped) } } } @@ -407,36 +409,37 @@ final class DrawingSimpleShapeEntititySelectionView: DrawingEntitySelectionView, if isAspectLocked { delta = CGPoint(x: delta.x, y: delta.x) } - updatedSize.width -= delta.x + + updatedSize.width = max(minimumSize.width, updatedSize.width - delta.x) updatedPosition.x -= delta.x * -0.5 - updatedSize.height -= delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height - delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.topRightHandle { var delta = delta if isAspectLocked { delta = CGPoint(x: delta.x, y: -delta.x) } - updatedSize.width += delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width + delta.x) updatedPosition.x += delta.x * 0.5 - updatedSize.height -= delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height - delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.bottomLeftHandle { var delta = delta if isAspectLocked { delta = CGPoint(x: delta.x, y: -delta.x) } - updatedSize.width -= delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width - delta.x) updatedPosition.x -= delta.x * -0.5 - updatedSize.height += delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height + delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.bottomRightHandle { var delta = delta if isAspectLocked { delta = CGPoint(x: delta.x, y: delta.x) } - updatedSize.width += delta.x + updatedSize.width = max(minimumSize.width, updatedSize.width + delta.x) updatedPosition.x += delta.x * 0.5 - updatedSize.height += delta.y + updatedSize.height = max(minimumSize.height, updatedSize.height + delta.y) updatedPosition.y += delta.y * 0.5 } else if self.currentHandle === self.layer { updatedPosition.x += delta.x diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index c41087397c..234b1b6938 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -254,6 +254,7 @@ final class DrawingStickerEntityView: DrawingEntityView { self.applyVisibility() } } + self.update(animated: false) } } } @@ -537,15 +538,20 @@ class DrawingEntitySnapTool { private var xState: (skipped: CGFloat, waitForLeave: Bool)? private var yState: (skipped: CGFloat, waitForLeave: Bool)? + private var rotationState: (skipped: CGFloat, waitForLeave: Bool)? + var onSnapXUpdated: (Bool) -> Void = { _ in } var onSnapYUpdated: (Bool) -> Void = { _ in } + var onSnapRotationUpdated: (CGFloat?) -> Void = { _ in } func reset() { self.xState = nil self.yState = nil + self.rotationState = nil self.onSnapXUpdated(false) self.onSnapYUpdated(false) + self.onSnapRotationUpdated(nil) } func maybeSkipFromStart(entityView: DrawingEntityView, position: CGPoint) { @@ -565,7 +571,7 @@ class DrawingEntitySnapTool { } } } - + func update(entityView: DrawingEntityView, velocity: CGPoint, delta: CGPoint, updatedPosition: CGPoint) -> CGPoint { var updatedPosition = updatedPosition @@ -640,4 +646,17 @@ class DrawingEntitySnapTool { return updatedPosition } + + private let snapRotations: [CGFloat] = [0.0, .pi / 4.0, .pi / 2.0, .pi * 1.5,] + func maybeSkipFromStart(entityView: DrawingEntityView, rotation: CGFloat) { + self.rotationState = nil + + let snapDelta: CGFloat = 0.087 + for snapRotation in self.snapRotations { + if rotation > snapRotation - snapDelta && rotation < snapRotation + snapDelta { + self.rotationState = (0.0, true) + break + } + } + } } diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index c8d80d6498..be5e8ec5ef 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -350,7 +350,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.customEmojiContainerView = customEmojiContainerView } - customEmojiContainerView.update(fontSize: self.displayFontSize * 0.8, textColor: textColor, emojiRects: customEmojiRects) + customEmojiContainerView.update(fontSize: self.displayFontSize * 0.78, textColor: textColor, emojiRects: customEmojiRects) } else if let customEmojiContainerView = self.customEmojiContainerView { customEmojiContainerView.removeFromSuperview() self.customEmojiContainerView = nil @@ -684,7 +684,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { let scale = self.textEntity.scale let rotation = self.textEntity.rotation - let itemSize: CGFloat = floor(24.0 * self.displayFontSize * 0.8 / 17.0) + let itemSize: CGFloat = floor(24.0 * self.displayFontSize * 0.78 / 17.0) var entities: [DrawingStickerEntity] = [] for (emojiRect, emojiAttribute) in self.emojiRects { @@ -754,7 +754,7 @@ final class DrawingTextEntititySelectionView: DrawingEntitySelectionView, UIGest self.snapTool.onSnapYUpdated = { [weak self] snapped in if let strongSelf = self, let entityView = strongSelf.entityView { - entityView.onSnapToXAxis(snapped) + entityView.onSnapToYAxis(snapped) } } } @@ -1033,8 +1033,8 @@ private class DrawingTextLayoutManager: NSLayoutManager { context.saveGState() context.translateBy(x: origin.x, y: origin.y) - - context.setBlendMode(.normal) + + context.setBlendMode(.copy) context.setFillColor(frameColor.cgColor) context.setStrokeColor(frameColor.cgColor) diff --git a/submodules/DrawingUI/Sources/DrawingTools.swift b/submodules/DrawingUI/Sources/DrawingTools.swift index c43b98c8be..b80fc8e714 100644 --- a/submodules/DrawingUI/Sources/DrawingTools.swift +++ b/submodules/DrawingUI/Sources/DrawingTools.swift @@ -19,6 +19,10 @@ final class MarkerTool: DrawingElement, Codable { var points: [CGPoint] = [] weak var metalView: DrawingMetalView? + + var isValid: Bool { + return !self.points.isEmpty + } required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat) { self.uuid = UUID() @@ -162,6 +166,10 @@ final class NeonTool: DrawingElement, Codable { var translation = CGPoint() private var currentRenderLayer: DrawingRenderLayer? + + var isValid: Bool { + return self.renderPath != nil + } required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat) { self.uuid = UUID() @@ -267,17 +275,26 @@ final class NeonTool: DrawingElement, Codable { } final class FillTool: DrawingElement, Codable { + let uuid: UUID let drawingSize: CGSize let color: DrawingColor + let isBlur: Bool + var blurredImage: UIImage? var translation = CGPoint() - required init(drawingSize: CGSize, color: DrawingColor) { + var isValid: Bool { + return true + } + + required init(drawingSize: CGSize, color: DrawingColor, blur: Bool, blurredImage: UIImage?) { self.uuid = UUID() self.drawingSize = drawingSize self.color = color + self.isBlur = blur + self.blurredImage = blurredImage } private enum CodingKeys: String, CodingKey { @@ -291,6 +308,7 @@ final class FillTool: DrawingElement, Codable { self.uuid = try container.decode(UUID.self, forKey: .uuid) self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize) self.color = try container.decode(DrawingColor.self, forKey: .color) + self.isBlur = false // self.points = try container.decode([CGPoint].self, forKey: .points) } @@ -313,10 +331,16 @@ final class FillTool: DrawingElement, Codable { context.setShouldAntialias(false) context.setBlendMode(.copy) - - context.setFillColor(self.color.toCGColor()) - context.fill(CGRect(origin: .zero, size: size)) - + + if self.isBlur { + if let blurredImage = self.blurredImage?.cgImage { + context.draw(blurredImage, in: CGRect(origin: .zero, size: size)) + } + } else { + context.setFillColor(self.color.toCGColor()) + context.fill(CGRect(origin: .zero, size: size)) + } + context.setBlendMode(.normal) } @@ -382,127 +406,10 @@ final class BlurTool: DrawingElement, Codable { private var currentRenderLayer: DrawingRenderLayer? - required init(drawingSize: CGSize, lineWidth: CGFloat) { - self.uuid = UUID() - self.drawingSize = drawingSize - - let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.003) - let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.09) - let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth - - self.renderLineWidth = lineWidth + var isValid: Bool { + return self.renderPath != nil } - private enum CodingKeys: String, CodingKey { - case uuid - case drawingSize - case renderLineWidth - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.uuid = try container.decode(UUID.self, forKey: .uuid) - self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize) - self.renderLineWidth = try container.decode(CGFloat.self, forKey: .renderLineWidth) -// self.points = try container.decode([CGPoint].self, forKey: .points) - } - - 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.drawingSize, forKey: .drawingSize) - try container.encode(self.renderLineWidth, forKey: .renderLineWidth) -// try container.encode(self.points, forKey: .points) - } - - func setupRenderLayer() -> DrawingRenderLayer? { - let layer = RenderLayer() - layer.setup(size: self.drawingSize, lineWidth: self.renderLineWidth, image: self.getFullImage()) - self.currentRenderLayer = layer - return layer - } - - func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { - guard case let .smoothCurve(bezierPath) = path else { - return - } - - self.path = bezierPath - - let renderPath = bezierPath.path.cgPath - self.renderPath = renderPath - - if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { - currentRenderLayer.updatePath(renderPath) - } - } - - func draw(in context: CGContext, size: CGSize) { - context.translateBy(x: self.translation.x, y: self.translation.y) - - let renderLayer: DrawingRenderLayer? - if let currentRenderLayer = self.currentRenderLayer { - renderLayer = currentRenderLayer - } else { - renderLayer = self.setupRenderLayer() - } - renderLayer?.render(in: context) - } -} - - -final class EraserTool: DrawingElement, Codable { - class RenderLayer: SimpleLayer, DrawingRenderLayer { - var lineWidth: CGFloat = 0.0 - - let blurLayer = SimpleLayer() - let fillLayer = SimpleShapeLayer() - - func setup(size: CGSize, lineWidth: CGFloat, image: UIImage?) { - self.contentsScale = 1.0 - self.lineWidth = lineWidth - - let bounds = CGRect(origin: .zero, size: size) - self.frame = bounds - - self.blurLayer.frame = bounds - self.fillLayer.frame = bounds - - if self.blurLayer.contents == nil, let image = image { - self.blurLayer.contents = image.cgImage - self.blurLayer.contentsGravity = .resize - } - self.blurLayer.mask = self.fillLayer - - self.fillLayer.frame = bounds - self.fillLayer.contentsScale = 1.0 - self.fillLayer.strokeColor = UIColor.white.cgColor - self.fillLayer.fillColor = UIColor.clear.cgColor - self.fillLayer.lineCap = .round - self.fillLayer.lineWidth = lineWidth - - self.addSublayer(self.blurLayer) - } - - func updatePath(_ path: CGPath) { - self.fillLayer.path = path - } - } - - var getFullImage: () -> UIImage? = { return nil } - - let uuid: UUID - let drawingSize: CGSize - - var path: BezierPath? - - var renderPath: CGPath? - let renderLineWidth: CGFloat - - var translation = CGPoint() - - private var currentRenderLayer: DrawingRenderLayer? - required init(drawingSize: CGSize, lineWidth: CGFloat) { self.uuid = UUID() self.drawingSize = drawingSize @@ -575,7 +482,6 @@ enum CodableDrawingElement { case pen(PenTool) case marker(MarkerTool) case neon(NeonTool) - case eraser(EraserTool) case blur(BlurTool) case fill(FillTool) @@ -586,8 +492,6 @@ enum CodableDrawingElement { self = .marker(element) } else if let element = element as? NeonTool { self = .neon(element) - } else if let element = element as? EraserTool { - self = .eraser(element) } else if let element = element as? BlurTool { self = .blur(element) } else if let element = element as? FillTool { @@ -597,7 +501,7 @@ enum CodableDrawingElement { } } - var entity: DrawingElement { + var element: DrawingElement { switch self { case let .pen(element): return element @@ -605,8 +509,6 @@ enum CodableDrawingElement { return element case let .neon(element): return element - case let .eraser(element): - return element case let .blur(element): return element case let .fill(element): @@ -625,7 +527,6 @@ extension CodableDrawingElement: Codable { case pen case marker case neon - case eraser case blur case fill } @@ -640,8 +541,6 @@ extension CodableDrawingElement: Codable { self = .marker(try container.decode(MarkerTool.self, forKey: .element)) case .neon: self = .neon(try container.decode(NeonTool.self, forKey: .element)) - case .eraser: - self = .eraser(try container.decode(EraserTool.self, forKey: .element)) case .blur: self = .blur(try container.decode(BlurTool.self, forKey: .element)) case .fill: @@ -661,9 +560,6 @@ extension CodableDrawingElement: Codable { case let .neon(payload): try container.encode(ElementType.neon, forKey: .type) try container.encode(payload, forKey: .element) - case let .eraser(payload): - try container.encode(ElementType.eraser, forKey: .type) - try container.encode(payload, forKey: .element) case let .blur(payload): try container.encode(ElementType.blur, forKey: .type) try container.encode(payload, forKey: .element) diff --git a/submodules/DrawingUI/Sources/DrawingVectorEntity.swift b/submodules/DrawingUI/Sources/DrawingVectorEntity.swift index 7b671dc8e7..226f2b9d11 100644 --- a/submodules/DrawingUI/Sources/DrawingVectorEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingVectorEntity.swift @@ -51,6 +51,8 @@ public final class DrawingVectorEntity: DrawingEntity, Codable { return self.start } + public var scale: CGFloat = 1.0 + public var renderImage: UIImage? init(type: VectorType, color: DrawingColor, lineWidth: CGFloat) { diff --git a/submodules/DrawingUI/Sources/DrawingView.swift b/submodules/DrawingUI/Sources/DrawingView.swift index 87e00a7520..5bf86f3eea 100644 --- a/submodules/DrawingUI/Sources/DrawingView.swift +++ b/submodules/DrawingUI/Sources/DrawingView.swift @@ -10,7 +10,8 @@ import ImageBlur protocol DrawingElement: AnyObject { var uuid: UUID { get } var translation: CGPoint { get set } - + var isValid: Bool { get } + func setupRenderLayer() -> DrawingRenderLayer? func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) @@ -43,23 +44,21 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw enum Tool { case pen + case arrow case marker case neon case eraser - case lasso case blur } var tool: Tool = .pen var toolColor: DrawingColor = DrawingColor(color: .white) var toolBrushSize: CGFloat = 0.25 - var toolHasArrow: Bool = false var stateUpdated: (NavigationState) -> Void = { _ in } var shouldBegin: (CGPoint) -> Bool = { _ in return true } - var requestMenu: ([UUID], CGRect) -> Void = { _, _ in } - var getFullImage: (Bool) -> UIImage? = { _ in return nil } + var getFullImage: () -> UIImage? = { return nil } private var elements: [DrawingElement] = [] private var undoStack: [DrawingOperation] = [] @@ -69,18 +68,17 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw private(set) var drawingImage: UIImage? private let renderer: UIGraphicsImageRenderer - private var currentDrawingView: UIView + private var currentDrawingView: UIImageView private var currentDrawingLayer: DrawingRenderLayer? private var pannedSelectionView: UIView - var lassoView: DrawingLassoView private var metalView: DrawingMetalView private let brushSizePreviewLayer: SimpleShapeLayer let imageSize: CGSize - private var zoomScale: CGFloat = 1.0 + private(set) var zoomScale: CGFloat = 1.0 private var drawingGesturePipeline: DrawingGesturePipeline? private var longPressGestureRecognizer: UILongPressGestureRecognizer? @@ -109,13 +107,15 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.loadedTemplates.append(template) } } - + load("shape_rectangle") load("shape_circle") load("shape_star") load("shape_arrow") } + private let hapticFeedback = HapticFeedback() + init(size: CGSize) { self.imageSize = size @@ -123,7 +123,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw format.scale = 1.0 self.renderer = UIGraphicsImageRenderer(size: size, format: format) - self.currentDrawingView = UIView() + self.currentDrawingView = UIImageView() self.currentDrawingView.frame = CGRect(origin: .zero, size: size) self.currentDrawingView.contentScaleFactor = 1.0 self.currentDrawingView.backgroundColor = .clear @@ -135,9 +135,6 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.pannedSelectionView.backgroundColor = .clear self.pannedSelectionView.isUserInteractionEnabled = false - self.lassoView = DrawingLassoView(size: size) - self.lassoView.isHidden = true - self.metalView = DrawingMetalView(size: size)! self.metalView.isHidden = true @@ -160,11 +157,11 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.backgroundColor = .clear self.contentScaleFactor = 1.0 + self.isExclusiveTouch = true self.addSubview(self.currentDrawingView) self.addSubview(self.metalView) - self.lassoView.addSubview(self.pannedSelectionView) - self.addSubview(self.lassoView) + self.layer.addSublayer(self.brushSizePreviewLayer) let drawingGesturePipeline = DrawingGesturePipeline(view: self) @@ -173,7 +170,10 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw if !strongSelf.shouldBegin(point) { return false } - if !strongSelf.lassoView.isHidden && strongSelf.lassoView.point(inside: strongSelf.convert(point, to: strongSelf.lassoView), with: nil) { + if strongSelf.elements.isEmpty && !strongSelf.hasOpaqueData && strongSelf.tool == .eraser { + return false + } + if let uncommitedElement = strongSelf.uncommitedElement as? PenTool, uncommitedElement.isFinishingArrow { return false } return true @@ -200,7 +200,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } if newElement is MarkerTool { - self?.metalView.isHidden = false + strongSelf.metalView.isHidden = false } if let renderLayer = newElement.setupRenderLayer() { @@ -208,7 +208,20 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw strongSelf.currentDrawingLayer = nil currentDrawingLayer.removeFromSuperlayer() } - strongSelf.currentDrawingView.layer.addSublayer(renderLayer) + if strongSelf.tool == .eraser { + strongSelf.currentDrawingView.removeFromSuperview() + strongSelf.currentDrawingView.backgroundColor = .white + + renderLayer.compositingFilter = "xor" + + strongSelf.currentDrawingView.layer.addSublayer(renderLayer) + strongSelf.mask = strongSelf.currentDrawingView + } else if strongSelf.tool == .blur { + strongSelf.currentDrawingView.layer.mask = renderLayer + strongSelf.currentDrawingView.image = strongSelf.preparedBlurredImage + } else { + strongSelf.currentDrawingView.layer.addSublayer(renderLayer) + } strongSelf.currentDrawingLayer = renderLayer } newElement.updatePath(path, state: state) @@ -309,45 +322,6 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw longPressGestureRecognizer.delegate = self self.addGestureRecognizer(longPressGestureRecognizer) self.longPressGestureRecognizer = longPressGestureRecognizer - - self.lassoView.requestMenu = { [weak self] elements, rect in - if let strongSelf = self { - strongSelf.requestMenu(elements, rect) - } - } - - self.lassoView.panBegan = { [weak self] elements in - if let strongSelf = self { - strongSelf.skipDrawing = Set(elements) - strongSelf.commit(reset: true) - strongSelf.updateSelectionContent() - } - } - - self.lassoView.panChanged = { [weak self] elements, offset in - if let strongSelf = self { - let offset = CGPoint(x: offset.x * -1.0, y: offset.y * -1.0) - strongSelf.lassoView.bounds = CGRect(origin: offset, size: strongSelf.lassoView.bounds.size) - } - } - - self.lassoView.panEnded = { [weak self] elements, offset in - if let strongSelf = self { - let elementsSet = Set(elements) - for element in strongSelf.elements { - if elementsSet.contains(element.uuid) { - element.translation = element.translation.offsetBy(offset) - } - } - strongSelf.skipDrawing = Set() - strongSelf.commit(reset: true, completion: { - strongSelf.pannedSelectionView.layer.contents = nil - - strongSelf.lassoView.bounds = CGRect(origin: .zero, size: strongSelf.lassoView.bounds.size) - strongSelf.lassoView.translate(offset) - }) - } - } } required init?(coder: NSCoder) { @@ -360,26 +334,33 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } public func setup(withDrawing drawingData: Data!) { - + if let drawingData = drawingData, let image = UIImage(data: drawingData) { + self.hasOpaqueData = true + self.drawingImage = image + self.layer.contents = image.cgImage + //let codableElements = try? JSONDecoder().decode([CodableDrawingElement].self, from: drawingData) { + //self.elements = codableElements.map { $0.element } + //self.commit(reset: true) + self.updateInternalState() + } } + var hasOpaqueData = false var drawingData: Data? { - guard !self.elements.isEmpty else { + guard !self.elements.isEmpty || self.hasOpaqueData else { return nil } + return self.drawingImage?.pngData() - let codableElements = self.elements.compactMap({ CodableDrawingElement(element: $0) }) - if let data = try? JSONEncoder().encode(codableElements) { - return data - } else { - return nil - } +// let codableElements = self.elements.compactMap({ CodableDrawingElement(element: $0) }) +// if let data = try? JSONEncoder().encode(codableElements) { +// return data +// } else { +// return nil +// } } public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer === self.longPressGestureRecognizer, !self.lassoView.isHidden { - return false - } return true } @@ -390,10 +371,6 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw private var longPressTimer: SwiftSignalKit.Timer? private var fillCircleLayer: CALayer? @objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { - guard ![.eraser, .blur].contains(self.tool) else { - return - } - let location = gestureRecognizer.location(in: self) switch gestureRecognizer.state { case .began: @@ -401,33 +378,64 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.longPressTimer = nil if self.longPressTimer == nil { - self.longPressTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self] in + var toolColor = self.toolColor + var blurredImage: UIImage? + if self.tool == .marker { + toolColor = toolColor.withUpdatedAlpha(toolColor.alpha * 0.7) + } else if self.tool == .eraser { + toolColor = DrawingColor.clear + } else if self.tool == .blur { + blurredImage = self.preparedBlurredImage + } + + self.hapticFeedback.tap() + + let fillCircleLayer = SimpleShapeLayer() + self.longPressTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self, weak fillCircleLayer] in if let strongSelf = self { strongSelf.cancelDrawing() - let newElement = FillTool(drawingSize: strongSelf.imageSize, color: strongSelf.toolColor) - strongSelf.uncommitedElement = newElement - strongSelf.finishDrawing() + let action = { + let newElement = FillTool(drawingSize: strongSelf.imageSize, color: toolColor, blur: blurredImage != nil, blurredImage: blurredImage) + strongSelf.uncommitedElement = newElement + strongSelf.finishDrawing(synchronous: true) + } + if [.eraser, .blur].contains(strongSelf.tool) { + UIView.transition(with: strongSelf, duration: 0.2, options: .transitionCrossDissolve) { + action() + } + } else { + action() + } + + strongSelf.fillCircleLayer = nil + fillCircleLayer?.removeFromSuperlayer() + + strongSelf.hapticFeedback.impact(.medium) } }, queue: Queue.mainQueue()) self.longPressTimer?.start() - let fillCircleLayer = SimpleShapeLayer() - fillCircleLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 160.0, height: 160.0)) - fillCircleLayer.position = location - fillCircleLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: CGSize(width: 160.0, height: 160.0))).cgPath - fillCircleLayer.fillColor = self.toolColor.toCGColor() - self.layer.addSublayer(fillCircleLayer) - self.fillCircleLayer = fillCircleLayer - - fillCircleLayer.animateScale(from: 0.01, to: 12.0, duration: 0.35, removeOnCompletion: false, completion: { [weak self] _ in - if let strongSelf = self { - if let fillCircleLayer = strongSelf.fillCircleLayer { - strongSelf.fillCircleLayer = nil - fillCircleLayer.removeFromSuperlayer() + if [.eraser, .blur].contains(self.tool) { + + } else { + fillCircleLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 160.0, height: 160.0)) + fillCircleLayer.position = location + fillCircleLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: CGSize(width: 160.0, height: 160.0))).cgPath + fillCircleLayer.fillColor = toolColor.toCGColor() + + self.layer.addSublayer(fillCircleLayer) + self.fillCircleLayer = fillCircleLayer + + fillCircleLayer.animateScale(from: 0.01, to: 12.0, duration: 0.35, removeOnCompletion: false, completion: { [weak self] _ in + if let strongSelf = self { + if let fillCircleLayer = strongSelf.fillCircleLayer { + strongSelf.fillCircleLayer = nil + fillCircleLayer.removeFromSuperlayer() + } } - } - }) + }) + } } case .ended, .cancelled: self.longPressTimer?.invalidate() @@ -475,13 +483,24 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.layer.contents = updatedImage.cgImage if let currentDrawingLayer = self.currentDrawingLayer { + if case .eraser = self.tool { + currentDrawingLayer.removeFromSuperlayer() + self.mask = nil + self.insertSubview(self.currentDrawingView, at: 0) + self.currentDrawingView.backgroundColor = .clear + } else if case .blur = self.tool { + self.currentDrawingView.layer.mask = nil + self.currentDrawingView.image = nil + } else { + currentDrawingLayer.removeFromSuperlayer() + } self.currentDrawingLayer = nil - currentDrawingLayer.removeFromSuperlayer() } - self.metalView.clear() - self.metalView.isHidden = true - + if self.tool == .marker { + self.metalView.clear() + self.metalView.isHidden = true + } completion() } } @@ -509,13 +528,25 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.uncommitedElement = nil if let currentDrawingLayer = self.currentDrawingLayer { + if self.tool == .eraser { + currentDrawingLayer.removeFromSuperlayer() + self.mask = nil + self.insertSubview(self.currentDrawingView, at: 0) + self.currentDrawingView.backgroundColor = .clear + } else if self.tool == .blur { + self.currentDrawingView.mask = nil + } else { + currentDrawingLayer.removeFromSuperlayer() + } self.currentDrawingLayer = nil - currentDrawingLayer.removeFromSuperlayer() } } - fileprivate func finishDrawing() { + fileprivate func finishDrawing(synchronous: Bool = false) { let complete: (Bool) -> Void = { synchronous in + if let uncommitedElement = self.uncommitedElement, !uncommitedElement.isValid { + self.uncommitedElement = nil + } self.commit(interactive: true, synchronous: synchronous) self.redoStack.removeAll() @@ -532,7 +563,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw complete(true) }) } else { - complete(false) + complete(synchronous) } } @@ -544,6 +575,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.elements.removeAll() self.undoStack.removeAll() self.redoStack.removeAll() + self.hasOpaqueData = false let snapshotView = UIImageView(image: self.drawingImage) snapshotView.frame = self.bounds @@ -560,7 +592,11 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.updateInternalState() - self.lassoView.reset() + self.updateBlurredImage() + } + + var canUndo: Bool { + return !self.undoStack.isEmpty } private func undo() { @@ -573,16 +609,11 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.redoStack.append(.element(element)) self.elements.removeAll(where: { $0.uuid == element.uuid }) - let snapshotView = UIImageView(image: self.drawingImage) - snapshotView.frame = self.bounds - self.addSubview(snapshotView) - self.commit(reset: true) - - Queue.mainQueue().justDispatch { - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) + UIView.transition(with: self, duration: 0.2, options: .transitionCrossDissolve) { + self.commit(reset: true) } + + self.updateBlurredImage() case let .addEntity(uuid): if let entityView = self.entitiesView?.getView(for: uuid) { self.entitiesView?.remove(uuid: uuid, animated: true, announce: false) @@ -592,7 +623,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw if let view = self.entitiesView?.add(entity, announce: false) { view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) if !(entity is DrawingVectorEntity) { - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: entity.scale, duration: 0.2) } } self.redoStack.append(.addEntity(entity.uuid)) @@ -615,8 +646,12 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.undoStack.append(.element(element)) self.uncommitedElement = element - self.commit(reset: false) + UIView.transition(with: self, duration: 0.2, options: .transitionCrossDissolve) { + self.commit(reset: false) + } self.uncommitedElement = nil + + self.updateBlurredImage() case let .addEntity(uuid): if let entityView = self.entitiesView?.getView(for: uuid) { self.entitiesView?.remove(uuid: uuid, animated: true, announce: false) @@ -626,7 +661,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw if let view = self.entitiesView?.add(entity, announce: false) { view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) if !(entity is DrawingVectorEntity) { - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: entity.scale, duration: 0.2) } } self.undoStack.append(.addEntity(entity.uuid)) @@ -651,71 +686,61 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.updateInternalState() } - private var preparredEraserImage: UIImage? + private var preparedBlurredImage: UIImage? func updateToolState(_ state: DrawingToolState) { + let previousTool = self.tool switch state { case let .pen(brushState): - self.drawingGesturePipeline?.mode = .polyline + self.drawingGesturePipeline?.mode = .direct self.tool = .pen self.toolColor = brushState.color self.toolBrushSize = brushState.size - self.toolHasArrow = false case let .arrow(brushState): - self.drawingGesturePipeline?.mode = .polyline - self.tool = .pen + self.drawingGesturePipeline?.mode = .direct + self.tool = .arrow self.toolColor = brushState.color self.toolBrushSize = brushState.size - self.toolHasArrow = true case let .marker(brushState): self.drawingGesturePipeline?.mode = .location self.tool = .marker self.toolColor = brushState.color self.toolBrushSize = brushState.size - self.toolHasArrow = false case let .neon(brushState): self.drawingGesturePipeline?.mode = .smoothCurve self.tool = .neon self.toolColor = brushState.color self.toolBrushSize = brushState.size - self.toolHasArrow = false - case let .eraser(eraserState): - self.tool = .eraser - self.drawingGesturePipeline?.mode = .smoothCurve - self.toolBrushSize = eraserState.size case let .blur(blurState): self.tool = .blur - self.drawingGesturePipeline?.mode = .smoothCurve + self.drawingGesturePipeline?.mode = .direct self.toolBrushSize = blurState.size + case let .eraser(eraserState): + self.tool = .eraser + self.drawingGesturePipeline?.mode = .direct + self.toolBrushSize = eraserState.size } - if [.eraser, .blur].contains(self.tool) { + if self.tool != previousTool { + self.updateBlurredImage() + } else { + self.preparedBlurredImage = nil + } + + } + + func updateBlurredImage() { + if case .blur = self.tool { Queue.concurrentDefaultQueue().async { - if let image = self.getFullImage(self.tool == .blur) { - if case .eraser = self.tool { - Queue.mainQueue().async { - self.preparredEraserImage = image - } - } else { -// let format = UIGraphicsImageRendererFormat() -// format.scale = 1.0 -// let size = image.size.fitted(CGSize(width: 256, height: 256)) -// let renderer = UIGraphicsImageRenderer(size: size, format: format) -// let scaledImage = renderer.image { _ in -// image.draw(in: CGRect(origin: .zero, size: size)) -// } - - let blurredImage = blurredImage(image, radius: 60.0) - Queue.mainQueue().async { - self.preparredEraserImage = blurredImage - } + if let image = self.getFullImage() { + Queue.mainQueue().async { + self.preparedBlurredImage = image } } } } else { - self.preparredEraserImage = nil + self.preparedBlurredImage = nil } - } func performAction(_ action: Action) { @@ -733,15 +758,16 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw private func updateInternalState() { self.stateUpdated(NavigationState( - canUndo: !self.elements.isEmpty || !self.undoStack.isEmpty, + canUndo: !self.undoStack.isEmpty, canRedo: !self.redoStack.isEmpty, - canClear: !self.elements.isEmpty || !(self.entitiesView?.entities.isEmpty ?? true), + canClear: !self.elements.isEmpty || self.hasOpaqueData || !(self.entitiesView?.entities.isEmpty ?? true), canZoomOut: self.zoomScale > 1.0 + .ulpOfOne, isDrawing: self.isDrawing )) } public func updateZoomScale(_ scale: CGFloat) { + self.cancelDrawing() self.zoomScale = scale self.updateInternalState() } @@ -755,7 +781,21 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw drawingSize: self.imageSize, color: self.toolColor, lineWidth: self.toolBrushSize * scale, - hasArrow: self.toolHasArrow + hasArrow: false, + isEraser: false, + isBlur: false, + blurredImage: nil + ) + element = penTool + case .arrow: + let penTool = PenTool( + drawingSize: self.imageSize, + color: self.toolColor, + lineWidth: self.toolBrushSize * scale, + hasArrow: true, + isEraser: false, + isBlur: false, + blurredImage: nil ) element = penTool case .marker: @@ -773,25 +813,27 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw lineWidth: self.toolBrushSize * scale ) case .blur: - let blurTool = BlurTool( + let penTool = PenTool( drawingSize: self.imageSize, - lineWidth: self.toolBrushSize * scale + color: self.toolColor, + lineWidth: self.toolBrushSize * scale, + hasArrow: false, + isEraser: false, + isBlur: true, + blurredImage: self.preparedBlurredImage ) - blurTool.getFullImage = { [weak self] in - return self?.preparredEraserImage - } - element = blurTool + element = penTool case .eraser: - let eraserTool = EraserTool( + let penTool = PenTool( drawingSize: self.imageSize, - lineWidth: self.toolBrushSize * scale + color: self.toolColor, + lineWidth: self.toolBrushSize * scale, + hasArrow: false, + isEraser: true, + isBlur: false, + blurredImage: nil ) - eraserTool.getFullImage = { [weak self] in - return self?.preparredEraserImage - } - element = eraserTool - default: - element = nil + element = penTool } return element } @@ -804,15 +846,16 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw func removeElements(_ elements: [UUID]) { self.elements.removeAll(where: { elements.contains($0.uuid) }) self.commit(reset: true) - - self.lassoView.reset() } func setBrushSizePreview(_ size: CGFloat?) { let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) if let size = size { - let minBrushSize = 2.0 - let maxBrushSize = 28.0 + let minLineWidth = max(1.0, max(self.frame.width, self.frame.height) * 0.002) + let maxLineWidth = max(10.0, max(self.frame.width, self.frame.height) * 0.07) + + let minBrushSize = minLineWidth + let maxBrushSize = maxLineWidth let brushSize = minBrushSize + (maxBrushSize - minBrushSize) * size self.brushSizePreviewLayer.transform = CATransform3DMakeScale(brushSize / 100.0, brushSize / 100.0, 1.0) @@ -831,10 +874,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.currentDrawingView.frame = self.bounds self.drawingGesturePipeline?.transform = CGAffineTransformMakeScale(1.0 / scale, 1.0 / scale) - - self.lassoView.transform = transform - self.lassoView.frame = self.bounds - + self.metalView.transform = transform self.metalView.frame = self.bounds @@ -853,171 +893,3 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw return self.uncommitedElement != nil } } - -class DrawingLassoView: UIView { - private var lassoBlackLayer: SimpleShapeLayer - private var lassoWhiteLayer: SimpleShapeLayer - - var requestMenu: ([UUID], CGRect) -> Void = { _, _ in } - - var panBegan: ([UUID]) -> Void = { _ in } - var panChanged: ([UUID], CGPoint) -> Void = { _, _ in } - var panEnded: ([UUID], CGPoint) -> Void = { _, _ in } - - private var currentScale: CGFloat = 1.0 - private var currentPoints: [CGPoint] = [] - private var selectedElements: [UUID] = [] - private var currentExpand: CGFloat = 0.0 - - init(size: CGSize) { - self.lassoBlackLayer = SimpleShapeLayer() - self.lassoBlackLayer.frame = CGRect(origin: .zero, size: size) - - self.lassoWhiteLayer = SimpleShapeLayer() - self.lassoWhiteLayer.frame = CGRect(origin: .zero, size: size) - - super.init(frame: CGRect(origin: .zero, size: size)) - - self.layer.addSublayer(self.lassoBlackLayer) - self.layer.addSublayer(self.lassoWhiteLayer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) - tapGestureRecognizer.numberOfTouchesRequired = 1 - self.addGestureRecognizer(tapGestureRecognizer) - - let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) - panGestureRecognizer.maximumNumberOfTouches = 1 - self.addGestureRecognizer(panGestureRecognizer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setup(scale: CGFloat) { - self.isHidden = false - - let dash: CGFloat = 5.0 / scale - - self.lassoBlackLayer.opacity = 0.5 - self.lassoBlackLayer.fillColor = UIColor.clear.cgColor - self.lassoBlackLayer.strokeColor = UIColor.black.cgColor - self.lassoBlackLayer.lineWidth = 2.0 / scale - self.lassoBlackLayer.lineJoin = .round - self.lassoBlackLayer.lineCap = .round - self.lassoBlackLayer.lineDashPattern = [dash as NSNumber, dash * 2.5 as NSNumber] - - let blackAnimation = CABasicAnimation(keyPath: "lineDashPhase") - blackAnimation.fromValue = dash * 3.5 - blackAnimation.toValue = 0 - blackAnimation.duration = 0.45 - blackAnimation.repeatCount = .infinity - self.lassoBlackLayer.add(blackAnimation, forKey: "lineDashPhase") - - self.lassoWhiteLayer.opacity = 0.5 - self.lassoWhiteLayer.fillColor = UIColor.clear.cgColor - self.lassoWhiteLayer.strokeColor = UIColor.white.cgColor - self.lassoWhiteLayer.lineWidth = 2.0 / scale - self.lassoWhiteLayer.lineJoin = .round - self.lassoWhiteLayer.lineCap = .round - self.lassoWhiteLayer.lineDashPattern = [dash as NSNumber, dash * 2.5 as NSNumber] - - let whiteAnimation = CABasicAnimation(keyPath: "lineDashPhase") - whiteAnimation.fromValue = dash * 3.5 + dash * 1.75 - whiteAnimation.toValue = dash * 1.75 - whiteAnimation.duration = 0.45 - whiteAnimation.repeatCount = .infinity - self.lassoWhiteLayer.add(whiteAnimation, forKey: "lineDashPhase") - } - - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { - guard let path = self.lassoBlackLayer.path else { - return - } - self.requestMenu(self.selectedElements, path.boundingBox) - } - - @objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { - let translation = gestureRecognizer.translation(in: self) - - switch gestureRecognizer.state { - case .began: - self.panBegan(self.selectedElements) - case .changed: - self.panChanged(self.selectedElements, translation) - case .ended: - self.panEnded(self.selectedElements, translation) - default: - break - } - } - - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - if let path = self.lassoBlackLayer.path { - return path.contains(point) - } else { - return false - } - } - - func updatePath(_ bezierPath: BezierPath) { - self.lassoBlackLayer.path = bezierPath.path.cgPath - self.lassoWhiteLayer.path = bezierPath.path.cgPath - } - - func translate(_ offset: CGPoint) { - let updatedPoints = self.currentPoints.map { $0.offsetBy(offset) } - - self.apply(scale: self.currentScale, points: updatedPoints, selectedElements: self.selectedElements, expand: self.currentExpand) - } - - func apply(scale: CGFloat, points: [CGPoint], selectedElements: [UUID], expand: CGFloat) { - self.currentScale = scale - self.currentPoints = points - self.selectedElements = selectedElements - self.currentExpand = expand - - let dash: CGFloat = 5.0 / scale - - let hullPath = concaveHullPath(points: points) - let expandedPath = expandPath(hullPath, width: expand) - self.lassoBlackLayer.path = expandedPath - self.lassoWhiteLayer.path = expandedPath - - self.lassoBlackLayer.removeAllAnimations() - self.lassoWhiteLayer.removeAllAnimations() - - let blackAnimation = CABasicAnimation(keyPath: "lineDashPhase") - blackAnimation.fromValue = 0 - blackAnimation.toValue = dash * 3.5 - blackAnimation.duration = 0.45 - blackAnimation.repeatCount = .infinity - self.lassoBlackLayer.add(blackAnimation, forKey: "lineDashPhase") - - self.lassoWhiteLayer.fillColor = UIColor.clear.cgColor - self.lassoWhiteLayer.strokeColor = UIColor.white.cgColor - self.lassoWhiteLayer.lineWidth = 2.0 / scale - self.lassoWhiteLayer.lineJoin = .round - self.lassoWhiteLayer.lineCap = .round - self.lassoWhiteLayer.lineDashPattern = [dash as NSNumber, dash * 2.5 as NSNumber] - - let whiteAnimation = CABasicAnimation(keyPath: "lineDashPhase") - whiteAnimation.fromValue = dash * 1.75 - whiteAnimation.toValue = dash * 3.5 + dash * 1.75 - whiteAnimation.duration = 0.45 - whiteAnimation.repeatCount = .infinity - self.lassoWhiteLayer.add(whiteAnimation, forKey: "lineDashPhase") - } - - func reset() { - self.bounds = CGRect(origin: .zero, size: self.bounds.size) - - self.selectedElements = [] - - self.isHidden = true - self.lassoBlackLayer.path = nil - self.lassoWhiteLayer.path = nil - self.lassoBlackLayer.removeAllAnimations() - self.lassoWhiteLayer.removeAllAnimations() - } -} diff --git a/submodules/DrawingUI/Sources/EyedropperView.swift b/submodules/DrawingUI/Sources/EyedropperView.swift index b22e95673d..90181b3d32 100644 --- a/submodules/DrawingUI/Sources/EyedropperView.swift +++ b/submodules/DrawingUI/Sources/EyedropperView.swift @@ -54,6 +54,7 @@ final class EyedropperView: UIView { private let sourceImage: (data: Data, size: CGSize, bytesPerRow: Int, info: CGBitmapInfo)? var completed: (DrawingColor) -> Void = { _ in } + var dismissed: () -> Void = { } init(containerSize: CGSize, drawingView: DrawingView, sourceImage: UIImage) { self.drawingView = drawingView @@ -162,6 +163,15 @@ final class EyedropperView: UIView { } } + func dismiss() { + self.containerView.alpha = 0.0 + self.containerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in + self?.removeFromSuperview() + }) + self.containerView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2) + self.dismissed() + } + private func getColorAt(_ point: CGPoint) -> UIColor? { guard var sourceImage = self.sourceImage, point.x >= 0 && point.x < sourceImage.size.width && point.y >= 0 && point.y < sourceImage.size.height else { return UIColor.black diff --git a/submodules/DrawingUI/Sources/PenTool.swift b/submodules/DrawingUI/Sources/PenTool.swift index 93b3e3cacd..10b323fc1b 100644 --- a/submodules/DrawingUI/Sources/PenTool.swift +++ b/submodules/DrawingUI/Sources/PenTool.swift @@ -2,49 +2,53 @@ import Foundation import UIKit import Display -final class PenTool: DrawingElement, Codable { +final class PenTool: DrawingElement, Codable { class RenderLayer: SimpleLayer, DrawingRenderLayer { private weak var element: PenTool? + private var isEraser = false + private var accumulationImage: UIImage? + private var activeLayer: ActiveLayer? + + private var start = 0 private var segmentsCount = 0 private var velocity: CGFloat? - private var previousRect: CGRect? var displayLink: ConstantDisplayLinkAnimator? - func setup(size: CGSize) { + func setup(size: CGSize, isEraser: Bool, useDisplayLink: Bool) { + self.isEraser = isEraser + self.shouldRasterize = true self.contentsScale = 1.0 + self.allowsGroupOpacity = true let bounds = CGRect(origin: .zero, size: size) self.frame = bounds - self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in - if let strongSelf = self { - if let element = strongSelf.element, strongSelf.segmentsCount < element.segments.count, let velocity = strongSelf.velocity { - let delta = max(9, Int(velocity / 100.0)) - let start = strongSelf.segmentsCount - strongSelf.segmentsCount = min(strongSelf.segmentsCount + delta, element.segments.count) - - let rect = element.boundingRect(from: start, to: strongSelf.segmentsCount) - strongSelf.setNeedsDisplay(rect.insetBy(dx: -80.0, dy: -80.0)) - } - } - }) - self.displayLink?.frameInterval = 1 - self.displayLink?.isPaused = false - } - - - fileprivate func draw(element: PenTool, velocity: CGFloat, rect: CGRect) { - self.element = element + let activeLayer = ActiveLayer() + activeLayer.shouldRasterize = true + activeLayer.contentsScale = 1.0 + activeLayer.frame = bounds + activeLayer.parent = self + self.addSublayer(activeLayer) + self.activeLayer = activeLayer - self.previousRect = rect - if let previous = self.velocity { - self.velocity = velocity * 0.4 + previous * 0.6 - } else { - self.velocity = velocity + if useDisplayLink { + self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in + if let strongSelf = self { + if let element = strongSelf.element, strongSelf.segmentsCount < element.segments.count, let velocity = strongSelf.velocity { + let delta = max(12, Int(velocity / 100.0)) + let start = strongSelf.segmentsCount + strongSelf.segmentsCount = min(strongSelf.segmentsCount + delta, element.segments.count) + + let rect = element.boundingRect(from: start, to: strongSelf.segmentsCount) + strongSelf.activeLayer?.setNeedsDisplay(rect.insetBy(dx: -10.0, dy: -10.0)) + } + } + }) + self.displayLink?.frameInterval = 1 + self.displayLink?.isPaused = false } - self.setNeedsDisplay(rect.insetBy(dx: -80.0, dy: -80.0)) } func animateArrowPaths(leftArrowPath: UIBezierPath, rightArrowPath: UIBezierPath, lineWidth: CGFloat, completion: @escaping () -> Void) { @@ -73,8 +77,63 @@ final class PenTool: DrawingElement, Codable { }) } - override func draw(in ctx: CGContext) { - self.element?.drawSegments(in: ctx, upTo: self.segmentsCount) + var i = 0 + fileprivate func draw(element: PenTool, velocity: CGFloat, rect: CGRect) { + self.element = element + + self.opacity = Float(element.color.alpha) + + guard !rect.isInfinite && !rect.isEmpty && !rect.isNull else { + return + } + + var rect: CGRect? = rect + + let limit = 512 + let activeCount = self.segmentsCount - self.start + if activeCount > limit { + rect = nil + let newStart = self.start + limit + let image = generateImage(self.bounds.size, contextGenerator: { size, context in + if let accumulationImage = self.accumulationImage, let cgImage = accumulationImage.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: size)) + } + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + element.drawSegments(in: context, from: self.start, upTo: newStart) + }, opaque: false, scale: 1.0) + self.accumulationImage = image + self.contents = image?.cgImage + i += 1 + print("accumulated \(i)") + + self.start = newStart + } + + self.segmentsCount = element.segments.count + + if let previous = self.velocity { + self.velocity = velocity * 0.4 + previous * 0.6 + } else { + self.velocity = velocity + } + if let rect = rect { + self.activeLayer?.setNeedsDisplay(rect.insetBy(dx: -10.0, dy: -10.0)) + } else { + self.activeLayer?.setNeedsDisplay() + } + } + + class ActiveLayer: SimpleLayer { + weak var parent: RenderLayer? + + override func draw(in ctx: CGContext) { + guard let parent = self.parent, let element = parent.element else { + return + } + element.drawSegments(in: ctx, from: parent.start, upTo: parent.segmentsCount) + } } } @@ -83,44 +142,73 @@ final class PenTool: DrawingElement, Codable { let color: DrawingColor let renderLineWidth: CGFloat let renderMinLineWidth: CGFloat + let renderColor: UIColor let hasArrow: Bool let renderArrowLength: CGFloat let renderArrowLineWidth: CGFloat - + + let isEraser: Bool + + let isBlur: Bool + + var arrowStart: CGPoint? + var arrowDirection: CGFloat? var arrowLeftPath: UIBezierPath? var arrowRightPath: UIBezierPath? var translation: CGPoint = .zero + + var blurredImage: UIImage? private weak var currentRenderLayer: DrawingRenderLayer? + + var isValid: Bool { + if self.hasArrow { + return self.arrowStart != nil && self.arrowDirection != nil + } else { + return self.segments.count > 0 + } + } - required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, hasArrow: Bool) { + required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, hasArrow: Bool, isEraser: Bool, isBlur: Bool, blurredImage: UIImage?) { self.uuid = UUID() self.drawingSize = drawingSize - self.color = color + self.color = isEraser || isBlur ? DrawingColor(rgb: 0x000000) : color self.hasArrow = hasArrow + self.isEraser = isEraser + self.isBlur = isBlur + self.blurredImage = blurredImage let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.002) let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.07) let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth + let minRenderArrowLength = max(10.0, max(drawingSize.width, drawingSize.height) * 0.02) + self.renderLineWidth = lineWidth - self.renderMinLineWidth = minLineWidth + (lineWidth - minLineWidth) * 0.3 - self.renderArrowLength = lineWidth * 3.0 - self.renderArrowLineWidth = lineWidth * 0.8 + self.renderMinLineWidth = isEraser || isBlur ? lineWidth : minLineWidth + (lineWidth - minLineWidth) * 0.3 + self.renderArrowLength = max(minRenderArrowLength, lineWidth * 3.0) + self.renderArrowLineWidth = max(minLineWidth * 1.8, lineWidth * 0.75) + + self.renderColor = color.withUpdatedAlpha(1.0).toUIColor() } private enum CodingKeys: String, CodingKey { case uuid case drawingSize case color + case isEraser case hasArrow + case isBlur case renderLineWidth case renderMinLineWidth case renderArrowLength case renderArrowLineWidth + + case arrowStart + case arrowDirection case renderSegments } @@ -130,11 +218,21 @@ final class PenTool: DrawingElement, Codable { self.uuid = try container.decode(UUID.self, forKey: .uuid) self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize) self.color = try container.decode(DrawingColor.self, forKey: .color) + self.isEraser = try container.decode(Bool.self, forKey: .isEraser) self.hasArrow = try container.decode(Bool.self, forKey: .hasArrow) + self.isBlur = try container.decode(Bool.self, forKey: .isBlur) self.renderLineWidth = try container.decode(CGFloat.self, forKey: .renderLineWidth) self.renderMinLineWidth = try container.decode(CGFloat.self, forKey: .renderMinLineWidth) self.renderArrowLength = try container.decode(CGFloat.self, forKey: .renderArrowLength) self.renderArrowLineWidth = try container.decode(CGFloat.self, forKey: .renderArrowLineWidth) + + self.arrowStart = try container.decodeIfPresent(CGPoint.self, forKey: .arrowStart) + self.arrowDirection = try container.decodeIfPresent(CGFloat.self, forKey: .arrowDirection) + + self.segments = try container.decode([Segment].self, forKey: .renderSegments) + + self.renderColor = self.color.withUpdatedAlpha(1.0).toUIColor() + self.maybeSetupArrow() } public func encode(to encoder: Encoder) throws { @@ -142,16 +240,26 @@ final class PenTool: DrawingElement, Codable { try container.encode(self.uuid, forKey: .uuid) try container.encode(self.drawingSize, forKey: .drawingSize) try container.encode(self.color, forKey: .color) + try container.encode(self.isEraser, forKey: .isEraser) try container.encode(self.hasArrow, forKey: .hasArrow) + try container.encode(self.isBlur, forKey: .isBlur) try container.encode(self.renderLineWidth, forKey: .renderLineWidth) try container.encode(self.renderMinLineWidth, forKey: .renderMinLineWidth) try container.encode(self.renderArrowLength, forKey: .renderArrowLength) try container.encode(self.renderArrowLineWidth, forKey: .renderArrowLineWidth) + + try container.encodeIfPresent(self.arrowStart, forKey: .arrowStart) + try container.encodeIfPresent(self.arrowDirection, forKey: .arrowDirection) + + try container.encode(self.segments, forKey: .renderSegments) } + var isFinishingArrow = false func finishArrow(_ completion: @escaping () -> Void) { if let arrowLeftPath, let arrowRightPath { - (self.currentRenderLayer as? RenderLayer)?.animateArrowPaths(leftArrowPath: arrowLeftPath, rightArrowPath: arrowRightPath, lineWidth: self.renderArrowLineWidth, completion: { + self.isFinishingArrow = true + (self.currentRenderLayer as? RenderLayer)?.animateArrowPaths(leftArrowPath: arrowLeftPath, rightArrowPath: arrowRightPath, lineWidth: self.renderArrowLineWidth, completion: { [weak self] in + self?.isFinishingArrow = false completion() }) } else { @@ -161,35 +269,36 @@ final class PenTool: DrawingElement, Codable { func setupRenderLayer() -> DrawingRenderLayer? { let layer = RenderLayer() - layer.setup(size: self.drawingSize) + layer.setup(size: self.drawingSize, isEraser: self.isEraser, useDisplayLink: self.color.toUIColor().rgb == 0xbf5af2) self.currentRenderLayer = layer return layer } var previousPoint: CGPoint? func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { - guard case let .polyline(line) = path, let point = line.points.last else { + guard case let .point(point) = path else { return } - let filterDistance: CGFloat + var filterDistance: CGFloat if point.velocity > 1200.0 { - filterDistance = 75.0 + filterDistance = 90.0 } else { - filterDistance = 35.0 + filterDistance = 20.0 } +// filterDistance = 0.0 - if let previousPoint, point.location.distance(to: previousPoint) < filterDistance, state == .changed, self.segments.count > 0 { + if let previousPoint, point.location.distance(to: previousPoint) < filterDistance, state == .changed, self.segments.count > 1 { return } self.previousPoint = point.location var velocity = point.velocity if velocity.isZero { - velocity = 600.0 + velocity = 1000.0 } - var effectiveRenderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth + 1.0 - (velocity / 180.0), self.renderLineWidth)) + var effectiveRenderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth + 1.0 - (velocity / 220.0), self.renderLineWidth)) if let previousRenderLineWidth = self.previousRenderLineWidth { effectiveRenderLineWidth = effectiveRenderLineWidth * 0.2 + previousRenderLineWidth * 0.8 } @@ -214,23 +323,42 @@ final class PenTool: DrawingElement, Codable { } } } - - if let point = self.smoothPoints.last?.position, let direction { - let arrowLeftPath = UIBezierPath() - arrowLeftPath.move(to: point) - arrowLeftPath.addLine(to: point.pointAt(distance: self.renderArrowLength, angle: direction - 0.45)) - - let arrowRightPath = UIBezierPath() - arrowRightPath.move(to: point) - arrowRightPath.addLine(to: point.pointAt(distance: self.renderArrowLength, angle: direction + 0.45)) - - self.arrowLeftPath = arrowLeftPath - self.arrowRightPath = arrowRightPath - } + + self.arrowStart = self.smoothPoints.last?.position + self.arrowDirection = direction + self.maybeSetupArrow() + } else if self.segments.isEmpty { + let radius = self.renderLineWidth / 2.0 + self.segments.append( + Segment( + a: CGPoint(x: point.x - radius, y: point.y), + b: CGPoint(x: point.x + radius, y: point.y), + c: CGPoint(x: point.x - radius, y: point.y + 0.1), + d: CGPoint(x: point.x + radius, y: point.y + 0.1), + radius1: radius, + radius2: radius, + rect: .zero + ) + ) } } } + func maybeSetupArrow() { + if let start = self.arrowStart, let direction = self.arrowDirection { + let arrowLeftPath = UIBezierPath() + arrowLeftPath.move(to: start) + arrowLeftPath.addLine(to: start.pointAt(distance: self.renderArrowLength, angle: direction - 0.45)) + + let arrowRightPath = UIBezierPath() + arrowRightPath.move(to: start) + arrowRightPath.addLine(to: start.pointAt(distance: self.renderArrowLength, angle: direction + 0.45)) + + self.arrowLeftPath = arrowLeftPath + self.arrowRightPath = arrowRightPath + } + } + func draw(in context: CGContext, size: CGSize) { guard !self.segments.isEmpty else { return @@ -238,14 +366,36 @@ final class PenTool: DrawingElement, Codable { context.saveGState() + if self.isEraser { + context.setBlendMode(.clear) + } else { + context.setAlpha(self.color.alpha) + context.setBlendMode(.copy) + } + context.translateBy(x: self.translation.x, y: self.translation.y) context.setShouldAntialias(true) - self.drawSegments(in: context, upTo: self.segments.count) + if self.isBlur, let blurredImage = self.blurredImage { + let maskContext = DrawingContext(size: size, scale: 0.5, clear: true) + maskContext?.withFlippedContext { maskContext in + self.drawSegments(in: maskContext, from: 0, upTo: self.segments.count) + } + if let maskImage = maskContext?.generateImage()?.cgImage, let blurredImage = blurredImage.cgImage { + context.clip(to: CGRect(origin: .zero, size: size), mask: maskImage) + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + context.draw(blurredImage, in: CGRect(origin: .zero, size: size)) + } + + } else { + self.drawSegments(in: context, from: 0, upTo: self.segments.count) + } if let arrowLeftPath, let arrowRightPath { - context.setStrokeColor(self.color.toCGColor()) + context.setStrokeColor(self.renderColor.cgColor) context.setLineWidth(self.renderArrowLineWidth) context.setLineCap(.round) @@ -257,9 +407,11 @@ final class PenTool: DrawingElement, Codable { } context.restoreGState() + + self.segmentPaths = [:] } - private struct Segment { + private struct Segment: Codable { let a: CGPoint let b: CGPoint let c: CGPoint @@ -267,6 +419,48 @@ final class PenTool: DrawingElement, Codable { let radius1: CGFloat let radius2: CGFloat let rect: CGRect + + init(a: CGPoint, b: CGPoint, c: CGPoint, d: CGPoint, radius1: CGFloat, radius2: CGFloat, rect: CGRect) { + self.a = a + self.b = b + self.c = c + self.d = d + self.radius1 = radius1 + self.radius2 = radius2 + self.rect = rect + } + + private enum CodingKeys: String, CodingKey { + case a + case b + case c + case d + case radius1 + case radius2 + case rect + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.a = try container.decode(CGPoint.self, forKey: .a) + self.b = try container.decode(CGPoint.self, forKey: .b) + self.c = try container.decode(CGPoint.self, forKey: .c) + self.d = try container.decode(CGPoint.self, forKey: .d) + self.radius1 = try container.decode(CGFloat.self, forKey: .radius1) + self.radius2 = try container.decode(CGFloat.self, forKey: .radius2) + self.rect = try container.decode(CGRect.self, forKey: .rect) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.a, forKey: .a) + try container.encode(self.b, forKey: .b) + try container.encode(self.c, forKey: .c) + try container.encode(self.d, forKey: .d) + try container.encode(self.radius1, forKey: .radius1) + try container.encode(self.radius2, forKey: .radius2) + try container.encode(self.rect, forKey: .rect) + } } private struct Point { @@ -291,57 +485,71 @@ final class PenTool: DrawingElement, Codable { private func append(point: Point) -> CGRect? { self.points.append(point) - guard self.points.count > 2 else { return .null } + guard self.points.count > 2 else { return nil } let index = self.points.count - 1 let point0 = self.points[index - 2] let point1 = self.points[index - 1] let point2 = self.points[index] - let newSmoothPoints = smoothPoints( + var newSmoothPoints = self.smoothPoints( fromPoint0: point0, point1: point1, point2: point2 ) - let lastOldSmoothPoint = smoothPoints.last + let previousSmoothPoint = self.smoothPoints.last self.smoothPoints.append(contentsOf: newSmoothPoints) guard self.smoothPoints.count > 1 else { return nil } - let newSegments: ([Segment], CGRect) = { - guard let lastOldSmoothPoint = lastOldSmoothPoint else { - return segments(fromSmoothPoints: newSmoothPoints) - } - return segments(fromSmoothPoints: [lastOldSmoothPoint] + newSmoothPoints) - }() - self.segments.append(contentsOf: newSegments.0) + if let previousSmoothPoint = previousSmoothPoint { + newSmoothPoints.insert(previousSmoothPoint, at: 0) + } + let (nextSegments, rect) = self.segments(fromSmoothPoints: newSmoothPoints) + self.segments.append(contentsOf: nextSegments) + + for i in self.segments.count - nextSegments.count ..< self.segments.count { + let segment = self.segments[i] + let path = self.pathForSegment(segment) + self.segmentPaths[i] = path + } - return newSegments.1 + return rect } private func smoothPoints(fromPoint0 point0: Point, point1: Point, point2: Point) -> [Point] { var smoothPoints: [Point] = [] - let midPoint1 = (point0.position + point1.position) * 0.5 - let midPoint2 = (point1.position + point2.position) * 0.5 + let midPoint1 = CGPoint( + x: (point0.position.x + point1.position.x) * 0.5, + y: (point0.position.y + point1.position.y) * 0.5 + ) + let midPoint2 = CGPoint( + x: (point1.position.x + point2.position.x) * 0.5, + y: (point1.position.y + point2.position.y) * 0.5 + ) - let segmentDistance: CGFloat = 3.0 + let midWidth1 = (point0.width + point1.width) * 0.5 + let midWidth2 = (point1.width + point2.width) * 0.5 + + let segmentDistance: CGFloat = 6.0 let distance = midPoint1.distance(to: midPoint2) let numberOfSegments = min(128, max(floor(distance / segmentDistance), 32)) let step = 1.0 / numberOfSegments for t in stride(from: 0, to: 1, by: step) { - let position = midPoint1 * pow(1 - t, 2) + point1.position * 2 * (1 - t) * t + midPoint2 * t * t - let size = pow(1 - t, 2) * ((point0.width + point1.width) * 0.5) + 2 * (1 - t) * t * point1.width + t * t * ((point1.width + point2.width) * 0.5) - let point = Point(position: position, width: size) - smoothPoints.append(point) + let pX = midPoint1.x * pow(1 - t, 2) + point1.position.x * 2.0 * (1 - t) * t + midPoint2.x * t * t + let pY = midPoint1.y * pow(1 - t, 2) + point1.position.y * 2.0 * (1 - t) * t + midPoint2.y * t * t + + let w = midWidth1 * pow(1 - t, 2) + point1.width * 2.0 * (1 - t) * t + midWidth2 * t * t + + smoothPoints.append(Point(position: CGPoint(x: pX, y: pY), width: w)) } - let finalPoint = Point(position: midPoint2, width: (point1.width + point2.width) * 0.5) - smoothPoints.append(finalPoint) + smoothPoints.append(Point(position: midPoint2, width: midWidth2)) return smoothPoints } @@ -372,7 +580,7 @@ final class PenTool: DrawingElement, Codable { return CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY) } - private func segments(fromSmoothPoints smoothPoints: [Point]) -> ([Segment], CGRect) { + private func segments(fromSmoothPoints smoothPoints: [Point]) -> ([Segment], CGRect?) { var segments: [Segment] = [] var updateRect = CGRect.null for i in 1 ..< smoothPoints.count { @@ -380,7 +588,10 @@ final class PenTool: DrawingElement, Codable { let previousWidth = smoothPoints[i - 1].width let currentPoint = smoothPoints[i].position let currentWidth = smoothPoints[i].width - let direction = currentPoint - previousPoint + let direction = CGPoint( + x: currentPoint.x - previousPoint.x, + y: currentPoint.y - previousPoint.y + ) guard !currentPoint.isEqual(to: previousPoint, epsilon: 0.0001) else { continue @@ -389,25 +600,55 @@ final class PenTool: DrawingElement, Codable { var perpendicular = CGPoint(x: -direction.y, y: direction.x) let length = perpendicular.length if length > 0.0 { - perpendicular = perpendicular / length + perpendicular = CGPoint( + x: perpendicular.x / length, + y: perpendicular.y / length + ) } - let a = previousPoint + perpendicular * previousWidth / 2 - let b = previousPoint - perpendicular * previousWidth / 2 - let c = currentPoint + perpendicular * currentWidth / 2 - let d = currentPoint - perpendicular * currentWidth / 2 + let a = CGPoint( + x: previousPoint.x + perpendicular.x * previousWidth / 2.0, + y: previousPoint.y + perpendicular.y * previousWidth / 2.0 + ) + let b = CGPoint( + x: previousPoint.x - perpendicular.x * previousWidth / 2.0, + y: previousPoint.y - perpendicular.y * previousWidth / 2.0 + ) + let c = CGPoint( + x: currentPoint.x + perpendicular.x * currentWidth / 2.0, + y: currentPoint.y + perpendicular.y * currentWidth / 2.0 + ) + let d = CGPoint( + x: currentPoint.x - perpendicular.x * currentWidth / 2.0, + y: currentPoint.y - perpendicular.y * currentWidth / 2.0 + ) - let ab: CGPoint = { - let center = (b + a) / 2 - let radius = center - b - return .init(x: center.x - radius.y, y: center.y + radius.x) - }() - let cd: CGPoint = { - let center = (c + d) / 2 - let radius = center - c - return .init(x: center.x + radius.y, y: center.y - radius.x) - }() + let abCenter = CGPoint( + x: (a.x + b.x) / 2.0, + y: (a.y + b.y) / 2.0 + ) + let abRadius = CGPoint( + x: abCenter.x - b.x, + y: abCenter.y - b.y + ) + let ab = CGPoint( + x: abCenter.x - abRadius.y, + y: abCenter.y + abRadius.x + ) + let cdCenter = CGPoint( + x: (c.x + d.x) / 2.0, + y: (c.y + d.y) / 2.0 + ) + let cdRadius = CGPoint( + x: cdCenter.x - c.x, + y: cdCenter.y - c.y + ) + let cd = CGPoint( + x: cdCenter.x - cdRadius.y, + y: cdCenter.y + cdRadius.x + ) + let minX = min(a.x, b.x, c.x, d.x, ab.x, cd.x) let minY = min(a.y, b.y, c.y, d.y, ab.y, cd.y) let maxX = max(a.x, b.x, c.x, d.x, ab.x, cd.x) @@ -415,50 +656,72 @@ final class PenTool: DrawingElement, Codable { let segmentRect = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY) updateRect = updateRect.union(segmentRect) - - segments.append(Segment(a: a, b: b, c: c, d: d, radius1: previousWidth / 2.0, radius2: currentWidth / 2.0, rect: segmentRect)) + + let segment = Segment(a: a, b: b, c: c, d: d, radius1: previousWidth / 2.0, radius2: currentWidth / 2.0, rect: segmentRect) + segments.append(segment) } - return (segments, updateRect) + return (segments, !updateRect.isNull ? updateRect : nil) } - private func drawSegments(in context: CGContext, upTo: Int) { - context.setStrokeColor(self.color.toCGColor()) - context.setFillColor(self.color.toCGColor()) + private var segmentPaths: [Int: CGPath] = [:] + + private func pathForSegment(_ segment: Segment) -> CGPath { + let path = CGMutablePath() + path.move(to: segment.b) - for i in 0 ..< upTo { + let abStartAngle = atan2( + segment.b.y - segment.a.y, + segment.b.x - segment.a.x + ) + path.addArc( + center: CGPoint( + x: (segment.a.x + segment.b.x) / 2, + y: (segment.a.y + segment.b.y) / 2 + ), + radius: segment.radius1, + startAngle: abStartAngle, + endAngle: abStartAngle + .pi, + clockwise: true + ) + path.addLine(to: segment.c) + + let cdStartAngle = atan2( + segment.c.y - segment.d.y, + segment.c.x - segment.d.x + ) + path.addArc( + center: CGPoint( + x: (segment.c.x + segment.d.x) / 2, + y: (segment.c.y + segment.d.y) / 2 + ), + radius: segment.radius2, + startAngle: cdStartAngle, + endAngle: cdStartAngle + .pi, + clockwise: true + ) + path.closeSubpath() + return path + } + + private func drawSegments(in context: CGContext, from: Int, upTo: Int) { +// context.setStrokeColor(self.renderColor.cgColor) + context.setFillColor(self.renderColor.cgColor) + + for i in from ..< upTo { let segment = self.segments[i] - context.beginPath() - - context.move(to: segment.b) - let abStartAngle = atan2( - segment.b.y - segment.a.y, - segment.b.x - segment.a.x - ) - context.addArc( - center: (segment.a + segment.b)/2, - radius: segment.radius1, - startAngle: abStartAngle, - endAngle: abStartAngle + .pi, - clockwise: true - ) - context.addLine(to: segment.c) - - let cdStartAngle = atan2( - segment.c.y - segment.d.y, - segment.c.x - segment.d.x - ) - context.addArc( - center: (segment.c + segment.d) / 2, - radius: segment.radius2, - startAngle: cdStartAngle, - endAngle: cdStartAngle + .pi, - clockwise: true - ) - context.closePath() + var segmentPath: CGPath + if let current = self.segmentPaths[i] { + segmentPath = current + } else { + let path = self.pathForSegment(segment) + self.segmentPaths[i] = path + segmentPath = path + } + context.addPath(segmentPath) context.fillPath() - context.strokePath() +// context.strokePath() } } } diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index 11d1e54f5d..898b28e6d8 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -13,6 +13,8 @@ import EntityKeyboard import PagerComponent import FeaturedStickersScreen import TelegramNotices +import ChatEntityKeyboardInputNode +import ContextUI struct StickerPickerInputData: Equatable { var emoji: EmojiPagerContentComponent @@ -440,6 +442,18 @@ class StickerPickerScreen: ViewController { hideBackground: true ) + var stickerPeekBehavior: EmojiContentPeekBehaviorImpl? + if let controller = self.controller { + stickerPeekBehavior = EmojiContentPeekBehaviorImpl( + context: controller.context, + interaction: nil, + chatPeerId: nil, + present: { [weak controller] c, a in + controller?.presentInGlobalOverlay(c, with: a) + } + ) + } + content.stickers?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak self] _, item, _, _, _, _ in guard let strongSelf = self, let file = item.itemFile else { @@ -541,7 +555,7 @@ class StickerPickerScreen: ViewController { updateSearchQuery: { _, _ in }, chatPeerId: nil, - peekBehavior: nil, + peekBehavior: stickerPeekBehavior, customLayout: nil, externalBackground: nil, externalExpansionView: nil, @@ -594,6 +608,10 @@ class StickerPickerScreen: ViewController { if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { if otherGestureRecognizer is PagerPanGestureRecognizer { return false + } else if otherGestureRecognizer is UIPanGestureRecognizer, let scrollView = otherGestureRecognizer.view, scrollView.frame.width > scrollView.frame.height { + return false + } else if otherGestureRecognizer is PeekControllerGestureRecognizer { + return false } return true } diff --git a/submodules/DrawingUI/Sources/TextSettingsComponent.swift b/submodules/DrawingUI/Sources/TextSettingsComponent.swift index 7d2dcde162..a5b48f6263 100644 --- a/submodules/DrawingUI/Sources/TextSettingsComponent.swift +++ b/submodules/DrawingUI/Sources/TextSettingsComponent.swift @@ -760,6 +760,8 @@ final class TextSizeSliderComponent: Component { init() { super.init(frame: CGRect()) + self.isExclusiveTouch = true + let pressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handlePress(_:))) pressGestureRecognizer.minimumPressDuration = 0.01 pressGestureRecognizer.delegate = self diff --git a/submodules/DrawingUI/Sources/ToolsComponent.swift b/submodules/DrawingUI/Sources/ToolsComponent.swift index 035cb37a06..36cbfdeedf 100644 --- a/submodules/DrawingUI/Sources/ToolsComponent.swift +++ b/submodules/DrawingUI/Sources/ToolsComponent.swift @@ -17,6 +17,7 @@ private class ToolView: UIView, UIGestureRecognizerDelegate { var isVisible = false private var currentSize: CGFloat? + private let shadow: SimpleLayer private let tip: UIImageView private let background: SimpleLayer private let band: SimpleGradientLayer @@ -27,6 +28,8 @@ private class ToolView: UIView, UIGestureRecognizerDelegate { init(type: DrawingToolState.Key) { self.type = type + self.shadow = SimpleLayer() + self.tip = UIImageView() self.tip.isUserInteractionEnabled = false @@ -41,6 +44,7 @@ private class ToolView: UIView, UIGestureRecognizerDelegate { let backgroundImage: UIImage? let tipImage: UIImage? + let shadowImage: UIImage? var tipAbove = true var hasBand = true @@ -49,39 +53,49 @@ private class ToolView: UIView, UIGestureRecognizerDelegate { case .pen: backgroundImage = UIImage(bundleImageName: "Media Editor/ToolPen") tipImage = UIImage(bundleImageName: "Media Editor/ToolPenTip")?.withRenderingMode(.alwaysTemplate) + shadowImage = UIImage(bundleImageName: "Media Editor/ToolPenShadow") case .arrow: backgroundImage = UIImage(bundleImageName: "Media Editor/ToolArrow") tipImage = UIImage(bundleImageName: "Media Editor/ToolArrowTip")?.withRenderingMode(.alwaysTemplate) + shadowImage = UIImage(bundleImageName: "Media Editor/ToolArrowShadow") case .marker: backgroundImage = UIImage(bundleImageName: "Media Editor/ToolMarker") tipImage = UIImage(bundleImageName: "Media Editor/ToolMarkerTip")?.withRenderingMode(.alwaysTemplate) tipAbove = false + shadowImage = UIImage(bundleImageName: "Media Editor/ToolMarkerShadow") case .neon: backgroundImage = UIImage(bundleImageName: "Media Editor/ToolNeon") tipImage = UIImage(bundleImageName: "Media Editor/ToolNeonTip")?.withRenderingMode(.alwaysTemplate) tipAbove = false + shadowImage = UIImage(bundleImageName: "Media Editor/ToolNeonShadow") case .eraser: backgroundImage = UIImage(bundleImageName: "Media Editor/ToolEraser") tipImage = nil hasBand = false + shadowImage = UIImage(bundleImageName: "Media Editor/ToolEraserShadow") case .blur: backgroundImage = UIImage(bundleImageName: "Media Editor/ToolBlur") tipImage = UIImage(bundleImageName: "Media Editor/ToolBlurTip") tipAbove = false hasBand = false + shadowImage = UIImage(bundleImageName: "Media Editor/ToolBlurShadow") } self.tip.image = tipImage self.background.contents = backgroundImage?.cgImage + self.shadow.contents = shadowImage?.cgImage super.init(frame: CGRect(origin: .zero, size: toolSize)) self.tip.frame = CGRect(origin: .zero, size: toolSize) + self.shadow.frame = CGRect(origin: .zero, size: toolSize).insetBy(dx: -4.0, dy: 0.0) self.background.frame = CGRect(origin: .zero, size: toolSize) self.band.frame = CGRect(origin: CGPoint(x: 3.0, y: 64.0), size: CGSize(width: toolSize.width - 6.0, height: toolSize.width - 16.0)) self.band.anchorPoint = CGPoint(x: 0.5, y: 0.0) + self.layer.addSublayer(self.shadow) + if tipAbove { self.layer.addSublayer(self.background) self.addSubview(self.tip) @@ -107,7 +121,7 @@ private class ToolView: UIView, UIGestureRecognizerDelegate { override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer { - if self.isSelected && !self.isToolFocused { + if self.isSelected { return true } else { return false @@ -164,12 +178,12 @@ private class ToolView: UIView, UIGestureRecognizerDelegate { } func update(state: DrawingToolState) { + self.currentSize = state.size + if let _ = self.tip.image { let color = state.color?.toUIColor() self.tip.tintColor = color - self.currentSize = state.size - guard let color = color else { return } @@ -234,7 +248,7 @@ final class ToolsComponent: Component { } public final class View: UIView, ComponentTaggedView { - private let toolViews: [ToolView] + private var toolViews: [ToolView] = [] private let maskImageView: UIImageView private var isToolFocused: Bool? @@ -251,20 +265,12 @@ final class ToolsComponent: Component { } override init(frame: CGRect) { - var toolViews: [ToolView] = [] - for type in DrawingToolState.Key.allCases { - toolViews.append(ToolView(type: type)) - } - self.toolViews = toolViews - self.maskImageView = UIImageView() self.maskImageView.image = generateGradientImage(size: CGSize(width: 1.0, height: 120.0), colors: [UIColor.white, UIColor.white, UIColor.white.withAlphaComponent(0.0)], locations: [0.0, 0.88, 1.0], direction: .vertical) super.init(frame: frame) self.mask = self.maskImageView - - toolViews.forEach { self.addSubview($0) } } required init?(coder: NSCoder) { @@ -304,6 +310,18 @@ final class ToolsComponent: Component { func update(component: ToolsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.component = component + if self.toolViews.isEmpty { + var toolViews: [ToolView] = [] + for type in DrawingToolState.Key.allCases { + if component.state.tools.contains(where: { $0.key == type }) { + let toolView = ToolView(type: type) + toolViews.append(toolView) + self.addSubview(toolView) + } + } + self.toolViews = toolViews + } + let wasFocused = self.isToolFocused self.isToolFocused = component.isFocused @@ -455,64 +473,6 @@ final class ToolsComponent: Component { } } - -final class BrushButtonContent: CombinedComponent { - let title: String - let image: UIImage - - init( - title: String, - image: UIImage - ) { - self.title = title - self.image = image - } - - static func ==(lhs: BrushButtonContent, rhs: BrushButtonContent) -> Bool { - if lhs.title != rhs.title { - return false - } - if lhs.image !== rhs.image { - return false - } - return true - } - - static var body: Body { - let title = Child(Text.self) - let image = Child(Image.self) - - return { context in - let component = context.component - - let title = title.update( - component: Text( - text: component.title, - font: Font.regular(17.0), - color: .white - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let image = image.update( - component: Image(image: component.image), - availableSize: CGSize(width: 24.0, height: 24.0), - transition: .immediate - ) - context.add(image - .position(CGPoint(x: context.availableSize.width - image.size.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - context.add(title - .position(CGPoint(x: context.availableSize.width - image.size.width - title.size.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - return context.availableSize - } - } -} - final class ZoomOutButtonContent: CombinedComponent { let title: String let image: UIImage diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponentsContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponentsContext.h index 06ab15f7d1..a7c7db0a09 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponentsContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponentsContext.h @@ -56,6 +56,7 @@ typedef enum { - (void)lockPortrait; - (void)unlockPortrait; +- (void)disableInteractiveKeyboardGesture; - (TGNavigationBarPallete *)navigationBarPallete; - (TGMenuSheetPallete *)menuSheetPallete; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index c4b51ab07a..58749d539b 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -103,6 +103,7 @@ @property (nonatomic, copy) void(^requestDismiss)(void); @property (nonatomic, copy) void(^requestApply)(void); @property (nonatomic, copy) UIImage *(^getCurrentImage)(void); +@property (nonatomic, copy) void(^updateVideoPlayback)(bool); - (TGPaintingData *)generateResultData; - (void)animateOut:(void(^)(void))completion; @@ -142,7 +143,7 @@ - (UIView *)solidRoundedButton:(NSString *)title action:(void(^)(void))action; -- (id)drawingAdapter:(CGSize)size originalSize:(CGSize)originalSize isAvatar:(bool)isAvatar; +- (id)drawingAdapter:(CGSize)size originalSize:(CGSize)originalSize isVideo:(bool)isVideo isAvatar:(bool)isAvatar; - (UIView *)drawingEntitiesViewWithSize:(CGSize)size; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m index eb8149c18b..d57defabf2 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -495,6 +495,8 @@ } else { [(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarImage:resultImage]; } + + commit(); }; controller.didFinishEditingVideo = ^(AVAsset *asset, id adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void)) { if (!hasChanges) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m index 4168cd47c5..53ebd775e0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m @@ -440,6 +440,8 @@ [videoItemView setScrubbingPanelApperanceLocked:false]; [videoItemView presentScrubbingPanelAfterReload:hasChanges]; } + + commit(); }; controller.didFinishRenderingFullSizeImage = ^(UIImage *image) diff --git a/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m b/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m index 10f8e9b155..206e935a2e 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m @@ -30,7 +30,7 @@ const CGFloat TGPhotoPaintTopPanelSize = 44.0f; const CGFloat TGPhotoPaintBottomPanelSize = 79.0f; const CGSize TGPhotoPaintingLightMaxSize = { 1280.0f, 1280.0f }; -const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; +const CGSize TGPhotoPaintingMaxSize = { 1920.0f, 1920.0f }; @interface TGPhotoDrawingController () { @@ -85,7 +85,7 @@ const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; _stickersContext = stickersContext; CGSize size = TGScaleToSize(photoEditor.originalSize, [TGPhotoDrawingController maximumPaintingSize]); - _drawingAdapter = [_stickersContext drawingAdapter:size originalSize:photoEditor.originalSize isAvatar:isAvatar]; + _drawingAdapter = [_stickersContext drawingAdapter:size originalSize:photoEditor.originalSize isVideo:photoEditor.forVideo isAvatar:isAvatar]; _interfaceController = (UIViewController *)_drawingAdapter.interfaceController; __weak TGPhotoDrawingController *weakSelf = self; @@ -108,6 +108,13 @@ const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; return [strongSelf.photoEditor currentResultImage]; }; + _interfaceController.updateVideoPlayback = ^(bool play) { + __strong TGPhotoDrawingController *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + strongSelf.controlVideoPlayback(play); + }; self.photoEditor = photoEditor; self.previewView = previewView; @@ -474,6 +481,7 @@ const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; - (void)transitionIn { [_context lockPortrait]; + [_context disableInteractiveKeyboardGesture]; // if (self.presentedForAvatarCreation) { // _drawingView.hidden = true; // } @@ -900,6 +908,7 @@ const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; - (CGPoint)entityCenterPoint { + //return [_scrollView convertPoint:TGPaintCenterOfRect(_scrollView.bounds) toView:_entitiesView]; return [_previewView convertPoint:TGPaintCenterOfRect(_previewView.bounds) toView:_entitiesView]; } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index cdd041cfd0..30b8773175 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -485,7 +485,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender public func entities(for time: CMTime, fps: Int, size: CGSize, completion: (([CIImage]?) -> Void)!) { let entities = self.entities let maxSide = max(size.width, size.height) - let paintingScale = maxSide / 2560.0 + let paintingScale = maxSide / 1920.0 self.queue.async { if entities.isEmpty { @@ -607,8 +607,8 @@ public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersCon let contentWrapperView: UIView! let interfaceController: TGPhotoDrawingInterfaceController! - init(context: AccountContext, size: CGSize, originalSize: CGSize, isAvatar: Bool) { - let interfaceController = DrawingScreen(context: context, size: size, originalSize: originalSize, isAvatar: isAvatar) + init(context: AccountContext, size: CGSize, originalSize: CGSize, isVideo: Bool, isAvatar: Bool) { + let interfaceController = DrawingScreen(context: context, size: size, originalSize: originalSize, isVideo: isVideo, isAvatar: isAvatar) self.interfaceController = interfaceController self.drawingView = interfaceController.drawingView self.drawingEntitiesView = interfaceController.entitiesView @@ -619,8 +619,8 @@ public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersCon } } - public func drawingAdapter(_ size: CGSize, originalSize: CGSize, isAvatar: Bool) -> TGPhotoDrawingAdapter! { - return LegacyDrawingAdapter(context: self.context, size: size, originalSize: originalSize, isAvatar: isAvatar) + public func drawingAdapter(_ size: CGSize, originalSize: CGSize, isVideo: Bool, isAvatar: Bool) -> TGPhotoDrawingAdapter! { + return LegacyDrawingAdapter(context: self.context, size: size, originalSize: originalSize, isVideo: isVideo, isAvatar: isAvatar) } public func solidRoundedButton(_ title: String!, action: (() -> Void)!) -> (UIView & TGPhotoSolidRoundedButtonView)! { diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 66406534dc..d40dec056e 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -121,6 +121,13 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext { controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) } } + + public func disableInteractiveKeyboardGesture() { + if let controller = self.controller as? LegacyController { + controller.view.disablesInteractiveModalDismiss = true + controller.view.disablesInteractiveKeyboardGestureRecognizer = true + } + } public func keyCommandController() -> TGKeyCommandController! { return nil diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 68fcd353ed..722045ef8b 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -342,9 +342,10 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account for photo in photos { let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) if result.isEmpty, let first = initialEntries.first { - var videoRepresentations: [VideoRepresentationWithReference] = photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }) - if videoRepresentations.isEmpty { - videoRepresentations = first.videoRepresentations + var videoRepresentations: [VideoRepresentationWithReference] = first.videoRepresentations + let isPersonal = first.representations.first?.representation.isPersonal == true + if videoRepresentations.isEmpty, !isPersonal { + videoRepresentations = photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }) } result.append(.image(photo.image.imageId, photo.image.reference, first.representations, videoRepresentations, peer, secondEntry != nil ? 0 : photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false)) } else { diff --git a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift index 2cd2bbdf12..bf42171425 100644 --- a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift +++ b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift @@ -76,6 +76,11 @@ public struct SegmentedControlItem: Equatable { } private class SegmentedControlItemNode: HighlightTrackingButtonNode { + override func didLoad() { + super.didLoad() + + self.view.isExclusiveTouch = true + } } public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDelegate { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index dc85de65c3..1e06aaf916 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -700,9 +700,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present } let exceptionsInfo: String if case .profilePhoto = kind { - if case .nobody = state.setting { + switch state.setting { + case .nobody: exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideAddInfo - } else { + case .contacts: + exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideBothInfo + case .everybody: exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideInfo } } else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift index b71521b026..2376b1d908 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift @@ -247,6 +247,14 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state return nil } } + } else if let customPeerPhotoMode, case .custom = customPeerPhotoMode { + transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in + if let cachedPeerData = cachedPeerData as? CachedUserData { + return cachedPeerData.withUpdatedPersonalPhoto(.known(image)) + } else { + return nil + } + } } } return (.complete(representations), photoResult.resource, videoResult?.resource) @@ -376,6 +384,14 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state return nil } } + } else if let customPeerPhotoMode, case .custom = customPeerPhotoMode { + transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in + if let cachedPeerData = cachedPeerData as? CachedUserData { + return cachedPeerData.withUpdatedPersonalPhoto(.known(nil)) + } else { + return nil + } + } } return .complete([]) } |> mapError { _ -> UploadPeerPhotoError in } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 365d4b0aef..a8c3001c82 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -889,8 +889,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { if let controllerInteraction = controllerInteraction { stickerPeekBehavior = EmojiContentPeekBehaviorImpl( context: self.context, - controllerInteraction: controllerInteraction, - chatPeerId: chatPeerId + interaction: EmojiContentPeekBehaviorImpl.Interaction(sendSticker: controllerInteraction.sendSticker, presentController: controllerInteraction.presentController, presentGlobalOverlayController: controllerInteraction.presentGlobalOverlayController, navigationController: controllerInteraction.navigationController), + chatPeerId: chatPeerId, + present: { c, a in + controllerInteraction.presentGlobalOverlayController(c, a) + } ) } self.stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( @@ -1974,21 +1977,37 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi } } -private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { +public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { + public class Interaction { + public let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool + public let presentController: (ViewController, Any?) -> Void + public let presentGlobalOverlayController: (ViewController, Any?) -> Void + public let navigationController: () -> NavigationController? + + public init(sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool, presentController: @escaping (ViewController, Any?) -> Void, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?) { + self.sendSticker = sendSticker + self.presentController = presentController + self.presentGlobalOverlayController = presentGlobalOverlayController + self.navigationController = navigationController + } + } + private let context: AccountContext - private let controllerInteraction: ChatControllerInteraction + private let interaction: Interaction? private let chatPeerId: EnginePeer.Id? + private let present: (ViewController, Any?) -> Void private var peekRecognizer: PeekControllerGestureRecognizer? private weak var peekController: PeekController? - init(context: AccountContext, controllerInteraction: ChatControllerInteraction, chatPeerId: EnginePeer.Id?) { + public init(context: AccountContext, interaction: Interaction?, chatPeerId: EnginePeer.Id?, present: @escaping (ViewController, Any?) -> Void) { self.context = context - self.controllerInteraction = controllerInteraction + self.interaction = interaction self.chatPeerId = chatPeerId + self.present = present } - func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, EmojiPagerContentComponent.View.ItemLayer, TelegramMediaFile)?) { + public func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, EmojiPagerContentComponent.View.ItemLayer, TelegramMediaFile)?) { if self.peekRecognizer == nil { let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self, weak view] point in guard let strongSelf = self else { @@ -2021,125 +2040,116 @@ private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { return nil } var menuItems: [ContextMenuItem] = [] - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Void = { fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in - guard let strongSelf = self else { - return - } - let _ = strongSelf.controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets) - } - let isLocked = file.isPremiumSticker && !hasPremium - if let chatPeerId = strongSelf.chatPeerId, !isLocked { - if chatPeerId != strongSelf.context.account.peerId && chatPeerId.namespace != Namespaces.Peer.SecretChat { - menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) + if let interaction = strongSelf.interaction { + let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Void = { fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in + let _ = interaction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets) + } + + if let chatPeerId = strongSelf.chatPeerId, !isLocked { + if chatPeerId != strongSelf.context.account.peerId && chatPeerId.namespace != Namespaces.Peer.SecretChat { + menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + if let strongSelf = self, let peekController = strongSelf.peekController { + if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { + sendSticker(.standalone(media: file), true, false, nil, false, animationNode.view, animationNode.bounds, nil) + } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { + sendSticker(.standalone(media: file), true, false, nil, false, imageNode.view, imageNode.bounds, nil) + } + } + f(.default) + }))) + } + + menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in if let strongSelf = self, let peekController = strongSelf.peekController { if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - sendSticker(.standalone(media: file), true, false, nil, false, animationNode.view, animationNode.bounds, nil) + let _ = sendSticker(.standalone(media: file), false, true, nil, false, animationNode.view, animationNode.bounds, nil) } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - sendSticker(.standalone(media: file), true, false, nil, false, imageNode.view, imageNode.bounds, nil) + let _ = sendSticker(.standalone(media: file), false, true, nil, false, imageNode.view, imageNode.bounds, nil) } } f(.default) }))) } - - menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - if let strongSelf = self, let peekController = strongSelf.peekController { - if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - let _ = sendSticker(.standalone(media: file), false, true, nil, false, animationNode.view, animationNode.bounds, nil) - } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - let _ = sendSticker(.standalone(media: file), false, true, nil, false, imageNode.view, imageNode.bounds, nil) - } - } - f(.default) - }))) - } - - menuItems.append( - .action(ContextMenuActionItem(text: isStarred ? presentationData.strings.Stickers_RemoveFromFavorites : presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.default) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let _ = (context.engine.stickers.toggleStickerSaved(file: file, saved: !isStarred) - |> deliverOnMainQueue).start(next: { result in + + menuItems.append( + .action(ContextMenuActionItem(text: isStarred ? presentationData.strings.Stickers_RemoveFromFavorites : presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = (context.engine.stickers.toggleStickerSaved(file: file, saved: !isStarred) + |> deliverOnMainQueue).start(next: { result in + switch result { + case .generic: + interaction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: !isStarred ? presentationData.strings.Conversation_StickerAddedToFavorites : presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), nil) + case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + let text: String + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { + text = presentationData.strings.Premium_MaxFavedStickersFinalText + } else { + text = presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string + } + interaction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: presentationData.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { action in + if case .info = action { + let controller = PremiumIntroScreen(context: context, source: .savedStickers) + interaction.navigationController()?.pushViewController(controller) + return true + } + return false + }), nil) + } + }) + })) + ) + menuItems.append( + .action(ContextMenuActionItem(text: presentationData.strings.StickerPack_ViewPack, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.default) + guard let strongSelf = self else { return } - switch result { - case .generic: - strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: !isStarred ? presentationData.strings.Conversation_StickerAddedToFavorites : presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), nil) - case let .limitExceeded(limit, premiumLimit): - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - let text: String - if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { - text = presentationData.strings.Premium_MaxFavedStickersFinalText - } else { - text = presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string - } - strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: presentationData.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil, customAction: nil), elevatedLayout: false, action: { action in - guard let strongSelf = self else { - return false - } - if case .info = action { - let controller = PremiumIntroScreen(context: context, source: .savedStickers) - strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) - return true - } - return false - }), nil) - } - }) - })) - ) - menuItems.append( - .action(ContextMenuActionItem(text: presentationData.strings.StickerPack_ViewPack, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.default) - - guard let strongSelf = self else { - return - } - loop: for attribute in file.attributes { - switch attribute { - case let .CustomEmoji(_, _, _, packReference), let .Sticker(_, packReference, _): - if let packReference = packReference { - let controller = strongSelf.context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: strongSelf.controllerInteraction.navigationController(), sendSticker: { file, sourceView, sourceRect in - sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil) - return true - }) - - strongSelf.controllerInteraction.navigationController()?.view.window?.endEditing(true) - strongSelf.controllerInteraction.presentController(controller, nil) + switch attribute { + case let .CustomEmoji(_, _, _, packReference), let .Sticker(_, packReference, _): + if let packReference = packReference { + let controller = strongSelf.context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: interaction.navigationController(), sendSticker: { file, sourceView, sourceRect in + sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil) + return true + }) + + interaction.navigationController()?.view.window?.endEditing(true) + interaction.presentController(controller, nil) + } + break loop + default: + break } - break loop - default: - break } - } - })) - ) + })) + ) + } guard let view = view else { return nil } return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked && !isStarred, menu: menuItems, openPremiumIntro: { - guard let strongSelf = self else { + guard let strongSelf = self, let interaction = strongSelf.interaction else { return } let controller = PremiumIntroScreen(context: context, source: .stickers) - strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) + interaction.navigationController()?.pushViewController(controller) })) } }, present: { [weak self] content, sourceView, sourceRect in @@ -2157,7 +2167,7 @@ private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { self?.simulateUpdateLayout(isVisible: !visible) }*/ strongSelf.peekController = controller - strongSelf.controllerInteraction.presentGlobalOverlayController(controller, nil) + strongSelf.present(controller, nil) return controller }, updateContent: { [weak self] content in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolArrowShadow.imageset/Arrow.png b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolArrowShadow.imageset/Arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..995d2735df4f5f32616c3f03f55d9d0a651579e8 GIT binary patch literal 26873 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolArrowShadow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolArrowShadow.imageset/Contents.json new file mode 100644 index 0000000000..39362cd425 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolArrowShadow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Arrow.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolBlurShadow.imageset/Blur.png b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolBlurShadow.imageset/Blur.png new file mode 100644 index 0000000000000000000000000000000000000000..952055729ba2c42042854e06be18da1ea5f95683 GIT binary patch literal 24274 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolBlurShadow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolBlurShadow.imageset/Contents.json new file mode 100644 index 0000000000..cf5d650c80 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolBlurShadow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Blur.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolEraserShadow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolEraserShadow.imageset/Contents.json new file mode 100644 index 0000000000..c699ad2b98 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolEraserShadow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Eraser.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolEraserShadow.imageset/Eraser.png b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolEraserShadow.imageset/Eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..30346ce6aa85a1f2be967ad8ebbf487887b2b56c GIT binary patch literal 25395 zcmV*@KrFwBP)M;)w!51)445CWKqCZ#S+YR00wgv(02WUG5-(x`)d}3K`I5!0n_g z%e(Ksdl)H_yf!^ng%~`qd(C4!XK>!a{fA08QMbu!Y&iP6JyN zEh;Zc zwM@y^e|IMa+CFzzQ-E<&UmXA1qb>Q(CZg}s{|NPHy?4(SGc$~MJuy5Yuwc#`I zT&L*z9Y$N2@htp3rks)CACj|#EC*4chO+||#6$st(ohL*{QQj`^V-pS7L-b6vf(yT zGFwrebH{_(>f0+}3Wp4Tg}U6V{Bo;u@YgNMb5Mn#!tixah})n%w}1S{e|)R&TdzoQ z#%i81IKQS$hw%T2%e95$XkUHvB^bRtH?&`5iK;e;{>o(;xEU-valp$y&K|BD@x9yl zIXPE0d@d-~RhuexSWeH)TC1MpzVyy%1%m&?LMxP_c~>> zRw>J#-_!afvHSm8n319vX=jXY`vxe}vJyk7@7cgpva#~$QWKYwZievJ_rulu$JmWxI-Ri?U2E$N&s(u&s5Bg)dpb*S&<09S6axz2N>Dexj(c7o zuZ`!zeIBfA%}l1t=U{6LdH!%TJh9@@jof|?$+BkyJ8FJ+PTpIhQ5@|VZd96c_J^Hj z01i?d#6~H&{pN4}=Ieg^x@^sBzovVS=c!B=l&sEd;d?y@vt<2Y#Q&=0OZcPLB}E5K2Q(_mumAe5zhR;z^7;*Key$$- zJ-TE0%^+wnm!hXGp_%BT>W6_58X=(%T{!^sX)OP z?u=luCmyJxL}ig#qYUn7!2S54zsCOIum0+<@^#t#b-gCO|0}=pE05Mb8#aA@Q7V4# zu%vd&9XDOR?PPr4QWR#G*R7px zy1}E;h%#W%A7Suy^YafyDIV#@YvNuvJ|ie+R7jo0bfy~4P!^O)Lk$)}1J)bdF`hK~ zVQ;5EYk8Vz<5)lI=zqFduA&#C%afh{xXmxy+8Lz|z7JS8&@%Xsas11_{L7DVEL%MH z3%=FkU;3qA;_FA;pHnX19)>N>PIU?7>}0J%)gM%}m8|#N8ZXVnzz$=i0p6D@lE*R| zD3J}8E0YMZNl=fjwJME;UnQ3!8z_y=Fkt|T63Bqp!T-fy{KY5o`UziiZ>_TE_9R|o zy<*j8o|&zhnajpY-_UM~;Q(3UG_KUS{MNhrX07A%!gBk9*B8aff`9mM%i6s7YvVqj zE0_P>-~HW^m1gnwjS^K(i52KZh}{$bg_{oB9U zzy9mLrmRHe^ZD<#Y$yj!}zB5^m%#Vsmm3p zK8xqyd+)u)l_uW1?1S<&`~1gQ@W;U)36hVOJmZuPQWibqmuFOmI6?;z9e}eM#o?v_ zIBYA*6K#iTYv2zKg~4F#Sz)f(@PB|kMKV)GA+Ut_-KYp=S(T$-UUi{k$ZMGD?v_2d|yfD(n! zMl%x$@}P6|L#)XBbu3}}xycf49$mpMhh-tLsW~ZYqrluAQfat6eH)bK8fCK6@>W9v zvb@E3Qcd29G7a5)|G|$Yg|9_M!1V~Z&}FOaLvoY zNVEP?ffPe-0_Y3M^m9?1sz5eC3-XI;V9-Lc(@slQHIng5g5vz;U;gF2S7iYAvSC)@ z%#FWeFbDK+em`KQbUEwbAgV;^=V1$_v|5GPEL{OYS`|o^uGd3u>Jg^pS7EUByun5s zDA4N)Cz0{ECm8Rmmaz0y(Tt*fBg1*0Nz&8*EAIv;AczTFK;(Y@wfzTc1{n{jA3>tLN1O3!}}0|HP{@ z7_aIEyAprvxQyArE3@F*f0ekJFqkQ1I_WGfS_6+w{hlB{3J+~}6B zSX*u(;%q0ctBW$vrR(KwFsjW1jRICvq+?vdupcgC>Hpz{HOtsa{drx!Lb`(Sp5ht6 z)qGX{_}`Qb$>xC7u5P^+LRt&h*9^l|%h+M%{J9SiXVRQg&r8n(=khzh^E=ON%1adh z=HU4PPn0EP6QYg}u|vL$mE{df7`*E|uF#2;uAF+77dWrln0(zV3Yaz7{_DT~s~p2^ z$M&Ni{YWvlpWBp|F2D9`zxEp3`m99BL1|QqqwE)qUsfib!+0GPT$Wm}StCP8D?U+h ztEn1P)u?jC!Bzu}pMU;2pq4S^QRMsI|NfkPHj>}_z2AGDXQ5j+q#;Ft0EvY$%~eAj zpfbJ!SFQ-gv|>yT708vR97gS<;vfk1*=L`Xdpou{dEl_%mnnJboC>s&z}xb}4j=tL z|MNdfc(2I!zV|)g!xcAeFN%?F45yV69ffLes{k=r{%*iMp5E7Xn+Fyv^g|#YvF1!O z_5W{pN0&U+l>ZvW+4_uo!LCdN+HS+&9%B%|XAw(XGxU<%jGma;3n)>ObsxCzk=3h# zsalI^{)JcNkNr*E%&}HXvRa5tE9~_e%yd+i)w(dR^-6xsbLDlmeGahPA=&;({vcTU zWzX8JDccK6pALsZwRPYno7+B&NW-SZnDd&fFh?!bT8l=ny79>ksB|ho(`ygm9tYHP zeeuN?2e@*$yHI3;; zk(xkaIyD~Qsbye+FPwe2$4Bs`Jl0SO|Lg^ft|?!yAZG~}4USQ?i9KETk+>_rcyKZ1 zLmL_5eW*aHJ^7~f_=68VI7l+?!059F5NrMnrM(}2{PCQDHk3Ss4^gT6^yyP#eiAEO zZ*Omt$AfhCq1lqSd?iR!wR3Hro+D_A2p=CixJmF02a2k3RaS zCQere-sC*S9si~ymsX&g5@hGPzej*gUfYFQ)26I zktr`xU_|Stp8BDnL!{)`l+!3y!cs{)DqfTZwi zEWnMcdJVLe%7fQg%k_(tbEJWrvYmk5&+d?+*ped@s{k8Bib3E!+rSrm) zH?vKrrnBWefv_f0)#V45@Hi}b1P~)|$?R*QN}KZk67|4fKqHXog`SIg+RS|l|7ACx zaRy49eXx8X`m7OXEL|XTYQDskGul9op)J?B8(43U*IH#wolDq0q>Po2cNW38VHI&xq$-WX zf9_5!FE)&$J{Oe%UCWpWBvYz7A`uZne>2M-!uj9kEsw<$5|QTPhsq;FP+8zq9Pufx zHfZTiaKgcq1veh&1ShhPuGigo-h;{pIXG=CNsFc%zoF2L$8nBGm!-j}gKKitX@W<= zM3ZyFK;mkkPn4$`Qziy9gNU+@77L#o&NgK?f;2`}p6VULVn%9{nd*s=iAA?f&e9Uhw3-=j_0RFDmb=P*`W!X%Xe(Ik2uV`+!b#eqht zjCg(-?8}C!?`J>z*}++`sEmNLE^b6ak!C7mLm6e`#SmP*X;DpsGAn zxQGj)Ky$gCIu?kZV^fi7Om8oBT-XhW=RK29ylJh@;QKP}T^4@*X;M_U(nO`{dtn1Q zJ82&BWDl{BZ}Z09X3|E2g`XXRs|5;R8`HfsGJ>=wb>V&8BdJ28iJ1D_jW@kH(Qh_q zpv@(FVaf48-I!qP+=ym{4ac{EGbfQt&#U%LYP+{oLMe6}^r&TtL^HbSm5-$sdq$%7 zvDsZqDQ(sg+iEtAeOcZd*~-#NW$Fk}#^Y)e!If7kUudf!5*OX_RC(}gm#jY%P`0tS z5v>@X4mg&_^C)Cf+?N@LD8zKah@CZ}V*;)G16Lk+AZ(sU)#zm-Zs||ikpLY!IuAr* z-W7TtE@5IscM#T#N!4_Q+Shts#M%oZG>n@@^!}DGj74Fw4^3Gr!2+myNb#c0dIvNw zjf`d>8c_OyC2%4#{GH&&1K}?~ZRTC3>_lKhd#zs6@R#B%O7b*Sqa-1Oa}iM1+(15@ zq$;B*7MC!^0-4~6CZKFLlHe6HM@jwyzR5h8UXQWXu}>$?gv&;BIXNH}hav4s)J$h^ z1BF1Hle@{P;28t;aC)5MX~kl>td@;w+{#7HQvI z6^Mm}c&%QTO+3)%a!!HVh{h^%r1D7dM5JkX-08zGck&J)hTYn+|6+D`Do zNNQx)hQFtL?Lr$!1?Gnr7A~-Z)CR(eBf^iIcf(|jQ-q9Oj5b-pa8+a|YUv z=vXX{peW)@E2ZLwvyfKfywD7!e*X$Tkj8%5j|yNAtoAeAgKv!##9*X zO9{I&v9Pq+Y{-{6T+@~_$IHs`A->L2ClaZ6_(UXbD0$FfPN1A8o!KDCvF=csCJy9N z4_BeHGf-DRKR_cEP`wo7o0B#+Qy!*GyJU1tc^Q({lBpCL(TMM%HeT@1MI0YCyvJ|` zNOYF(>RwcjkO}m~f8m7 zyRb)U19phL>Q+m@xs)ATI6Q+?kFzInO`zMEZLBr zfy9jkV}NnYF8m_Oqf!wR!n$HCC=H;p%gI!|p#@t<&ke{GH2r}ueglTGIRkAgIs@6A z{d08eR-JihL_>K4;CW~`2WU41a$2;%5vl@jaA`xGNi8**+X0_Epxs^@XT%=c6>E5h7(Ms*& z%h$qo7G}1rRVCMZhu6$`l;9EPEWJsnpGKP}x`fq83uNO{HWsuxAkDA&j~YLEDGDu_ zcovT4kPSVwM$)BL2Wd>>K_uehXtjf$$Cp4N6@94O3#)-dn{lAcg>fLZe9|8BKqNY* zy%^GpNTl5x?B8;2bN~3jBouc+o7B@Puf-1Pa$(rdNc8ps1Pf~y$OQD8(ng>zTJ1sSC=MiS#Wqr$?< z(uz$OT5D~*GeJy1*}ec}1=4D5(>X#~Ii|N38qO)eFkQpaYJKpqv3sDUp;uUa09^Sm zzWCyX%~n9OKW(6GMd2JyfXe(AZJ@>}Ql@$a6D6H-btJkwc4Q(INZ)HB(AILv%v{*U zLPVm8?Za$5Iya-=+mk0xBDq4OCZuhoX^kRXSx>r?q00u5fKwHM>g8cYW+Zxhapi$j z2gu8tivxriD6o?yO`ucKl`O4n7*>OdG^Ec2lnTNhhM5PbmdVn(9|6gxlS~ z*dqnwo{x{`SXyu6!=mz_W1)>fMKz*n@`WS{jb!R76aJK!BRP~(_8yI{RLvG<9|$qg z5P@-&y~G92+Ca&*j8Vva7>m?+np7q&+8TXSGaIE{@47xO3~kve6+Qp^jW646n>G0`OrMVem)lj&aXVV6!= z@5V2Ip%p zJxQ@=1RGTCSFR=lvhlkQp7LPbO-I@I@*o;b17fM}7#*9v(aM~G zb|#2K0~gKm=0Upy3&+Qm^Csu24B0ut2ejdC!9rU|cW56@TZv>(*-pH?8D!(7B`H}{ z$kHm8GBeLSC|;f&!#D$--dL7HxQ0+x1=H8_-tMV<22FPn2g+{zGT|KC3Tz-u4A{^= z-rN=n#f4`5EL6_1;h?Wb*Lh(hf1gPq#`%aJ@UzrnJjQb1IAIffxuAqgCG3hHl?&V zq~Y7VVGJ-9&KO5XM}c)X6sNSS@bSDCS{__%wz8%~h1N`zrz(&?pmyn@@_@t|I3On( zf08Zv*It!B@;80U%5Yy4Xh<|=(l28~I36aKsz{@bg%y#gZ@{(l%EiWj=2QqAG}9Pa ziH=$G=km1^$;r^?tjusTqZrbW)Ml-LhcU;p)Nw!1U=(?9*wssvtk)m5m%M8QPy!3Q6#q9{ansxD|Vu@N(C*K#_SCuo&<6k0T_LODV?&nU54 zgHCCd^6XML1Lf7?7?{c+(F0REw#%0#Pi-_VIJ-3mp6GfEyvPbCP}w^h=!9|XT*qGQI*sZlcVg z_OHC>N!7|7)x4S7;7|=5u^QE^5->))N9`Xd5c;J~FKnkGURbk%D4x((WaiZq!#R{V zyIoxG#n%VFvAXb8yi&7Ok-rL{7ZAJG07hyi&)VR;YA z!-@;z= z+-y2H+bIhld&oCY#STzrCn!hL>f6gdq5?gl0)Z9uRr!N{+qO6FJesKqmwY8g(4pTDdfWxGrMZxPuW&jx*l_ur0CI#W8q6|biyTZ zoSRgJkc%%)E>%CF2{tGZQyGY60%Oa@uG0(KzvPrtHHri2I5Q9R1e(r?PAs18>arKr z{xLx%nxhdNBYV*X^5V5gF22((kIfSwqgq^OV;kr`iwkMexNdY6streCbmOUcu*jj6 zpMU6HOy}r-B%eGnBD0 za*ad_DzT2AWa5(=(&-fTxl^F`-g^)GA+Cd>h82xy34`nBCaiV;0?)Y|NNG=S3pAd!Y+~Ocxa35FYVL@A&Gw68A54H>!RQ?2A4=O9%)`Qc4QOMx?f>7 z-q~7lYSad^DiGit5=boKlTCfeT6Ut$Ornm-A+5=4gvxpH=?M(7ocD(>v(4qc1$HX? zWst3vW9bDGW&GGPL&YJjjUA**5?8V~8`T*o2ND-=vTJTg&n9!PkSx-{IXgfsZ(fD9 zI;7QXfCJ(&<3Jk68MSvRf_fnuTfjwAgLzG;e4MgVLFXQLVQ~g>sgsJAb(I%R_lrZW zR%Oi9o~dh_T-@E1ea7K|0nX}QqI`Y=%JwgqeVC0W92s6UCI;60xiqE-%ihgb8jl^e zQAIl~K{SeQ1XRXEYN2+KZq09am*$zucm+}!K1N+C!<_EdT0j|zrVf%+txAN018be3 zG&GFfe*5iVbtyshfz4qxF*jwu0tk+nAe1_;!~=!IdWYjUxZ!Nv90$~tH}mFRk&##z z1}us2BhCo)&V^MOSi;yhEZ~8P3y(d$Fp?@zQp=}#SJsnG$<(qR*gxea_wU~?#s2f+i?+!t&hU`(8Qn3npSfp#e12ZEV_AmWIvo>>O( z$fSsj6q2fyrS%{NboIg@zLYgVMBztd$h#O~2ud*{KeO?>mYN(V9!LcfgeOzWYgXhy z)Uk5Uz4hmlWZ}aDn^{R}<44Iy7H&L_8UAJ@nsjJ3-TB=LI!LOg?8=k0-2p^qpc2N3t5cHhg4m)_UYMQO>MxZx-^dnwWGqo}6t39VKt7F?cn{-14(X^0=LAzQ&iIgN1MN`q z^7RPqzw_Y{k!Uobb5MQV7>RcLR}&Y)ER>U~N70eg1QMem?Ne7}EGMYU4QUM|>g+Y$ z_??R4fxNs~1Bo$UIO-pd$>AvNFj`JT*gvUt<%=Q-puW6@7ltl9)~<((JZ#E-h00UL z32gO|vf1A&lY-!ZG*(zws>;rh?%=FRIHQ}VQi))x1C0o+jdfwOtmoAdc+hz)k4TKi zstmBS=3T?os*=(PP`0>ey-zuy_W8|c>lWDK8kOyme#)n>E6HxZ*cwv>D!^g0zjkgoI)Q$8B>pZba zu9;4yVYF@PMd;W=h7M2ntGjPDv+=Klc&`jdjYiu5Oh0<9m}qgfPMK9sPcWq4J+*jN zpZ|dA&4Y**-amlm7fIE;j7>Lwr@}xYvj%{3n2lLlvG{ zywoMkT7sz(NSqvryI#VO>25I5PSK-qj=F?Jk|_&$Q45Mwwo4?N{-6g6s#gWF$xyau z1?p@dlSs6USz2o(I<*X~y%8N}AOgzlL^9PF(RjEUVGo_SXwE>dfC6Exq$=YLYg>Qc z_pv6B@I1}$K&w=F&dxxgXZ)OjcD@Nina)6OT$q8*c5UvIC@f(`iIikhlt`1Qbq1OW zwBHRgkk^Ja3bZx@nbd`a8K}k#8A!AsO^SjpJOz?;#)NoqwCDEloFmm!ee} z=ve64c)WE)v8aOuHU7hWhXG2dO{Nx&BA5)K03wF9p`l(IRs=Ogj!4vp%v{(jAYkSo zA`$tQOx+=yl>W7fvM8k^R}TX5q^;Y>&vs1gGdG_dU1Gamma$)=ZxEVD9o-y#j?YL`hIV=TRya$;+DuS{J;7m7IKr6KvKc4o0-{M5_>yIJ5Duj3oZ(c>+rqCWu%Y&+GAzPcDgNV0uB#uyV?hI8a@} z2B{2<191tPZv0L+y9*>XaUhr>Ar91KZNJMN)sHr1?^4DHKYm?BSgz$+hlm5J70j_xRd33f zB<8^iN1_|iG@xrX-ZQ(iuyE&_#DN}-@IdYy-IH8cHKJu}Ag#@^G}0<@=Z2weVdp64 z<_xr7kvP!8W-HRNt7I9FuF}3J6s}6?1c9zq8RdMrtN8Yz7P_2=7pB2POo1d!q51VUOekKkE5}~aQ?BuQSEWKK178Zcu$j)-uL#ZWBCWqwYJQ9m z9==A`9_I=w*L7|@jiNzmb1|CLu#z&^VgmtJf@`Mo>|gLB=kkNGgB>f~dQ2))%U&x} ztu=A(G|5ggNlICu%4}#tW<<0q~qtXuW?JdurKRR*{yggLuD-sQ7V#o ztmceT03BjlUN6M-CESM<4W*bVQWodailHLomHtSjv zX_$dDmBD>pH{;D?;&sZ-1j`P{rEu*1jwKg_TKqXeASh_YB?~ElNPR>|F*3>CG6<-~aW7w+=BqcpX81s`< zRGxl_47K`LHvtMRVNX% zGtuapVI3`DqD(v=c@jt4VJ>0&l<$1!I}fufUsD*`tq=jw^ za%NiAsi(KAwP8~BT57|DBj_Q#FfVa_yhjgYZp8~@P1qwZOCQnO-O8pN3zg>1OuaBx z8$PzshgR_WkdFAG6J4AEPD?B;&c(FO|d!RGxJOvbH*d>*#d2hTbf8cNGhA}YbMr6ZXA&4(QL^6}vN?EfiO=?+vyJRXu*35@B(-(V*3rA4d zqq-5C!^nlP<&)ik(C*umO$B4XEPOVg%Xpy>6b3YoVTw4qqGaKPi7tgbq~^ftiNP~R zcoChpqASniNWe&=e1gk1l&n0DpFMllRG{V==otHi$@}#>*v26%5s!zJsg%)LS7sJT zs+OA(SmxCk^#PM;VS7QD6;c>XV_=M6Kuf13NSz3j3v*Ot5v9Wem8YZ>v##|<$`eGg zO%|uI{Wml6vqo%t;g6V54vY~p@tA>>VTF^z*^jHJLBtsFrEs=LM$v%nMN zWwetkXyfh8I}Lh51KP*vp>qO74{VJiy6csCPKW`Cunr|0uQl>)zUy1@!u-ol1=_)8 zcl|L6v6OIGHZDB9FyD^?h^xvoT*9z_=bd+|fkj|hfVP%n7Nwyw1ABO4Ugl#@o;;a} z)g6d?VT&V1hwBlj?U)N=zj#OpDtF_#ti4Xo8Lj7WV?Z{%LgQ>c%%&H%3&}(H807Cc z)N+(wsfh&Rc>?H4d;Ftkld1Lo`oh<_u5&X*D1b}MfboOug&m$he_n{D09Hbc7}&6E zD$wQv1v0?^NxSdr+Dj&WRe_Q;nQQKE?N$EFg#i1q!~s+$nc2uxc5pzFk}%F0XeR;% z$~NiIgNO9ybewUZT}UZ8 z7xIfTW^~~LnZlyAU}5#S%7|_xmUAe?(N0RF@q{LjI0-lRA&ASNau_i>yNg7FU8S*a zRnD~f?#^9`M0Zwrw$|$dfr~~tNT7)=;(@E_h3!BXM09d-N|I_+EL}<4yQ@HP6!N38 z7K!GKJKks@)=0t#J{iGAC#f}>vNO>Vb!iP04E zCClLW5ccIgV528f$MymkEB0uPs?qO555%Ivxm8Y3FKL7l4rS=_}E~~QY#t*J(W*~_JE$+r^019tpvS0$~*g6|M zv+)I=u27C96Yu!;0^<NrcHto>k+Z!uz)>=?#ePJohk*loeW+~#0s6=jP-IHRp z2d+F77fq(JsX*Ir=0$}TjA-^rg%KTFecMWlr;!4!L0W72ULYOK`zoRJWmkMQWm9R! zjQL6=z>!Qv97wwm2rp)H54OL!_JL=YF!LFRHjujUCNq%D=5u?QfgbyqbWP^WZP{Q0 z)k>TcDs@K6GCUV&AnT*h(QVh``oJ{Z_)P^~7{Gnx1p=e4!dY$LeTC!bd96pHb&W$+ zhBw%#5+js$Lbo*!Ao@+1(Wh)Eip@eK8mw{z0V;L#K$dHhs_{cLj$A z+I$hxN^wYJvXxLa0cB2gm&gWTQ?`~e1`en%;>;Y&T7L}KzOa2*jg!t~;x8ssdEm8S z%~}Wp42@a`xv=VurRjxjDqdI!uj|NM7-B&WA3l683Y13V;$d&2*Q*MY+TN+vW!I{V z9Du4>c;SmK%9CdxfXSu;Z7j^jn<$XKRor?86HQ!tuZ^z?6lS3Ft4cb#^6G(cf6)fY z+an1iP8(cnf3yk33vM2{qU}WS^7>Su{cZHZ zB0H;iVMT!?65Zv(QX7r#lx6MQvU2_)Z1qDtkhcNn!zUhSLy;w{0_j=SLrgRq?bEwZ}dR$Z<3rYr#AXlD6zQ~lFh;vw0NY!n!0Aa-ho7v_1;<%Jl^(kT*s_huHv zf4tCa88DRbddrzTT1 z7v}C=bmOrxF<0Gqo4K&vZ04zqH8(!AIjCM(YQ6C5lBuc9g(;-9W@(WV6HGK+`PcnR zHO+Tzy78ND?#4g1w%P6*!HutgvJeMSH{R?8-T3&rTejfBfBEH?i}M2=5F+>9spa)7 zZQfqgjn6;sW2_6a6GKPY=cTMfsFhqSrS;nQj-A72p`^9&T407$h8Gr=7=qUo)wL4J zqlvpIyOBEZNTv#_Tp2sqKro_BCJOJ7nyxKrE;mbU)Ny13-D)&i0A-C03EOxx zw}BKB?b~PxV_8pE+HBX@KqDm@6c#qM@rpGj8cfhv zQXo|xD3cggsytd3#@?w#fqeFvGtdq;I|0fFYwf%+RiKd(eNK5|B)U09FbN`kGrJTA zqM6>Yr#^&@&8F;1%9y~>VJZVcf(*8KQifJBqPrO3`cq{2^5dp1Ozj_Afpge|wC-lp zCCmo&E`vDGur@xO;zEtw2IVnlsVC|9;z-15or|STtu7XQcHfh?JE(au^k}dtn@WyC z^RUi9H#awB7IK>3D0R%-m51`AwQA$5;T+QLpeT}aB-Ym!QW;=r#s1@uKmMsr*-Tat ziNiRMr!z*yhKh?m59ct4Fos^4iia)|DDum-th+`W2noD&C4z0rOOdl<;hhSb*~j;H z@QfFU1zCs#X*z@K9I%CmNHlTdNpUy%f%dUs9Na5zYU3sRQVR=hYy+L8cj-!-)0zI5 z&fHK$KxG=?wBzx(fMgRhkgUH`HWG}3!#V0WAq&q&f%qq^Ce_AeB$_%gvV$TXNMVc( z-942Dbog}nND-^eTan7}#%8lNejie8TE_z=X|z&gR3A^R3A8Sklc~w6@VNGYWWl-d z$_B*RhaY};paJcu!~;-n@WM2iYO^kEb5V{&b7R`yBC@pNfq*t0)P+%*hB>iI+4yC! zrx#XEkgYz{g_T|zc{!U*^&5+NVZJZU&VvUJj!1-K0wA(9k0q6>0?XW$*QIN{urQ68 z>f^Dx?BzB3$75PVmKSX=&1SQey$G7Euvf+zH@=y5(Cz@SKac+~4m8Znug*YJCfY$r zYY^*9NUN8F#9YY%ZoHTE%q47NIX455>KC?bzyq1JO0|U7r~B&*RXBfmwZ@%nd;}^p z)`ejPavKOU&@z{>?Zp+SCXl?gYHm~(7JyY{muD^z8JO&bzx!&kf#v5U3 z5S;x_`p7V`VX9q^1aZ%Rr1G4ec zh~9^+6c$1lSS-g}!XzJ7moQn4IwSh9^4th4DxbpihA6Z{If@tNuzpS?Ugaz`0d^JrdNj6#@l++-G(Px_SY$G z5|%QPsthO*t1|3FE>(M`B9ZD$ScP1{W@YN;8xq=HmGQ{OhW%}uIFQ8n&b52$mF2>@ zc+7N$eg669rLYh-(0~8;e;4gULKRfMd7Ui}&EI|Efi{-Bggx}b(egz=S%t6;QW@ut z&mt0GG#$cu)x^4YNb9KW<+On|7B*YSv(FJ&Es|_n8%Ru2nHmv^3S~WC))Su{PmqXO z0#8M}OeDq6kdYDv=GF-yaDeBlPS8TeQ6-gDZAyuFaG}}GxLnIEQHjoWo zr$HXCziu{QK3n=e98_4J>$)jqCpzO2`UP1#tm62P2rBU%d!&G)SDYrteO^Q6=loK_j0 zOrG|T57z6Tp|Mv+aN~79ZJ>=sK=m~nk0ne%^=1+qf~TFwx9O9?+2|rWhf7r+Z~4Oq z6k#?VmOEQMktzf>koTmTw$HxgYE*>dfr|1-zp!Nu4-}^wc-ZWfu>-*p zc5`#1^e$y^(e4R*2b7g_?#AQr7;dwg%INCCAheeVXVtdfj96-41+TN#R7R0mm9f2m zB@S>_;M6g$p3bOdhkMh=YsOBKjiRKs_4ipRN}<_UfpcKH2Tnb17P9f=NVJ)a-^GTq zW>m?*tl$!M0nV}3FKks=hUZhu86DTbF3Fm|6bPjj4P{qjVJs}n_UlpNK)Nr> zS01tIClruF!P4?a*s`ru6Ui!;^NIM6hrw--QK5sl8{P;+050|_45 zti=iyT03nx=l9}u>(5IkSZk$r5uRwSk=m(;ZoIa8nvm8V*+AC!3yW_2J=tQno)HGD zO1Fej%cymOzVXFEcL;!LV-0=F5;ncC9SHD1%Sx{cyYs}FOoR2~x*7)>_KnA(M4_uV zLs=0a8n5E54yq#Al+6Whyt2h%jDQCMt1i!xXk6eNiUq0@MH-OMuX!imITF3Br^|`ipcc$uH}@X}66+OS6I5Pn zBQh*s1l7xf%XI-dIhU{<2)r;I7v)GKN3=^h_L{H`m3pzTFr8Rh%X?VHeCDZ9XMRp8 z?)<#A{hlnXI}xTbFs__|{H-IZGS6&dvlQ#y|t3y5$>Xf2fouR+`>U!WkN99UoNU(HHH>AbMy4OY2~2}g8&2r)t6iO#zt`(!Z6X}wlY z*^#IR1_PSJ3U;SFb<9{7&xPn3Kw0AKgXIg+XN^E(=>o|nU$bM6Q|8y3qff>pnA)8* zsSJpM>NzRES&}Z@H8YV`x;95Ij8=Bp<7VQ@u_B{^bGRE=Z;;noq%`z?Q?{2fR^Du- zp2WCe6>(FfDosS8UJK)>*QdMAg~hY76B@DXM~Flv*>uRjV;~YIQ~mZ^9*ZX!3nx~1 z=q&Xmm4WfY#xeE%kX%7@qE(i7A_<_L-Rcxj=Ai4dU7Mv_&xCVqEOg^>{PgM5vNXVu z&P!QMuCgk_KN8(}o*M=dS4(e1c}z%#l2Y73WF0LQzAMjMx^^VMX*r~|ddxzyX(1q7 z$C?4b7qNj9M3&-*yricb)Ah@#3_iGvL{D;s?L`_NS38G&!YG8bzV+5yoFEDg9y;JP zVkxb(a37^Isxr_MqgC#~sKt|R%?;TrgVzBYG+RFHLb{NO^I4W<4RIj6hb4?Nkz;|% zSYx01{wY;Pfy71qeR1iyG^z5GJ*Gox;!&={L-YE3v?-ekvH|g&_sS^8lFEQ+61|SG zw7=-$K%-PfJiiR~Wy93xO!YWlt1{rq&z6__kWn^1ZWu@J+}zAof=cWRYRU)?RFx;4 z;fsbqbGe?9@xed0cpz~a)7wiOmq2`xJd;qoX@@9R5;2oBmWlmz`DrJAU&}hQ0S6nc zD6F-K1I=bD``HL-WgN&((BrOSq4g}SwYtVuP>CZ*Yf=~9*FBO?H=4-Wc+t`#-ZR5& z&On5Js?Og(wj<`A_Afa`nsR@Ki}*<^BwG?eSRM#^Cb6LS?juf$*isl3Y*}W zLhPUu#71%(v=0@NcUELDgerl{DdN^ta^ zgJplW##(`fv1v?F zWgX~GJS^r*9O0f;@Deb6Ie^}qf6nGj#)`Zx!Y$q7iV@;ZKnzFkb8Xdj`5LY>c<{!b zY9{Y6Vb#N=_oJ}r2O-`6gRmr+)k z9g&AX9XH37iK?8zqLj>`YJ7$|w37O-bpA!xO;3?o{dJ>u3a8#yJhQV%E;?1XaPr_T zVfEFUWu2rQNhpKDWCF;@46RhL`jZ6Q(O7qkZ_`4nbH9(-%`rYXsewmlS1pPSNo${h z{ZX0pZ5cVsvCJ_XGdg}!~I@5Qtki&W4p_OUImOvq~3>;ZoJrL}`K0`VWkT?5IZn7J- z%d3=fzH#Wd_Lw1LE0+;sf*^Ur@E$$fZhTD)T?xmPHdKWAI&KDIQ00EGqHT~*&+KFf zD45#p{--A&ACv)K>!rtI$Z+q!eEe;;-X#-1+W7p>#8W>Xg|;c-OQ*NUXAm*_%I#8( zvZKIfN#Dp`0l#HQHrTvW=0cGdmp+bpU#4};jkseUfwcLi) zDWJPG#GGM#Pf7oeTVFf43>$W|4eq!dFviOV3S|qs{iO|G)M4o@I zA_T^78p!FDiLs57Ee{u$%>=$!y@*q;ID)a_@y!J-Ez*36hNLaI=2k8{P$FU-xX;#+ z^>K~DGY>-mF9T5=CsdoDyV%k5kIWq6XDYMl-SLQymZOor|K`>koBxnuy=Tnq@9lH{ z7i2v!H|OX93;(-9$y_?&oLgG@zDenQ5o4A0^^MK$Y!7E0p@*grz|oOIE;#nSxd7&T z|EIvZuydjBMX3sPyB9JzW#{LQjloRAI!G0rFVxrZnIV8Kef8XF)Drz=2AEwDfqVwkeGhBwOur<s?xK@Q$%hl^}L^=yJR*&$k?ve0rbD{ok9$H9rhq3fAVV zi@^9Rcd39Y;y=viF|eumN2c0VmHa9;nY%}VgS=o#Y&hF3`OkzhamI(uv4f#dV7y+<-E_WQ|5gne^rm|qNiTiv2I!aG z3)yzkuf5F}x9XzdB|NL?fq69%)fz=Bo}h%f=&=^4Y5O7pbrr}W0WZ2tJiU6ran>UM ziKk&z8=%1U#_whw5QI3SG4(OW z%$?k<)a zkiG*a6=zsqg7fu~0!A#c`5Ooz_+0YIY_(>$M|p;D$GV5^O52G!UQ$kyybl!x$7k%o zvx^cdJJUBXrxFpNaswiZJQpUo346iT^u9a#_vqh27NAmAXqBio z4N`2~;}ba@npt(=R*$>}0skmqBc1b?u-P`G!zs-`%ZOQ)o-Ywe<_9>B4jMrzllH4`SiZ80f{IX%{Yi}bjyiBmC$Z-56MGQ zM}r_LS$Qx(ht91RP0(#}OE6n7hZypSwiF_TGphuMzVqreqy0+`pBu%7xpWqAx9jzl z>tb}#e=>dUTv?(f9?jJpbo1|#IrriclS2U>MKHf{T6kcwQierF)OL_9AY^MHS-zMw zYWIGuC-8d7&`!T}>g~zYCrZqE-lfVr{Blhb*UR3gj@s=qDz_nmx;F6z!zR~G zlMdn59h1gJ-ac@V;zhoqr!h1ndPhOmS^W&t^wrB~7=scdf0y}@j=i|z=H_Eh5T}-C z&e3thlZ;pu{mj=9|h;f;{7IsHz3N{hG|YYW`eVfyUC=w9_4(|1 zRQtZy8x$B@J|B6c#r1D>5b~bj6;3~tu|gXz?^DP=&BPs_fJLoYHgyNIm%*RT0Vdcb zOK@jvYs;`?2dNymyuJCNhS$yKodDvNU+>jq)>@$M;ztaXn={J=wd*G~`r0=)lXMSt zDQ}=g31!!*y^ z`qxanq?Z8Um~`!nW>--(Kict0EXVqxJL&CoPGGh>?FfgH8T>I*)GQpSR>-v%YGK_(cF=bNau<9{Q+tW4Ze zukDK^LS4NxLWkbZInY%xeg?z!2(*bJ#T1({EM^bwn4gY6SnH>Jo=}y9LZkarIr`g+ zWPlIk5H~s5!k&iR|C3>?(W266$vUyHq>46`S>mg>c+`48ExdOTNwmW*yD#f>En^^) z`=;a$wTYDYbRUueDd>*# z6rY1%xK*0Sr(r{E>#=BAYm>@A}_}8Y3&1;d^`EOeHSCH)K~XVCm8-Lg(64(3eOEzn{h*BGuE7-vKog$ z+gy%}(R7m%>2CF%m^WUWMJO(G1R)@5q%10!C`xHbI^6Pp_hf3}p5iGOd!VIF0=J>J*K3rCE2d23UfSedrV#Hj$e}R1Yh(((ee0S2n?AN{@#EDiacjS! z+LX9Opz~6g6pyTbkceQoq(q%g!OY3NeFvsc?AM#2?dTlo2TR|q_hPCY)>Z|!fNkmT z28ccs`MW~s>T{Bn>L|-d@eL?Ae_Y;medn;FW9U)X(7! zr7BVAu;b_IEnkpJ-vvQsk()InS#dGTY;&Q>8(X6--0ncu^+V;A6^2$!4xw~E>=ew6 zdKcfMVQBz6sDKi~g?aDqCqe$zgPYp2OFx6Pk3ilqI@;U@vB_4Z^Zun*N+{#rq6KXl zDH+1Mt3ycP2JuR_;_Rxnbj2OUuiDdg<=}o?d$gM)S9p$KWisT|LCH5Feu1CO=GNrp zxM{bweQ1|X;;W<18Bf2;Sk1#Y0sDw2wp<@eiVrm;fe&cy+3p9^;#9Sv~57Vx zh0E^X`9iI?VCT zshW+w1AsK<&ZTvIj9a>!MZlcfSjWRMfJt9J)%h=IJkvKaBEG+9hwd9H__ zOx6|XL|)88rP+VIOCz^!i6&-io+OZv?PfCdQ6o<;w%mKy4>svK*U58Lyf9yI=; z^#}~){j*RrCvYsDcV^&a_NlV4?mCO=&15o6yg|Dm@RQ9Q9!Tu?;!5zzumdHpZ(!MJBy^jc44a z&PW+`J)q(`%~1!(uhnL^{gpG*cQioRdnhJ#o}m`5V~`{X(Cr<_@Nu>#Mn3=4zqS|N zeO4`qZtoMfh#qn5suE70cpGk55$pEBn&-=EN3_99VwF-#x8u4r>0(&4@E*&F+i7oE zkhVIw7OTj?iV9rmeU1|;{S_907h3oYL>+#lHEK}+WBX?(j;OWR#6Xh2 z8rK7zWc~9|DN?TyqHV}Va<4)10-{TN^|>g=y@%#l{HlS4tqR}dk60_{SkD$7TW#rc ztwGHO>SF^9`arYT#H5T<16P{YB&$}n80A6|y_}mkIx57i0ZgBBC{mi+`Ijbe43&`B zl5MC=mPZ@ets%`s&)pSgXv+LJ2K%H8goDsy9@V-$wHkMZTC6_RM0VBBP?%S*T-{Ma zWyd4F6}TitJgA=RL$wrHh%WT$fF`|Ird^*U%{nwx>?0wDeW|<%$hqeyz5~0(2Cd!d z-W&`6pDwaRP<~bAE|6-cLq8zp*VfdBFgpK2&3z9c_s1O|GhP&l~E<(uTc8clh^yx-qEG0A%A1MP?p3v5{#*SYBJwX zc0vo}KKt8hkY-1_cBj?r2#aG+L|l6vUv-?cFD}8X)B-V$8R;KpBsmuX-eO0n$E9$^ zk0*~@JSX9d4_#maU44(`coaOJLd zPS_LTm7%geFtgh)UAkqtefa#J4OZw^vRG&Wl8sHVFru`B){f@+>ph=*x@}lkD*a9V zQrQs;=ZB&4*%-<%X*M(;c(Thz76#Gzj@CSYtsq#Hga+^h6?cgjX(`!5(9D;`zznx$cQ zvl`Cmdxi~bV%e7M@=s&jwF8HKP;M;;e_Cx!8FrlSr}133m%*HBFF$aOf_){+@tU$* zg5D~t18>yX@Howw&5)``{Nuolz#N!Mmd0wXzDq<_C6WmgHw~UCT&iOC487rZ+c!*} zC9w^{bY=&ot!poOIgoH_)cfXfC0Z&VC4EPan2cZ9BaWXh8X-VzvMo;nE=Gf3vj7cxQA2rbINXDo zy4-#g+0S5>|4K`4z;iI^Ah9;8XMYb6JzBEsElk} zWAj&IUO7{->3%M`q@x>tOeUQ~J{o4s_cxDKiE^siWEtg~l9d0>3{25V*$p?UJQRF> z+K5xj@yg5zU5@{AY`aU|_q-&R?)gDyh4HSG4v)@8YWwB~Pl+WXIB3v@qbTrNqW6!4Egc)1#pY0qI3 z=s;`)F0Lha&n54d6<2aO^2m~k-~%YGtJb|q3unMlDPKnHV(s?E3zDMMRoe=?gN9-SEqk&Laf`1!{miNr#Xr^YpSZ2Wy-&~kDQ0~`sP+Pq z2olP5fUx}nf6VfVG<q`dBvo_?T_ zGwn`PNr-5H_mXB})Cae4VfCrWRMvgEH+{m~q-=RQXYyRGe)tNbS8Cu*)Oc@JrK=aI zJ^Xx1$^(cvYFgm0?3B-n6wFL1OB!kG6hst7vusxRGWx-M$0 zq*vZSpXuAc%7QwxA1B3qPicc^q2a^SmBsmF&BT{;nmlS7~ zE~W1X24soX)`#d)AA8GES#imYey(ZXhiFEB)4rSh*VJ&&d;FyA}C-l&(k0@w%mQ|HdAyP^rI;u;a=m+L{aR;W@Ff(9HY=$Rs6f-@v!W14I-HY$VkG)OrE!aop6k3o;%D^ z?z`tMpu55N7_imjfRLA4NBW&RN^ro}Cpk0t22?8w*Yoys4!ar2Q)}JBQp4r3U?Pjx zXFQQTSTdISD77)fCta`okoGSg@=S8eB&X*{XdeiEUDWU#eG24^92Xzn0XIA)Py4NV z!o}mCW&P-r)!a_G5MvyA=FhJo?uQ<|Z9l!=dCRt; z;Y%a@Md0sG1zt4AYAE&Xz;+sl&?A7*57rJFLqd%Oh9BKSd&3q|f_>G&8W3&=VOD$q z&mOxE^5o|3CP9!kn0Aiw*(94ySBIKF<@e!B%hwH_rUrs~5>nEA-uuu=z5KvYKFT0B z{Q)JQ?YP2V4Xu*Mp5>)0@`%Df83rz0Uj{M<%Z1`;CtC+uz0#{xRuY|W3eSpK{b`*g zI24-7EJQq_nZ~t8j@uyVTFk}oEvdE*_r3hVkhxgz$hGBhH!bNm(YHq5L7$#9fM&4F zudYg(P+F~db$6PxpeiX@FMn47w24M#jv?o~oVYFsJ^7FK`Qp}3aqUajPnjY-=1mZt z?VS^~-fxefPk_3+Rwn_`Q1G68g>srFJp&lehs_0Y9z8bM<{Fca(2KaZoZKxSl&drT zIt>k07So0%m#FdLIRJSd#gkkAs&VT?AH@qvD!epwN8~JF=mRhmHMR4(_nb1Ii%?Qk zj#v$to%8(7KE-<=sA5ppNGvQ*P$jaaJN0-A!YB8_h3zADcyDm{fD7qJGkpo-;=KCJ zL>4S@CvVq)UU`I|C2W!noz!7UO0pM`?T{=qQt}$H7p6$@gs&loP!$~SqnN*B<7YtQ zvY0I;l+VS=C=#J{`Y$f-Z>ztb9fM~?px$*qM^!0V<$Qi~JG!Kun~)ONCSakvW-cns zO5q!(Cgr*FKcILt;D@mKj!DG1SZ%)I+*?P~#1@&JUO?V?MPfc{Lq5@CG_Y?FRlGNB z*41Vaj#Z;}KDtlzUXI$;V|I&xUI)s@^Y$n*ddGw0!aZ*P+P4_i$eVsI2kN3uI?S|p2wC#MZ7m_XVku! zivQ!TmC-n5QgbL+K-c6X&9?esGS8d&G|8-H3@M+~as?gI+ZKPSwx6L9cniU2W}f0S3eC zgR`RhqHpi>ZB}bAl(+l}?1%?P>;OTrQ2W$BWMB?2K+SamK!-K z#Z#&WfyyVKeBA>7VaDkgv2T-n_3r^I3BzXDeIUcx0GLF0N&SnAU&6P0#5PZ$)SubT zUZH*(>8BV*D7RgSz?qKNs;+v;;#3PG;U1&OT%Nw`gz{O2F4_EoMW;M%NB@PU~WCGiNMe zL9#AvWT*nAdREsDWjDkzQHd;i61!W$n{OlvT7qEwLpCobeJHY25NRQ7<6^k=(Pf&) zrl*KHh8Mc)oW_L*yigyKZXB6hfpB3-Bq9MW`3i7d2=r=VDJmn!r?ng`;^@we=}AK% zENd^kae8jZhIcZ2H&em8lphyPnZ+`@7>NPpoWEd6RA61<6Un2};!AigLsEl`_C>)_ zmlfU37IL!`Fl|@p=@jQ{4R{>iC46;{-?K>H98N8KEC1>~j(Q9OL{Ai&<;p`R+8Ec6 zy^Cj|j~P^gp;?cZZW~?gRAX&$+;*?Be(^-_>B`TIfG&Rl9HMB+h2(v+I3_SKa7dc; zhpfAs_eP3&)5w3O-@aF!w4!AxdXSBYv!OvdHvBzXPrdJFjmYHCU6a4eevVa+9BT$z z2&(EwWLp>R5-yFCCJfS=S^l_wewD$O^ZxYd^=1nN688Wy+K( zQ>ILrGG)q?DO09QnKEU{lqplDOnFUZ;l7lz=pHB6V~hPF*PfK@Y|6{ZA_`E?aSNs3 zxkhmP`|rQMTUNpI7rVpj@qFjKhm@1l`T?Y;6h$?m^SoX1boaXSnjtl5TA&w`&GKUx zH?~opp4Gt9&tp~A>x#a{22~u&RG`ac=^ZBp*!VBcvNTjCU8;JYvo3QB_p&5TITewc zTzwl0|EA*PX`7iCzIGGIG1 zQ4&=m6J?8Ys*FqCI*L1GP|49$*V6pipZyt>V~KrFIjRC-&+GJhOBSf3T-5@l6u?xV zL1d#Oy_}7;O4C!4v$*+wjO*gsy8X$Y{K>gK?rggw+eV?~Z?`G0PA*j(`YC}Spvm1rN z=Zi(E{!dB)Ot}h?BwR0_bJR(;L!{OCE3!4##6nqALE@ZlZ*zw#@;@ zx1ay{pMPi+a8R$oV>pUW%`{To@P-ul^}{n@vE z=4XE9?e+L=T=zGB^EY>1MsakrHd<@^AzgAih=5)w|Jp0j#=4Z${g5M)Tz-hHTAP#r zY@#ZYFx*=I=1o-`Y*d^V@pGP2f%v(NFGR&pMcJmzQTmC~*H5xDDA84Kxl*N3JkF-T zDb`2xIlM_!pa-(KLu{fr>juSn1O-}e<{MX~I2{GLFMnH($nK;_QX{dsgoPaSb#jDd z$-8)!HoU zRN@DW1jSd&%h*|t#$G#EM>4gWWZ<#T+ypP9;kgz)t=eiu^=h;Gu z-=t5*qO6G@)QQB-5+vnDeV|t@U8_?L8P-i1!dfTt4izXT1MSFiW#TBu-jxPR)^$3_ z&uM{Nfh?3K)yVEj03uK3AO#PsyHbzU-E2@^)p{!9-i^^pRq@{=>j&jcJFf?b`A&2L#I5+7azla5j zzjc!jO4;;HLb1F~?LH-Cl}lr;_0TY!7@e>!b64fH}Sjs-@?WXY1vmn__zSLGnTz!LUWvOxC}#Dhw7 zE(z2P$jol@5>^th-E0HF$iViE@>sGu8ETOWpI@g^ep8$HWk>y>R2MsjP`tGYv~ieA z+boyEb?uw#q67&k?S$vGQa-xk6j{P7e6#q!G@*O~;5Q|W-2QC0*aCGM`#N~v+OgRQ z(;MYkj~|2us=f{sY9t5wMHDFh4^@HE5{AA1sA#3t|0%Z_-dZka!N@R-+>@1;Ef3Xm zMj47UqJ#V*3MAn?e;ADg>U6R;ndowZ4)l!{Q^EgNgg6p=6#s%>u&g&e|7!f3SiiXD zd=x_cZ{O{9xMo2C^gG}A&KLjjAOG?B2OoU!)J{}ZAIR1MZdrdA{(nC^JFD8T7KDBO z&;R_-*zr3lC@z=Kn$p5wZkN!+c5uA)SEL9+Yayo%Vo>uuh^$V<&$(=i0|H~Y{3{Lket|MD;Qb_->7qOo&W?S~(J zXzPy!kITRP+rJ?abw+$fVt}>&!$16kpNp3lfwK-jW8+0+gDxxrjPEu6ne=Ui>23<7 zr(jK9WbT9bzLDnyG_tjTl zxy5|)txuH_VO+;a(q&~0I7}%`YSmReEGWb5>|17KZ(O=fBO_Y^qWRUT_K-grs zDo<^wywq!cfzKz3+yY%XWTG1pOpxb+;0IBV11h(z8>95wcXHcKrPNjIk91ko<;z|Z3uFg4$A&A&T7re9 ztp{JUSfXo}8_7f`E2N(Z%d^9;Y27dDzJ~jeg{FnfA?p9yEtJ&)z4`ISA2Y-3nP^y{ zwfndsYPhDyH+qb3opQv-{2pZK4LuagDRt_(Zg7v?@tCZH&7 zm+!U9jbNhH2a@^fj)UfT^?|5(c_CBJMu5TPW|m^UhnJeDXHpZF(-c0!-dzS)wMh#PPx&rsNAXFA4u=2mgn@08o0bf$+QX zUtILOp%1Rs1@+oMCHgr=V(-28o?H5xa)oXR(&|bB<-zvd?|#>prAQ)H1zV#pvt)Lm zIN0DDJbwK6R{KBv|Aim40M?5Ax|I9gB$UTvDi7pe{^ei%nBT4Vo!2Tic0P5E&4@6uFEvnYRRweTPB)<$u)WH0U!zLm8t(P?d^JQ%Pq2M8xFct?mGR}oj8I7&h@wn&^dw=dJ%Vs1 z5M`94TcG%Lz5}q;=+@*#H*i(s|7I6fwp&!~FxTrOLxs2YsFP1lHr4kI2Iwfmypn?3 zg#tZ}4PWv%@P%Vc|JJv@^^;YBzFf1eC+{HQ|L_~X@f%;_ z|FkY!Uow=Z;5&$1m_=}r|Ie-H!Rt9=#$TVV)qhXVsG?8L#13!rK`8JA!IN5QXI5S z*{NL^*br%*!i~PpjR{q zWQV?{-h{#02X<7oF# z^}T*#Q}BJ>$1=7i6-h2y@)nhOJh!L2kh?U>HB{)Y!=yFJLl`UPU;u^q>8GEv3pJZo zw^s3G;TQCOHU&2w5!fEz%lY~F9<4s>kB$gM^>1pY##&s3vZ)_h$^|NPTt5gJUbbDR z3@y(j%*;6;ovvKFFh*WhZgLamG7sfD6MUK>bSAJ4_RW_Q@Pm#y&RV{xe2BKCa?ud} zR@1n==w|H*sE65+Rv$=yq}@@NW#IZ)UXqs1%=fcQq?vO-I+puXZtx8#P&0r1;Y6d&2gebKRVO-HolR(8 zvSY1{oLAoNb7h%mc=tMqFp2-s=wZUsBnS}YaryAW4-4=wVB&ZkSU8?nqRnUFbjIjZ z^&cG={c#PH=(Ep0TbSiXBt{HY{HT@S;>zSoOBp3ZPmzTK{~?6$IL3d}lD`BH+4?K1 ze^0cwOtHgv5iL$6olvPt1Pg?65grSOuU=mU2bpc0XpO#fltID(HAPel4D^Wp$gA>! zNdL=0blH)%Bh+_x&E%U`?U8RX(D)UgZyQa7AOm0$F}f?=;$V8Gt^@AGnhT)`aXQDP?dEAc2J-$XOQ*h$=~TD5;ZS3tSp_aM=0+9*)-08 zYx-+J=lc>0RQts8IB-j(Q<;FgV}XX2MtOQTAUpJe} zFy0Oo<7OGNR7j0q+f<;WKo<3|)Tvs3ka-ZDq)|=_m1xI{sprS?K*Gi`eW0xZZRW|# z5EddDbot7Y44V!O8(5Bp@YX_E70OYuKw+L~fu;h*#_uDME$9+fBQPcsr8n$a!}u!; z>q>uZ?U5frw9s0}#!f~qr0!QAX!wZXriA3`q`lL5^2%C90eLBTqgBd9n#D$zY<(P& zXhG{jAX$fzW8*~Iv_N&U)(7fZAd^u3vP9xlE6{cEM3<7l^+AN>bHV=SfBwhMxi7wmoyOlH#p zH5Mq(QYBZA^o1$3wP|rK+Y-~2$yKJd^B-2VoV0c*re*;!3Kj>)Ae6Kz8v*5=T4eY@ zfd{IB;>9THb&OFf80$FF0A*_&u@*3eb zlMKtL(A_0dnK9A18$-kRTuhw`loUwuK-K})o7BPZJ*P34bfV3!Sb?t8iM|BV>XO)1 z{W!3_&3QmL%WWGT`}L%L8Ax@kK4V?>A)K87G$PytOaNGwXVKS|Lr^pB5|&J54dbPp zp~ci%$_OXA8SOo5VXXzS2;;RHkL639XlpakjT2o?rIb+)Mb%vl$ZZ@6ho&xnOP)#Iu+MCq^sRG^D z5SEvfO&%y&p<1tDF;#f~GN5cK&{lz(TG+b1AwR?on9M|h?3D08cBB&Di^3EWJ&URR zEz$lyjOD944xAP!_Pwr*E0wEkEcAWZ5*gL*0GS6-BLSr2SxiM&D)tj}uE*S(o?n(MKNvz2F={ z7HvIIPgLZ?o`YAu`OR;Z&N%RKYhhx6dfd|nJqeFG8vVNC)f!AEw5_YB4_&leR z^E_-A5)o==EKjPKJ$5D(3L@L%>dl!vZTfeQaX?!)^pGNsUh zq1BH`FfO7I+AM}8L5&NLRtTqBhmi5Mnc_@>(|XY|cwj3E)Xhd~Z+z~`2>(Z`@zaTp z&HAm)O<$O$$vqiVU#_Vm<4QsGVJ7{LOxjPsh{v!#VA-nwA&^T2pzK&0;B}TI2)CAC z=OC!epCqsz*8Q!w-tzAd;d_3QSR%&$UKuhL!31S;1#NAX5FYypY3*B}qkuB4#-|aO z)%tYC9tGuX{l%Zqx#`i5ym)=FE^_XO$X@Pp(8Fqzo)lPH`6^~45X2Oe(UkOLh zU$lNf({a8;@Ex;zmjZe_SaM5>!PJS?!mcBXze-GWp%RrtVKpmK{U4wJ7>$MXTtpaC ziTZ*woW%_ei5c(HvI16 zk3Yt8_^D07jel$XPRjb7U}8_<*e8@Suz&F2!CpuHp%Gtp)TcCv8T^0a1kqU)H;fG2n+ zI>LB3jgLolYF~j9{n!qnyuw;HJc&B;$!s$xV|WeH)}9TZUgxB0OOKFC)iQ=n1==W3 z{J4~%qbg7yf0^?@W`p;}7a6vaq?%L`j6>i&V+jLof8>8MgT$1O$B!TTKAh-UP=%-) zfM_fxL27TjFJtTdxfZwJN_`;yjZmT(bLlV+IL2|AGpe%&7It|KBsGn&XH%(qEH$ONBTOJvac&SJkQSaF=jG$ zr9KRpLiK;v?VZtmlbiH`(CTf%)M_#UJh%7Az!er%t3w?Y=*qxlS89Rm&=a=N{6Hes zzj}3^H3vlTr3`ESqeqWy%7x(jfYnILd;Q)tn@=kgOBknR*RVdWD6kLCnR)GfEQAP;q%aLx>JSWWh%NN6UpVA{R zj{0St$_>8mEsD!I7evBwwuU#*4ogau!g{@LUrWB$5|+AAU6}pUFfzZ42dL-3fkq81 zLR6P^`C7j7o$q{ZQ(XSw5B>n(MJ@rc5drBCzX2>zo6sr8eJD~?H9Jx-dliy-XBr3x zk%*c=v;(0+WhLXNZwm_qYji5s;ik5*(2u3Tu$h}-0qszDM-N~o=tQ$CvO-@+HpS6i z^ktoHR68woqOo6?ov|NYQkLpSzoR&9dg=l%C z;$AI}0_dUjX+~<7Oo7oCmal1jp5F?=V&k|I&i9I|n{eJ*3v{E)ShHl6c0QQ;K1UVE zWcjiK^Tea+`#gIKU+8(W(wS_baz}UW0q6}a;HcR!T9O@VHfmqE=nz|V+gxT5<5jRppTvW7rBv}eA#N4anQMuZkf)8VC^k6Mw3n){Jog)MgIsG>Z+e4)-WeV-TK@SR1C ztjpedN!#;=L)x~vp(~fFFr|g8_MGRO_vsDRt+}5WdAkGa^;SQ=l-r(N$dmvQ+zu#!^ zB;YK>pJ!`&NlbH>$@5sC=9Q=9U& zMXiusbZwOQ!ERVQV(*X9@Q_k4YLymy4lDFb3#4X8m4`Wm;NvR_nzzVD#2>-jkN-ke z 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolPenShadow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolPenShadow.imageset/Contents.json new file mode 100644 index 0000000000..aa99f8a6e5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolPenShadow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Pen.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/ToolPenShadow.imageset/Pen.png b/submodules/TelegramUI/Images.xcassets/Media Editor/ToolPenShadow.imageset/Pen.png new file mode 100644 index 0000000000000000000000000000000000000000..62291a163d57487177ae3235926437f46d7fe7d6 GIT binary patch literal 23540 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

8}|9X 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 + if let representation, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(user), authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: 28.0, height: 28.0)) { + imageSignal = signal + |> map { data -> UIImage? in + return data?.0 + } + } else { + imageSignal = peerAvatarCompleteImage(account: context.account, peer: EnginePeer(user), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)) + } + + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemReset, text: removeText, color: .accent, icon: nil, iconSignal: imageSignal, action: { interaction.resetCustomPhoto() })) } @@ -6872,6 +6882,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate (strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicPhotoSuccess, round: true, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) case .custom: strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + + let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).start() case .suggest: if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in @@ -7076,6 +7088,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate (strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) case .custom: strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + + let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).start() case .suggest: if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in