Various improvements

This commit is contained in:
Ilya Laktyushin
2020-10-14 01:49:49 +04:00
parent 645bd9789b
commit 36d705576a
45 changed files with 5041 additions and 4271 deletions

View File

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