diff --git a/macos/Sources/Features/About/About.xib b/macos/Sources/Features/About/About.xib index e884beff1..5803a32de 100644 --- a/macos/Sources/Features/About/About.xib +++ b/macos/Sources/Features/About/About.xib @@ -14,7 +14,7 @@ - + diff --git a/macos/Sources/Features/About/AboutController.swift b/macos/Sources/Features/About/AboutController.swift index d2ae68ea7..efd7a515a 100644 --- a/macos/Sources/Features/About/AboutController.swift +++ b/macos/Sources/Features/About/AboutController.swift @@ -10,6 +10,7 @@ class AboutController: NSWindowController, NSWindowDelegate { override func windowDidLoad() { guard let window = window else { return } window.center() + window.isMovableByWindowBackground = true window.contentView = NSHostingView(rootView: AboutView()) } diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index 02f899cc4..a7b0834fc 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -1,35 +1,142 @@ import SwiftUI struct AboutView: View { + @Environment(\.openURL) var openURL + + /// Eventually this should be a redirect like https://go.ghostty.dev/discord or https://go.ghostty.dev/github + @State var discordLink: String = "https://discord.gg/ghostty" + @State var githubLink: String = "https://github.com/ghostty-org/ghostty" + /// Read the commit from the bundle. var build: String? { Bundle.main.infoDictionary?["CFBundleVersion"] as? String } var commit: String? { Bundle.main.infoDictionary?["GhosttyCommit"] as? String } var version: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String } + var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String } + + struct ValuePair: Identifiable { + var id = UUID() + public let key: LocalizedStringResource + public let value: Value + + } + + var computedStrings: [ValuePair] { + let list: [ValuePair] = [ + ValuePair(key: "Version", value: self.version), + ValuePair(key: "Build", value: self.build), + ValuePair(key: "Commit", value: self.commit == "" ? nil : self.commit) + ] + + let strings: [ValuePair] = list.compactMap { + guard let value = $0.value else { return nil } + + return ValuePair(key: $0.key, value: value) + } + + return strings + } + + #if os(macOS) + struct VisualEffectBackground: NSViewRepresentable { + let material: NSVisualEffectView.Material + let blendingMode: NSVisualEffectView.BlendingMode + let isEmphasized: Bool + + init(material: NSVisualEffectView.Material, blendingMode: NSVisualEffectView.BlendingMode = .behindWindow, isEmphasized: Bool = false) { + self.material = material + self.blendingMode = blendingMode + self.isEmphasized = isEmphasized + } + + func updateNSView(_ nsView: NSVisualEffectView, context: Context) { + nsView.material = material + nsView.blendingMode = blendingMode + nsView.isEmphasized = isEmphasized + } + + func makeNSView(context: Context) -> NSVisualEffectView { + let visualEffect = NSVisualEffectView() + + visualEffect.autoresizingMask = [.width, .height] + + return visualEffect + } + } + #endif var body: some View { VStack(alignment: .center) { Image("AppIconImage") .resizable() .aspectRatio(contentMode: .fit) - .frame(maxHeight: 96) + .frame(height: 128) - Text("Ghostty") - .font(.title3) - .textSelection(.enabled) + VStack(alignment: .center, spacing: 32) { + VStack(alignment: .center, spacing: 8) { + Text("Ghostty") + .bold() + .font(.title) + Text("Fast, native, feature-rich terminal emulator pushing modern features.") + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + .font(.caption) + .tint(.secondary) + .opacity(0.8) + } + VStack(spacing: 2) { + ForEach(computedStrings) { item in - if let version = self.version { - Text("Version: \(version)") - .font(.body) - .textSelection(.enabled) - } - - if let build = self.build { - Text("Build: \(build)") - .font(.body) - .textSelection(.enabled) + HStack(spacing: 4) { + Text(item.key) + .frame(width: 126, alignment: .trailing) + .padding(.trailing, 2) + Text(item.value) + .frame(width: 125, alignment: .leading) + .padding(.leading, 2) + .tint(.secondary) + .opacity(0.8) + } + .font(.callout) + .frame(maxWidth: .infinity) + } + } + .frame(maxWidth: .infinity) + + HStack(spacing: 8) { + Button("Discord") { + guard let url = URL(string: discordLink) else { return + } + openURL(url) + } + Button("Github") { + guard let url = URL(string: githubLink) else { return + } + openURL(url) + } + } + + if let copy = self.copyright { + Text(copy) + .font(.caption) + .tint(.secondary) + .opacity(0.8) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity) + } } + .frame(maxWidth: .infinity) } - .frame(minWidth: 300) - .padding() + .padding(.top, 8) + .padding(32) + .frame(minWidth: 256) + #if os(macOS) + .background(VisualEffectBackground(material: .underWindowBackground).ignoresSafeArea()) + #endif + } +} + +struct AboutView_Previews: PreviewProvider { + static var previews: some View { + AboutView() } }