diff --git a/Sources/Adwaita/Model/AdwaitaAboutDialogConfig.swift b/Sources/Adwaita/Model/AdwaitaAboutDialogConfig.swift index 94d7173..0ceade3 100644 --- a/Sources/Adwaita/Model/AdwaitaAboutDialogConfig.swift +++ b/Sources/Adwaita/Model/AdwaitaAboutDialogConfig.swift @@ -11,6 +11,40 @@ import Foundation /// URL links for about dialog. public typealias AboutLink = (title: String, url: URL?) +/// The type of contribution made by a credited person. +public enum ContributionRole { + + /// A person who contributed to development. + case developer + /// A person who contributed to design. + case designer + /// A person who contributed artwork or visuals. + case artist + /// A person who contributed translations. + case translator + +} + +/// A credited person and their contribution role. +public struct CreditEntry { + + /// The contributor's role. + public var role: ContributionRole + + /// The contributor's name. + public var name: String + + /// Create a new credit entry. + /// - Parameters: + /// - role: The contributor's role. + /// - name: The contributor's name. + public init(role: ContributionRole, name: String) { + self.role = role + self.name = name + } + +} + /// Initialization options for the about dialog wrapper. public struct AdwaitaAboutDialogConfig { @@ -89,10 +123,12 @@ public struct AdwaitaAboutDialogConfig { public var copyright: String? /// The app's license. public var license: String? - /// The app's release notes + /// The app's release notes. public var releaseNotes: String? - /// The comments about the application + /// The comments about the application. public var comments: String? + /// Recognition by name and role of contributors. + public var credits: [CreditEntry]? /// Initialize the about dialog wrapper. /// - Parameters: @@ -108,6 +144,7 @@ public struct AdwaitaAboutDialogConfig { /// - license: The app's license. /// - releaseNotes: The app's release notes. /// - comments: The comments about the application. + /// - credits: Recognition by name and role of contributors. public init( appName: String? = nil, developer: String? = nil, @@ -120,7 +157,8 @@ public struct AdwaitaAboutDialogConfig { copyright: String? = nil, license: String? = nil, releaseNotes: String? = nil, - comments: String? = nil + comments: String? = nil, + credits: [CreditEntry]? = nil ) { self.appName = appName self.developer = developer @@ -134,6 +172,48 @@ public struct AdwaitaAboutDialogConfig { self.license = license self.releaseNotes = releaseNotes self.comments = comments + self.credits = credits + } + + /// Apply the credit entries to the given dialog. + /// - Parameters: + /// - credits: The list of credit entries to apply. + /// - dialog: The underlying Adwaita dialog instance to update with the credit information. + private func applyCredits(_ credits: [CreditEntry], to dialog: OpaquePointer) { + var developers: [String] = [] + var designers: [String] = [] + var artists: [String] = [] + var translators: [String] = [] + + for entry in credits { + switch entry.role { + case .developer: + developers.append(entry.name) + case .designer: + designers.append(entry.name) + case .artist: + artists.append(entry.name) + case .translator: + translators.append(entry.name) + } + } + + if let ptr = developers.cMutableArray, !developers.isEmpty { + adw_about_dialog_set_developers(dialog, ptr) + } + + if let ptr = designers.cMutableArray, !designers.isEmpty { + adw_about_dialog_set_designers(dialog, ptr) + } + + if let ptr = artists.cMutableArray, !artists.isEmpty { + adw_about_dialog_set_artists(dialog, ptr) + } + + if !translators.isEmpty { + let joined = translators.joined(separator: "\n") + adw_about_dialog_set_translator_credits(dialog, joined) + } } /// Apply the configuration values to the given dialog. @@ -159,6 +239,10 @@ public struct AdwaitaAboutDialogConfig { } links?.forEach { (title: String, url: URL?) in adw_about_dialog_add_link(dialog, title, url?.absoluteString) } + + if let credits { + applyCredits(credits, to: dialog) + } } } diff --git a/Sources/Adwaita/Model/Extensions/Array.swift b/Sources/Adwaita/Model/Extensions/Array.swift index 3dceff3..b067bac 100644 --- a/Sources/Adwaita/Model/Extensions/Array.swift +++ b/Sources/Adwaita/Model/Extensions/Array.swift @@ -5,11 +5,13 @@ // Created by david-swift on 06.08.23. // +import Foundation + extension Array where Element == String { /// Get the C version of the array. var cArray: UnsafePointer?>? { - let cStrings = self.map { $0.utf8CString } + let cStrings = map { $0.utf8CString } let cStringPointers = cStrings.map { $0.withUnsafeBufferPointer { $0.baseAddress } } let optionalCStringPointers = cStringPointers + [nil] var optionalCStringPointersCopy = optionalCStringPointers @@ -25,4 +27,23 @@ extension Array where Element == String { return UnsafePointer(pointer) } + /// Get the mutable C version of the array. + var cMutableArray: UnsafeMutablePointer?>? { + // Allocate space for N strings + NULL terminator + let pointer = UnsafeMutablePointer?>.allocate( + capacity: count + 1 + ) + + // Convert each Swift string into a stable C string + for (index, string) in enumerated() { + let cstr = strdup(string) // allocate real C string + pointer[index] = UnsafePointer(cstr) + } + + // Add NULL terminator + pointer[count] = nil + + return pointer + } + } diff --git a/Sources/Demo/Demo.swift b/Sources/Demo/Demo.swift index 29ed26e..e42fcb0 100644 --- a/Sources/Demo/Demo.swift +++ b/Sources/Demo/Demo.swift @@ -166,6 +166,14 @@ struct Demo: App { cfg.license = "MIT" cfg.releaseNotes = AdwaitaAboutDialogConfig.demoReleaseNotes cfg.comments = AdwaitaAboutDialogConfig.demoComments + cfg.credits = [ + .init(role: .developer, name: "Jane Doe"), + .init(role: .developer, name: "John Roe"), + .init(role: .designer, name: "Mika Sato"), + .init(role: .artist, name: "Leo Martins"), + .init(role: .translator, name: "Yuki Nakamura"), + .init(role: .translator, name: "Tod Brown") + ] } .preferencesDialog(visible: $preferences) .preferencesPage("Page 1", icon: .default(icon: .audioHeadset)) { page in