79 lines
3.5 KiB
Swift
79 lines
3.5 KiB
Swift
// Draws AppIcon.png, the Viewer's icon: ProdDash's 2×2 tile mark on a
|
||||
|
|
// display, so the Viewer and ProdDash itself tell apart in Finder and the
|
|||
|
|
// Dock. Run it again only to change the icon; build.sh uses the PNG.
|
|||
|
|
//
|
|||
|
|
// swift Resources/make-icon.swift Resources/AppIcon.png
|
|||
|
|
import AppKit
|
|||
|
|
|
|||
|
|
let side = 1024
|
|||
|
|
let output = CommandLine.arguments.dropFirst().first ?? "AppIcon.png"
|
|||
|
|
|
|||
|
|
guard let bitmap = NSBitmapImageRep(
|
|||
|
|
bitmapDataPlanes: nil, pixelsWide: side, pixelsHigh: side, bitsPerSample: 8,
|
|||
|
|
samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB,
|
|||
|
|
bytesPerRow: 0, bitsPerPixel: 0),
|
|||
|
|
let context = NSGraphicsContext(bitmapImageRep: bitmap) else { fatalError("no bitmap") }
|
|||
|
|
NSGraphicsContext.current = context
|
|||
|
|
let g = context.cgContext
|
|||
|
|
// Top-left origin, as the measurements below are written.
|
|||
|
|
g.translateBy(x: 0, y: CGFloat(side))
|
|||
|
|
g.scaleBy(x: 1, y: -1)
|
|||
|
|
|
|||
|
|
func color(_ hex: UInt32, _ alpha: CGFloat = 1) -> CGColor {
|
|||
|
|
CGColor(srgbRed: CGFloat((hex >> 16) & 0xff) / 255, green: CGFloat((hex >> 8) & 0xff) / 255,
|
|||
|
|
blue: CGFloat(hex & 0xff) / 255, alpha: alpha)
|
|||
|
|
}
|
|||
|
|
func rounded(_ rect: CGRect, _ radius: CGFloat) -> CGPath {
|
|||
|
|
CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil)
|
|||
|
|
}
|
|||
|
|
func fill(_ path: CGPath, _ fillColor: CGColor) {
|
|||
|
|
g.addPath(path); g.setFillColor(fillColor); g.fillPath()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// The body: Apple's icon grid — an 824-point rounded square, centred, on the
|
|||
|
|
// soft shadow macOS icons sit on. ProdDash's own near-black.
|
|||
|
|
let body = CGRect(x: 100, y: 100, width: 824, height: 824)
|
|||
|
|
let bodyPath = rounded(body, 185)
|
|||
|
|
g.saveGState()
|
|||
|
|
g.setShadow(offset: CGSize(width: 0, height: 12), blur: 28, color: color(0x000000, 0.35))
|
|||
|
|
fill(bodyPath, color(0x0b0e12))
|
|||
|
|
g.restoreGState()
|
|||
|
|
g.saveGState()
|
|||
|
|
g.addPath(bodyPath); g.clip()
|
|||
|
|
let shade = CGGradient(colorsSpace: CGColorSpace(name: CGColorSpace.sRGB),
|
|||
|
|
colors: [color(0x1b222b), color(0x0a0d11)] as CFArray, locations: [0, 1])!
|
|||
|
|
g.drawLinearGradient(shade, start: CGPoint(x: 0, y: body.minY), end: CGPoint(x: 0, y: body.maxY), options: [])
|
|||
|
|
g.restoreGState()
|
|||
|
|
g.addPath(rounded(body.insetBy(dx: 1.5, dy: 1.5), 183.5))
|
|||
|
|
g.setStrokeColor(color(0xffffff, 0.08)); g.setLineWidth(3); g.strokePath()
|
|||
|
|
|
|||
|
|
// The display: bezel, screen, neck and foot.
|
|||
|
|
let bezel = CGRect(x: 212, y: 250, width: 600, height: 410)
|
|||
|
|
let metal = color(0x3a434f)
|
|||
|
|
fill(rounded(CGRect(x: 474, y: 650, width: 76, height: 84), 8), metal)
|
|||
|
|
fill(rounded(CGRect(x: 372, y: 724, width: 280, height: 36), 18), color(0x4a5462))
|
|||
|
|
fill(rounded(bezel, 44), metal)
|
|||
|
|
let screen = bezel.insetBy(dx: 22, dy: 22)
|
|||
|
|
fill(rounded(screen, 26), color(0x0c1015))
|
|||
|
|
|
|||
|
|
// ProdDash's mark on the screen: three outlined tiles and the lit one.
|
|||
|
|
let padding: CGFloat = 46, gap: CGFloat = 30
|
|||
|
|
let tileW = (screen.width - padding * 2 - gap) / 2
|
|||
|
|
let tileH = (screen.height - padding * 2 - gap) / 2
|
|||
|
|
for (column, row) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
|
|||
|
|
let tile = CGRect(x: screen.minX + padding + CGFloat(column) * (tileW + gap),
|
|||
|
|
y: screen.minY + padding + CGFloat(row) * (tileH + gap),
|
|||
|
|
width: tileW, height: tileH)
|
|||
|
|
if column == 1 && row == 1 {
|
|||
|
|
fill(rounded(tile, 22), color(0x2fe39a))
|
|||
|
|
} else {
|
|||
|
|
let inset = tile.insetBy(dx: 5, dy: 5)
|
|||
|
|
fill(rounded(inset, 18), color(0x121820))
|
|||
|
|
g.addPath(rounded(inset, 18)); g.setStrokeColor(color(0x3d4652)); g.setLineWidth(10); g.strokePath()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
guard let png = bitmap.representation(using: .png, properties: [:]) else { fatalError("no png") }
|
|||
|
|
try png.write(to: URL(fileURLWithPath: output))
|
|||
|
|
print("wrote \(output)")
|