mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Various improvements
This commit is contained in:
parent
6ece02e998
commit
a0f3cfef58
@ -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";
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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<float> 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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
277
submodules/DrawingUI/Sources/DrawingNeonTool.swift
Normal file
277
submodules/DrawingUI/Sources/DrawingNeonTool.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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<CGFloat>?
|
||||
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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -209,7 +209,6 @@ class StickerPickerScreen: ViewController {
|
||||
let dim: ASDisplayNode
|
||||
let wrappingView: UIView
|
||||
let containerView: UIView
|
||||
let scrollView: UIScrollView
|
||||
let hostView: ComponentHostView<Empty>
|
||||
|
||||
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 {
|
||||
|
@ -1458,6 +1458,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}))
|
||||
}
|
||||
},
|
||||
updateScrollingToItemGroup: {
|
||||
},
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil,
|
||||
customLayout: emojiContentLayout,
|
||||
|
@ -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,
|
||||
|
@ -554,6 +554,8 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
}))
|
||||
}
|
||||
},
|
||||
updateScrollingToItemGroup: {
|
||||
},
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil,
|
||||
customLayout: nil,
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -964,6 +964,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
},
|
||||
updateSearchQuery: { _, _ in
|
||||
},
|
||||
updateScrollingToItemGroup: {
|
||||
},
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil,
|
||||
customLayout: nil,
|
||||
|
@ -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: {
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user