AutoCat2/AutoCat2SUI/Views/PlateView/PlateView.swift

233 lines
8.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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