From 582c827adef8ec014f3a21a5f7cf6f91a2a498f0 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Fri, 22 May 2026 16:42:40 +0800 Subject: [PATCH] {"schema":"decodex/commit/1","summary":"Clean Swift vstyle violations","authority":"manual"} --- .../Sources/DecodexApp/AccountPanelView.swift | 50 ++++++++--------- .../Sources/DecodexApp/AccountStore.swift | 16 +++--- .../Sources/DecodexApp/DecodexAppBridge.swift | 8 +-- .../DecodexApp/DecodexServerBridge.swift | 26 ++++++--- .../DecodexApp/LoginPanelPresenter.swift | 4 +- .../Sources/DecodexApp/LoginSheetView.swift | 8 +-- .../DecodexApp/OperatorSnapshotModels.swift | 32 +++++------ scripts/assets/render_decodex_app_icons.swift | 53 ++++++++++++++----- 8 files changed, 118 insertions(+), 79 deletions(-) diff --git a/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift b/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift index 5d9a7a28f..089fd9b41 100644 --- a/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift +++ b/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift @@ -155,8 +155,8 @@ private struct PanelInteractiveSurfaceModifier: ViewModifier { let hoverShadowRadius: CGFloat func body(content: Content) -> some View { - let responds = !isDisabled - let hoverActive = responds && isHovered && !isPressed + let responds = isDisabled == false + let hoverActive = responds && isHovered && isPressed == false let pressActive = responds && isPressed content @@ -222,7 +222,7 @@ struct AccountPanelView: View { isPresented: Binding( get: { pendingLogout != nil }, set: { visible in - if !visible { + if visible == false { pendingLogout = nil disarmLogout() } @@ -334,7 +334,7 @@ struct AccountPanelView: View { size: 25, action: { Task { - await store.setFastMode(!store.fastModeEnabled) + await store.setFastMode(store.fastModeEnabled == false) } }, help: store.fastModeEnabled ? "Turn fast mode off" : "Turn fast mode on" @@ -515,7 +515,7 @@ struct AccountPanelView: View { return "Not loaded" } - if let selector = control.accountSelector, !selector.isEmpty { + if let selector = control.accountSelector, selector.isEmpty == false { if emailsHidden { let value = account(matching: selector)?.panelDisplayName(emailsHidden: true) ?? AccountDisplay.alias(forIdentity: selector) @@ -541,7 +541,7 @@ struct AccountPanelView: View { return false } - return !selector.isEmpty + return selector.isEmpty == false } private var accountListHeight: CGFloat { @@ -727,7 +727,7 @@ struct AccountRowView: View { symbol: account.codexActive ? "person.crop.circle.fill" : "person.crop.circle", tint: PanelPalette.codexAccent(colorScheme), isActive: account.codexActive, - isDisabled: account.codexActive || !account.canUseInCodex, + isDisabled: account.codexActive || account.canUseInCodex == false, isSubtle: true, size: 21, action: useInCodex, @@ -741,7 +741,7 @@ struct AccountRowView: View { ? PanelPalette.routeAccent(colorScheme) : PanelPalette.actionBlue(colorScheme), isActive: account.selected, - isDisabled: !account.canRouteRuns && !account.selected, + isDisabled: account.canRouteRuns == false && account.selected == false, isSubtle: true, size: 21, action: routeRunsHere, @@ -753,7 +753,7 @@ struct AccountRowView: View { tint: PanelPalette.destructive(colorScheme), isActive: isLogoutArmed, isDestructive: true, - isSubtle: !isLogoutArmed, + isSubtle: isLogoutArmed == false, size: 21, action: logout, help: isLogoutArmed ? "Click again to confirm removal" : "Remove account" @@ -762,7 +762,7 @@ struct AccountRowView: View { } } - if !runs.isEmpty { + if runs.isEmpty == false { AccountRunSummaryView(runs: runs) } @@ -1096,7 +1096,7 @@ struct AccountPoolUsageEstimateView: View { let measuredAccounts = accounts.filter { account in account.sevenDayUsedPercent != nil } - guard !measuredAccounts.isEmpty, estimate.totalCapacityPercent > 0 else { + guard measuredAccounts.isEmpty == false, estimate.totalCapacityPercent > 0 else { return nil } @@ -1305,7 +1305,7 @@ struct AccountUsageMeterView: View { .monospacedDigit() .lineLimit(1) - if !resetDisplay.date.isEmpty { + if resetDisplay.date.isEmpty == false { Text(resetDisplay.date) .font(PanelFont.tertiary) .foregroundStyle(PanelPalette.secondaryText(colorScheme).opacity(colorScheme == .dark ? 0.68 : 0.78)) @@ -1535,7 +1535,7 @@ struct OperatorStatusStripView: View { var body: some View { VStack(alignment: .leading, spacing: 5) { - if !metrics.isEmpty { + if metrics.isEmpty == false { HStack(spacing: 0) { ForEach(Array(metrics.enumerated()), id: \.element.id) { index, metric in if index > 0 { @@ -1548,7 +1548,7 @@ struct OperatorStatusStripView: View { .frame(height: 32) } - if !snapshot.activeRuns.isEmpty { + if snapshot.activeRuns.isEmpty == false { OperatorLaneListView(runs: snapshot.activeRuns, showsAllLanes: $showsAllLanes) .padding(.top, metrics.isEmpty ? 0 : 1) } @@ -1817,7 +1817,7 @@ struct OperatorLanePopoverView: View { OperatorLaneReadoutRow(title: humanizedPanelToken(bucket.name), items: bucketReadoutItems(bucket)) } - if !contextReadoutItems.isEmpty { + if contextReadoutItems.isEmpty == false { OperatorLaneReadoutDivider() OperatorLaneReadoutRow(title: "Context", items: contextReadoutItems) } @@ -1872,7 +1872,7 @@ struct OperatorLanePopoverView: View { private var detailBuckets: [OperatorChildAgentBucket] { orderedBuckets.filter { bucket in bucket.name.caseInsensitiveCompare("Model") != .orderedSame - && !bucketReadoutItems(bucket).isEmpty + && bucketReadoutItems(bucket).isEmpty == false } } @@ -1955,7 +1955,7 @@ struct OperatorLanePopoverView: View { if bucket.outputBytes > 0 { items.append(OperatorLaneReadoutItem(label: "output bytes", value: formatCompactBytes(bucket.outputBytes))) } - if !normalizedName.contains("tracker") { + if normalizedName.contains("tracker") == false { if bucket.inputTokens > 0 { items.append(OperatorLaneReadoutItem(label: "input", value: "\(formatCompactCount(bucket.inputTokens)) tok")) } @@ -2329,7 +2329,7 @@ struct PanelIconButtonView: View { ) ) .disabled(isDisabled) - .opacity(isDisabled && !isActive ? 0.56 : 1) + .opacity(isDisabled && isActive == false ? 0.56 : 1) .help(help) } @@ -2575,7 +2575,7 @@ private func compactUsageDate(_ value: String) -> String { var components = DateComponents() components.calendar = Calendar(identifier: .gregorian) - components.year = 2000 + components.year = 2_000 components.month = month components.day = day guard let date = components.date else { @@ -2726,11 +2726,11 @@ private func parsePanelTimestamp(_ value: String) -> Date? { } private func compactLanePath(_ value: String?) -> String? { - guard var text = panelTrimmed(value), !text.isEmpty else { + guard var text = panelTrimmed(value), text.isEmpty == false else { return nil } - if let home = ProcessInfo.processInfo.environment["HOME"], !home.isEmpty { + if let home = ProcessInfo.processInfo.environment["HOME"], home.isEmpty == false { text = text.replacingOccurrences(of: home, with: "~") } if text.count <= 42 { @@ -2834,13 +2834,13 @@ struct ModernGlassSurfaceModifier: ViewModifier { private extension CodexAccount { var randomNameSeed: String { - if !accountFingerprint.isEmpty { + if accountFingerprint.isEmpty == false { return accountFingerprint } - if let email, !email.isEmpty { + if let email, email.isEmpty == false { return email } - if let planType, !planType.isEmpty { + if let planType, planType.isEmpty == false { return planType } @@ -2850,7 +2850,7 @@ private extension CodexAccount { func panelDisplayName(emailsHidden: Bool) -> String { if emailsHidden { if let randomName = randomName?.trimmingCharacters(in: .whitespacesAndNewlines), - !randomName.isEmpty + randomName.isEmpty == false { return randomName } diff --git a/apps/decodex-app/Sources/DecodexApp/AccountStore.swift b/apps/decodex-app/Sources/DecodexApp/AccountStore.swift index 5c0a1c932..19d7025df 100644 --- a/apps/decodex-app/Sources/DecodexApp/AccountStore.swift +++ b/apps/decodex-app/Sources/DecodexApp/AccountStore.swift @@ -41,7 +41,7 @@ final class AccountStore: ObservableObject { } let codexLabel = accountList?.codexAuth?.displayName ?? "no Codex auth" - if let selector = control.accountSelector, !selector.isEmpty { + if let selector = control.accountSelector, selector.isEmpty == false { return "Codex: \(codexLabel) / Decodex: \(selector)" } @@ -71,7 +71,7 @@ final class AccountStore: ObservableObject { } func refresh(force: Bool = false) async { - guard !isRefreshing else { + guard isRefreshing == false else { return } @@ -112,7 +112,7 @@ final class AccountStore: ObservableObject { } func resetLoginSession() { - guard !isLoggingIn else { + guard isLoggingIn == false else { return } @@ -126,7 +126,7 @@ final class AccountStore: ObservableObject { } automaticRefreshTask = Task { [weak self] in - while !Task.isCancelled { + while Task.isCancelled == false { do { try await Task.sleep(nanoseconds: 60_000_000_000) } catch { @@ -151,7 +151,7 @@ final class AccountStore: ObservableObject { } private func runOperatorSnapshotStream() async { - while !Task.isCancelled { + while Task.isCancelled == false { do { try await connectOperatorSnapshotStream() } catch { @@ -176,7 +176,7 @@ final class AccountStore: ObservableObject { socket.cancel(with: .normalClosure, reason: nil) } - while !Task.isCancelled { + while Task.isCancelled == false { let event = try await receiveOperatorDashboardEvent(from: socket) applyOperatorDashboardEvent(event) @@ -281,7 +281,7 @@ final class AccountStore: ObservableObject { } func setFastMode(_ enabled: Bool) async { - guard !isSettingFastMode else { + guard isSettingFastMode == false else { return } @@ -434,7 +434,7 @@ struct DeviceLoginPrompt: Equatable { private static func normalizedCode(from line: String) -> String? { let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { + guard trimmed.isEmpty == false else { return nil } diff --git a/apps/decodex-app/Sources/DecodexApp/DecodexAppBridge.swift b/apps/decodex-app/Sources/DecodexApp/DecodexAppBridge.swift index 08de47e0d..07a087880 100644 --- a/apps/decodex-app/Sources/DecodexApp/DecodexAppBridge.swift +++ b/apps/decodex-app/Sources/DecodexApp/DecodexAppBridge.swift @@ -114,7 +114,7 @@ private final class AppBridgeEventParser: @unchecked Sendab } func append(_ data: Data) throws { - guard !data.isEmpty else { + guard data.isEmpty == false else { return } @@ -134,7 +134,7 @@ private final class AppBridgeEventParser: @unchecked Sendab buffer = "" lock.unlock() - if !remainder.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + if remainder.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false { try handle(remainder) } @@ -161,7 +161,7 @@ private final class AppBridgeEventParser: @unchecked Sendab private func handle(_ line: String) throws { let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { + guard trimmed.isEmpty == false else { return } guard let data = trimmed.data(using: .utf8) else { @@ -269,7 +269,7 @@ struct DecodexAppBridge: Sendable { } private func helperExecutableURL() throws -> URL { - if let override = ProcessInfo.processInfo.environment["DECODEX_APP_HELPER"], !override.isEmpty { + if let override = ProcessInfo.processInfo.environment["DECODEX_APP_HELPER"], override.isEmpty == false { let overrideURL = URL(fileURLWithPath: override) if FileManager.default.isExecutableFile(atPath: overrideURL.path) { return overrideURL diff --git a/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift b/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift index f2d73975a..d052b86a9 100644 --- a/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift +++ b/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift @@ -15,7 +15,7 @@ private enum DecodexServerProbe: Equatable { actor DecodexServerBridge { static let shared = DecodexServerBridge() - private let defaultBaseURL = URL(string: "http://127.0.0.1:8912")! + private let defaultBaseURL = DecodexServerBridge.makeDefaultBaseURL() private let defaultListenAddress = "127.0.0.1:8912" private let liveCheckFreshness: TimeInterval = 5 private var serverBaseURL: URL? @@ -70,7 +70,7 @@ actor DecodexServerBridge { baseURL: URL, as type: T.Type ) async throws -> T { - let url = routeURL(baseURL: baseURL, route: route) + let url = try routeURL(baseURL: baseURL, route: route) var urlRequest = URLRequest(url: url) urlRequest.httpMethod = route.method @@ -239,7 +239,7 @@ actor DecodexServerBridge { } private func decodexExecutableURL() throws -> URL { - if let override = ProcessInfo.processInfo.environment["DECODEX_APP_DECODEX"], !override.isEmpty { + if let override = ProcessInfo.processInfo.environment["DECODEX_APP_DECODEX"], override.isEmpty == false { let overrideURL = URL(fileURLWithPath: override) if FileManager.default.isExecutableFile(atPath: overrideURL.path) { return overrideURL @@ -259,16 +259,30 @@ actor DecodexServerBridge { ) } - private func routeURL(baseURL: URL, route: ServerRoute) -> URL { + private func routeURL(baseURL: URL, route: ServerRoute) throws -> URL { let parts = route.path.split(separator: "?", maxSplits: 1).map(String.init) - var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)! + guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { + throw DecodexAppBridgeError.invalidResponse("Decodex server URL is invalid") + } components.path = "/" + parts[0] if parts.count == 2 { components.query = parts[1] } - return components.url! + guard let url = components.url else { + throw DecodexAppBridgeError.invalidResponse("Decodex server route URL is invalid") + } + + return url + } + + private static func makeDefaultBaseURL() -> URL { + guard let url = URL(string: "http://127.0.0.1:8912") else { + preconditionFailure("default Decodex server URL must be valid") + } + + return url } } diff --git a/apps/decodex-app/Sources/DecodexApp/LoginPanelPresenter.swift b/apps/decodex-app/Sources/DecodexApp/LoginPanelPresenter.swift index 04d9e1481..c33b4b975 100644 --- a/apps/decodex-app/Sources/DecodexApp/LoginPanelPresenter.swift +++ b/apps/decodex-app/Sources/DecodexApp/LoginPanelPresenter.swift @@ -132,7 +132,7 @@ struct LoginPanelPresenter: NSViewRepresentable { private func origin(for panelSize: NSSize) -> NSPoint { let parentWindow = hostView?.window let screen = parentWindow?.screen ?? NSScreen.main - let visibleFrame = screen?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1280, height: 800) + let visibleFrame = screen?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1_280, height: 800) let margin: CGFloat = 8 var x: CGFloat @@ -158,7 +158,7 @@ struct LoginPanelPresenter: NSViewRepresentable { } panel = nil hostingView = nil - guard !isClosingProgrammatically else { + guard isClosingProgrammatically == false else { return } diff --git a/apps/decodex-app/Sources/DecodexApp/LoginSheetView.swift b/apps/decodex-app/Sources/DecodexApp/LoginSheetView.swift index b4ab6c40d..14e3e154b 100644 --- a/apps/decodex-app/Sources/DecodexApp/LoginSheetView.swift +++ b/apps/decodex-app/Sources/DecodexApp/LoginSheetView.swift @@ -18,7 +18,7 @@ enum AccountLoginSheetMode: Equatable { case .newAccount: return fallback case .account(let name): - return !isActive && !name.isEmpty ? name : fallback + return isActive == false && name.isEmpty == false ? name : fallback } } } @@ -350,7 +350,7 @@ private struct LoginIconActionButton: View { isFeedbackActive: isFeedbackActive ) ) - .disabled(!isEnabled) + .disabled(isEnabled == false) .onHover { hovering in withAnimation(PanelMotion.hover) { isHovered = hovering @@ -389,7 +389,7 @@ private struct LoginSmallButtonStyle: ButtonStyle { } private var foreground: Color { - if !isEnabled { + if isEnabled == false { return LoginPalette.secondaryText(colorScheme).opacity(0.62) } if isFeedbackActive { @@ -402,7 +402,7 @@ private struct LoginSmallButtonStyle: ButtonStyle { } private var stroke: Color { - if !isEnabled { + if isEnabled == false { return LoginPalette.secondaryText(colorScheme).opacity(0.08) } if isFeedbackActive { diff --git a/apps/decodex-app/Sources/DecodexApp/OperatorSnapshotModels.swift b/apps/decodex-app/Sources/DecodexApp/OperatorSnapshotModels.swift index e81300fc2..0d47952d9 100644 --- a/apps/decodex-app/Sources/DecodexApp/OperatorSnapshotModels.swift +++ b/apps/decodex-app/Sources/DecodexApp/OperatorSnapshotModels.swift @@ -16,7 +16,7 @@ struct OperatorSnapshotResponse: Decodable, Sendable { var queuedCount: Int { max( - queuedCandidates.filter { !$0.isClosed }.count, + queuedCandidates.filter { $0.isClosed == false }.count, projects.reduce(0) { $0 + $1.queuedCandidateCount } ) } @@ -50,15 +50,15 @@ struct OperatorSnapshotResponse: Decodable, Sendable { || waitingCount > 0 || attentionCount > 0 || cleanupCount > 0 - || !warnings.isEmpty + || warnings.isEmpty == false } var shouldDisplayInPanel: Bool { - hasVisibleSignal && (activeRunCount > 0 || !isAPIOnlySnapshot) + hasVisibleSignal && (activeRunCount > 0 || isAPIOnlySnapshot == false) } var warningSummary: String? { - let labels = warnings.compactMap(Self.warningLabel).filter { !$0.isEmpty } + let labels = warnings.compactMap(Self.warningLabel).filter { $0.isEmpty == false } guard let first = labels.first else { return nil } @@ -300,10 +300,10 @@ struct OperatorRunStatus: Decodable, Identifiable, Sendable { } var compactTitle: String { - if let issueIdentifier = trimmed(issueIdentifier), !issueIdentifier.isEmpty { + if let issueIdentifier = trimmed(issueIdentifier), issueIdentifier.isEmpty == false { return issueIdentifier } - if let title = trimmed(title), !title.isEmpty { + if let title = trimmed(title), title.isEmpty == false { return title } @@ -311,25 +311,25 @@ struct OperatorRunStatus: Decodable, Identifiable, Sendable { } var compactDetail: String { - if let currentDetail = trimmed(childAgentActivity?.currentDetail), !currentDetail.isEmpty { + if let currentDetail = trimmed(childAgentActivity?.currentDetail), currentDetail.isEmpty == false { return currentDetail } - if let currentBucket = trimmed(childAgentActivity?.currentBucket), !currentBucket.isEmpty { + if let currentBucket = trimmed(childAgentActivity?.currentBucket), currentBucket.isEmpty == false { return readable(currentBucket) } - if let waitReason = trimmed(waitReason), !waitReason.isEmpty { + if let waitReason = trimmed(waitReason), waitReason.isEmpty == false { return readable(waitReason) } - if let operation = trimmed(currentOperation), !operation.isEmpty, operation != "idle" { + if let operation = trimmed(currentOperation), operation.isEmpty == false, operation != "idle" { return readable(operation) } - if let phase = trimmed(phase), !phase.isEmpty { + if let phase = trimmed(phase), phase.isEmpty == false { return readable(phase) } - if let threadStatus = trimmed(threadStatus), !threadStatus.isEmpty { + if let threadStatus = trimmed(threadStatus), threadStatus.isEmpty == false { return readable(threadStatus) } - if let status = trimmed(status), !status.isEmpty { + if let status = trimmed(status), status.isEmpty == false { return readable(status) } @@ -386,7 +386,7 @@ struct OperatorRunStatus: Decodable, Identifiable, Sendable { } private func mergedTitle(from activity: OperatorRunStatus) -> String? { - if let title = activity.title, !title.isEmpty, !activity.titleIsOperationFallback { + if let title = activity.title, title.isEmpty == false, activity.titleIsOperationFallback == false { return title } @@ -394,7 +394,7 @@ struct OperatorRunStatus: Decodable, Identifiable, Sendable { } private var titleIsOperationFallback: Bool { - guard let title, !title.isEmpty else { + guard let title, title.isEmpty == false else { return false } @@ -643,7 +643,7 @@ struct OperatorRunAccountSummary: Decodable, Sendable { let email: String? func matches(_ account: CodexAccount) -> Bool { - if !accountFingerprint.isEmpty, accountFingerprint == account.accountFingerprint { + if accountFingerprint.isEmpty == false, accountFingerprint == account.accountFingerprint { return true } if let email, let accountEmail = account.email { diff --git a/scripts/assets/render_decodex_app_icons.swift b/scripts/assets/render_decodex_app_icons.swift index 5486595c6..a274cd8a3 100755 --- a/scripts/assets/render_decodex_app_icons.swift +++ b/scripts/assets/render_decodex_app_icons.swift @@ -13,7 +13,7 @@ for directory in [appIconGenerated, appIconComposerAssets, trayIconGenerated] { try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) } -let canvasSize = 1024 +let canvasSize = 1_024 let appIconURL = appIconGenerated.appendingPathComponent("app-icon-flat.png") let previewURL = appIconGenerated.appendingPathComponent("app-icon-default-preview.png") let composerLayerURL = appIconComposerAssets.appendingPathComponent("app-icon-composer-layer.png") @@ -42,6 +42,22 @@ enum TemplateMark { static let promptScale: CGFloat = 0.88 } +func requiredGradient(colors: [NSColor]) -> NSGradient { + guard let gradient = NSGradient(colors: colors) else { + preconditionFailure("icon gradient colors must be valid") + } + + return gradient +} + +func currentCGContext() -> CGContext { + guard let context = NSGraphicsContext.current?.cgContext else { + preconditionFailure("icon drawing requires a graphics context") + } + + return context +} + func bitmap(size: Int = canvasSize, drawing: (CGContext) -> Void) throws -> NSBitmapImageRep { guard let rep = NSBitmapImageRep( bitmapDataPlanes: nil, @@ -59,12 +75,18 @@ func bitmap(size: Int = canvasSize, drawing: (CGContext) -> Void) throws -> NSBi } NSGraphicsContext.saveGraphicsState() - NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) - NSGraphicsContext.current?.cgContext.setShouldAntialias(true) - NSGraphicsContext.current?.cgContext.setAllowsAntialiasing(true) - NSGraphicsContext.current?.cgContext.interpolationQuality = .high - drawing(NSGraphicsContext.current!.cgContext) - NSGraphicsContext.restoreGraphicsState() + defer { + NSGraphicsContext.restoreGraphicsState() + } + guard let graphicsContext = NSGraphicsContext(bitmapImageRep: rep) else { + throw NSError(domain: "DecodexIconRender", code: 3) + } + NSGraphicsContext.current = graphicsContext + let context = graphicsContext.cgContext + context.setShouldAntialias(true) + context.setAllowsAntialiasing(true) + context.interpolationQuality = .high + drawing(context) return rep } @@ -157,7 +179,7 @@ func appBoltCorePoints() -> [NSPoint] { func drawTile() { let tile = roundedRect(NSRect(x: 58, y: 58, width: 908, height: 908), radius: 222) - NSGradient(colors: [Palette.fieldTop, Palette.fieldBottom])!.draw(in: tile, angle: -48) + requiredGradient(colors: [Palette.fieldTop, Palette.fieldBottom]).draw(in: tile, angle: -48) Palette.white.withAlphaComponent(0.11).setStroke() let rim = roundedRect(NSRect(x: 82, y: 82, width: 860, height: 860), radius: 198) @@ -186,7 +208,7 @@ func drawCloudContainer() { cloudPath().fill() NSShadow().set() - NSGradient(colors: [Palette.cloudTop, Palette.cloudBottom])!.draw(in: cloudPath(), angle: -58) + requiredGradient(colors: [Palette.cloudTop, Palette.cloudBottom]).draw(in: cloudPath(), angle: -58) } func drawPromptMark(color: NSColor, width: CGFloat, alpha: CGFloat = 1) { @@ -220,7 +242,7 @@ func drawTemplateBolt() { } func drawTemplateCloud() { - let context = NSGraphicsContext.current!.cgContext + let context = currentCGContext() context.saveGState() context.translateBy(x: 512, y: 512) context.scaleBy(x: TemplateMark.cloudScale, y: TemplateMark.cloudScale) @@ -231,7 +253,7 @@ func drawTemplateCloud() { } func clearTemplatePrompt() { - let context = NSGraphicsContext.current!.cgContext + let context = currentCGContext() context.saveGState() context.setBlendMode(.clear) context.translateBy(x: TemplateMark.promptOffset.width, y: TemplateMark.promptOffset.height) @@ -243,7 +265,7 @@ func clearTemplatePrompt() { } func drawTemplateMark() { - let context = NSGraphicsContext.current!.cgContext + let context = currentCGContext() context.saveGState() context.translateBy(x: 512, y: 512) context.scaleBy(x: TemplateMark.canvasScale, y: TemplateMark.canvasScale) @@ -274,9 +296,12 @@ func drawTrayIcon() throws -> NSBitmapImageRep { } func scaledPNG(from source: NSBitmapImageRep, size: Int, to url: URL) throws { + guard let sourceImage = source.cgImage else { + throw NSError(domain: "DecodexIconRender", code: 4) + } let rep = try bitmap(size: size) { ctx in ctx.interpolationQuality = .high - ctx.draw(source.cgImage!, in: CGRect(x: 0, y: 0, width: size, height: size)) + ctx.draw(sourceImage, in: CGRect(x: 0, y: 0, width: size, height: size)) } try writePNG(rep, to: url) } @@ -297,7 +322,7 @@ func buildICNS(from source: NSBitmapImageRep) throws { ("icon_256x256.png", 256), ("icon_256x256@2x.png", 512), ("icon_512x512.png", 512), - ("icon_512x512@2x.png", 1024), + ("icon_512x512@2x.png", 1_024), ] for (name, size) in sizes { try scaledPNG(from: source, size: size, to: iconset.appendingPathComponent(name))