diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 97a82aa15a..fb7c42af16 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8161,6 +8161,7 @@ Sorry for the inconvenience."; "EmojiSearch.SearchReactionsEmptyResult" = "No emoji found"; "EmojiSearch.SearchStatusesPlaceholder" = "Search Statuses"; "EmojiSearch.SearchStatusesEmptyResult" = "No emoji found"; +"EmojiSearch.SearchEmojiEmptyResult" = "No emoji found"; "ChatList.StartAction" = "Start"; "ChatList.CloseAction" = "Close"; diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index 1c5b8d363f..0cab716179 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -458,7 +458,7 @@ private final class BrowserScreenNode: ViewControllerTracingNode { strongSelf.interaction?.updateForceSerif(false) action(.default) } - })), .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.InstantPage_FontNewYork, textFont: .custom(Font.with(size: 17.0, design: .serif, traits: [])), icon: forceSerif ? checkIcon : emptyIcon, action: { [weak self] (controller, action) in + })), .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.InstantPage_FontNewYork, textFont: .custom(font: Font.with(size: 17.0, design: .serif, traits: []), height: nil, verticalOffset: nil), icon: forceSerif ? checkIcon : emptyIcon, action: { [weak self] (controller, action) in if let strongSelf = self { strongSelf.interaction?.updateForceSerif(true) action(.default) diff --git a/submodules/ContextUI/Sources/ContextActionNode.swift b/submodules/ContextUI/Sources/ContextActionNode.swift index f9bc303629..af50961aee 100644 --- a/submodules/ContextUI/Sources/ContextActionNode.swift +++ b/submodules/ContextUI/Sources/ContextActionNode.swift @@ -89,7 +89,7 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { case .small: titleFont = smallTextFont titleBoldFont = smallBoldTextFont - case let .custom(customFont): + case let .custom(customFont, _, _): titleFont = customFont titleBoldFont = customFont } @@ -340,7 +340,7 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { titleFont = textFont case .small: titleFont = smallTextFont - case let .custom(customFont): + case let .custom(customFont, _, _): titleFont = customFont } @@ -387,7 +387,7 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { titleFont = textFont case .small: titleFont = smallTextFont - case let .custom(customFont): + case let .custom(customFont, _, _): titleFont = customFont } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index eb19b29182..dd197c7e08 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -52,7 +52,7 @@ public enum ContextMenuActionResult { public enum ContextMenuActionItemFont { case regular case small - case custom(UIFont) + case custom(font: UIFont, height: CGFloat?, verticalOffset: CGFloat?) } public struct ContextMenuActionItemIconSource { diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index ce8367c994..e1cb4444da 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -201,12 +201,16 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin self.titleLabelNode.lineSpacing = 0.1 } + var forcedHeight: CGFloat? + var titleVerticalOffset: CGFloat? let titleFont: UIFont let titleBoldFont: UIFont switch self.item.textFont { - case let .custom(font): + case let .custom(font, height, verticalOffset): titleFont = font titleBoldFont = font + forcedHeight = height + titleVerticalOffset = verticalOffset case .small: let smallTextFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)) titleFont = smallTextFont @@ -320,15 +324,22 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin } else { minSize.width += sideInset } - minSize.height += verticalInset * 2.0 - minSize.height += titleSize.height - if subtitle != nil { - minSize.height += titleSubtitleSpacing - minSize.height += subtitleSize.height + if let forcedHeight { + minSize.height = forcedHeight + } else { + minSize.height += verticalInset * 2.0 + minSize.height += titleSize.height + if subtitle != nil { + minSize.height += titleSubtitleSpacing + minSize.height += subtitleSize.height + } } return (minSize: minSize, apply: { size, transition in - let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: titleSize) + var titleFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: titleSize) + if let titleVerticalOffset { + titleFrame = titleFrame.offsetBy(dx: 0.0, dy: titleVerticalOffset) + } let subtitleFrame = CGRect(origin: CGPoint(x: sideInset, y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize) transition.updateFrame(node: self.highlightBackgroundNode, frame: CGRect(origin: CGPoint(), size: size), beginWithCurrentState: true) diff --git a/submodules/DrawingUI/MetalResources/Drawing.metal b/submodules/DrawingUI/MetalResources/Drawing.metal index bc4bd8eb9e..7b8f6ca1e6 100644 --- a/submodules/DrawingUI/MetalResources/Drawing.metal +++ b/submodules/DrawingUI/MetalResources/Drawing.metal @@ -41,7 +41,6 @@ float2 transformPointCoord(float2 pointCoord, float a, float2 anchor) { return float2(x, y) + anchor; } - vertex Point vertex_point_func(constant Point *points [[ buffer(0) ]], constant Uniforms &uniforms [[ buffer(1) ]], uint vid [[ vertex_id ]]) @@ -62,23 +61,3 @@ fragment float4 fragment_point_func(Point point_data [[ stage_in ]], float4 color = float4(tex2d.sample(textureSampler, tex_coord)); return float4(point_data.color.rgb, color.a * point_data.color.a); }; - -// franment shader that applys original color of the texture -fragment half4 fragment_point_func_original(Point point_data [[ stage_in ]], - texture2d tex2d [[ texture(0) ]], - float2 pointCoord [[ point_coord ]]) -{ - constexpr sampler textureSampler(mag_filter::linear, min_filter::linear); - half4 color = half4(tex2d.sample(textureSampler, pointCoord)); - return half4(color.rgb, color.a * point_data.color.a); -}; - -fragment float4 fragment_point_func_without_texture(Point point_data [[ stage_in ]], - float2 pointCoord [[ point_coord ]]) -{ - float dist = length(pointCoord - float2(0.5)); - if (dist >= 0.5) { - return float4(0); - } - return point_data.color; -} diff --git a/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift b/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift index 4d8cd77a13..a182caee13 100644 --- a/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift @@ -178,7 +178,7 @@ final class DrawingBubbleEntityView: DrawingEntityView { 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) + return CGSize(width: minSize * 0.2, height: minSize * 0.2) } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { diff --git a/submodules/DrawingUI/Sources/DrawingGesture.swift b/submodules/DrawingUI/Sources/DrawingGesture.swift index 15cd4d354b..d167a69854 100644 --- a/submodules/DrawingUI/Sources/DrawingGesture.swift +++ b/submodules/DrawingUI/Sources/DrawingGesture.swift @@ -1,184 +1,13 @@ import Foundation import UIKit -public typealias TouchEventIdentifier = String -public typealias PointIdentifier = String -public typealias EstimationUpdateIndex = NSNumber - -class Touch: Equatable, Hashable { - public let touchIdentifier: UITouchIdentifier - lazy public var pointIdentifier: PointIdentifier = { - if let estimationUpdateIndex = estimationUpdateIndex { - return touchIdentifier + ":\(estimationUpdateIndex)" - } else { - return touchIdentifier + ":" + identifier - } - }() - public let identifier: String - public let timestamp: TimeInterval - public let type: UITouch.TouchType - public let phase: UITouch.Phase - public let force: CGFloat - public let maximumPossibleForce: CGFloat - public let altitudeAngle: CGFloat - public let azimuthUnitVector: CGVector - public let azimuth: CGFloat - public let location: CGPoint - public let estimationUpdateIndex: EstimationUpdateIndex? - public let estimatedProperties: UITouch.Properties - public let estimatedPropertiesExpectingUpdates: UITouch.Properties - public let isUpdate: Bool - public let isPrediction: Bool - - public let view: UIView? - - public var expectsLocationUpdate: Bool { - return estimatedPropertiesExpectingUpdates.contains(UITouch.Properties.location) - } - - public var expectsForceUpdate: Bool { - return estimatedPropertiesExpectingUpdates.contains(UITouch.Properties.force) - } - - public var expectsAzimuthUpdate: Bool { - return estimatedPropertiesExpectingUpdates.contains(UITouch.Properties.azimuth) - } - - public var expectsUpdate: Bool { - return expectsForceUpdate || expectsAzimuthUpdate || expectsLocationUpdate - } - - public convenience init( - coalescedTouch: UITouch, - touch: UITouch, - in view: UIView, - isUpdate: Bool, - isPrediction: Bool, - phase: UITouch.Phase? = nil, - transform: CGAffineTransform = .identity - ) { - let originalLocation = coalescedTouch.location(in: view) - let location = !transform.isIdentity ? originalLocation.applying(transform) : originalLocation - - self.init( - identifier: UUID.init().uuidString, - touchIdentifier: touch.identifer, - timestamp: coalescedTouch.timestamp, - type: coalescedTouch.type, - phase: phase ?? coalescedTouch.phase, - force: coalescedTouch.force, - maximumPossibleForce: coalescedTouch.maximumPossibleForce, - altitudeAngle: coalescedTouch.altitudeAngle, - azimuthUnitVector: coalescedTouch.azimuthUnitVector(in: view), - azimuth: coalescedTouch.azimuthAngle(in: view), - location: location, - estimationUpdateIndex: coalescedTouch.estimationUpdateIndex, - estimatedProperties: coalescedTouch.estimatedProperties, - estimatedPropertiesExpectingUpdates: coalescedTouch.estimatedPropertiesExpectingUpdates, - isUpdate: isUpdate, - isPrediction: isPrediction, - in: view - ) - } - - public init( - identifier: TouchEventIdentifier, - touchIdentifier: UITouchIdentifier, - timestamp: TimeInterval, - type: UITouch.TouchType, - phase: UITouch.Phase, - force: CGFloat, - maximumPossibleForce: CGFloat, - altitudeAngle: CGFloat, - azimuthUnitVector: CGVector, - azimuth: CGFloat, - location: CGPoint, - estimationUpdateIndex: EstimationUpdateIndex?, - estimatedProperties: UITouch.Properties, - estimatedPropertiesExpectingUpdates: UITouch.Properties, - isUpdate: Bool, - isPrediction: Bool, - in view: UIView? - ) { - self.identifier = identifier - self.touchIdentifier = touchIdentifier - self.timestamp = timestamp - self.type = type - self.phase = phase - self.force = force - self.maximumPossibleForce = maximumPossibleForce - self.altitudeAngle = altitudeAngle - self.azimuthUnitVector = azimuthUnitVector - self.azimuth = azimuth - self.location = location - self.estimationUpdateIndex = estimationUpdateIndex - self.estimatedProperties = estimatedProperties - self.estimatedPropertiesExpectingUpdates = estimatedPropertiesExpectingUpdates - self.isUpdate = isUpdate - self.isPrediction = isPrediction - self.view = view - } - - public static func == (lhs: Touch, rhs: Touch) -> Bool { - return lhs.identifier == rhs.identifier - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(identifier) - } -} - -class DrawingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { +class DrawingGestureRecognizer: UIPanGestureRecognizer { var shouldBegin: (CGPoint) -> Bool = { _ in return true } - var onTouches: ([Touch]) -> Void = { _ in } - - var transform: CGAffineTransform = .identity - - var usePredictedTouches = false - - private var currentTouches = Set() - - override init(target: Any?, action: Selector?) { - super.init(target: target, action: action) - - self.delegate = self - self.cancelsTouchesInView = false - self.delaysTouchesBegan = false - self.delaysTouchesEnded = false - self.allowedTouchTypes = [ - NSNumber(value: UITouch.TouchType.direct.rawValue), - NSNumber(value: UITouch.TouchType.stylus.rawValue) - ] - } - - func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - let location = gestureRecognizer.location(in: self.view) - if self.shouldBegin(location) { - return true - } else { - return false - } - } - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if otherGestureRecognizer is UIPinchGestureRecognizer { - return true - } - return true - } - - override func touchesEstimatedPropertiesUpdated(_ touches: Set) { - self.process(touches: touches, with: nil, isUpdate: true) - } override func touchesBegan(_ touches: Set, with event: UIEvent) { - if let location = touches.first?.location(in: self.view), touches.count == 1 && self.shouldBegin(location) { + if touches.count == 1, let touch = touches.first, self.shouldBegin(touch.location(in: self.view)) { super.touchesBegan(touches, with: event) - - self.process(touches: touches, with: event) self.state = .began - } else { - self.state = .cancelled } } @@ -187,70 +16,24 @@ class DrawingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate self.state = .cancelled } else { super.touchesMoved(touches, with: event) - self.process(touches: touches, with: event) - - self.state = .changed } } - - override func touchesEnded(_ touches: Set, with event: UIEvent) { - super.touchesEnded(touches, with: event) - - self.process(touches: touches, with: event) - - self.state = .ended - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent) { - super.touchesCancelled(touches, with: event) - - self.state = .cancelled - } - - func process(touches: Set, with event: UIEvent?, isUpdate: Bool = false) { - guard let view = self.view else { - return - } - - var allTouches: [Touch] = [] - - if let touch = touches.first { - allTouches.append(Touch( - coalescedTouch: touch, - touch: touch, - in: view, - isUpdate: isUpdate, - isPrediction: false, - transform: self.transform - )) - } - - self.onTouches(allTouches) - } } -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 - } +struct DrawingPoint { + let location: CGPoint + let velocity: CGFloat + + var x: CGFloat { + return self.location.x } - enum Mode { - case direct - case location - case smoothCurve - case polyline + var y: CGFloat { + return self.location.y } - +} + +class DrawingGesturePipeline: NSObject, UIGestureRecognizerDelegate { enum DrawingGestureState { case began case changed @@ -258,35 +41,28 @@ class DrawingGesturePipeline { case cancelled } - enum DrawingResult { - case point(DrawingGesturePipeline.Point) - case location(Polyline.Point) - case smoothCurve(BezierPath) - case polyline(Polyline) - } - - private var pendingTouches: [Touch] = [] - var onDrawing: (DrawingGestureState, DrawingResult) -> Void = { _, _ in } + var onDrawing: (DrawingGestureState, DrawingPoint) -> Void = { _, _ in } var gestureRecognizer: DrawingGestureRecognizer? - var transform: CGAffineTransform = .identity { - didSet { - self.gestureRecognizer?.transform = transform - } - } - - var mode: Mode = .location - + var transform: CGAffineTransform = .identity + init(view: DrawingView) { + super.init() + let gestureRecognizer = DrawingGestureRecognizer(target: self, action: #selector(self.handleGesture(_:))) - gestureRecognizer.onTouches = { [weak self] touches in - self?.pendingTouches.append(contentsOf: touches) - } + gestureRecognizer.delegate = self self.gestureRecognizer = gestureRecognizer view.addGestureRecognizer(gestureRecognizer) } - var previousPoint: Point? + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is UIPinchGestureRecognizer { + return true + } + return false + } + + var previousPoint: DrawingPoint? @objc private func handleGesture(_ gestureRecognizer: DrawingGestureRecognizer) { let state: DrawingGestureState switch gestureRecognizer.state { @@ -306,979 +82,12 @@ 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 originalLocation = gestureRecognizer.location(in: gestureRecognizer.view) + let location = originalLocation.applying(self.transform) + let velocity = gestureRecognizer.velocity(in: gestureRecognizer.view).applying(self.transform) + let velocityValue = velocity.length - let touchDeltas = self.processTouchEvents(self.pendingTouches) - let polylineDeltas = self.processTouchPaths(inputDeltas: touchDeltas) - let simplifiedPolylineDeltas = self.simplifyPolylines(inputDeltas: polylineDeltas) - - switch self.mode { - case .location: - if let touchPath = self.touchPaths.last, let point = touchPath.points.last { - self.onDrawing(state, .location(Polyline.Point(touchPoint: point))) - } - case .smoothCurve: - if let path = self.processPolylines(inputDeltas: simplifiedPolylineDeltas) { - self.onDrawing(state, .smoothCurve(path)) - } - case .polyline: - if let polyline = self.simplifiedPolylines.last { - self.onDrawing(state, .polyline(polyline)) - } - case .direct: - break - } - - self.pendingTouches.removeAll() - } - - enum TouchPathDelta: Equatable { - case addedTouchPath(index: Int) - case updatedTouchPath(index: Int, updatedIndexes: MinMaxIndex) - case completedTouchPath(index: Int) - } - - private var touchPaths: [TouchPath] = [] - private var touchToIndex: [UITouchIdentifier: Int] = [:] - private func processTouchEvents(_ touches: [Touch]) -> [TouchPathDelta] { - var deltas: [TouchPathDelta] = [] - var processedTouchIdentifiers: [UITouchIdentifier] = [] - let updatedEventsPerTouch = touches.reduce(into: [String: [Touch]](), { (result, event) in - if result[event.touchIdentifier] != nil { - result[event.touchIdentifier]?.append(event) - } else { - result[event.touchIdentifier] = [event] - } - }) - - for touchToProcess in touches { - let touchIdentifier = touchToProcess.touchIdentifier - guard !processedTouchIdentifiers.contains(touchIdentifier), let events = updatedEventsPerTouch[touchIdentifier] else { - continue - } - - processedTouchIdentifiers.append(touchIdentifier) - if let index = self.touchToIndex[touchIdentifier] { - let path = self.touchPaths[index] - let updatedIndexes = path.add(touchEvents: events) - deltas.append(.updatedTouchPath(index: index, updatedIndexes: updatedIndexes)) - - if path.isComplete { - deltas.append(.completedTouchPath(index: index)) - } - } else if let touchIdentifier = events.first?.touchIdentifier, let path = TouchPath(touchEvents: events) { - let index = self.touchPaths.count - self.touchToIndex[touchIdentifier] = index - self.touchPaths.append(path) - deltas.append(.addedTouchPath(index: index)) - - if path.isComplete { - deltas.append(.completedTouchPath(index: index)) - } - } - } - return deltas - } - - enum PolylineDelta: Equatable { - case addedPolyline(index: Int) - case updatedPolyline(index: Int, updatedIndexes: MinMaxIndex) - case completedPolyline(index: Int) - } - - private var indexToIndex: [Int: Int] = [:] - private var polylines: [Polyline] = [] - func processTouchPaths(inputDeltas: [TouchPathDelta]) -> [PolylineDelta] { - var deltas: [PolylineDelta] = [] - for delta in inputDeltas { - switch delta { - case .addedTouchPath(let pathIndex): - let line = self.touchPaths[pathIndex] - let smoothStroke = Polyline(touchPath: line) - let index = polylines.count - indexToIndex[pathIndex] = index - polylines.append(smoothStroke) - deltas.append(.addedPolyline(index: index)) - case .updatedTouchPath(let pathIndex, let indexSet): - let line = self.touchPaths[pathIndex] - if let index = indexToIndex[pathIndex] { - let updates = polylines[index].update(with: line, indexSet: indexSet) - deltas.append(.updatedPolyline(index: index, updatedIndexes: updates)) - } - case .completedTouchPath(let pointCollectionIndex): - if let index = indexToIndex[pointCollectionIndex] { - deltas.append(.completedPolyline(index: index)) - } - } - } - - return deltas - } - - var simplifiedPolylines: [Polyline] = [] - func simplifyPolylines(inputDeltas: [PolylineDelta]) -> [PolylineDelta] { - var outDeltas: [PolylineDelta] = [] - - for delta in inputDeltas { - switch delta { - case .addedPolyline(let strokeIndex): - assert(strokeIndex == self.simplifiedPolylines.count) - let line = self.polylines[strokeIndex] - self.simplifiedPolylines.append(line) - let indexes = MinMaxIndex(0.. CGFloat { - guard abs(windowLoc) <= windowSize else { fatalError("Invalid coefficient") } - if let cached = cache { - return cached[abs(windowLoc)] - } - var coeffs: [CGFloat] = [] - for windowLoc in 0...windowSize { - coeffs.append(Self.calcWeight(index, windowLoc, windowSize, order, derivative)) - } - - cache = coeffs - - return coeffs[abs(windowLoc)] - } - - // MARK: - Coefficients - - /// calculates the generalised factorial (a)(a-1)...(a-b+1) - private static func genFact(_ a: Int, _ b: Int) -> CGFloat { - var gf: CGFloat = 1.0 - - for jj in (a - b + 1) ..< (a + 1) { - gf *= CGFloat(jj) - } - return gf - } - - private static func gramPoly(_ index: Int, _ window: Int, _ order: Int, _ derivative: Int) -> CGFloat { - var gp_val: CGFloat - - if order > 0 { - let g1 = gramPoly(index, window, order - 1, derivative) - let g2 = gramPoly(index, window, order - 1, derivative - 1) - let g3 = gramPoly(index, window, order - 2, derivative) - let i: CGFloat = CGFloat(index) - let m: CGFloat = CGFloat(window) - let k: CGFloat = CGFloat(order) - let s: CGFloat = CGFloat(derivative) - gp_val = (4.0 * k - 2.0) / (k * (2.0 * m - k + 1.0)) * (i * g1 + s * g2) - - ((k - 1.0) * (2.0 * m + k)) / (k * (2.0 * m - k + 1.0)) * g3 - } else if order == 0 && derivative == 0 { - gp_val = 1.0 - } else { - gp_val = 0.0 - } - return gp_val - } - - private static func calcWeight(_ index: Int, _ windowLoc: Int, _ windowSize: Int, _ order: Int, _ derivative: Int) -> CGFloat { - var sum: CGFloat = 0.0 - - for k in 0 ..< order + 1 { - sum += CGFloat(2 * k + 1) * CGFloat(genFact(2 * windowSize, k) / genFact(2 * windowSize + k + 1, k + 1)) - * gramPoly(index, windowSize, k, 0) * gramPoly(windowLoc, windowSize, k, derivative) - } - - return sum - } - } - - - private var window: Int = 2 - private var strength: CGFloat = 1 - - var deriv: Int = 0 - var order: Int = 3 - var coeffs: [Coeffs] = [] - - private func smoothStroke(stroke: inout Polyline, at indexes: MinMaxIndex?, input: Polyline) -> MinMaxIndex { - if input.points.count > stroke.points.count { - stroke.points.append(contentsOf: input.points[stroke.points.count...]) - } else if input.points.count < stroke.points.count { - stroke.points.removeSubrange(input.points.count...) - } - let outIndexes = { () -> MinMaxIndex in - if let indexes = indexes, - let minIndex = indexes.first, - let maxIndex = indexes.last { - var outIndexes = MinMaxIndex() - let start = max(0, minIndex - window) - let end = min(stroke.points.count - 1, maxIndex + window) - outIndexes.insert(integersIn: start...end) - return outIndexes - } - return MinMaxIndex(stroke.points.indices) - }() - - for pIndex in outIndexes { - let minWin = min(min(window, pIndex), stroke.points.count - 1 - pIndex) - // copy over the point in question so that not only our location will be smoothed below, - // but also the azimuth/altitude/etc will be the same - stroke.points[pIndex] = input.points[pIndex] - while coeffs.count < minWin + 1 { - coeffs.append(Coeffs(index: 0, windowSize: coeffs.count)) - } - if minWin > 1 { - var outPoint = CGPoint.zero - for windowPos in -minWin ... minWin { - let wght = coeffs[minWin].weight(windowPos, order, deriv) - outPoint.x += wght * input.points[pIndex + windowPos].location.x - outPoint.y += wght * input.points[pIndex + windowPos].location.y - } - let origPoint = stroke.points[pIndex].location - - stroke.points[pIndex].location = origPoint * CGFloat(1 - strength) + outPoint * strength - } - } - - return outIndexes - } - - private var builders: [BezierBuilder] = [] - private var bezierIndexToIndex: [Int: Int] = [:] - func processPolylines(inputDeltas: [PolylineDelta]) -> BezierPath? { - for delta in inputDeltas { - switch delta { - case .addedPolyline(let lineIndex): - assert(bezierIndexToIndex[lineIndex] == nil, "Cannot add existing line") - let line = self.simplifiedPolylines[lineIndex] - let builder = BezierBuilder(smoother: Smoother()) - builder.update(with: line, at: MinMaxIndex(0 ..< line.points.count)) - let builderIndex = builders.count - bezierIndexToIndex[lineIndex] = builderIndex - builders.append(builder) - case .updatedPolyline(let lineIndex, let updatedIndexes): - let line = self.simplifiedPolylines[lineIndex] - guard let builderIndex = bezierIndexToIndex[lineIndex] else { - continue - } - let builder = builders[builderIndex] - let _ = builder.update(with: line, at: updatedIndexes) - case .completedPolyline: - break - } - } - - return builders.last?.path - } - -} - -private var TOUCH_IDENTIFIER: UInt8 = 0 -public typealias UITouchIdentifier = String - -extension UITouch { - var identifer: UITouchIdentifier { - if let identifier = objc_getAssociatedObject(self, &TOUCH_IDENTIFIER) as? String { - return identifier - } else { - let identifier = UUID().uuidString - objc_setAssociatedObject(self, &TOUCH_IDENTIFIER, identifier, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return identifier - } + let point = DrawingPoint(location: location, velocity: velocityValue) + self.onDrawing(state, point) } } - -public struct MinMaxIndex: Sequence, Equatable { - private var start: Int - private var end: Int - - public static let null = MinMaxIndex() - - public init() { - start = .max - end = .max - } - - public init(_ indexes: ClosedRange) { - guard let first = indexes.first, let last = indexes.last else { self = .null; return } - start = first - end = last - } - - public init(_ indexes: Range) { - guard let first = indexes.first, let last = indexes.last else { self = .null; return } - start = first - end = last - } - - public init(_ integers: [Int]) { - guard !integers.isEmpty else { self = .null; return } - start = integers.min()! - end = integers.max()! - } - - public init(_ integer: Int) { - start = integer - end = integer - } - - public init(_ indexSet: IndexSet) { - guard !indexSet.isEmpty else { self = .null; return } - start = indexSet.min()! - end = indexSet.max()! - } - - public var count: Int { - guard self != .null else { return 0 } - return end - start + 1 - } - - public var first: Int? { - guard self != .null else { return nil } - return start - } - - public var last: Int? { - guard self != .null else { return nil } - return end - } - - @inlinable @inline(__always) - public func asIndexSet() -> IndexSet { - guard let first = first, let last = last else { return IndexSet() } - return IndexSet(integersIn: first...last) - } - - public mutating func insert(_ index: Int) { - if self == Self.null { - start = index - end = index - } else { - start = Swift.min(start, index) - end = Swift.max(end, index) - } - } - - @inlinable @inline(__always) - public mutating func insert(integersIn indexes: ClosedRange) { - guard let first = indexes.first, let last = indexes.last else { return } - insert(first) - insert(last) - } - - public mutating func remove(_ index: Int) { - if start == index { - start += 1 - } - if end == index { - end -= 1 - } - if start > end { - start = .max - end = .max - } - } - - @inlinable @inline(__always) - public func contains(_ index: Int) -> Bool { - guard let first = first, let last = last else { return false } - return index >= first && index <= last - } - - public func makeIterator() -> Iterator { - return Iterator(min: start, max: end) - } - - public struct Iterator: IteratorProtocol { - public typealias Element = Int - - var min: Int - let max: Int - - init(min: Int, max: Int) { - self.min = min - self.max = max - } - - public mutating func next() -> Int? { - if min == max, min == .max { - return nil - } else if min > max { - return nil - } else { - let ret = min - min += 1 - return ret - } - } - } -} - -extension Array { - @inline(__always) @inlinable - mutating func pop() -> Element? { - guard !isEmpty else { return nil } - return removeLast() - } - - @inline(__always) @inlinable - mutating func dequeue() -> Element? { - guard !isEmpty else { return nil } - return removeFirst() - } -} - - -class TouchPath: Hashable { - class Point: Hashable { - public private(set) var events: [Touch] - - public var event: Touch { - return events.last! - } - - public var expectsUpdate: Bool { - return self.event.isPrediction || self.event.expectsUpdate - } - - public var isPrediction: Bool { - return events.allSatisfy({ $0.isPrediction }) - } - - public init(event: Touch) { - events = [event] - events.reserveCapacity(10) - } - - func add(event: Touch) { - events.append(event) - } - - static func == (lhs: Point, rhs: Point) -> Bool { - return lhs.expectsUpdate == rhs.expectsUpdate && lhs.events == rhs.events - } - - func hash(into hasher: inout Hasher) { - hasher.combine(events) - } - } - - public private(set) var touchIdentifier: String - - private var _points: [Point]? - public var points: [Point] { - if let _points = _points { - return _points - } - let ret = confirmedPoints + predictedPoints - _points = ret - return ret - } - public var bounds: CGRect { - return points.reduce(.null) { partialResult, point -> CGRect in - return CGRect(x: min(partialResult.origin.x, point.event.location.x), - y: min(partialResult.origin.y, point.event.location.y), - width: max(partialResult.origin.x, point.event.location.x), - height: max(partialResult.origin.y, point.event.location.y)) - } - } - public var isComplete: Bool { - let phase = confirmedPoints.last?.event.phase - return (phase == .ended || phase == .cancelled) && predictedPoints.isEmpty - } - - private var confirmedPoints: [Point] { - didSet { - _points = nil - } - } - - private var predictedPoints: [Point] { - didSet { - _points = nil - } - } - - private var consumable: [Point] - private var expectingUpdate: Set - private var eventToPoint: [PointIdentifier: Point] - private var eventToIndex: [PointIdentifier: Int] - - init?(touchEvents: [Touch]) { - guard !touchEvents.isEmpty else { return nil } - self.confirmedPoints = [] - self.predictedPoints = [] - self.consumable = [] - self.eventToPoint = [:] - self.eventToIndex = [:] - self.expectingUpdate = Set() - self.touchIdentifier = touchEvents.first!.touchIdentifier - add(touchEvents: touchEvents) - } - - @discardableResult - func add(touchEvents: [Touch]) -> MinMaxIndex { - var indexSet = MinMaxIndex() - let startingCount = points.count - - for event in touchEvents { - assert(touchIdentifier == event.touchIdentifier) - if event.isPrediction { - if let prediction = consumable.dequeue() { - prediction.add(event: event) - predictedPoints.append(prediction) - let index = confirmedPoints.count + predictedPoints.count - 1 - indexSet.insert(index) - } else { - let prediction = Point(event: event) - predictedPoints.append(prediction) - let index = confirmedPoints.count + predictedPoints.count - 1 - indexSet.insert(index) - } - } else if eventToPoint[event.pointIdentifier] != nil, let index = eventToIndex[event.pointIdentifier] { - eventToPoint[event.pointIdentifier]?.add(event: event) - if !event.expectsUpdate { - self.expectingUpdate.remove(event.pointIdentifier) - } - indexSet.insert(index) - - if event.phase == .ended || event.phase == .cancelled { - consumable.append(contentsOf: predictedPoints) - predictedPoints.removeAll() - } - } else if isComplete { - } else { - consumable.append(contentsOf: predictedPoints) - predictedPoints.removeAll() - - if let point = consumable.dequeue() ?? predictedPoints.dequeue() { - if event.expectsUpdate { - self.expectingUpdate.insert(event.pointIdentifier) - } - point.add(event: event) - eventToPoint[event.pointIdentifier] = point - confirmedPoints.append(point) - let index = confirmedPoints.count - 1 - eventToIndex[event.pointIdentifier] = index - indexSet.insert(index) - } else { - if event.expectsUpdate { - self.expectingUpdate.insert(event.pointIdentifier) - } - let point = Point(event: event) - eventToPoint[event.pointIdentifier] = point - confirmedPoints.append(point) - let index = confirmedPoints.count - 1 - eventToIndex[event.pointIdentifier] = index - indexSet.insert(index) - } - } - } - - for index in consumable.indices { - let possiblyRemovedIndex = confirmedPoints.count + predictedPoints.count + index - if possiblyRemovedIndex < startingCount { - indexSet.insert(possiblyRemovedIndex) - } else { - indexSet.remove(possiblyRemovedIndex) - } - } - - return indexSet - } - - public static func == (lhs: TouchPath, rhs: TouchPath) -> Bool { - return lhs.touchIdentifier == rhs.touchIdentifier && lhs.points == rhs.points - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(touchIdentifier) - } -} - -struct Polyline { - struct Point: Equatable { - var location: CGPoint - var force: CGFloat - var altitudeAngle: CGFloat - var azimuth: CGFloat - var velocity: CGFloat = 0.0 - - let touchPoint: TouchPath.Point - var event: Touch { - return touchPoint.event - } - var expectsUpdate: Bool { - return touchPoint.expectsUpdate - } - - var x: CGFloat { - return self.location.x - } - - var y: CGFloat { - return self.location.y - } - - init( - location: CGPoint, - force: CGFloat, - altitudeAngle: CGFloat, - azimuth: CGFloat, - velocity: CGFloat, - touchPoint: TouchPath.Point - ) { - self.location = location - self.force = force - self.altitudeAngle = altitudeAngle - self.azimuth = azimuth - self.touchPoint = touchPoint - } - - init(touchPoint: TouchPath.Point) { - self.location = touchPoint.event.location - self.force = touchPoint.event.force - self.altitudeAngle = touchPoint.event.altitudeAngle - self.azimuth = touchPoint.event.azimuth - - self.touchPoint = touchPoint - } - - func offsetBy(_ point: CGPoint) -> Polyline.Point { - return Point( - location: self.location.offsetBy(dx: point.x, dy: point.y), - force: self.force, - altitudeAngle: self.altitudeAngle, - azimuth: self.azimuth, - velocity: self.velocity, - touchPoint: self.touchPoint - ) - } - - func withLocation(_ point: CGPoint) -> Polyline.Point { - return Point( - location: point, - force: self.force, - altitudeAngle: self.altitudeAngle, - azimuth: self.azimuth, - velocity: self.velocity, - touchPoint: self.touchPoint - ) - } - } - - var isComplete: Bool - let touchIdentifier: String - var points: [Point] - var bounds: CGRect { - return self.points.reduce(.null) { partialResult, point -> CGRect in - return CGRect(x: min(partialResult.origin.x, point.x), - y: min(partialResult.origin.y, point.y), - width: max(partialResult.size.width, point.x), - height: max(partialResult.size.height, point.y)) - } - } - init(touchPath: TouchPath) { - isComplete = touchPath.isComplete - touchIdentifier = touchPath.touchIdentifier - - var points: [Point] = [] - var previousTouchPoint: TouchPath.Point? - for touchPoint in touchPath.points { - var point = Point(touchPoint: touchPoint) - if let previousTouchPoint = previousTouchPoint { - let distance = touchPoint.event.location.distance(to: previousTouchPoint.event.location) - let elapsed = max(0.0, touchPoint.event.timestamp - previousTouchPoint.event.timestamp) - let velocity = elapsed > 0.0 ? distance / elapsed : 0.0 - point.velocity = velocity - } - points.append(point) - previousTouchPoint = touchPoint - } - self.points = points - } - - init(points: [Point]) { - self.isComplete = true - self.touchIdentifier = points.first?.event.touchIdentifier ?? "" - self.points = points - } - - mutating func update(with path: TouchPath, indexSet: MinMaxIndex) -> MinMaxIndex { - var indexesToRemove = MinMaxIndex() - for index in indexSet { - if index < path.points.count { - if index < points.count { - points[index].location = path.points[index].event.location - points[index].force = path.points[index].event.force - points[index].azimuth = path.points[index].event.azimuth - points[index].altitudeAngle = path.points[index].event.altitudeAngle - - if index > 0 { - let previousTouchPoint = points[index - 1] - let distance = path.points[index].event.location.distance(to: previousTouchPoint.event.location) - let elapsed = max(0.0, path.points[index].event.timestamp - previousTouchPoint.event.timestamp) - let velocity = elapsed > 0.0 ? distance / elapsed : 0.0 - points[index].velocity = velocity - } - } else if index == points.count { - points.append(Point(touchPoint: path.points[index])) - if index > 0 { - let previousTouchPoint = points[index - 1] - let distance = path.points[index].event.location.distance(to: previousTouchPoint.event.location) - let elapsed = max(0.0, path.points[index].event.timestamp - previousTouchPoint.event.timestamp) - let velocity = elapsed > 0.0 ? distance / elapsed : 0.0 - points[index].velocity = velocity - } - } else { - assertionFailure("Attempting to modify a point that doesn't yet exist. maybe an update is out of order?") - } - } else { - indexesToRemove.insert(index) - } - } - - for index in indexesToRemove.reversed() { - guard index < points.count else { - print("Error: unknown polyline index \(index)") - continue - } - points.remove(at: index) - } - - isComplete = path.isComplete - - return indexSet - } -} - -private class BezierBuilder { - private var elements: [BezierPath.Element] = [] - private let smoother: Smoother - private(set) var path = BezierPath() - - init(smoother: Smoother) { - self.smoother = smoother - } - - @discardableResult - func update(with line: Polyline, at lineIndexes: MinMaxIndex) -> MinMaxIndex { - let updatedPathIndexes = smoother.elementIndexes(for: line, at: lineIndexes, with: path) - guard - let min = updatedPathIndexes.first, - let max = updatedPathIndexes.last - else { - return updatedPathIndexes - } - let updatedPath: BezierPath - if min - 1 < path.elementCount, - min - 1 >= 0 { - updatedPath = path.trimming(to: min - 1) - } else { - updatedPath = BezierPath() - } - for elementIndex in min ... max { - assert(elementIndex <= elements.count, "Invalid element index") - if updatedPathIndexes.contains(elementIndex) { - if elementIndex > smoother.maxIndex(for: line) { - - } else { - let element = smoother.element(for: line, at: elementIndex) - if elementIndex == elements.count { - elements.append(element) - } else { - elements[elementIndex] = element - } - updatedPath.append(element) - } - } else { - // use the existing element - let element = elements[elementIndex] - updatedPath.append(element) - } - } - for elementIndex in max + 1 ..< elements.count { - let element = elements[elementIndex] - updatedPath.append(element) - } - path = updatedPath - return updatedPathIndexes - } -} - -private class Smoother { - let smoothFactor: CGFloat - - init(smoothFactor: CGFloat = 1.0) { - self.smoothFactor = smoothFactor - } - - func element(for line: Polyline, at elementIndex: Int) -> BezierPath.Element { - assert(elementIndex >= 0 && elementIndex <= maxIndex(for: line)) - - if elementIndex == 0 { - return BezierPath.Element(type: .moveTo, startPoint: line.points[0], endPoint: line.points[0], controlPoints: []) - } - - if elementIndex == 1 { - return Self.newCurve(smoothFactor: smoothFactor, - startPoint: line.points[0], - p1: line.points[0].location, - p2: line.points[1], - p3: line.points[2].location) - } - - if line.isComplete && elementIndex == maxIndex(for: line) { - return Self.newCurve(smoothFactor: smoothFactor, - startPoint: line.points[elementIndex - 1], - p0: line.points[elementIndex - 2].location, - p1: line.points[elementIndex - 1].location, - p2: line.points[elementIndex], - p3: line.points[elementIndex].location) - } - - return Self.newCurve(smoothFactor: smoothFactor, - startPoint: line.points[elementIndex - 1], - p0: line.points[elementIndex - 2].location, - p1: line.points[elementIndex - 1].location, - p2: line.points[elementIndex], - p3: line.points[elementIndex + 1].location) - } - - func maxIndex(for line: Polyline) -> Int { - let lastIndex = line.points.count - 1 - return Swift.max(0, lastIndex - 1) + (line.points.count > 2 && line.isComplete ? 1 : 0) - } - - func elementIndexes(for line: Polyline, at lineIndexes: MinMaxIndex, with bezier: BezierPath) -> MinMaxIndex { - var curveIndexes = MinMaxIndex() - - for index in lineIndexes { - elementIndexes(for: line, at: index, with: bezier, into: &curveIndexes) - } - - return curveIndexes - } - - func elementIndexes(for line: Polyline, at lineIndex: Int, with bezier: BezierPath) -> MinMaxIndex { - var ret = MinMaxIndex() - elementIndexes(for: line, at: lineIndex, with: bezier, into: &ret) - return ret - } - - // Below are the examples of input indexes, and which smoothed elements that point index affects - // 0 => 2, 1, 0 - // 1 => 3, 2, 1, 0 - // 2 => 4, 3, 2, 1 - // 3 => 5, 4, 3, 2 - // 4 => 6, 5, 4, 3 - // 5 => 7, 6, 5, 4 - // 6 => 8, 7, 6, 5 - // 7 => 9, 8, 7, 6 - private func elementIndexes(for line: Polyline, at lineIndex: Int, with bezier: BezierPath, into indexes: inout MinMaxIndex) { - guard lineIndex >= 0 else { - return - } - let max = maxIndex(for: line) - - if lineIndex > 1, - (lineIndex - 1 <= max) || (lineIndex - 1 < bezier.elementCount) { - indexes.insert(lineIndex - 1) - } - if (lineIndex <= max) || (lineIndex < bezier.elementCount) { - indexes.insert(lineIndex) - } - if (lineIndex + 1 <= max) || (lineIndex + 1 < bezier.elementCount) { - indexes.insert(lineIndex + 1) - } - if (lineIndex + 2 <= max) || (lineIndex + 2 < bezier.elementCount) { - indexes.insert(lineIndex + 2) - } - } - - // MARK: - Helper - - private static func newCurve( - smoothFactor: CGFloat, - startPoint: Polyline.Point, - p0: CGPoint? = nil, - p1: CGPoint, - p2: Polyline.Point, - p3: CGPoint - ) -> BezierPath.Element { - let p0 = p0 ?? p1 - - let c1 = CGPoint(x: (p0.x + p1.x) / 2.0, y: (p0.y + p1.y) / 2.0) - let c2 = CGPoint(x: (p1.x + p2.x) / 2.0, y: (p1.y + p2.y) / 2.0) - let c3 = CGPoint(x: (p2.x + p3.x) / 2.0, y: (p2.y + p3.y) / 2.0) - - let len1 = sqrt((p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p0.y) * (p1.y - p0.y)) - let len2 = sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)) - let len3 = sqrt((p3.x - p2.x) * (p3.x - p2.x) + (p3.y - p2.y) * (p3.y - p2.y)) - - let k1 = len1 / (len1 + len2) - let k2 = len2 / (len2 + len3) - - let m1 = CGPoint(x: c1.x + (c2.x - c1.x) * k1, y: c1.y + (c2.y - c1.y) * k1) - let m2 = CGPoint(x: c2.x + (c3.x - c2.x) * k2, y: c2.y + (c3.y - c2.y) * k2) - - // Resulting control points. Here smooth_value is mentioned - // above coefficient K whose value should be in range [0...1]. - var ctrl1 = CGPoint(x: m1.x + (c2.x - m1.x) * smoothFactor + p1.x - m1.x, - y: m1.y + (c2.y - m1.y) * smoothFactor + p1.y - m1.y) - - var ctrl2 = CGPoint(x: m2.x + (c2.x - m2.x) * smoothFactor + p2.x - m2.x, - y: m2.y + (c2.y - m2.y) * smoothFactor + p2.y - m2.y) - - if ctrl1.x.isNaN || ctrl1.y.isNaN { - ctrl1 = p1 - } - - if ctrl2.x.isNaN || ctrl2.y.isNaN { - ctrl2 = p2.location - } - - return BezierPath.Element(type: .cubicCurve, startPoint: startPoint, endPoint: p2, controlPoints: [ctrl1, ctrl2]) - } -} - diff --git a/submodules/DrawingUI/Sources/DrawingMetalView.swift b/submodules/DrawingUI/Sources/DrawingMetalView.swift index 688c2226f1..c8ca470188 100644 --- a/submodules/DrawingUI/Sources/DrawingMetalView.swift +++ b/submodules/DrawingUI/Sources/DrawingMetalView.swift @@ -49,11 +49,10 @@ final class DrawingMetalView: MTKView { super.init(frame: CGRect(origin: .zero, size: size), device: device) + self.drawableSize = self.size self.autoResizeDrawable = false self.isOpaque = false self.contentScaleFactor = 1.0 - self.drawableSize = self.size - //self.presentsWithTransaction = true self.setup() } @@ -124,6 +123,8 @@ final class DrawingMetalView: MTKView { } self.drawable?.clear() + + self.markerBrush?.pushPoint(CGPoint(x: 100.0, y: 100.0), color: DrawingColor.clear, size: 0.0, isEnd: true) } @@ -137,9 +138,6 @@ final class DrawingMetalView: MTKView { } 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 { @@ -187,19 +185,12 @@ final class DrawingMetalView: MTKView { case marker } - func updated(_ point: Polyline.Point, state: DrawingGesturePipeline.DrawingGestureState, brush: BrushType, color: DrawingColor, size: CGFloat) { + func updated(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState, brush: BrushType, color: DrawingColor, size: CGFloat) { switch brush { case .marker: self.markerBrush?.updated(point, color: color, state: state, size: size) } } - - func setup(_ points: [CGPoint], brush: BrushType, color: DrawingColor, size: CGFloat) { - switch brush { - case .marker: - self.markerBrush?.setup(points, color: color, size: size) - } - } } private class Drawable { @@ -229,9 +220,7 @@ private class Drawable { self.updateBuffer(with: size) } - var isClearing = false func clear() { - self.isClearing = true self.texture?.clear() self.commit(wait: true) } @@ -262,10 +251,6 @@ private class Drawable { self.commandBuffer?.waitUntilCompleted() } self.commandBuffer = nil - - if self.isClearing && wait { - self.isClearing = false - } } internal func makeTexture() -> Texture? { @@ -364,7 +349,7 @@ private class Brush { } private let bezier = BezierGenerator() - func updated(_ point: Polyline.Point, color: DrawingColor, state: DrawingGesturePipeline.DrawingGestureState, size: CGFloat) { + func updated(_ point: DrawingPoint, color: DrawingColor, state: DrawingGesturePipeline.DrawingGestureState, size: CGFloat) { let point = point.location switch state { case .began: @@ -491,7 +476,7 @@ private class Stroke { let overlapping = max(1, line.pointSize / line.pointStep) var renderingColor = self.color - renderingColor.alpha = renderingColor.alpha / overlapping * 2.5 + renderingColor.alpha = renderingColor.alpha / overlapping * 5.5 for i in 0 ..< Int(count) { let index = CGFloat(i) @@ -524,30 +509,30 @@ class BezierGenerator { } init(beginPoint: CGPoint) { - begin(with: beginPoint) + self.begin(with: beginPoint) } func begin(with point: CGPoint) { - step = 0 - points.removeAll() - points.append(point) + self.step = 0 + self.points.removeAll() + self.points.append(point) } func pushPoint(_ point: CGPoint) -> [CGPoint] { - if point == points.last { + if point == self.points.last { return [] } - points.append(point) - if points.count < 3 { + self.points.append(point) + if self.points.count < 3 { return [] } - step += 1 + self.step += 1 return self.generateSmoothPathPoints() } func finish() { - step = 0 - points.removeAll() + self.step = 0 + self.points.removeAll() } var points: [CGPoint] = [] @@ -556,16 +541,16 @@ class BezierGenerator { private func generateSmoothPathPoints() -> [CGPoint] { var begin: CGPoint var control: CGPoint - let end = CGPoint.middle(p1: points[step], p2: points[step + 1]) + let end = CGPoint.middle(p1: self.points[step], p2: self.points[self.step + 1]) var vertices: [CGPoint] = [] - if step == 1 { - begin = points[0] - let middle1 = CGPoint.middle(p1: points[0], p2: points[1]) - control = CGPoint.middle(p1: middle1, p2: points[1]) + if self.step == 1 { + begin = self.points[0] + let middle1 = CGPoint.middle(p1: self.points[0], p2: self.points[1]) + control = CGPoint.middle(p1: middle1, p2: self.points[1]) } else { - begin = CGPoint.middle(p1: points[step - 1], p2: points[step]) - control = points[step] + begin = CGPoint.middle(p1: self.points[self.step - 1], p2: self.points[self.step]) + control = self.points[self.step] } let distance = begin.distance(to: end) @@ -573,9 +558,7 @@ class BezierGenerator { for i in 0 ..< segements { let t = CGFloat(i) / CGFloat(segements) - let x = pow(1 - t, 2) * begin.x + 2.0 * (1 - t) * t * control.x + t * t * end.x - let y = pow(1 - t, 2) * begin.y + 2.0 * (1 - t) * t * control.y + t * t * end.y - vertices.append(CGPoint(x: x, y: y)) + vertices.append(begin.quadBezierPoint(to: end, controlPoint: control, t: t)) } vertices.append(end) return vertices diff --git a/submodules/DrawingUI/Sources/DrawingNeonTool.swift b/submodules/DrawingUI/Sources/DrawingNeonTool.swift new file mode 100644 index 0000000000..53f7f44d93 --- /dev/null +++ b/submodules/DrawingUI/Sources/DrawingNeonTool.swift @@ -0,0 +1,277 @@ +import Foundation +import UIKit +import Display + +final class NeonTool: DrawingElement { + class RenderView: UIView, DrawingRenderView { + private weak var element: NeonTool? + private var drawScale = CGSize(width: 1.0, height: 1.0) + + let shadowLayer = SimpleShapeLayer() + let borderLayer = SimpleShapeLayer() + let fillLayer = SimpleShapeLayer() + + func setup(element: NeonTool, size: CGSize, screenSize: CGSize) { + self.element = element + + self.backgroundColor = .clear + self.isOpaque = false + self.contentScaleFactor = 1.0 + + let shadowRadius = element.renderShadowRadius + let strokeWidth = element.renderStrokeWidth + var shadowColor = element.color.toUIColor() + var fillColor: UIColor = .white + if shadowColor.lightness < 0.01 { + fillColor = shadowColor + shadowColor = UIColor(rgb: 0x440881) + } + + let bounds = CGRect(origin: .zero, size: size) + self.frame = bounds + + self.shadowLayer.frame = bounds + self.shadowLayer.contentsScale = 1.0 + self.shadowLayer.backgroundColor = UIColor.clear.cgColor + self.shadowLayer.lineWidth = strokeWidth * 0.5 + self.shadowLayer.lineCap = .round + self.shadowLayer.lineJoin = .round + self.shadowLayer.fillColor = fillColor.cgColor + self.shadowLayer.strokeColor = fillColor.cgColor + self.shadowLayer.shadowColor = shadowColor.cgColor + self.shadowLayer.shadowRadius = shadowRadius + self.shadowLayer.shadowOpacity = 1.0 + self.shadowLayer.shadowOffset = .zero + + self.borderLayer.frame = bounds + self.borderLayer.contentsScale = 1.0 + self.borderLayer.lineWidth = strokeWidth + self.borderLayer.lineCap = .round + self.borderLayer.lineJoin = .round + self.borderLayer.fillColor = UIColor.clear.cgColor + self.borderLayer.strokeColor = fillColor.mixedWith(shadowColor, alpha: 0.25).cgColor + + self.fillLayer.frame = bounds + self.fillLayer.contentsScale = 1.0 + self.fillLayer.fillColor = fillColor.cgColor + + self.layer.addSublayer(self.shadowLayer) + self.layer.addSublayer(self.borderLayer) + self.layer.addSublayer(self.fillLayer) + } + + fileprivate func updatePath(_ path: CGPath) { + self.shadowLayer.path = path + self.borderLayer.path = path + self.fillLayer.path = path + } + } + + let uuid: UUID + let drawingSize: CGSize + let color: DrawingColor + let renderStrokeWidth: CGFloat + let renderShadowRadius: CGFloat + let renderLineWidth: CGFloat + let renderColor: UIColor + + private var pathStarted = false + let path = UIBezierPath() + var activePath: UIBezierPath? + var addedSegments = 0 + + var renderPath: CGPath? + + var translation: CGPoint = .zero + + private weak var currentRenderView: DrawingRenderView? + + var isValid: Bool { + return self.renderPath != nil + } + + var bounds: CGRect { + if let renderPath = self.renderPath { + return normalizeDrawingRect(renderPath.boundingBoxOfPath.insetBy(dx: -self.renderShadowRadius - 30.0, dy: -self.renderShadowRadius - 30.0), drawingSize: self.drawingSize) + } else { + return .zero + } + } + + required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat) { + self.uuid = UUID() + self.drawingSize = drawingSize + self.color = color + + let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.01 + let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.03 + + 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 + + self.renderStrokeWidth = strokeWidth + self.renderShadowRadius = shadowRadius + self.renderLineWidth = lineWidth + + self.renderColor = color.withUpdatedAlpha(1.0).toUIColor() + } + + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { + let view = RenderView() + view.setup(element: self, size: self.drawingSize, screenSize: screenSize) + self.currentRenderView = view + return view + } + + func setupRenderLayer() -> DrawingRenderLayer? { + return nil + } + + func updatePath(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState) { + guard self.addPoint(point, state: state) else { + return + } + + if let currentRenderView = self.currentRenderView as? RenderView { + let path = self.path.cgPath.mutableCopy() + if let activePath { + path?.addPath(activePath.cgPath) + } + if let renderPath = path?.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) { + self.renderPath = renderPath + currentRenderView.updatePath(renderPath) + } + } + + if state == .ended, self.addedSegments == 0, let point = self.points.first { + self.renderPath = CGPath(ellipseIn: CGRect(origin: CGPoint(x: point.x - self.renderLineWidth / 2.0, y: point.y - self.renderLineWidth / 2.0), size: CGSize(width: self.renderLineWidth, height: self.renderLineWidth)), transform: nil) + } + } + + func draw(in context: CGContext, size: CGSize) { + guard let path = self.renderPath else { + return + } + context.saveGState() + + context.translateBy(x: self.translation.x, y: self.translation.y) + + context.setShouldAntialias(true) + + context.setBlendMode(.normal) + + var shadowColor = self.color.toUIColor() + var fillColor: UIColor = .white + if shadowColor.lightness < 0.01 { + fillColor = shadowColor + shadowColor = UIColor(rgb: 0x440881) + } + + context.addPath(path) + context.setLineCap(.round) + context.setFillColor(fillColor.cgColor) + context.setStrokeColor(fillColor.cgColor) + context.setLineWidth(self.renderStrokeWidth * 0.5) + context.setShadow(offset: .zero, blur: self.renderShadowRadius * 1.9, color: shadowColor.cgColor) + context.drawPath(using: .fillStroke) + + context.addPath(path) + context.setShadow(offset: .zero, blur: 0.0, color: UIColor.clear.cgColor) + context.setLineWidth(self.renderStrokeWidth) + context.setStrokeColor(fillColor.mixedWith(shadowColor, alpha: 0.25).cgColor) + context.strokePath() + + context.addPath(path) + context.setFillColor(fillColor.cgColor) + + context.fillPath() + + context.restoreGState() + } + + private var points: [CGPoint] = Array(repeating: .zero, count: 4) + private var pointPtr = 0 + + private func addPoint(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState) -> Bool { + let filterDistance: CGFloat = 10.0 + + if self.pointPtr == 0 { + self.points[0] = point.location + self.pointPtr += 1 + } else { + let previousPoint = self.points[self.pointPtr - 1] + guard previousPoint.distance(to: point.location) > filterDistance else { + return false + } + + if self.pointPtr >= 4 { + self.points[3] = self.points[2].point(to: point.location, t: 0.5) + + if let bezierPath = self.currentBezierPath(3) { + self.path.append(bezierPath) + self.activePath = nil + } + + self.points[0] = self.points[3] + self.pointPtr = 1 + } + + self.points[self.pointPtr] = point.location + self.pointPtr += 1 + } + + guard let bezierPath = self.currentBezierPath(self.pointPtr - 1) else { + return false + } + + self.activePath = bezierPath + + return true + +// if self.points.isEmpty { +// self.points[self.ctr] = point +// return +// } +// self.ctr += 1 +// self.points[self.ctr] = point +// +// if self.ctr == 4 { +// self.points[3] = CGPoint(x: (self.points[2]!.x + self.points[4]!.x) / 2.0, y: (self.points[2]!.y + self.points[4]!.y) / 2.0) +// +// self.path.move(to: self.points[0]!) +// self.path.addCurve(to: self.points[3]!, controlPoint1: self.points[1]!, controlPoint2: self.points[2]!) +// +// self.points[0] = self.points[3]! +// self.points[1] = self.points[4]! +// +// self.ctr = 1 +// self.addedSegments += 1 +// } + } + + private func currentBezierPath(_ ctr: Int) -> UIBezierPath? { + switch ctr { + case 0: + return nil + case 1: + let path = UIBezierPath() + path.move(to: self.points[0]) + path.addLine(to: self.points[1]) + return path + case 2: + let path = UIBezierPath() + path.move(to: self.points[0]) + path.addQuadCurve(to: self.points[2], controlPoint: self.points[1]) + return path + case 3: + let path = UIBezierPath() + path.move(to: self.points[0]) + path.addCurve(to: self.points[3], controlPoint1: self.points[1], controlPoint2: self.points[2]) + return path + default: + return nil + } + } +} + diff --git a/submodules/DrawingUI/Sources/PenTool.swift b/submodules/DrawingUI/Sources/DrawingPenTool.swift similarity index 72% rename from submodules/DrawingUI/Sources/PenTool.swift rename to submodules/DrawingUI/Sources/DrawingPenTool.swift index 6a0f4791e3..1e3f80e6d2 100644 --- a/submodules/DrawingUI/Sources/PenTool.swift +++ b/submodules/DrawingUI/Sources/DrawingPenTool.swift @@ -23,7 +23,6 @@ final class PenTool: DrawingElement { self.contentMode = .redraw //let scale = CGSize(width: screenSize.width / max(1.0, size.width), height: screenSize.height / max(1.0, size.height)) - let scale = CGSize(width: 0.33, height: 0.33) let viewSize = CGSize(width: size.width * scale.width, height: size.height * scale.height) @@ -140,6 +139,7 @@ final class PenTool: DrawingElement { parent.displaySize = rect.size context.scaleBy(x: 1.0 / parent.drawScale.width, y: 1.0 / parent.drawScale.height) element.drawSegments(in: context, from: parent.start, to: parent.segmentsCount) + element.drawActiveSegments(in: context) } } } @@ -153,7 +153,7 @@ final class PenTool: DrawingElement { let hasArrow: Bool let renderArrowLength: CGFloat - let renderArrowLineWidth: CGFloat + var renderArrowLineWidth: CGFloat let isEraser: Bool @@ -179,7 +179,7 @@ final class PenTool: DrawingElement { } var bounds: CGRect { - return boundingRect(from: 0, to: self.segments.count).insetBy(dx: -20.0, dy: -20.0) + return normalizeDrawingRect(boundingRect(from: 0, to: self.segments.count).insetBy(dx: -20.0, dy: -20.0), drawingSize: self.drawingSize) } required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, hasArrow: Bool, isEraser: Bool, isBlur: Bool, blurredImage: UIImage?) { @@ -229,41 +229,36 @@ final class PenTool: DrawingElement { return nil } - var previousPoint: CGPoint? - func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { - guard case let .point(point) = path else { - return + func updatePath(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState) { + let result = self.addPoint(point, state: state) + let resetActiveRect = result?.0 ?? false + let updatedRect = result?.1 + var combinedRect = updatedRect + if let previousActiveRect = self.previousActiveRect { + combinedRect = updatedRect?.union(previousActiveRect) ?? previousActiveRect + } + if resetActiveRect { + self.previousActiveRect = updatedRect + } else { + self.previousActiveRect = combinedRect } - let filterDistance: CGFloat = 20.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 = 1000.0 - } - - var effectiveRenderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth - (velocity / 100.0), self.renderLineWidth)) - if let previousRenderLineWidth = self.previousRenderLineWidth { - effectiveRenderLineWidth = effectiveRenderLineWidth * 0.2 + previousRenderLineWidth * 0.8 - } - self.previousRenderLineWidth = effectiveRenderLineWidth - - let rect = append(point: Point(position: point.location, width: effectiveRenderLineWidth)) - if let currentRenderView = self.currentRenderView as? RenderView, let rect = rect { - currentRenderView.draw(element: self, rect: rect) + if let currentRenderView = self.currentRenderView as? RenderView, let combinedRect { + currentRenderView.draw(element: self, rect: combinedRect) } if state == .ended { + if !self.activeSegments.isEmpty { + self.segments.append(contentsOf: self.activeSegments) + self.smoothPoints.append(contentsOf: self.activeSmoothPoints) + } + if self.hasArrow { var direction: CGFloat? if self.smoothPoints.count > 4 { - let p2 = self.smoothPoints[self.smoothPoints.count - 1].position + let p2 = self.smoothPoints[self.smoothPoints.count - 1].location for i in 1 ..< min(self.smoothPoints.count - 2, 200) { - let p1 = self.smoothPoints[self.smoothPoints.count - 1 - i].position + let p1 = self.smoothPoints[self.smoothPoints.count - 1 - i].location if p1.distance(to: p2) > self.renderArrowLength * 0.5 { direction = p2.angle(to: p1) break @@ -271,7 +266,7 @@ final class PenTool: DrawingElement { } } - self.arrowStart = self.smoothPoints.last?.position + self.arrowStart = self.smoothPoints.last?.location self.arrowDirection = direction self.maybeSetupArrow() } else if self.segments.isEmpty { @@ -284,7 +279,7 @@ final class PenTool: DrawingElement { d: CGPoint(x: point.x + radius, y: point.y + 0.1), radius1: radius, radius2: radius, - rect: .zero + rect: CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius * 2.0, height: radius * 2.0)) ) ) } @@ -303,6 +298,7 @@ final class PenTool: DrawingElement { self.arrowLeftPath = arrowLeftPath self.arrowRightPath = arrowRightPath + self.renderArrowLineWidth = self.smoothPoints.last?.width ?? self.renderArrowLineWidth } } @@ -369,7 +365,15 @@ final class PenTool: DrawingElement { let radius2: CGFloat let rect: CGRect - init(a: CGPoint, b: CGPoint, c: CGPoint, d: CGPoint, radius1: CGFloat, radius2: CGFloat, 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 @@ -381,95 +385,185 @@ final class PenTool: DrawingElement { } private struct Point { - let position: CGPoint + let location: CGPoint let width: CGFloat init( - position: CGPoint, + location: CGPoint, width: CGFloat ) { - self.position = position + self.location = location self.width = width } } - private var points: [Point] = [] + private var points: [Point] = Array(repeating: Point(location: .zero, width: 0.0), count: 4) + private var pointPtr = 0 + private var smoothPoints: [Point] = [] + private var activeSmoothPoints: [Point] = [] + private var segments: [Segment] = [] + private var activeSegments: [Segment] = [] + + private var previousActiveRect: CGRect? private var previousRenderLineWidth: CGFloat? - - private func append(point: Point) -> CGRect? { - self.points.append(point) - guard self.points.count > 2 else { return nil } + private func addPoint(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState) -> (Bool, CGRect)? { + let filterDistance: CGFloat = 10.0 + + var velocity = point.velocity + if velocity.isZero { + velocity = 1000.0 + } - let index = self.points.count - 1 - let point0 = self.points[index - 2] - let point1 = self.points[index - 1] - let point2 = self.points[index] + var renderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth - (velocity / 200.0), self.renderLineWidth)) + if let previousRenderLineWidth = self.previousRenderLineWidth { + renderLineWidth = renderLineWidth * 0.3 + previousRenderLineWidth * 0.7 + } + self.previousRenderLineWidth = renderLineWidth + + var resetActiveRect = false + var finalizedRect: CGRect? + if self.pointPtr == 0 { + self.points[0] = Point(location: point.location, width: renderLineWidth) + self.pointPtr += 1 + } else { + let previousPoint = self.points[self.pointPtr - 1].location + guard previousPoint.distance(to: point.location) > filterDistance else { + return nil + } + + if self.pointPtr >= 4 { + self.points[3] = Point( + location: self.points[2].location.point(to: point.location, t: 0.5), + width: self.points[2].width + ) + if var smoothPoints = self.currentSmoothPoints(3) { + if let previousSmoothPoint = self.smoothPoints.last { + smoothPoints.insert(previousSmoothPoint, at: 0) + } + let (segments, rect) = self.segments(fromSmoothPoints: smoothPoints) + self.smoothPoints.append(contentsOf: smoothPoints) + self.segments.append(contentsOf: segments) + finalizedRect = rect + + self.activeSmoothPoints.removeAll() + self.activeSegments.removeAll() + + resetActiveRect = true + } + + self.points[0] = self.points[3] + self.pointPtr = 1 + } + + let point = Point(location: point.location, width: renderLineWidth) + self.points[self.pointPtr] = point + self.pointPtr += 1 + } - var newSmoothPoints = self.smoothPoints( - fromPoint0: point0, - point1: point1, - point2: point2 - ) + guard let smoothPoints = self.currentSmoothPoints(self.pointPtr - 1) else { + if let finalizedRect { + return (resetActiveRect, finalizedRect) + } else { + return nil + } + } - let previousSmoothPoint = self.smoothPoints.last - self.smoothPoints.append(contentsOf: newSmoothPoints) + let (segments, rect) = self.segments(fromSmoothPoints: smoothPoints) + self.activeSmoothPoints = smoothPoints + self.activeSegments = segments - guard self.smoothPoints.count > 1 else { + var combinedRect: CGRect? + if let finalizedRect, let rect { + combinedRect = finalizedRect.union(rect) + } else { + combinedRect = rect ?? finalizedRect + } + if let combinedRect { + return (resetActiveRect, combinedRect) + } else { return nil } - - 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 rect } - private func smoothPoints(fromPoint0 point0: Point, point1: Point, point2: Point) -> [Point] { - var smoothPoints: [Point] = [] + private func currentSmoothPoints(_ ctr: Int) -> [Point]? { + switch ctr { + case 0: + return [self.points[0]] + case 1: + return self.smoothPoints(.line(self.points[0], self.points[1])) + case 2: + return self.smoothPoints(.quad(self.points[0], self.points[1], self.points[2])) + case 3: + return self.smoothPoints(.cubic(self.points[0], self.points[1], self.points[2], self.points[3])) + default: + return nil + } + } - 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 - ) + private enum SmootherInput { + case line(Point, Point) + case quad(Point, Point, Point) + case cubic(Point, Point, Point, Point) - let midWidth1 = (point0.width + point1.width) * 0.5 - let midWidth2 = (point1.width + point2.width) * 0.5 + var start: Point { + switch self { + case let .line(start, _), let .quad(start, _, _), let .cubic(start, _, _, _): + return start + } + } + var end: Point { + switch self { + case let .line(_, end), let .quad(_, _, end), let .cubic(_, _, _, end): + return end + } + } + + var distance: CGFloat { + return self.start.location.distance(to: self.end.location) + } + } + private func smoothPoints(_ input: SmootherInput) -> [Point] { let segmentDistance: CGFloat = 6.0 - let distance = midPoint1.distance(to: midPoint2) + let distance = input.distance let numberOfSegments = min(48, max(floor(distance / segmentDistance), 24)) let step = 1.0 / numberOfSegments + + var smoothPoints: [Point] = [] for t in stride(from: 0, to: 1, by: step) { - let x = midPoint1.x * pow(1 - t, 2) + point1.position.x * 2.0 * (1 - t) * t + midPoint2.x * t * t - let y = 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: x, y: y), width: w)) + let point: Point + switch input { + case let .line(start, end): + point = Point( + location: start.location.linearBezierPoint(to: end.location, t: t), + width: CGPoint(x: start.width, y: 0.0).linearBezierPoint(to: CGPoint(x: end.width, y: 0.0), t: t).x + ) + case let .quad(start, control, end): + let location = start.location.quadBezierPoint(to: end.location, controlPoint: control.location, t: t) + let width = CGPoint(x: start.width, y: 0.0).quadBezierPoint(to: CGPoint(x: end.width, y: 0.0), controlPoint: CGPoint(x: (start.width + end.width) / 2.0, y: 0.0), t: t).x + point = Point( + location: location, + width: width + ) + case let .cubic(start, control1, control2, end): + let location = start.location.cubicBezierPoint(to: end.location, controlPoint1: control1.location, controlPoint2: control2.location, t: t) + let width = CGPoint(x: start.width, y: 0.0).cubicBezierPoint(to: CGPoint(x: end.width, y: 0.0), controlPoint1: CGPoint(x: (start.width + control1.width) / 2.0, y: 0.0), controlPoint2: CGPoint(x: (control2.width + end.width) / 2.0, y: 0.0), t: t).x + point = Point( + location: location, + width: width + ) + } + smoothPoints.append(point) } - - smoothPoints.append(Point(position: midPoint2, width: midWidth2)) - + smoothPoints.append(input.end) return smoothPoints } - + fileprivate func boundingRect(from: Int, to: Int) -> CGRect { var minX: CGFloat = .greatestFiniteMagnitude var minY: CGFloat = .greatestFiniteMagnitude @@ -500,9 +594,9 @@ final class PenTool: DrawingElement { var segments: [Segment] = [] var updateRect = CGRect.null for i in 1 ..< smoothPoints.count { - let previousPoint = smoothPoints[i - 1].position + let previousPoint = smoothPoints[i - 1].location let previousWidth = smoothPoints[i - 1].width - let currentPoint = smoothPoints[i].position + let currentPoint = smoothPoints[i].location let currentWidth = smoothPoints[i].width let direction = CGPoint( x: currentPoint.x - previousPoint.x, @@ -638,5 +732,14 @@ final class PenTool: DrawingElement { context.fillPath() } } + + private func drawActiveSegments(in context: CGContext) { + context.setFillColor(self.renderColor.cgColor) + + for segment in self.activeSegments { + let path = self.pathForSegment(segment) + context.addPath(path) + context.fillPath() + } + } } - diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 99a1ac87fc..6b7b433c51 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -844,7 +844,7 @@ private final class DrawingScreenComponent: CombinedComponent { action: { [weak self] f in f.dismissWithResult(.default) if let strongSelf = self { - strongSelf.insertEntity.invoke(DrawingVectorEntity(type: .oneSidedArrow, color: strongSelf.currentColor, lineWidth: 0.5)) + strongSelf.insertEntity.invoke(DrawingVectorEntity(type: .oneSidedArrow, color: strongSelf.currentColor, lineWidth: 0.3)) } } ) @@ -2299,9 +2299,16 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { guard !self.dismissFontPicker(), let validLayout = self.validLayout?.0 else { return } + + if let entityView = self.entitiesView.selectedEntityView as? DrawingTextEntityView { + entityView.textChanged = { [weak self] in + self?.dismissFontPicker() + } + } + let fonts: [DrawingTextFont] = [ .sanFrancisco, - .other("AmericanTypewriter", "American Typewriter"), + .other("AmericanTypewriter", "Typewriter"), .other("AvenirNext-DemiBoldItalic", "Avenir Next"), .other("CourierNewPS-BoldMT", "Courier New"), .other("Noteworthy-Bold", "Noteworthy"), @@ -2312,7 +2319,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { var items: [ContextMenuItem] = [] for font in fonts { - items.append(.action(ContextMenuActionItem(text: font.title, textFont: .custom(font.uiFont(size: 17.0)), icon: { _ in return nil }, animationName: nil, action: { [weak self] f in + items.append(.action(ContextMenuActionItem(text: font.title, textFont: .custom(font: font.uiFont(size: 17.0), height: 42.0, verticalOffset: font.title == "Noteworthy" ? -6.0 : nil), icon: { _ in return nil }, animationName: nil, action: { [weak self] f in f.dismissWithResult(.default) guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else { return diff --git a/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift b/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift index 7abc0bd201..49cfd20c03 100644 --- a/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift @@ -189,7 +189,7 @@ final class DrawingSimpleShapeEntityView: DrawingEntityView { fileprivate var minimumSize: CGSize { let minSize = min(self.shapeEntity.referenceDrawingSize.width, self.shapeEntity.referenceDrawingSize.height) - return CGSize(width: minSize * 0.1, height: minSize * 0.1) + return CGSize(width: minSize * 0.2, height: minSize * 0.2) } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index 71ed7a3c96..5f829a9e97 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -44,7 +44,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { init(file: TelegramMediaFile) { self.uuid = UUID() - self.isAnimated = file.isAnimatedSticker + self.isAnimated = file.isAnimatedSticker || file.isVideoSticker self.file = file diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index 746ec0a2b5..ab104ca149 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -241,6 +241,8 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { var customEmojiContainerView: CustomEmojiContainerView? var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? + var textChanged: () -> Void = {} + init(context: AccountContext, entity: DrawingTextEntity) { self.textView = DrawingTextView(frame: .zero) self.textView.clipsToBounds = false @@ -482,6 +484,8 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.sizeToFit() self.update(afterAppendingEmoji: true) + + self.textChanged() } func insertText(_ text: NSAttributedString) { @@ -1283,7 +1287,6 @@ private var availableFonts: [String: (String, String)] = { var preferredFont: String? for name in names { - print(name) let originalName = name let name = name.lowercased() if (!name.contains("-") || name.contains("regular")) && preferredFont == nil { @@ -1299,6 +1302,5 @@ private var availableFonts: [String: (String, String)] = { result[shortname] = (preferredFont, family) } } - //print(result) return result }() diff --git a/submodules/DrawingUI/Sources/DrawingTools.swift b/submodules/DrawingUI/Sources/DrawingTools.swift index 8a64ce99a2..7b277ca189 100644 --- a/submodules/DrawingUI/Sources/DrawingTools.swift +++ b/submodules/DrawingUI/Sources/DrawingTools.swift @@ -2,7 +2,7 @@ import Foundation import UIKit import Display -final class MarkerTool: DrawingElement, Codable { +final class MarkerTool: DrawingElement { let uuid: UUID let drawingSize: CGSize @@ -17,11 +17,31 @@ final class MarkerTool: DrawingElement, Codable { weak var metalView: DrawingMetalView? var isValid: Bool { - return !self.points.isEmpty + return self.points.count > 6 } var bounds: CGRect { - return CGRect(origin: .zero, size: self.drawingSize) + var minX: CGFloat = .greatestFiniteMagnitude + var minY: CGFloat = .greatestFiniteMagnitude + var maxX: CGFloat = 0.0 + var maxY: CGFloat = 0.0 + + for point in self.points { + if point.x < minX { + minX = point.x + } + if point.x > maxX { + maxX = point.x + } + if point.y < minY { + minY = point.y + } + if point.y > maxY { + maxY = point.y + } + } + + return normalizeDrawingRect(CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY).insetBy(dx: -80.0, dy: -80.0), drawingSize: self.drawingSize) } required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat) { @@ -35,33 +55,7 @@ final class MarkerTool: DrawingElement, Codable { self.renderLineWidth = lineWidth } - - private enum CodingKeys: String, CodingKey { - case uuid - case drawingSize - case color - case renderLineWidth - case points - } - - 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.color = try container.decode(DrawingColor.self, forKey: .color) - 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.color, forKey: .color) - try container.encode(self.renderLineWidth, forKey: .renderLineWidth) - try container.encode(self.points, forKey: .points) - } - + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { return nil } @@ -71,12 +65,11 @@ final class MarkerTool: DrawingElement, Codable { } private var didSetup = false - func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { - guard case let .location(point) = path else { - return + func updatePath(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState) { + if let lastPoint = self.points.last, lastPoint.isEqual(to: point.location, epsilon: 0.1) { + } else { + self.points.append(point.location) } - - self.points.append(point.location) self.didSetup = true self.metalView?.updated(point, state: state, brush: .marker, color: self.color, size: self.renderLineWidth) @@ -90,203 +83,14 @@ final class MarkerTool: DrawingElement, Codable { context.translateBy(x: self.translation.x, y: self.translation.y) - let didSetup = self.didSetup - if didSetup { - self.didSetup = false - } else { - self.metalView?.setup(self.points, brush: .marker, color: self.color, size: self.renderLineWidth) - } self.metalView?.drawInContext(context) - if !didSetup { - self.metalView?.clear() - } - - context.restoreGState() - } -} - -final class NeonTool: DrawingElement, Codable { - class RenderLayer: SimpleLayer, DrawingRenderLayer { - var lineWidth: CGFloat = 0.0 - - let shadowLayer = SimpleShapeLayer() - let borderLayer = SimpleShapeLayer() - let fillLayer = SimpleShapeLayer() - - func setup(size: CGSize, color: DrawingColor, lineWidth: CGFloat, strokeWidth: CGFloat, shadowRadius: CGFloat) { - self.contentsScale = 1.0 - self.lineWidth = lineWidth - - let bounds = CGRect(origin: .zero, size: size) - self.frame = bounds - - self.shadowLayer.frame = bounds - self.shadowLayer.backgroundColor = UIColor.clear.cgColor - self.shadowLayer.contentsScale = 1.0 - self.shadowLayer.lineWidth = strokeWidth * 0.5 - self.shadowLayer.lineCap = .round - self.shadowLayer.lineJoin = .round - self.shadowLayer.fillColor = UIColor.white.cgColor - self.shadowLayer.strokeColor = UIColor.white.cgColor - self.shadowLayer.shadowColor = color.toCGColor() - self.shadowLayer.shadowRadius = shadowRadius - self.shadowLayer.shadowOpacity = 1.0 - self.shadowLayer.shadowOffset = .zero - - self.borderLayer.frame = bounds - self.borderLayer.contentsScale = 1.0 - self.borderLayer.lineWidth = strokeWidth - self.borderLayer.lineCap = .round - self.borderLayer.lineJoin = .round - self.borderLayer.fillColor = UIColor.clear.cgColor - self.borderLayer.strokeColor = UIColor.white.mixedWith(color.toUIColor(), alpha: 0.25).cgColor - - self.fillLayer.frame = bounds - self.fillLayer.contentsScale = 1.0 - self.fillLayer.fillColor = UIColor.white.cgColor - - self.addSublayer(self.shadowLayer) - self.addSublayer(self.borderLayer) - self.addSublayer(self.fillLayer) - } - - func updatePath(_ path: CGPath) { - self.shadowLayer.path = path - self.borderLayer.path = path - self.fillLayer.path = path - } - } - - let uuid: UUID - - let drawingSize: CGSize - let color: DrawingColor - - var renderPath: CGPath? - let renderStrokeWidth: CGFloat - let renderShadowRadius: CGFloat - let renderLineWidth: CGFloat - - var translation = CGPoint() - - private var currentRenderLayer: DrawingRenderLayer? - - var isValid: Bool { - return self.renderPath != nil - } - - var bounds: CGRect { - return CGRect(origin: .zero, size: self.drawingSize) - } - - required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat) { - self.uuid = UUID() - self.drawingSize = drawingSize - self.color = color - - let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.008 - let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.03 - - 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.renderStrokeWidth = strokeWidth - self.renderShadowRadius = shadowRadius - self.renderLineWidth = lineWidth - } - - private enum CodingKeys: String, CodingKey { - case uuid - case drawingSize - case color - case renderStrokeWidth - case renderShadowRadius - 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.color = try container.decode(DrawingColor.self, forKey: .color) - self.renderStrokeWidth = try container.decode(CGFloat.self, forKey: .renderStrokeWidth) - self.renderShadowRadius = try container.decode(CGFloat.self, forKey: .renderShadowRadius) - 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.color, forKey: .color) - try container.encode(self.renderStrokeWidth, forKey: .renderStrokeWidth) - try container.encode(self.renderShadowRadius, forKey: .renderShadowRadius) - try container.encode(self.renderLineWidth, forKey: .renderLineWidth) -// try container.encode(self.points, forKey: .points) - } - - func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { - return nil - } - - func setupRenderLayer() -> DrawingRenderLayer? { - let layer = RenderLayer() - layer.setup(size: self.drawingSize, color: self.color, lineWidth: self.renderLineWidth, strokeWidth: self.renderStrokeWidth, shadowRadius: self.renderShadowRadius) - self.currentRenderLayer = layer - return layer - } - - func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { - guard case let .smoothCurve(bezierPath) = path else { - return - } - - let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) - self.renderPath = cgPath - - if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { - currentRenderLayer.updatePath(cgPath) - } - } - - func draw(in context: CGContext, size: CGSize) { - guard let path = self.renderPath else { - return - } - context.saveGState() - - context.translateBy(x: self.translation.x, y: self.translation.y) - - context.setShouldAntialias(true) - - context.setBlendMode(.normal) - - context.addPath(path) - context.setFillColor(UIColor.white.cgColor) - context.setStrokeColor(UIColor.white.cgColor) - context.setLineWidth(self.renderStrokeWidth * 0.5) - context.setShadow(offset: .zero, blur: self.renderShadowRadius * 1.9, color: self.color.toCGColor()) - context.drawPath(using: .fillStroke) - - context.addPath(path) - context.setShadow(offset: .zero, blur: 0.0, color: UIColor.clear.cgColor) - context.setLineCap(.round) - context.setLineWidth(self.renderStrokeWidth) - context.setStrokeColor(UIColor.white.mixedWith(self.color.toUIColor(), alpha: 0.25).cgColor) - context.strokePath() - - context.addPath(path) - context.setFillColor(UIColor.white.cgColor) - - context.fillPath() + self.metalView?.clear() context.restoreGState() } } -final class FillTool: DrawingElement, Codable { +final class FillTool: DrawingElement { let uuid: UUID let drawingSize: CGSize @@ -311,30 +115,7 @@ final class FillTool: DrawingElement, Codable { self.isBlur = blur self.blurredImage = blurredImage } - - private enum CodingKeys: String, CodingKey { - case uuid - case drawingSize - case color - } - - 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.color = try container.decode(DrawingColor.self, forKey: .color) - self.isBlur = false -// 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.color, forKey: .color) -// try container.encode(self.points, forKey: .points) - } - + func setupRenderView(screenSize: CGSize) -> DrawingRenderView? { return nil } @@ -343,7 +124,7 @@ final class FillTool: DrawingElement, Codable { return nil } - func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { + func updatePath(_ path: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState) { } func draw(in context: CGContext, size: CGSize) { @@ -365,12 +146,4 @@ final class FillTool: DrawingElement, Codable { context.setBlendMode(.normal) } - - func containsPoint(_ point: CGPoint) -> Bool { - return false - } - - func hasPointsInsidePath(_ path: UIBezierPath) -> Bool { - return false - } } diff --git a/submodules/DrawingUI/Sources/DrawingUtils.swift b/submodules/DrawingUI/Sources/DrawingUtils.swift index 8711f005e9..3570f6cbea 100644 --- a/submodules/DrawingUI/Sources/DrawingUtils.swift +++ b/submodules/DrawingUI/Sources/DrawingUtils.swift @@ -587,118 +587,6 @@ private func configureControlPoints(data: [CGPoint]) -> [(ctrl1: CGPoint, ctrl2: return [] } -class BezierPath { - struct Element { - enum ElementType { - case moveTo - case addLine - case cubicCurve - case quadCurve - } - - let type: ElementType - - var startPoint: Polyline.Point - var endPoint: Polyline.Point - var controlPoints: [CGPoint] - - var lengthRange: ClosedRange? - var calculatedLength: CGFloat? - - func point(at t: CGFloat) -> CGPoint { - switch self.type { - case .addLine: - return self.startPoint.location.linearBezierPoint(to: self.endPoint.location, t: t) - case .cubicCurve: - return self.startPoint.location.cubicBezierPoint(to: self.endPoint.location, controlPoint1: self.controlPoints[0], controlPoint2: self.controlPoints[1], t: t) - case .quadCurve: - return self.startPoint.location.quadBezierPoint(to: self.endPoint.location, controlPoint: self.controlPoints[0], t: t) - default: - return .zero - } - } - } - - let path = UIBezierPath() - - var elements: [Element] = [] - var elementCount: Int { - return self.elements.count - } - - func element(at t: CGFloat) -> (element: Element, innerT: CGFloat)? { - let t = min(max(0.0, t), 1.0) - - for element in elements { - if let lengthRange = element.lengthRange, lengthRange.contains(t) { - let innerT = (t - lengthRange.lowerBound) / (lengthRange.upperBound - lengthRange.lowerBound) - return (element, innerT) - } - } - return nil - } - - var points: [Int: CGPoint] = [:] - func point(at t: CGFloat) -> CGPoint? { - if let (element, innerT) = self.element(at: t) { - return element.point(at: innerT) - } else { - return nil - } - } - - func append(_ element: Element) { - self.elements.append(element) - switch element.type { - case .moveTo: - self.move(to: element.startPoint.location) - case .addLine: - self.addLine(to: element.endPoint.location) - case .cubicCurve: - self.addCurve(to: element.endPoint.location, controlPoint1: element.controlPoints[0], controlPoint2: element.controlPoints[1]) - case .quadCurve: - self.addQuadCurve(to: element.endPoint.location, controlPoint: element.controlPoints[0]) - } - } - - private func move(to point: CGPoint) { - self.path.move(to: point) - } - - private func addLine(to point: CGPoint) { - self.path.addLine(to: point) - } - - private func addQuadCurve(to point: CGPoint, controlPoint: CGPoint) { - self.path.addQuadCurve(to: point, controlPoint: controlPoint) - } - - private func addCurve(to point: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint) { - self.path.addCurve(to: point, controlPoint1: controlPoint1, controlPoint2: controlPoint2) - } - - private func close() { - self.path.close() - } - - func trimming(to elementIndex: Int) -> BezierPath { - let outputPath = BezierPath() - for element in self.elements[0 ... elementIndex] { - outputPath.append(element) - } - return outputPath - } - - func closedCopy() -> BezierPath { - let outputPath = BezierPath() - for element in self.elements { - outputPath.append(element) - } - outputPath.close() - return outputPath - } -} - class Matrix { private(set) var m: [Float] @@ -767,3 +655,22 @@ extension CGPoint { return self.offsetBy(dx: offset.x, dy: offset.y) } } + +func normalizeDrawingRect(_ rect: CGRect, drawingSize: CGSize) -> CGRect { + var rect = rect + if rect.origin.x < 0.0 { + rect.size.width += rect.origin.x + rect.origin.x = 0.0 + } + if rect.origin.y < 0.0 { + rect.size.height += rect.origin.y + rect.origin.y = 0.0 + } + if rect.maxX > drawingSize.width { + rect.size.width -= (rect.maxX - drawingSize.width) + } + if rect.maxY > drawingSize.height { + rect.size.height -= (rect.maxY - drawingSize.height) + } + return rect +} diff --git a/submodules/DrawingUI/Sources/DrawingView.swift b/submodules/DrawingUI/Sources/DrawingView.swift index f448bdb95e..868724d478 100644 --- a/submodules/DrawingUI/Sources/DrawingView.swift +++ b/submodules/DrawingUI/Sources/DrawingView.swift @@ -23,7 +23,7 @@ protocol DrawingElement: AnyObject { func setupRenderView(screenSize: CGSize) -> DrawingRenderView? func setupRenderLayer() -> DrawingRenderLayer? - func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) + func updatePath(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState) func draw(in: CGContext, size: CGSize) } @@ -84,7 +84,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw private var pannedSelectionView: UIView - private var metalView: DrawingMetalView + private var metalView: DrawingMetalView? private let brushSizePreviewLayer: SimpleShapeLayer @@ -153,9 +153,6 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.pannedSelectionView.backgroundColor = .clear self.pannedSelectionView.isUserInteractionEnabled = false - self.metalView = DrawingMetalView(size: size)! - self.metalView.isHidden = true - self.brushSizePreviewLayer = SimpleShapeLayer() self.brushSizePreviewLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 100.0, height: 100.0)) self.brushSizePreviewLayer.strokeColor = UIColor(rgb: 0x919191).cgColor @@ -178,8 +175,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.isExclusiveTouch = true self.addSubview(self.currentDrawingViewContainer) - self.addSubview(self.metalView) - + self.layer.addSublayer(self.brushSizePreviewLayer) let drawingGesturePipeline = DrawingGesturePipeline(view: self) @@ -202,7 +198,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw return false } } - drawingGesturePipeline.onDrawing = { [weak self] state, path in + drawingGesturePipeline.onDrawing = { [weak self] state, point in guard let strongSelf = self else { return } @@ -216,14 +212,14 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw strongSelf.finishDrawing(rect: CGRect(origin: .zero, size: strongSelf.imageSize), synchronous: true) } - guard let newElement = strongSelf.prepareNewElement() else { + if case .marker = strongSelf.tool, let metalView = strongSelf.metalView { + metalView.isHidden = false + } + + guard let newElement = strongSelf.setupNewElement() else { return } - - if newElement is MarkerTool { - strongSelf.metalView.isHidden = false - } - + if let renderView = newElement.setupRenderView(screenSize: strongSelf.screenSize) { if let currentDrawingView = strongSelf.currentDrawingRenderView { strongSelf.currentDrawingRenderView = nil @@ -267,84 +263,83 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } strongSelf.currentDrawingLayer = renderLayer } - newElement.updatePath(path, state: state) + newElement.updatePath(point, state: state) strongSelf.uncommitedElement = newElement strongSelf.updateInternalState() case .changed: - strongSelf.uncommitedElement?.updatePath(path, state: state) - - if case let .polyline(line) = path, let lastPoint = line.points.last { - if let previousStrokePoint = strongSelf.previousStrokePoint, line.points.count > 10 { - let currentTimestamp = CACurrentMediaTime() - if lastPoint.location.distance(to: previousStrokePoint) > 10.0 { - strongSelf.previousStrokePoint = lastPoint.location - - strongSelf.strokeRecognitionTimer?.invalidate() - strongSelf.strokeRecognitionTimer = nil - } - - if strongSelf.strokeRecognitionTimer == nil, let startTimestamp = strongSelf.drawingGestureStartTimestamp, currentTimestamp - startTimestamp < 3.0 { - strongSelf.strokeRecognitionTimer = SwiftSignalKit.Timer(timeout: 0.85, repeat: false, completion: { [weak self] in - guard let strongSelf = self else { - return - } - if let previousStrokePoint = strongSelf.previousStrokePoint, lastPoint.location.distance(to: previousStrokePoint) <= 10.0 { - let strokeRecognizer = Unistroke(points: line.points.map { $0.location }) - if let template = strokeRecognizer.match(templates: strongSelf.loadedTemplates, minThreshold: 0.5) { - let edges = line.bounds - let bounds = CGRect(origin: edges.origin, size: CGSize(width: edges.width - edges.minX, height: edges.height - edges.minY)) - - var entity: DrawingEntity? - if template == "shape_rectangle" { - let shapeEntity = DrawingSimpleShapeEntity(shapeType: .rectangle, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) - shapeEntity.referenceDrawingSize = strongSelf.imageSize - shapeEntity.position = bounds.center - shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1) - entity = shapeEntity - } else if template == "shape_circle" { - let shapeEntity = DrawingSimpleShapeEntity(shapeType: .ellipse, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) - shapeEntity.referenceDrawingSize = strongSelf.imageSize - shapeEntity.position = bounds.center - shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1) - entity = shapeEntity - } else if template == "shape_star" { - let shapeEntity = DrawingSimpleShapeEntity(shapeType: .star, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) - shapeEntity.referenceDrawingSize = strongSelf.imageSize - shapeEntity.position = bounds.center - shapeEntity.size = CGSize(width: max(bounds.width, bounds.height) * 1.1, height: max(bounds.width, bounds.height) * 1.1) - entity = shapeEntity - } else if template == "shape_arrow" { - let arrowEntity = DrawingVectorEntity(type: .oneSidedArrow, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) - arrowEntity.referenceDrawingSize = strongSelf.imageSize - arrowEntity.start = line.points.first?.location ?? .zero - arrowEntity.end = line.points[line.points.count - 4].location - entity = arrowEntity - } - - if let entity = entity { - strongSelf.entitiesView?.add(entity) - strongSelf.entitiesView?.selectEntity(entity) - strongSelf.cancelDrawing() - strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = false - strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = true - } - } - } - strongSelf.strokeRecognitionTimer?.invalidate() - strongSelf.strokeRecognitionTimer = nil - }, queue: Queue.mainQueue()) - strongSelf.strokeRecognitionTimer?.start() - } - } else { - strongSelf.previousStrokePoint = lastPoint.location - } - } + strongSelf.uncommitedElement?.updatePath(point, state: state) +// if case let .direct(point) = path, let lastPoint = line.points.last { +// if let previousStrokePoint = strongSelf.previousStrokePoint, line.points.count > 10 { +// let currentTimestamp = CACurrentMediaTime() +// if lastPoint.location.distance(to: previousStrokePoint) > 10.0 { +// strongSelf.previousStrokePoint = lastPoint.location +// +// strongSelf.strokeRecognitionTimer?.invalidate() +// strongSelf.strokeRecognitionTimer = nil +// } +// +// if strongSelf.strokeRecognitionTimer == nil, let startTimestamp = strongSelf.drawingGestureStartTimestamp, currentTimestamp - startTimestamp < 3.0 { +// strongSelf.strokeRecognitionTimer = SwiftSignalKit.Timer(timeout: 0.85, repeat: false, completion: { [weak self] in +// guard let strongSelf = self else { +// return +// } +// if let previousStrokePoint = strongSelf.previousStrokePoint, lastPoint.location.distance(to: previousStrokePoint) <= 10.0 { +// let strokeRecognizer = Unistroke(points: line.points.map { $0.location }) +// if let template = strokeRecognizer.match(templates: strongSelf.loadedTemplates, minThreshold: 0.5) { +// let edges = line.bounds +// let bounds = CGRect(origin: edges.origin, size: CGSize(width: edges.width - edges.minX, height: edges.height - edges.minY)) +// +// var entity: DrawingEntity? +// if template == "shape_rectangle" { +// let shapeEntity = DrawingSimpleShapeEntity(shapeType: .rectangle, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) +// shapeEntity.referenceDrawingSize = strongSelf.imageSize +// shapeEntity.position = bounds.center +// shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1) +// entity = shapeEntity +// } else if template == "shape_circle" { +// let shapeEntity = DrawingSimpleShapeEntity(shapeType: .ellipse, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) +// shapeEntity.referenceDrawingSize = strongSelf.imageSize +// shapeEntity.position = bounds.center +// shapeEntity.size = CGSize(width: bounds.size.width * 1.1, height: bounds.size.height * 1.1) +// entity = shapeEntity +// } else if template == "shape_star" { +// let shapeEntity = DrawingSimpleShapeEntity(shapeType: .star, drawType: .stroke, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) +// shapeEntity.referenceDrawingSize = strongSelf.imageSize +// shapeEntity.position = bounds.center +// shapeEntity.size = CGSize(width: max(bounds.width, bounds.height) * 1.1, height: max(bounds.width, bounds.height) * 1.1) +// entity = shapeEntity +// } else if template == "shape_arrow" { +// let arrowEntity = DrawingVectorEntity(type: .oneSidedArrow, color: strongSelf.toolColor, lineWidth: strongSelf.toolBrushSize) +// arrowEntity.referenceDrawingSize = strongSelf.imageSize +// arrowEntity.start = line.points.first?.location ?? .zero +// arrowEntity.end = line.points[line.points.count - 4].location +// entity = arrowEntity +// } +// +// if let entity = entity { +// strongSelf.entitiesView?.add(entity) +// strongSelf.entitiesView?.selectEntity(entity) +// strongSelf.cancelDrawing() +// strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = false +// strongSelf.drawingGesturePipeline?.gestureRecognizer?.isEnabled = true +// } +// } +// } +// strongSelf.strokeRecognitionTimer?.invalidate() +// strongSelf.strokeRecognitionTimer = nil +// }, queue: Queue.mainQueue()) +// strongSelf.strokeRecognitionTimer?.start() +// } +// } else { +// strongSelf.previousStrokePoint = lastPoint.location +// } +// } case .ended: strongSelf.isDrawing = false strongSelf.strokeRecognitionTimer?.invalidate() strongSelf.strokeRecognitionTimer = nil - strongSelf.uncommitedElement?.updatePath(path, state: state) + strongSelf.uncommitedElement?.updatePath(point, state: state) let bounds = strongSelf.uncommitedElement?.bounds Queue.mainQueue().after(0.05) { @@ -550,8 +545,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } if self.tool == .marker { - self.metalView.clear() - self.metalView.isHidden = true + //self.metalView?.clear() + self.metalView?.isHidden = true } completion() } @@ -782,32 +777,33 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw let previousTool = self.tool switch state { case let .pen(brushState): - self.drawingGesturePipeline?.mode = .direct self.tool = .pen self.toolColor = brushState.color self.toolBrushSize = brushState.size case let .arrow(brushState): - self.drawingGesturePipeline?.mode = .direct self.tool = .arrow self.toolColor = brushState.color self.toolBrushSize = brushState.size case let .marker(brushState): - self.drawingGesturePipeline?.mode = .location self.tool = .marker self.toolColor = brushState.color self.toolBrushSize = brushState.size + + if self.metalView == nil, let metalView = DrawingMetalView(size: self.imageSize) { + metalView.transform = self.currentDrawingViewContainer.transform + metalView.frame = self.currentDrawingViewContainer.frame + self.insertSubview(metalView, aboveSubview: self.currentDrawingViewContainer) + self.metalView = metalView + } case let .neon(brushState): - self.drawingGesturePipeline?.mode = .smoothCurve self.tool = .neon self.toolColor = brushState.color self.toolBrushSize = brushState.size case let .blur(blurState): self.tool = .blur - self.drawingGesturePipeline?.mode = .direct self.toolBrushSize = blurState.size case let .eraser(eraserState): self.tool = .eraser - self.drawingGesturePipeline?.mode = .direct self.toolBrushSize = eraserState.size } @@ -859,7 +855,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.updateInternalState() } - private func prepareNewElement() -> DrawingElement? { + private func setupNewElement() -> DrawingElement? { let scale = 1.0 / self.zoomScale let element: DrawingElement? switch self.tool { @@ -952,9 +948,11 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.drawingGesturePipeline?.transform = CGAffineTransformMakeScale(1.0 / scale, 1.0 / scale) - self.metalView.transform = transform - self.metalView.frame = self.bounds - + if let metalView = self.metalView { + metalView.transform = transform + metalView.frame = self.bounds + } + self.brushSizePreviewLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0) } diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index 898b28e6d8..f9b97ef85f 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -209,7 +209,6 @@ class StickerPickerScreen: ViewController { let dim: ASDisplayNode let wrappingView: UIView let containerView: UIView - let scrollView: UIScrollView let hostView: ComponentHostView private var content: StickerPickerInputData? @@ -234,16 +233,12 @@ class StickerPickerScreen: ViewController { self.dim.alpha = 0.0 self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) - self.wrappingView = UIView() - self.containerView = UIView() - self.scrollView = UIScrollView() + self.wrappingView = SparseContainerView() + self.containerView = SparseContainerView() self.hostView = ComponentHostView() super.init() - - self.scrollView.delegate = self - self.scrollView.showsVerticalScrollIndicator = false - + self.containerView.clipsToBounds = true self.containerView.backgroundColor = .clear @@ -251,8 +246,7 @@ class StickerPickerScreen: ViewController { self.view.addSubview(self.wrappingView) self.wrappingView.addSubview(self.containerView) - self.containerView.addSubview(self.scrollView) - self.scrollView.addSubview(self.hostView) + self.containerView.addSubview(self.hostView) self.contentDisposable.set(controller.inputData.start(next: { [weak self] inputData in if let strongSelf = self { @@ -359,6 +353,9 @@ class StickerPickerScreen: ViewController { }, updateSearchQuery: { _, _ in }, + updateScrollingToItemGroup: { [weak self] in + self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -433,6 +430,9 @@ class StickerPickerScreen: ViewController { }, updateSearchQuery: { _, _ in }, + updateScrollingToItemGroup: { [weak self] in + self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -554,6 +554,9 @@ class StickerPickerScreen: ViewController { }, updateSearchQuery: { _, _ in }, + updateScrollingToItemGroup: { [weak self] in + self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) + }, chatPeerId: nil, peekBehavior: stickerPeekBehavior, customLayout: nil, @@ -599,11 +602,6 @@ class StickerPickerScreen: ViewController { return true } - func scrollViewDidScroll(_ scrollView: UIScrollView) { - let contentOffset = self.scrollView.contentOffset.y - self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate) - } - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { if otherGestureRecognizer is PagerPanGestureRecognizer { @@ -727,7 +725,6 @@ class StickerPickerScreen: ViewController { } transition.setFrame(view: self.containerView, frame: clipFrame) - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil) if let content = self.content { var stickersTransition: Transition = transition @@ -756,7 +753,6 @@ class StickerPickerScreen: ViewController { ) contentSize.height = max(layout.size.height - navigationHeight, contentSize.height) transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) - self.scrollView.contentSize = contentSize } } @@ -817,11 +813,10 @@ class StickerPickerScreen: ViewController { let point = recognizer.location(in: self.view) let currentHitView = self.hitTest(point, with: nil) - var scrollViewAndListNode = self.findScrollView(view: currentHitView) - if scrollViewAndListNode?.frame.height == self.frame.width { - scrollViewAndListNode = nil + var scrollView = self.findScrollView(view: currentHitView) + if scrollView?.frame.height == self.frame.width { + scrollView = nil } - let scrollView = scrollViewAndListNode let topInset: CGFloat if self.isExpanded { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 129629bf69..78726fe60f 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -1458,6 +1458,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { })) } }, + updateScrollingToItemGroup: { + }, chatPeerId: nil, peekBehavior: nil, customLayout: emojiContentLayout, diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index a8c3001c82..d0258d654b 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -876,6 +876,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { })) } }, + updateScrollingToItemGroup: { + }, chatPeerId: chatPeerId, peekBehavior: nil, customLayout: nil, @@ -1090,6 +1092,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { }, updateSearchQuery: { _, _ in }, + updateScrollingToItemGroup: { + }, chatPeerId: chatPeerId, peekBehavior: stickerPeekBehavior, customLayout: nil, @@ -1111,11 +1115,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { var inputData = inputData inputData.gifs = gifs + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + if let emojiSearchResult = emojiSearchResult { var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults? if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) { emptySearchResults = EmojiPagerContentComponent.EmptySearchResults( - text: "No emoji found", //strongSelf.presentationData.strings.EmojiSearch_SearchStatusesEmptyResult, + text: presentationData.strings.EmojiSearch_SearchEmojiEmptyResult, iconFile: nil ) } @@ -1861,6 +1867,8 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi }, updateSearchQuery: { _, _ in }, + updateScrollingToItemGroup: { + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 89c8e373ac..0917647e6b 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -554,6 +554,8 @@ public final class EmojiStatusSelectionController: ViewController { })) } }, + updateScrollingToItemGroup: { + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index cb14429e6d..e17f17d9a7 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -2113,6 +2113,7 @@ public final class EmojiPagerContentComponent: Component { public let navigationController: () -> NavigationController? public let requestUpdate: (Transition) -> Void public let updateSearchQuery: (String, String) -> Void + public let updateScrollingToItemGroup: () -> Void public let chatPeerId: PeerId? public let peekBehavior: EmojiContentPeekBehavior? public let customLayout: CustomLayout? @@ -2135,6 +2136,7 @@ public final class EmojiPagerContentComponent: Component { navigationController: @escaping () -> NavigationController?, requestUpdate: @escaping (Transition) -> Void, updateSearchQuery: @escaping (String, String) -> Void, + updateScrollingToItemGroup: @escaping () -> Void, chatPeerId: PeerId?, peekBehavior: EmojiContentPeekBehavior?, customLayout: CustomLayout?, @@ -2156,6 +2158,7 @@ public final class EmojiPagerContentComponent: Component { self.navigationController = navigationController self.requestUpdate = requestUpdate self.updateSearchQuery = updateSearchQuery + self.updateScrollingToItemGroup = updateScrollingToItemGroup self.chatPeerId = chatPeerId self.peekBehavior = peekBehavior self.customLayout = customLayout @@ -7653,8 +7656,10 @@ public final class EmojiPagerContentComponent: Component { } } + let isMasks = stickerNamespaces.contains(Namespaces.ItemCollection.CloudMaskPacks) + return EmojiPagerContentComponent( - id: "stickers", + id: isMasks ? "masks" : "stickers", context: context, avatarPeer: avatarPeer, animationCache: animationCache, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index b27e146844..ce4688b83d 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -362,7 +362,7 @@ public final class EntityKeyboardComponent: Component { guard let strongSelf = self else { return } - strongSelf.reorderPacks(category: .stickers, items: items) + strongSelf.reorderPacks(category: .masks, items: items) } )))) contentIcons.append(PagerComponentContentIcon(id: "masks", imageName: "Chat/Input/Media/EntityInputMasksIcon")) @@ -913,6 +913,8 @@ public final class EntityKeyboardComponent: Component { if let topPanelView = pagerView.topPanelComponentView as? EntityKeyboardTopContainerPanelComponent.View { topPanelView.internalUpdatePanelsAreCollapsed() } + self.component?.emojiContent?.inputInteractionHolder.inputInteraction?.updateScrollingToItemGroup() + pagerContentView.scrollToItemGroup(id: groupId, subgroupId: subgroupId) pagerView.collapseTopPanel() } diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 9532cff772..5cf399cfda 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -964,6 +964,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { }, updateSearchQuery: { _, _ in }, + updateScrollingToItemGroup: { + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index fca1c5d022..f6ca5dd45e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -409,7 +409,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState let presentationData = context.sharedContext.currentPresentationData.with { $0 } var actions: [ContextMenuItem] = [] - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0)), badge: nil, icon: { theme in + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { c, _ in c.dismiss(completion: { @@ -419,7 +419,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) if !chatPresentationInterfaceState.isPremium && !premiumConfiguration.isPremiumDisabled { - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0)), badge: nil, icon: { theme in + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { c, _ in c.dismiss(completion: { diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index b8b5751829..b4e51c0256 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -256,11 +256,19 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { } func reveal() { + self.isRevealed = true self.blurredImageNode.removeFromSupernode() self.dustNode.removeFromSupernode() - self.isUserInteractionEnabled = false } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if self.isRevealed { + return nil + } + return result + } + func update(size: CGSize, text: String, imageSignal: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize, CGSize)?, imageFrame: CGRect, corners: ImageCorners?) { let spacing: CGFloat = 2.0 let padding: CGFloat = 10.0 @@ -274,14 +282,15 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { apply() self.blurredImageNode.isHidden = false - self.isUserInteractionEnabled = !self.dustNode.isRevealed - + + self.isRevealed = self.dustNode.isRevealed self.dustNode.revealed = { [weak self] in self?.isRevealed = true self?.blurredImageNode.removeFromSupernode() self?.isUserInteractionEnabled = false } self.dustNode.tapped = { [weak self] in + self?.isRevealed = true self?.tapped() } } else { @@ -1916,7 +1925,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } else if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) { displaySpoiler = true } - + if displaySpoiler { if self.extendedMediaOverlayNode == nil { let extendedMediaOverlayNode = ExtendedMediaOverlayNode() @@ -1929,6 +1938,16 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } self.extendedMediaOverlayNode?.frame = self.imageNode.frame + var tappable = false + switch state { + case .play, .pause, .download, .none: + tappable = false + default: + break + } + + self.extendedMediaOverlayNode?.isUserInteractionEnabled = tappable + var paymentText: String = "" outer: for attribute in message.attributes { if let attribute = attribute as? ReplyMarkupMessageAttribute {