Files
onyx/Scripts/dmg-background.swift
Scarriffle 928ee90b5e Das Installationsfenster sieht aus wie eines
Zwei Symbole auf grauem Grund sagen nicht, was zu tun ist. Jetzt: dunkler
Verlauf in den Onyx-Farben, Titel, ein Satz darunter, eine Haarlinie und ein
Pfeil, der vom Programm zum Programme-Ordner zeigt. Die Fassung steht in der
Fußzeile — damit man später weiß, was man installiert hat.

Der Hintergrund wird gezeichnet, nicht mitgeliefert: ein Bild im Verzeichnis
wäre eine Datei, die niemand mehr anfasst und die bei jeder Farbänderung
veraltet. So stehen die Onyx-Farben genau einmal, im selben Werkzeugkasten wie
der Rest.

Der Aufwand steckt nicht im Bild, sondern in der Reihenfolge: Fenstergröße,
Symbolplätze und Hintergrund leben in der .DS_Store des Abbilds. Die schreibt
nur der Finder, und nur in ein beschreibbares Abbild. Also erst UDRW, dann
über AppleScript einrichten, warten bis der Finder geschrieben hat, dann
zusammenpressen. Schlägt die Automatisierung fehl, bleibt das Fenster schlicht
statt das Skript abzubrechen.

Als Bibliothek, weil beide Wege dasselbe Fenster zeigen sollen — der lokale
wie der notarisierte. Und als TIFF mit beiden Auflösungen: als reines PNG wäre
der Hintergrund auf einem Retina-Bildschirm doppelt so groß wie das Fenster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 12:57:51 +02:00

126 lines
5.0 KiB
Swift

// Zeichnet den Hintergrund des Installationsfensters.
//
// Gezeichnet statt mitgeliefert: ein Bild im Verzeichnis wäre eine Datei, die
// niemand mehr anfasst und die bei jeder Farbänderung veraltet. So steht das
// Aussehen im selben Werkzeugkasten wie der Rest und die Onyx-Farben stehen
// hier genau einmal.
//
// Aufruf: dmg-background <ziel-ohne-endung>
// Erzeugt <ziel>.png und <ziel>@2x.png.
import AppKit
let arguments = CommandLine.arguments
guard arguments.count == 2 else {
FileHandle.standardError.write(Data("Aufruf: dmg-background <ziel-ohne-endung>\n".utf8))
exit(1)
}
let target = arguments[1]
// Maße des Fensters in Punkten. Die Symbole setzt das Skript an dieselben
// Stellen ändert sich hier etwas, muss es dort mitwandern.
let width: CGFloat = 640
let height: CGFloat = 400
let iconY: CGFloat = 196 // Mitte der Symbolreihe, von oben
let leftIcon: CGFloat = 170
let rightIcon: CGFloat = 470
func draw(scale: CGFloat, to path: String) {
let pixelWidth = Int(width * scale)
let pixelHeight = Int(height * scale)
guard let context = CGContext(data: nil, width: pixelWidth, height: pixelHeight,
bitsPerComponent: 8, bytesPerRow: 0,
space: CGColorSpace(name: CGColorSpace.sRGB)!,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
else { exit(1) }
context.scaleBy(x: scale, y: scale)
// Von oben nach unten rechnen wie im Fenster; Core Graphics fängt unten an.
context.translateBy(x: 0, y: height)
context.scaleBy(x: 1, y: -1)
let graphics = NSGraphicsContext(cgContext: context, flipped: true)
NSGraphicsContext.current = graphics
// Grundfläche: derselbe flache Verlauf wie im Panel. Zwei Prozent Weiß oben,
// nichts unten mehr wirkt billig.
let surface = NSColor(srgbRed: 0.043, green: 0.043, blue: 0.051, alpha: 1)
let elevated = NSColor(srgbRed: 0.078, green: 0.078, blue: 0.090, alpha: 1)
NSGradient(colors: [elevated, surface])?
.draw(in: NSRect(x: 0, y: 0, width: width, height: height), angle: -90)
// Die Haarlinie unter dem Kopf der halbe edle" Effekt steckt darin.
NSColor(white: 1, alpha: 0.08).setFill()
NSRect(x: 40, y: 118, width: width - 80, height: 1).fill()
let title = "Onyx"
let titleAttributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 30, weight: .semibold),
.foregroundColor: NSColor(srgbRed: 0.949, green: 0.949, blue: 0.957, alpha: 1),
.kern: -0.5,
]
let titleSize = title.size(withAttributes: titleAttributes)
title.draw(at: NSPoint(x: (width - titleSize.width) / 2, y: 48),
withAttributes: titleAttributes)
let subtitle = "Zum Installieren nach „Programme“ ziehen"
let subtitleAttributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 12, weight: .regular),
.foregroundColor: NSColor(white: 1, alpha: 0.58),
]
let subtitleSize = subtitle.size(withAttributes: subtitleAttributes)
subtitle.draw(at: NSPoint(x: (width - subtitleSize.width) / 2, y: 88),
withAttributes: subtitleAttributes)
// Der Pfeil zwischen den beiden Symbolen: er sagt in einem Bild, was der
// Satz darüber in sieben Wörtern sagt.
let accent = NSColor(srgbRed: 0.498, green: 0.659, blue: 1.0, alpha: 0.55)
accent.setStroke()
let shaft = NSBezierPath()
let from = leftIcon + 92
let to = rightIcon - 92
shaft.move(to: NSPoint(x: from, y: iconY))
shaft.line(to: NSPoint(x: to - 10, y: iconY))
shaft.lineWidth = 3
shaft.lineCapStyle = .round
shaft.stroke()
let head = NSBezierPath()
head.move(to: NSPoint(x: to - 18, y: iconY - 9))
head.line(to: NSPoint(x: to, y: iconY))
head.line(to: NSPoint(x: to - 18, y: iconY + 9))
head.lineWidth = 3
head.lineCapStyle = .round
head.lineJoinStyle = .round
head.stroke()
// Fußzeile: die Fassung gehört sichtbar dazu, damit man später weiß, was
// man da eigentlich installiert hat.
let version = ProcessInfo.processInfo.environment["ONYX_VERSION"] ?? ""
if !version.isEmpty {
let footer = "Fassung \(version)"
let footerAttributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 10, weight: .regular),
.foregroundColor: NSColor(white: 1, alpha: 0.34),
]
let footerSize = footer.size(withAttributes: footerAttributes)
footer.draw(at: NSPoint(x: (width - footerSize.width) / 2, y: height - 34),
withAttributes: footerAttributes)
}
NSGraphicsContext.current = nil
guard let image = context.makeImage(),
let destination = CGImageDestinationCreateWithURL(
URL(fileURLWithPath: path) as CFURL, "public.png" as CFString, 1, nil)
else { exit(1) }
CGImageDestinationAddImage(destination, image, nil)
CGImageDestinationFinalize(destination)
}
import ImageIO
draw(scale: 1, to: target + ".png")
draw(scale: 2, to: target + "@2x.png")