import AppKit /// Beschriftung und Breite eines Menüleisten-Elements. /// /// Die Breite wird **gemessen**, nicht geschätzt. Vorher stand je Darstellungsart /// eine feste Zahl im Code, großzügig gewählt, damit nichts abgeschnitten wird — /// mit dem Ergebnis, dass neben jedem Wert Platz für ein weiteres Symbol blieb, /// in dem nichts stand. In einer Menüleiste ist das der teuerste Platz des /// ganzen Bildschirms. /// /// Gemessen wird die **breiteste Zeichenfolge, die vorkommen kann**, nicht die /// gerade angezeigte. Sonst ändert das Element bei jeder Messung seine Größe und /// schiebt alle Symbole rechts davon hin und her. public enum MenuBarText { /// Feste Ziffernbreite: ohne sie wackelt der Text bei jeder Messung. /// /// Als berechnete Eigenschaft, nicht als gespeicherte: `NSFont` ist nicht /// `Sendable`, und die Schrift jedes Mal neu zu erfragen kostet nichts — /// AppKit gibt dieselbe zwischengespeicherte Instanz zurück. public static var font: NSFont { NSFont.monospacedDigitSystemFont(ofSize: 11, weight: .regular) } public static let sidePadding: CGFloat = 2 public static let symbolWidth: CGFloat = 13 /// Abstand zwischen Symbol und Zahl. public static let innerGap: CGFloat = 2 /// Schmaler geht nicht, ohne dass ein Glyph anstößt. public static let minimumWidth: CGFloat = 20 /// Die Breite eines Elements aus gemessener Textbreite. Rein, damit die /// Rechnung prüfbar bleibt. public static func itemWidth(textWidth: CGFloat, includesSymbol: Bool) -> CGFloat { let symbol = includesSymbol ? symbolWidth + innerGap : 0 let content = textWidth > 0 ? ceil(textWidth) + 2 * sidePadding : 2 * sidePadding return max(minimumWidth, content + symbol) } public static func width(of text: String) -> CGFloat { guard !text.isEmpty else { return 0 } return NSAttributedString(string: text, attributes: [.font: font]).size().width } /// Die Breite für die breiteste vorkommende Zeichenfolge. public static func width(for reference: String, includesSymbol: Bool = false) -> CGFloat { itemWidth(textWidth: width(of: reference), includesSymbol: includesSymbol) } public static func draw(_ text: String, color: NSColor, in rect: NSRect) { let string = NSAttributedString(string: text, attributes: [.font: font, .foregroundColor: color]) let size = string.size() string.draw(at: NSPoint(x: rect.midX - size.width / 2, y: rect.midY - size.height / 2)) } } /// Eine Statusansicht, deren nötige Breite sich ändern kann. /// /// Der Controller stellt das Statuselement daraufhin neu ein. Ohne diesen Weg /// bliebe ein Element in der Breite stehen, die beim allerersten Zeichnen /// gebraucht wurde — vor der ersten Messung also. @MainActor public protocol WidthReporting: AnyObject { var onWidthChange: ((CGFloat) -> Void)? { get set } }