233 lines
8.4 KiB
Swift
233 lines
8.4 KiB
Swift
#if os(macOS)
|
||
|
||
import AppKit
|
||
import AutoCatCore
|
||
import SwiftUI
|
||
|
||
extension CGRect {
|
||
|
||
func inset(by insets: NSEdgeInsets) -> CGRect {
|
||
|
||
CGRect(origin: CGPoint(x: origin.x + insets.left,
|
||
y: origin.y + insets.bottom),
|
||
size: CGSize(width: size.width - insets.left - insets.right,
|
||
height: size.height - insets.top - insets.bottom))
|
||
}
|
||
}
|
||
|
||
extension NSScreen {
|
||
|
||
static var mainScreenScale: CGFloat {
|
||
main?.backingScaleFactor ?? 1
|
||
}
|
||
}
|
||
|
||
class FlagLayer: CALayer {
|
||
let pixelWidth = 1/NSScreen.mainScreenScale
|
||
|
||
// Flag colors - https://ru.wikipedia.org/wiki/%D0%A4%D0%BB%D0%B0%D0%B3_%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D0%B8
|
||
let blue = NSColor(srgbRed: 0, green: 57/256.0, blue: 166/256.0, alpha: 1)
|
||
let red = NSColor(srgbRed: 213/256.0, green: 43/256.0, blue: 30/256.0, alpha: 1)
|
||
|
||
override func draw(in ctx: CGContext) {
|
||
ctx.saveGState()
|
||
super.draw(in: ctx)
|
||
|
||
ctx.setStrokeColor(NSColor.black.cgColor)
|
||
ctx.setLineWidth(0)
|
||
|
||
ctx.setFillColor(NSColor.white.cgColor)
|
||
ctx.fill(bounds.inset(by: NSEdgeInsets(top: 0, left: 0, bottom: bounds.height*2/3, right: 0)))
|
||
|
||
ctx.setFillColor(self.blue.cgColor)
|
||
ctx.fill(bounds.insetBy(dx: 0, dy: bounds.height/3))
|
||
|
||
ctx.setFillColor(self.red.cgColor)
|
||
ctx.fill(bounds.inset(by: NSEdgeInsets(top: bounds.height*2/3, left: 0, bottom: 0, right: 0)))
|
||
|
||
ctx.setLineWidth(pixelWidth)
|
||
ctx.stroke(bounds.insetBy(dx: pixelWidth/2, dy: pixelWidth/2))
|
||
|
||
ctx.restoreGState()
|
||
}
|
||
}
|
||
|
||
class PlateView: NSView {
|
||
// Some driver plate parameters from "ГОСТ Р 50577-93"
|
||
// http://docs.cntd.ru/document/gost-r-50577-93
|
||
private static let aspectRatio: CGFloat = 112.0/520.0
|
||
private static let fontHeightCoeff: CGFloat = 58.0/76.0
|
||
|
||
private var bgLayer = CALayer()
|
||
private var mainBgLayer = CALayer()
|
||
private var regionBgLayer = CALayer()
|
||
private var numberLayer = CenterTextLayer(coeff: fontHeightCoeff)
|
||
private var regionLayer = CenterTextLayer(coeff: fontHeightCoeff)
|
||
private var countryLayer = CenterTextLayer()
|
||
private var flagLayer = FlagLayer()
|
||
|
||
var number: PlateNumber? {
|
||
didSet {
|
||
self.layout()
|
||
self.setAccessibilityLabel(number?.asString())
|
||
}
|
||
}
|
||
|
||
var foreground: NSColor? {
|
||
didSet {
|
||
self.layout()
|
||
}
|
||
}
|
||
|
||
var fontSize: CGFloat = 10 {
|
||
didSet {
|
||
self.layout()
|
||
}
|
||
}
|
||
|
||
var onChange: (() -> Void)?
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
setup()
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
super.init(coder: coder)
|
||
setup()
|
||
}
|
||
|
||
func setup() {
|
||
self.wantsLayer = true
|
||
self.layer?.backgroundColor = NSColor.clear.cgColor
|
||
|
||
self.bgLayer.cornerRadius = 6 //self.bounds.height/8
|
||
self.layer?.addSublayer(self.bgLayer)
|
||
|
||
self.mainBgLayer.cornerRadius = 4
|
||
self.layer?.addSublayer(self.mainBgLayer)
|
||
|
||
self.regionBgLayer.cornerRadius = 4
|
||
self.layer?.addSublayer(self.regionBgLayer)
|
||
|
||
self.numberLayer.alignmentMode = .center
|
||
self.numberLayer.contentsScale = NSScreen.mainScreenScale
|
||
self.layer?.addSublayer(self.numberLayer)
|
||
|
||
self.regionLayer.alignmentMode = .center
|
||
self.regionLayer.contentsScale = NSScreen.mainScreenScale
|
||
self.layer?.addSublayer(self.regionLayer)
|
||
|
||
self.countryLayer.contentsScale = NSScreen.mainScreenScale
|
||
self.countryLayer.string = "RUS"
|
||
self.countryLayer.alignmentMode = .center
|
||
self.layer?.addSublayer(self.countryLayer)
|
||
|
||
self.flagLayer.contentsScale = NSScreen.mainScreenScale
|
||
self.layer?.addSublayer(self.flagLayer)
|
||
|
||
self.setAccessibilityElement(true)
|
||
}
|
||
|
||
private func pathForFlag(in rect: CGRect) -> CGPath {
|
||
let path = CGMutablePath()
|
||
let rect = CGPath(rect: rect, transform: nil)
|
||
path.addPath(rect)
|
||
return path
|
||
}
|
||
|
||
override func layout() {
|
||
super.layout()
|
||
|
||
guard let number = self.number else { return }
|
||
guard let fgColorMain = NSColor(named: "PlateForeground")?.cgColor else { return }
|
||
guard let bgColor = NSColor(named: "PlateBackground")?.cgColor else { return }
|
||
|
||
let fgColor = self.foreground?.cgColor ?? fgColorMain
|
||
|
||
self.bgLayer.backgroundColor = fgColor
|
||
self.bgLayer.frame = self.bounds
|
||
|
||
self.mainBgLayer.backgroundColor = bgColor
|
||
self.regionBgLayer.backgroundColor = bgColor
|
||
self.mainBgLayer.frame = self.bounds.inset(by: NSEdgeInsets(top: 2, left: 2, bottom: 2, right: self.bounds.width*0.27 + 1))
|
||
self.regionBgLayer.frame = self.bounds.inset(by: NSEdgeInsets(top: 2, left: self.bounds.width*0.73 + 1, bottom: 2, right: 2))
|
||
|
||
self.numberLayer.frame = self.mainBgLayer.frame.insetBy(dx: 4, dy: 0)
|
||
let font = NSFont(name: "RoadNumbers", size: self.mainBgLayer.frame.height*1.1) ?? NSFont.boldSystemFont(ofSize: 24)
|
||
let attributes: [NSAttributedString.Key: Any] = [
|
||
.kern: 3,
|
||
.font: font,
|
||
.foregroundColor: fgColor
|
||
]
|
||
let attributed = NSAttributedString(string: number.mainPart(), attributes: attributes)
|
||
self.numberLayer.string = attributed
|
||
|
||
let rbgSize = self.regionBgLayer.frame.size
|
||
|
||
self.regionLayer.frame = self.regionBgLayer.frame.inset(by: NSEdgeInsets(top: 2, left: 2, bottom: rbgSize.height*0.35, right: 2))
|
||
let regionFont = NSFont(name: "RoadNumbers", size: rbgSize.height*0.8) ?? NSFont.boldSystemFont(ofSize: 24)
|
||
let regionAttrs: [NSAttributedString.Key: Any] = [
|
||
.kern: 1,
|
||
.font: regionFont,
|
||
.foregroundColor: fgColor
|
||
]
|
||
let attributedRegion = NSAttributedString(string: number.region(), attributes: regionAttrs)
|
||
self.regionLayer.string = attributedRegion
|
||
|
||
self.countryLayer.foregroundColor = fgColor
|
||
self.countryLayer.frame = self.regionBgLayer.frame.inset(by: NSEdgeInsets(top: rbgSize.height*0.64,
|
||
left: rbgSize.width*0.08,
|
||
bottom: rbgSize.height*0.07,
|
||
right: rbgSize.width*0.42))
|
||
self.countryLayer.fontSize = self.countryLayer.frame.size.height
|
||
|
||
let top = (self.regionBgLayer.frame.origin.y + rbgSize.height*0.04).rounded(.toNearestOrAwayFromZero)
|
||
let left = (self.regionBgLayer.frame.origin.x + rbgSize.width*0.62).rounded(.toNearestOrAwayFromZero)
|
||
let w = (rbgSize.width*0.34).rounded(.toNearestOrAwayFromZero)
|
||
let h = (rbgSize.height*0.28).rounded(.toNearestOrAwayFromZero)
|
||
self.flagLayer.frame = CGRect(x: left, y: top, width: w, height: h)
|
||
self.flagLayer.setNeedsDisplay()
|
||
}
|
||
|
||
override var intrinsicContentSize: CGSize {
|
||
|
||
let height = fontSize/1.1 + 4
|
||
let width = height/PlateView.aspectRatio
|
||
return CGSize(width: width, height: height)
|
||
}
|
||
}
|
||
|
||
struct PlateNumberView: NSViewRepresentable {
|
||
|
||
var number: PlateNumber
|
||
var unrecognized: Bool
|
||
var outdated: Bool
|
||
var fontSize: CGFloat
|
||
|
||
func makeNSView(context: Context) -> PlateView {
|
||
let view = PlateView(frame: .zero)
|
||
view.fontSize = fontSize
|
||
return view
|
||
}
|
||
|
||
func updateNSView(_ nsView: PlateView, context: Context) {
|
||
nsView.number = number
|
||
nsView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||
nsView.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
||
}
|
||
}
|
||
|
||
struct PlateNumberView_Previews: PreviewProvider {
|
||
static var previews: some View {
|
||
Group {
|
||
PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: false, outdated: false, fontSize: 50)
|
||
PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: true, outdated: false, fontSize: 50)
|
||
PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: false, outdated: true, fontSize: 50)
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
#endif
|