Swiftgram/Display/StatusBarProxyNode.swift
2016-10-24 15:00:28 +03:00

335 lines
11 KiB
Swift

import Foundation
import AsyncDisplayKit
public enum StatusBarStyle {
case Black
case White
}
private enum StatusBarItemType {
case Generic
case Battery
}
func makeStatusBarProxy(_ statusBarStyle: StatusBarStyle, statusBar: UIView) -> StatusBarProxyNode {
return StatusBarProxyNode(statusBarStyle: statusBarStyle, statusBar: statusBar)
}
private class StatusBarItemNode: ASDisplayNode {
var statusBarStyle: StatusBarStyle
var targetView: UIView
init(statusBarStyle: StatusBarStyle, targetView: UIView) {
self.statusBarStyle = statusBarStyle
self.targetView = targetView
super.init()
}
func update() {
let context = DrawingContext(size: self.targetView.frame.size, clear: true)
if let contents = self.targetView.layer.contents, (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents as! CFTypeRef) == CGImage.typeID && false {
let image = contents as! CGImage
context.withFlippedContext { c in
c.setAlpha(CGFloat(self.targetView.layer.opacity))
c.draw(image, in: CGRect(origin: CGPoint(), size: context.size))
c.setAlpha(1.0)
}
if let sublayers = self.targetView.layer.sublayers {
for sublayer in sublayers {
let origin = sublayer.frame.origin
if let contents = sublayer.contents , CFGetTypeID(contents as! CFTypeRef) == CGImage.typeID {
let image = contents as! CGImage
context.withFlippedContext { c in
c.translateBy(x: origin.x, y: origin.y)
c.draw(image, in: CGRect(origin: CGPoint(), size: context.size))
c.translateBy(x: -origin.x, y: -origin.y)
}
} else {
context.withContext { c in
UIGraphicsPushContext(c)
c.translateBy(x: origin.x, y: origin.y)
sublayer.render(in: c)
c.translateBy(x: -origin.x, y: -origin.y)
UIGraphicsPopContext()
}
}
}
}
} else {
context.withContext { c in
UIGraphicsPushContext(c)
self.targetView.layer.render(in: c)
UIGraphicsPopContext()
}
}
let type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic
tintStatusBarItem(context, type: type, style: statusBarStyle)
self.contents = context.generateImage()?.cgImage
self.frame = self.targetView.frame
}
}
private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemType, style: StatusBarStyle) {
switch type {
case .Battery:
let minY = 0
let minX = 0
let maxY = Int(context.size.height * context.scale)
let maxX = Int(context.size.width * context.scale)
if minY < maxY && minX < maxX {
let basePixel = context.bytes.assumingMemoryBound(to: UInt32.self)
let pixelsPerRow = context.bytesPerRow / 4
let midX = (maxX + minX) / 2
let midY = (maxY + minY) / 2
let baseMidRow = basePixel + pixelsPerRow * midY
var baseX = minX
while baseX < maxX {
let pixel = baseMidRow + baseX
let alpha = pixel.pointee & 0xff000000
if alpha != 0 {
break
}
baseX += 1
}
baseX += 2
var targetX = baseX
while targetX < maxX {
let pixel = baseMidRow + targetX
let alpha = pixel.pointee & 0xff000000
if alpha == 0 {
break
}
targetX += 1
}
let batteryColor = (baseMidRow + baseX).pointee
let batteryR = (batteryColor >> 16) & 0xff
let batteryG = (batteryColor >> 8) & 0xff
let batteryB = batteryColor & 0xff
var baseY = minY
while baseY < maxY {
let baseRow = basePixel + pixelsPerRow * baseY
let pixel = baseRow + midX
let alpha = pixel.pointee & 0xff000000
if alpha != 0 {
break
}
baseY += 1
}
var targetY = maxY - 1
while targetY >= baseY {
let baseRow = basePixel + pixelsPerRow * targetY
let pixel = baseRow + midX
let alpha = pixel.pointee & 0xff000000
if alpha != 0 {
break
}
targetY -= 1
}
targetY -= 1
let baseColor: UInt32
switch style {
case .Black:
baseColor = 0x000000
case .White:
baseColor = 0xffffff
}
let baseR = (baseColor >> 16) & 0xff
let baseG = (baseColor >> 8) & 0xff
let baseB = baseColor & 0xff
var pixel = context.bytes.assumingMemoryBound(to: UInt32.self)
let end = context.bytes.advanced(by: context.length).assumingMemoryBound(to: UInt32.self)
while pixel != end {
let alpha = (pixel.pointee & 0xff000000) >> 24
let r = (baseR * alpha) / 255
let g = (baseG * alpha) / 255
let b = (baseB * alpha) / 255
pixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b
pixel += 1
}
if batteryColor != 0xffffffff && batteryColor != 0xff000000 {
var y = baseY + 2
while y < targetY {
let baseRow = basePixel + pixelsPerRow * y
var x = baseX
while x < targetX {
let pixel = baseRow + x
let alpha = (pixel.pointee >> 24) & 0xff
let r = (batteryR * alpha) / 255
let g = (batteryG * alpha) / 255
let b = (batteryB * alpha) / 255
pixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b
x += 1
}
y += 1
}
}
}
case .Generic:
var pixel = context.bytes.assumingMemoryBound(to: UInt32.self)
let end = context.bytes.advanced(by: context.length).assumingMemoryBound(to: UInt32.self)
let baseColor: UInt32
switch style {
case .Black:
baseColor = 0x000000
case .White:
baseColor = 0xffffff
}
let baseR = (baseColor >> 16) & 0xff
let baseG = (baseColor >> 8) & 0xff
let baseB = baseColor & 0xff
while pixel != end {
let alpha = (pixel.pointee & 0xff000000) >> 24
let r = (baseR * alpha) / 255
let g = (baseG * alpha) / 255
let b = (baseB * alpha) / 255
pixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b
pixel += 1
}
}
}
private let batteryItemClass: AnyClass? = NSClassFromString("UIStatusBarBatteryItemView")
private class StatusBarProxyNodeTimerTarget: NSObject {
let action: () -> Void
init(action: @escaping () -> Void) {
self.action = action
}
@objc func tick() {
action()
}
}
class StatusBarProxyNode: ASDisplayNode {
private let statusBar: UIView
var timer: Timer?
var statusBarStyle: StatusBarStyle {
didSet {
if oldValue != self.statusBarStyle {
if !self.isHidden {
self.updateItems()
}
}
}
}
private var itemNodes: [StatusBarItemNode] = []
override var isHidden: Bool {
get {
return super.isHidden
} set(value) {
if super.isHidden != value {
super.isHidden = value
if !value {
self.updateItems()
self.timer = Timer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in
self?.updateItems()
}, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true)
RunLoop.main.add(self.timer!, forMode: .commonModes)
} else {
self.timer?.invalidate()
self.timer = nil
}
}
}
}
init(statusBarStyle: StatusBarStyle, statusBar: UIView) {
self.statusBarStyle = statusBarStyle
self.statusBar = statusBar
super.init()
self.isHidden = true
self.clipsToBounds = true
//self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2)
for subview in statusBar.subviews {
let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview)
self.itemNodes.append(itemNode)
self.addSubnode(itemNode)
}
self.frame = statusBar.bounds
}
deinit {
self.timer?.invalidate()
}
private func updateItems() {
let statusBar = self.statusBar
var i = 0
while i < self.itemNodes.count {
var found = false
for subview in statusBar.subviews {
if self.itemNodes[i].targetView == subview {
found = true
break
}
}
if !found {
self.itemNodes[i].removeFromSupernode()
self.itemNodes.remove(at: i)
} else {
self.itemNodes[i].statusBarStyle = self.statusBarStyle
self.itemNodes[i].update()
i += 1
}
}
for subview in statusBar.subviews {
var found = false
for itemNode in self.itemNodes {
if itemNode.targetView == subview {
found = true
break
}
}
if !found {
let itemNode = StatusBarItemNode(statusBarStyle: self.statusBarStyle, targetView: subview)
itemNode.update()
self.itemNodes.append(itemNode)
self.addSubnode(itemNode)
}
}
}
}