Compare commits

...

3 Commits

4 changed files with 206 additions and 133 deletions

View File

@ -82,64 +82,34 @@ public struct AcknowledgementEntry {
}
/// Other (additional) application entry.
public struct OtherAppEntry {
/// The application identifier of the referenced app.
public var appID: String
/// The display name of the referenced app.
public var name: String
/// A short descriptive summary of the referenced app.
public var summary: String
/// Creates a new other application entry.
/// - Parameters:
/// - appID: The application identifier of the referenced app.
/// - name: The display name of the referenced app.
/// - summary: A short descriptive summary of the referenced app.
public init(appID: String, name: String, summary: String) {
self.appID = appID
self.name = name
self.summary = summary
}
}
/// Initialization options for the about dialog wrapper.
public struct AdwaitaAboutDialogConfig {
/// Example HTML release notes used for demonstrating how the About dialog
/// renders structured content such as paragraphs, lists, and inline markup.
/// This sample is intended for testing, previews, and documentation.
public static let demoReleaseNotes = """
<p>This template supports three structures: paragraphs using <code>&lt;p&gt;</code>, ordered lists using
<code>&lt;ol&gt;</code>, and unordered lists using <code>&lt;ul&gt;</code>.
Both list types must contain list items marked with <code>&lt;li&gt;</code>.</p>
<p>Within paragraphs and list items, you may use <code>&lt;em&gt;</code> to apply
<em>emphasis</em>(italic text) and <code>&lt;code&gt;</code> to mark <code>inline code</code>
for monospaced text.
These inline styles are supported only inside those elements.</p>
<p>Any text placed outside <code>&lt;p&gt;</code>, <code>&lt;ol&gt;</code>,
<code>&lt;ul&gt;</code>, or <code>&lt;li&gt;</code> tags is ignored by the template
processor.</p>
<ol>
<li>Ordered list items represent numbered content and may
include <code>&lt;em&gt;</code> for <em>emphasis</em> or <code>&lt;code&gt;</code> for inline code.</li>
<li>They follow the same rules as paragraphs regarding allowed inline styles.</li>
</ol>
<ul>
<li>Unordered list items represent bullet points and support the same inline styles.</li>
<li>They must contain only text and allowed inline formatting.</li>
</ul>
"""
/// Example Pangomarkup comments showcasing how styled text, links, and
/// formatting behave inside the About dialogs Details section.
/// Useful for previews, testing, and as a reference for developers who want
/// to embed formatted text in their own dialogs.
public static let demoComments = """
This text demonstrates basic Pango markup along with helpful documentation links.
Comments shown in an Adwaita AboutDialog will appear on the Details page.
They can be long and detailed, and they may include links and Pango markup for
formatting.
Pango markup supports tags like:
<b>bold</b>
<i>italic</i>
<span foreground="steelblue">colored text</span>
Full reference: <a href="https://docs.gtk.org/Pango/pango_markup.html">Pango Markup
Reference</a>
Example markup:
<span font="14pt" weight="bold">Demo Title</span>
<span foreground="tomato">Highlighted text</span>
<u>Underlined text</u>
Useful links:
<a href="https://adwaita-swift.aparoksha.dev/documentation/adwaita">AdwaitaSwift Documentation</a>
You can embed these links directly in your UI using Pango markup.
"""
/// The app's name.
public var appName: String?
/// The developer's name.
@ -168,6 +138,8 @@ public struct AdwaitaAboutDialogConfig {
public var credits: [CreditEntry]?
/// Acknowledgements to display in the dialog.
public var acknowledgements: [AcknowledgementEntry]?
/// Additional applications.
public var otherApps: [OtherAppEntry]?
/// Initialize the about dialog wrapper.
/// - Parameters:
@ -185,6 +157,7 @@ public struct AdwaitaAboutDialogConfig {
/// - comments: The comments about the application.
/// - credits: Recognition by name and role of contributors.
/// - acknowledgements: List of acknowledgements.
/// - otherApps: List of other applications.
public init(
appName: String? = nil,
developer: String? = nil,
@ -199,7 +172,8 @@ public struct AdwaitaAboutDialogConfig {
releaseNotes: String? = nil,
comments: String? = nil,
credits: [CreditEntry]? = nil,
acknowledgements: [AcknowledgementEntry]? = nil
acknowledgements: [AcknowledgementEntry]? = nil,
otherApps: [OtherAppEntry]? = nil
) {
self.appName = appName
self.developer = developer
@ -215,6 +189,7 @@ public struct AdwaitaAboutDialogConfig {
self.comments = comments
self.credits = credits
self.acknowledgements = acknowledgements
self.otherApps = otherApps
}
/// Applies a string value to the dialog using the given setter.
@ -228,9 +203,11 @@ public struct AdwaitaAboutDialogConfig {
using setter: (OpaquePointer, UnsafePointer<CChar>?) -> Void,
on dialog: OpaquePointer
) {
if let value {
setter(dialog, value)
guard let value else {
return
}
setter(dialog, value)
}
/// Applies a list of links to the dialog.
@ -252,45 +229,32 @@ public struct AdwaitaAboutDialogConfig {
/// - 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] = []
private func set(_ credits: [CreditEntry]?, on dialog: OpaquePointer) {
guard let credits else {
return
}
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)
}
}
let grouped = Dictionary(grouping: credits, by: \.role)
if let ptr = developers.cMutableArray, !developers.isEmpty {
adw_about_dialog_set_developers(dialog, ptr)
}
if let devs = grouped[.developer]?.map(\.name),
let ptr = devs.cMutableArray {
adw_about_dialog_set_developers(dialog, ptr)
}
if let ptr = designers.cMutableArray, !designers.isEmpty {
adw_about_dialog_set_designers(dialog, ptr)
}
if let designers = grouped[.designer]?.map(\.name),
let ptr = designers.cMutableArray {
adw_about_dialog_set_designers(dialog, ptr)
}
if let ptr = artists.cMutableArray, !artists.isEmpty {
adw_about_dialog_set_artists(dialog, ptr)
}
if let artists = grouped[.artist]?.map(\.name),
let ptr = artists.cMutableArray {
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 translators = grouped[.translator]?.map(\.name),
!translators.isEmpty {
let joined = translators.joined(separator: "\n")
adw_about_dialog_set_translator_credits(dialog, joined)
}
}
@ -303,18 +267,43 @@ public struct AdwaitaAboutDialogConfig {
_ acknowledgements: [AcknowledgementEntry]?,
on dialog: OpaquePointer
) {
if let acknowledgements {
let grouped = Dictionary(grouping: acknowledgements) { $0.title }
guard let acknowledgements else {
return
}
for (title, entries) in grouped {
let names = entries.map { $0.name }
guard let people = names.cMutableArray else { continue }
let grouped = Dictionary(grouping: acknowledgements) { $0.title }
adw_about_dialog_add_acknowledgement_section(dialog, title, people)
}
for (title, entries) in grouped {
let names = entries.map { $0.name }
guard let people = names.cMutableArray else { continue }
adw_about_dialog_add_acknowledgement_section(dialog, title, people)
}
}
/// Applies other apps entries to the dialog.
/// - Parameters:
/// - otherApps: The optional list of other app entries.
/// - dialog: The dialog instance.
@inline(__always)
private func set(
_ otherApps: [OtherAppEntry]?,
on dialog: OpaquePointer
) {
guard let otherApps else {
return
}
for entry in otherApps {
adw_about_dialog_add_other_app(
dialog,
entry.appID,
entry.name,
entry.summary
)
}
}
/// Apply the configuration values to the given dialog.
/// - Parameters:
/// - dialog: The underlying Adwaita dialog instance to update with the configuration.
@ -333,6 +322,7 @@ public struct AdwaitaAboutDialogConfig {
set(links, on: dialog)
set(credits, on: dialog)
set(acknowledgements, on: dialog)
set(otherApps, on: dialog)
}
}

View File

@ -29,18 +29,15 @@ extension Array where Element == String {
/// Get the mutable C version of the array.
var cMutableArray: UnsafeMutablePointer<UnsafePointer<CChar>?>? {
// Allocate space for N strings + NULL terminator
let pointer = UnsafeMutablePointer<UnsafePointer<CChar>?>.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
let cstr = strdup(string)
pointer[index] = UnsafePointer(cstr)
}
// Add NULL terminator
pointer[count] = nil
return pointer

View File

@ -0,0 +1,115 @@
//
// AboutDialogDemo.swift
// Adwaita
//
// Created by lambdaclan on 24.01.26.
//
// swiftlint:disable missing_docs
import Adwaita
import Foundation
enum AboutDialogDemo {
static var sample: (inout AdwaitaAboutDialogConfig) -> Void {
{ cfg in
applyDemoConfig(&cfg)
}
}
private static let demoReleaseNotes = """
<p>This template supports three structures: paragraphs using <code>&lt;p&gt;</code>, ordered lists using
<code>&lt;ol&gt;</code>, and unordered lists using <code>&lt;ul&gt;</code>.
Both list types must contain list items marked with <code>&lt;li&gt;</code>.</p>
<p>Within paragraphs and list items, you may use <code>&lt;em&gt;</code> to apply
<em>emphasis</em>(italic text) and <code>&lt;code&gt;</code> to mark <code>inline code</code>
for monospaced text.
These inline styles are supported only inside those elements.</p>
<p>Any text placed outside <code>&lt;p&gt;</code>, <code>&lt;ol&gt;</code>,
<code>&lt;ul&gt;</code>, or <code>&lt;li&gt;</code> tags is ignored by the template
processor.</p>
<ol>
<li>Ordered list items represent numbered content and may
include <code>&lt;em&gt;</code> for <em>emphasis</em> or <code>&lt;code&gt;</code> for inline code.</li>
<li>They follow the same rules as paragraphs regarding allowed inline styles.</li>
</ol>
<ul>
<li>Unordered list items represent bullet points and support the same inline styles.</li>
<li>They must contain only text and allowed inline formatting.</li>
</ul>
"""
private static let demoComments = """
This text demonstrates basic Pango markup along with helpful documentation links.
Comments shown in an Adwaita AboutDialog will appear on the Details page.
They can be long and detailed, and they may include links and Pango markup for
formatting.
Pango markup supports tags like:
<b>bold</b>
<i>italic</i>
<span foreground="steelblue">colored text</span>
Full reference: <a href="https://docs.gtk.org/Pango/pango_markup.html">Pango Markup
Reference</a>
Example markup:
<span font="14pt" weight="bold">Demo Title</span>
<span foreground="tomato">Highlighted text</span>
<u>Underlined text</u>
Useful links:
<a href="https://adwaita-swift.aparoksha.dev/documentation/adwaita">AdwaitaSwift Documentation</a>
You can embed these links directly in your UI using Pango markup.
"""
private static func applyDemoConfig(_ cfg: inout AdwaitaAboutDialogConfig) {
cfg.appName = "Demo"
cfg.developer = "david-swift"
cfg.version = "Test"
cfg.icon = .default(icon: .applicationXExecutable)
cfg.website = URL(string: "https://adwaita-swift.aparoksha.dev/tutorials/table-of-contents")
cfg.issues = URL(string: "https://git.aparoksha.dev/aparoksha/adwaita-swift/issues")
cfg.support = URL(string: "https://adwaita-swift.aparoksha.dev/")
cfg.links = [
.init(title: "Source Code", url: URL(string: "https://git.aparoksha.dev/aparoksha/adwaita-swift")),
.init(title: "Donate", url: URL(string: "https://ko-fi.com/david_swift"))
]
cfg.copyright = "© 2026 david-swift"
cfg.license = "MIT"
cfg.releaseNotes = demoReleaseNotes
cfg.comments = 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")
]
cfg.acknowledgements = [
.init(title: "Special Thanks", name: "GNOME Project"),
.init(title: "Special Thanks", name: "Swift Programming Language"),
.init(title: "Additional Support", name: "LibAdwaita Contributors")
]
cfg.otherApps = [
.init(
appID: "io.github.david_swift.Flashcards",
name: "Memorize",
summary: "An app for creating, studying, and importing flashcard sets with a builtin test mode."
)
]
}
}
// swiftlint:enable missing_docs

View File

@ -150,36 +150,7 @@ struct Demo: App {
}
.collapsed(!wide)
.breakpoint(minWidth: 550, matches: $wide)
.aboutDialog(visible: $about) { cfg in
cfg.appName = "Demo"
cfg.developer = "david-swift"
cfg.version = "Test"
cfg.icon = .default(icon: .applicationXExecutable)
cfg.website = URL(string: "https://adwaita-swift.aparoksha.dev/tutorials/table-of-contents")
cfg.issues = URL(string: "https://git.aparoksha.dev/aparoksha/adwaita-swift/issues")
cfg.support = URL(string: "https://adwaita-swift.aparoksha.dev/")
cfg.links = [
.init(title: "Source Code", url: URL(string: "https://git.aparoksha.dev/aparoksha/adwaita-swift")),
.init(title: "Donate", url: URL(string: "https://ko-fi.com/david_swift"))
]
cfg.copyright = "© 2026 david-swift"
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")
]
cfg.acknowledgements = [
.init(title: "Special Thanks", name: "GNOME Project"),
.init(title: "Special Thanks", name: "Swift Programming Language"),
.init(title: "Additional Support", name: "LibAdwaita Contributors")
]
}
.aboutDialog(visible: $about, configure: AboutDialogDemo.sample)
.preferencesDialog(visible: $preferences)
.preferencesPage("Page 1", icon: .default(icon: .audioHeadset)) { page in
page