From 42bf37af32ae6f20b32b447e54e5e208d95a9a2f Mon Sep 17 00:00:00 2001 From: Josh <36625023+JoshuaBrest@users.noreply.github.com> Date: Sat, 19 Oct 2024 00:31:43 -0700 Subject: [PATCH 1/5] feat: Update about menu design to match macOS. --- macos/Sources/Features/About/About.xib | 2 +- .../Features/About/AboutController.swift | 1 + macos/Sources/Features/About/AboutView.swift | 139 ++++++++++++++++-- 3 files changed, 125 insertions(+), 17 deletions(-) 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() } } From 4b08b3f8d8009a94c4c5fd5744e8a88c942ab764 Mon Sep 17 00:00:00 2001 From: Josh <36625023+JoshuaBrest@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:23:04 -0700 Subject: [PATCH 2/5] refactor: hide buttons when URLs are invalid and change variable mutability and visibility. --- macos/Sources/Features/About/AboutView.swift | 31 ++++++++++---------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index a7b0834fc..7c5c5de7f 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -4,23 +4,23 @@ 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" + private let discordLink = URL(string: "https://discord.gg/ghostty") + private let githubLink = URL(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 } + private var build: String? { Bundle.main.infoDictionary?["CFBundleVersion"] as? String } + private var commit: String? { Bundle.main.infoDictionary?["GhosttyCommit"] as? String } + private var version: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String } + private var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String } - struct ValuePair: Identifiable { + private struct ValuePair: Identifiable { var id = UUID() public let key: LocalizedStringResource public let value: Value } - var computedStrings: [ValuePair] { + private var computedStrings: [ValuePair] { let list: [ValuePair] = [ ValuePair(key: "Version", value: self.version), ValuePair(key: "Build", value: self.build), @@ -37,7 +37,7 @@ struct AboutView: View { } #if os(macOS) - struct VisualEffectBackground: NSViewRepresentable { + private struct VisualEffectBackground: NSViewRepresentable { let material: NSVisualEffectView.Material let blendingMode: NSVisualEffectView.BlendingMode let isEmphasized: Bool @@ -103,16 +103,17 @@ struct AboutView: View { .frame(maxWidth: .infinity) HStack(spacing: 8) { - Button("Discord") { - guard let url = URL(string: discordLink) else { return + if let url = discordLink { + Button("Discord") { + openURL(url) } - openURL(url) } - Button("Github") { - guard let url = URL(string: githubLink) else { return + if let url = githubLink { + Button("GitHub") { + openURL(url) } - openURL(url) } + } if let copy = self.copyright { From d291fcfd526e6c4622226b29a3f55ccc58ab400a Mon Sep 17 00:00:00 2001 From: Josh <36625023+JoshuaBrest@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:45:24 -0700 Subject: [PATCH 3/5] style: add line break in the caption of the about dialog. --- macos/Sources/Features/About/AboutView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index 7c5c5de7f..976207e9b 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -76,7 +76,7 @@ struct AboutView: View { Text("Ghostty") .bold() .font(.title) - Text("Fast, native, feature-rich terminal emulator pushing modern features.") + Text("Fast, native, feature-rich terminal \nemulator pushing modern features.") .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .font(.caption) From cc42dc57b942a342bd82e7e94ba0411434d6b6da Mon Sep 17 00:00:00 2001 From: Josh <36625023+JoshuaBrest@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:52:12 -0700 Subject: [PATCH 4/5] fix: add text-selection back in the about dialoge. --- macos/Sources/Features/About/AboutView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index 976207e9b..f56faaa1e 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -83,6 +83,7 @@ struct AboutView: View { .tint(.secondary) .opacity(0.8) } + .textSelection(.enabled) VStack(spacing: 2) { ForEach(computedStrings) { item in @@ -97,6 +98,7 @@ struct AboutView: View { .opacity(0.8) } .font(.callout) + .textSelection(.enabled) .frame(maxWidth: .infinity) } } @@ -119,6 +121,7 @@ struct AboutView: View { if let copy = self.copyright { Text(copy) .font(.caption) + .textSelection(.enabled) .tint(.secondary) .opacity(0.8) .multilineTextAlignment(.center) From 85db4d0277a39368df2e15d4d64017544ed77a8b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 19 Oct 2024 14:06:55 -0700 Subject: [PATCH 5/5] macos: personal nitpicks and improvements --- macos/Sources/Features/About/AboutView.swift | 50 ++++++++------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index f56faaa1e..71fe9c252 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -3,8 +3,6 @@ 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 - private let discordLink = URL(string: "https://discord.gg/ghostty") private let githubLink = URL(string: "https://github.com/ghostty-org/ghostty") /// Read the commit from the bundle. @@ -13,36 +11,36 @@ struct AboutView: View { private var version: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String } private var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String } - private struct ValuePair: Identifiable { + private var properties: [KeyValue] { + let list: [KeyValue] = [ + .init(key: "Version", value: version), + .init(key: "Build", value: build), + .init(key: "Commit", value: commit == "" ? nil : commit) + ] + + return list.compactMap { + guard let value = $0.value else { return nil } + return .init(key: $0.key, value: value) + } + } + + private struct KeyValue: Identifiable { var id = UUID() public let key: LocalizedStringResource public let value: Value - - } - - private 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) + // This creates a background style similar to the Apple "About My Mac" Window private 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) { + init(material: NSVisualEffectView.Material, + blendingMode: NSVisualEffectView.BlendingMode = .behindWindow, + isEmphasized: Bool = false) + { self.material = material self.blendingMode = blendingMode self.isEmphasized = isEmphasized @@ -56,9 +54,7 @@ struct AboutView: View { func makeNSView(context: Context) -> NSVisualEffectView { let visualEffect = NSVisualEffectView() - visualEffect.autoresizingMask = [.width, .height] - return visualEffect } } @@ -85,8 +81,7 @@ struct AboutView: View { } .textSelection(.enabled) VStack(spacing: 2) { - ForEach(computedStrings) { item in - + ForEach(properties) { item in HStack(spacing: 4) { Text(item.key) .frame(width: 126, alignment: .trailing) @@ -105,11 +100,6 @@ struct AboutView: View { .frame(maxWidth: .infinity) HStack(spacing: 8) { - if let url = discordLink { - Button("Discord") { - openURL(url) - } - } if let url = githubLink { Button("GitHub") { openURL(url)