Add support for acknowledgements and rework about links

This commit is contained in:
Ira Limitanei 2026-01-23 17:16:11 +09:00
parent 4ec1ac3769
commit 83af8478c3
Signed by: lambdaclan
GPG Key ID: 43D6A575B03B80E4

View File

@ -8,8 +8,25 @@
import CAdw
import Foundation
/// URL links for about dialog.
public typealias AboutLink = (title: String, url: URL?)
/// A link shown in the About dialog.
public struct AboutLink {
/// The link title.
public var title: String
/// The destination URL.
public var url: URL?
/// Create a new link.
/// - Parameters:
/// - title: The link title.
/// - url: The destination URL.
public init(title: String, url: URL?) {
self.title = title
self.url = url
}
}
/// The type of contribution made by a credited person.
public enum ContributionRole {
@ -45,6 +62,26 @@ public struct CreditEntry {
}
/// An acknowledgement entry.
public struct AcknowledgementEntry {
/// The acknowledgement section title.
public var title: String
/// Acknowledged person/organization name.
public var name: String
/// Create a new acknowledgement entry.
/// - Parameters:
/// - title: The acknowledgement section title.
/// - name: The acknowledged person or organization.
public init(title: String, name: String) {
self.title = title
self.name = name
}
}
/// Initialization options for the about dialog wrapper.
public struct AdwaitaAboutDialogConfig {
@ -129,6 +166,8 @@ public struct AdwaitaAboutDialogConfig {
public var comments: String?
/// Recognition by name and role of contributors.
public var credits: [CreditEntry]?
/// Acknowledgements to display in the dialog.
public var acknowledgements: [AcknowledgementEntry]?
/// Initialize the about dialog wrapper.
/// - Parameters:
@ -145,6 +184,7 @@ public struct AdwaitaAboutDialogConfig {
/// - releaseNotes: The app's release notes.
/// - comments: The comments about the application.
/// - credits: Recognition by name and role of contributors.
/// - acknowledgements: List of acknowledgements.
public init(
appName: String? = nil,
developer: String? = nil,
@ -158,7 +198,8 @@ public struct AdwaitaAboutDialogConfig {
license: String? = nil,
releaseNotes: String? = nil,
comments: String? = nil,
credits: [CreditEntry]? = nil
credits: [CreditEntry]? = nil,
acknowledgements: [AcknowledgementEntry]? = nil
) {
self.appName = appName
self.developer = developer
@ -173,46 +214,104 @@ public struct AdwaitaAboutDialogConfig {
self.releaseNotes = releaseNotes
self.comments = comments
self.credits = credits
self.acknowledgements = acknowledgements
}
/// Apply the credit entries to the given dialog.
/// Applies a string value to the dialog using the given setter.
/// - 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] = []
/// - value: The optional string to apply.
/// - setter: The C function that sets the value on the dialog.
/// - dialog: The dialog instance.
@inline(__always)
private func set(
_ value: String?,
using setter: (OpaquePointer, UnsafePointer<CChar>?) -> Void,
on dialog: OpaquePointer
) {
if let value {
setter(dialog, value)
}
}
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)
/// Applies a list of links to the dialog.
/// - Parameters:
/// - links: The optional list of links.
/// - dialog: The dialog instance.
@inline(__always)
private func set(
_ links: [AboutLink]?,
on dialog: OpaquePointer
) {
links?.forEach { link in
adw_about_dialog_add_link(dialog, link.title, link.url?.absoluteString)
}
}
/// Applies credit entries to the dialog.
/// - Parameters:
/// - credits: The optional list of credit entries.
/// - dialog: The dialog instance.
@inline(__always)
private func set(
_ credits: [CreditEntry]?,
on dialog: OpaquePointer
) {
if let credits {
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)
}
}
}
if let ptr = developers.cMutableArray, !developers.isEmpty {
adw_about_dialog_set_developers(dialog, ptr)
}
/// Applies acknowledgement entries to the dialog.
/// - Parameters:
/// - acknowledgements: The optional list of acknowledgement entries.
/// - dialog: The dialog instance.
@inline(__always)
private func set(
_ acknowledgements: [AcknowledgementEntry]?,
on dialog: OpaquePointer
) {
if let acknowledgements {
let grouped = Dictionary(grouping: acknowledgements) { $0.title }
if let ptr = designers.cMutableArray, !designers.isEmpty {
adw_about_dialog_set_designers(dialog, ptr)
}
for (title, entries) in grouped {
let names = entries.map { $0.name }
guard let people = names.cMutableArray else { continue }
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)
adw_about_dialog_add_acknowledgement_section(dialog, title, people)
}
}
}
@ -220,29 +319,20 @@ public struct AdwaitaAboutDialogConfig {
/// - Parameters:
/// - dialog: The underlying Adwaita dialog instance to update with the configuration.
func apply(to dialog: OpaquePointer) {
let handlers: [(String?, (OpaquePointer, UnsafePointer<CChar>?) -> Void)] = [
(appName, adw_about_dialog_set_application_name),
(developer, adw_about_dialog_set_developer_name),
(version, adw_about_dialog_set_version),
(icon?.string, adw_about_dialog_set_application_icon),
(website?.absoluteString, adw_about_dialog_set_website),
(issues?.absoluteString, adw_about_dialog_set_issue_url),
(support?.absoluteString, adw_about_dialog_set_support_url),
(copyright, adw_about_dialog_set_copyright),
(license, adw_about_dialog_set_license),
(releaseNotes, adw_about_dialog_set_release_notes),
(comments, adw_about_dialog_set_comments)
]
for (value, action) in handlers {
value.map { action(dialog, $0) }
}
links?.forEach { (title: String, url: URL?) in adw_about_dialog_add_link(dialog, title, url?.absoluteString) }
if let credits {
applyCredits(credits, to: dialog)
}
set(appName, using: adw_about_dialog_set_application_name, on: dialog)
set(developer, using: adw_about_dialog_set_developer_name, on: dialog)
set(version, using: adw_about_dialog_set_version, on: dialog)
set(icon?.string, using: adw_about_dialog_set_application_icon, on: dialog)
set(website?.absoluteString, using: adw_about_dialog_set_website, on: dialog)
set(issues?.absoluteString, using: adw_about_dialog_set_issue_url, on: dialog)
set(support?.absoluteString, using: adw_about_dialog_set_support_url, on: dialog)
set(copyright, using: adw_about_dialog_set_copyright, on: dialog)
set(license, using: adw_about_dialog_set_license, on: dialog)
set(releaseNotes, using: adw_about_dialog_set_release_notes, on: dialog)
set(comments, using: adw_about_dialog_set_comments, on: dialog)
set(links, on: dialog)
set(credits, on: dialog)
set(acknowledgements, on: dialog)
}
}