mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Various improvements
This commit is contained in:
@@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
|
||||
public enum SemanticStatusNodeState: Equatable {
|
||||
public struct ProgressAppearance: Equatable {
|
||||
@@ -14,10 +15,19 @@ public enum SemanticStatusNodeState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct CheckAppearance: Equatable {
|
||||
public var lineWidth: CGFloat
|
||||
|
||||
public init(lineWidth: CGFloat) {
|
||||
self.lineWidth = lineWidth
|
||||
}
|
||||
}
|
||||
|
||||
case none
|
||||
case download
|
||||
case play
|
||||
case pause
|
||||
case check(appearance: CheckAppearance?)
|
||||
case progress(value: CGFloat?, cancelEnabled: Bool, appearance: ProgressAppearance?)
|
||||
case customIcon(UIImage)
|
||||
}
|
||||
@@ -390,7 +400,7 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
|
||||
let previousValue = value
|
||||
self.value = value
|
||||
let timestamp = CACurrentMediaTime()
|
||||
if let value = value, let previousValue = previousValue {
|
||||
if let _ = value, let previousValue = previousValue {
|
||||
if let transition = self.transition {
|
||||
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: transition.valueAt(timestamp: timestamp, actualValue: previousValue))
|
||||
} else {
|
||||
@@ -403,6 +413,116 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
|
||||
}
|
||||
}
|
||||
|
||||
private final class SemanticStatusNodeCheckContext: SemanticStatusNodeStateContext {
|
||||
final class DrawingState: NSObject, SemanticStatusNodeStateDrawingState {
|
||||
let transitionFraction: CGFloat
|
||||
let value: CGFloat
|
||||
let appearance: SemanticStatusNodeState.CheckAppearance?
|
||||
|
||||
init(transitionFraction: CGFloat, value: CGFloat, appearance: SemanticStatusNodeState.CheckAppearance?) {
|
||||
self.transitionFraction = transitionFraction
|
||||
self.value = value
|
||||
self.appearance = appearance
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func draw(context: CGContext, size: CGSize, foregroundColor: UIColor) {
|
||||
let diameter = size.width
|
||||
|
||||
let factor = diameter / 50.0
|
||||
|
||||
context.saveGState()
|
||||
|
||||
if foregroundColor.alpha.isZero {
|
||||
context.setBlendMode(.destinationOut)
|
||||
context.setFillColor(UIColor(white: 0.0, alpha: self.transitionFraction).cgColor)
|
||||
context.setStrokeColor(UIColor(white: 0.0, alpha: self.transitionFraction).cgColor)
|
||||
} else {
|
||||
context.setBlendMode(.normal)
|
||||
context.setFillColor(foregroundColor.withAlphaComponent(foregroundColor.alpha * self.transitionFraction).cgColor)
|
||||
context.setStrokeColor(foregroundColor.withAlphaComponent(foregroundColor.alpha * self.transitionFraction).cgColor)
|
||||
}
|
||||
|
||||
let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0)
|
||||
|
||||
let lineWidth: CGFloat
|
||||
if let appearance = self.appearance {
|
||||
lineWidth = appearance.lineWidth
|
||||
} else {
|
||||
lineWidth = max(1.6, 2.25 * factor)
|
||||
}
|
||||
|
||||
context.setLineWidth(max(1.7, lineWidth * factor))
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.setMiterLimit(10.0)
|
||||
|
||||
let progress = self.value
|
||||
let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0))
|
||||
|
||||
var s = CGPoint(x: center.x - 10.0 * factor, y: center.y + 1.0 * factor)
|
||||
var p1 = CGPoint(x: 7.0 * factor, y: 7.0 * factor)
|
||||
var p2 = CGPoint(x: 13.0 * factor, y: -15.0 * factor)
|
||||
|
||||
if diameter < 36.0 {
|
||||
s = CGPoint(x: center.x - 7.0 * factor, y: center.y + 1.0 * factor)
|
||||
p1 = CGPoint(x: 4.5 * factor, y: 4.5 * factor)
|
||||
p2 = CGPoint(x: 10.0 * factor, y: -11.0 * factor)
|
||||
}
|
||||
|
||||
if !firstSegment.isZero {
|
||||
if firstSegment < 1.0 {
|
||||
context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
|
||||
context.addLine(to: s)
|
||||
} else {
|
||||
let secondSegment = (progress - 0.33) * 1.5
|
||||
context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
|
||||
context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
|
||||
context.addLine(to: s)
|
||||
}
|
||||
}
|
||||
context.strokePath()
|
||||
}
|
||||
}
|
||||
|
||||
var value: CGFloat
|
||||
let appearance: SemanticStatusNodeState.CheckAppearance?
|
||||
var transition: SemanticStatusNodeProgressTransition?
|
||||
|
||||
var isAnimating: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
init(value: CGFloat, appearance: SemanticStatusNodeState.CheckAppearance?) {
|
||||
self.value = value
|
||||
self.appearance = appearance
|
||||
|
||||
self.animate()
|
||||
}
|
||||
|
||||
func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
let resolvedValue: CGFloat
|
||||
if let transition = self.transition {
|
||||
resolvedValue = transition.valueAt(timestamp: timestamp, actualValue: value)
|
||||
} else {
|
||||
resolvedValue = value
|
||||
}
|
||||
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, appearance: self.appearance)
|
||||
}
|
||||
|
||||
func animate() {
|
||||
guard self.value < 1.0 else {
|
||||
return
|
||||
}
|
||||
let timestamp = CACurrentMediaTime()
|
||||
self.value = 1.0
|
||||
self.transition = SemanticStatusNodeProgressTransition(beginTime: timestamp, initialValue: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SemanticStatusNodeState {
|
||||
func context(current: SemanticStatusNodeStateContext?) -> SemanticStatusNodeStateContext {
|
||||
switch self {
|
||||
@@ -427,6 +547,12 @@ private extension SemanticStatusNodeState {
|
||||
} else {
|
||||
return SemanticStatusNodeIconContext(icon: icon)
|
||||
}
|
||||
case let .check(appearance):
|
||||
if let current = current as? SemanticStatusNodeCheckContext {
|
||||
return current
|
||||
} else {
|
||||
return SemanticStatusNodeCheckContext(value: 0.0, appearance: appearance)
|
||||
}
|
||||
case let .progress(value, cancelEnabled, appearance):
|
||||
if let current = current as? SemanticStatusNodeProgressContext, current.displayCancel == cancelEnabled {
|
||||
current.updateValue(value: value)
|
||||
@@ -454,14 +580,18 @@ private final class SemanticStatusNodeDrawingState: NSObject {
|
||||
let hollow: Bool
|
||||
let transitionState: SemanticStatusNodeTransitionDrawingState?
|
||||
let drawingState: SemanticStatusNodeStateDrawingState
|
||||
let backgroundImage: UIImage?
|
||||
let overlayForeground: UIColor?
|
||||
let cutout: SemanticStatusNode.Cutout?
|
||||
|
||||
init(background: UIColor, foreground: UIColor, hollow: Bool, transitionState: SemanticStatusNodeTransitionDrawingState?, drawingState: SemanticStatusNodeStateDrawingState, cutout: SemanticStatusNode.Cutout?) {
|
||||
init(background: UIColor, foreground: UIColor, hollow: Bool, transitionState: SemanticStatusNodeTransitionDrawingState?, drawingState: SemanticStatusNodeStateDrawingState, backgroundImage: UIImage?, overlayForeground: UIColor?, cutout: SemanticStatusNode.Cutout?) {
|
||||
self.background = background
|
||||
self.foreground = foreground
|
||||
self.hollow = hollow
|
||||
self.transitionState = transitionState
|
||||
self.drawingState = drawingState
|
||||
self.backgroundImage = backgroundImage
|
||||
self.overlayForeground = overlayForeground
|
||||
self.cutout = cutout
|
||||
|
||||
super.init()
|
||||
@@ -503,6 +633,14 @@ public final class SemanticStatusNode: ASControlNode {
|
||||
}
|
||||
}
|
||||
|
||||
public var overlayForegroundNodeColor: UIColor? {
|
||||
didSet {
|
||||
if !(self.overlayForegroundNodeColor?.isEqual(oldValue) ?? true) {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let hollow: Bool
|
||||
|
||||
private var animator: ConstantDisplayLinkAnimator?
|
||||
@@ -512,9 +650,13 @@ public final class SemanticStatusNode: ASControlNode {
|
||||
private var transtionContext: SemanticStatusNodeTransitionContext?
|
||||
private var stateContext: SemanticStatusNodeStateContext
|
||||
|
||||
public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, hollow: Bool = false) {
|
||||
private var disposable: Disposable?
|
||||
private var backgroundNodeImage: UIImage?
|
||||
|
||||
public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? = nil, overlayForegroundNodeColor: UIColor? = nil, hollow: Bool = false) {
|
||||
self.backgroundNodeColor = backgroundNodeColor
|
||||
self.foregroundNodeColor = foregroundNodeColor
|
||||
self.overlayForegroundNodeColor = overlayForegroundNodeColor
|
||||
self.hollow = hollow
|
||||
self.state = .none
|
||||
self.stateContext = self.state.context(current: nil)
|
||||
@@ -523,6 +665,22 @@ public final class SemanticStatusNode: ASControlNode {
|
||||
|
||||
self.isOpaque = false
|
||||
self.displaysAsynchronously = true
|
||||
|
||||
if let image = image {
|
||||
self.disposable = (image
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transform in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let context = transform(TransformImageArguments(corners: ImageCorners(radius: strongSelf.bounds.width / 2.0), imageSize: strongSelf.bounds.size, boundingSize: strongSelf.bounds.size, intrinsicInsets: UIEdgeInsets()))
|
||||
self?.backgroundNodeImage = context?.generateImage()
|
||||
self?.setNeedsDisplay()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateAnimations() {
|
||||
@@ -595,7 +753,7 @@ public final class SemanticStatusNode: ASControlNode {
|
||||
transitionState = SemanticStatusNodeTransitionDrawingState(transition: t, drawingState: transitionContext.previousStateContext.drawingState(transitionFraction: 1.0 - t))
|
||||
}
|
||||
|
||||
return SemanticStatusNodeDrawingState(background: self.backgroundNodeColor, foreground: self.foregroundNodeColor, hollow: self.hollow, transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), cutout: nil)
|
||||
return SemanticStatusNodeDrawingState(background: self.backgroundNodeColor, foreground: self.foregroundNodeColor, hollow: self.hollow, transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), backgroundImage: self.backgroundNodeImage, overlayForeground: self.overlayForegroundNodeColor, cutout: nil)
|
||||
}
|
||||
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
@@ -610,14 +768,29 @@ public final class SemanticStatusNode: ASControlNode {
|
||||
guard let parameters = parameters as? SemanticStatusNodeDrawingState else {
|
||||
return
|
||||
}
|
||||
|
||||
context.setFillColor(parameters.background.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: bounds.size))
|
||||
if let transitionState = parameters.transitionState {
|
||||
transitionState.drawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.foreground)
|
||||
|
||||
var foregroundColor = parameters.foreground
|
||||
if let backgroundImage = parameters.backgroundImage?.cgImage {
|
||||
context.saveGState()
|
||||
context.translateBy(x: 0.0, y: bounds.height)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.draw(backgroundImage, in: bounds)
|
||||
context.restoreGState()
|
||||
|
||||
if let overlayForegroundColor = parameters.overlayForeground {
|
||||
foregroundColor = overlayForegroundColor
|
||||
} else {
|
||||
foregroundColor = .white
|
||||
}
|
||||
} else {
|
||||
context.setFillColor(parameters.background.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: bounds.size))
|
||||
}
|
||||
parameters.drawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.foreground)
|
||||
|
||||
if let transitionState = parameters.transitionState {
|
||||
transitionState.drawingState.draw(context: context, size: bounds.size, foregroundColor: foregroundColor)
|
||||
}
|
||||
parameters.drawingState.draw(context: context, size: bounds.size, foregroundColor: foregroundColor)
|
||||
|
||||
if parameters.hollow {
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: bounds.insetBy(dx: 8.0, dy: 8.0))
|
||||
|
||||
Reference in New Issue
Block a user