macOS: stop cycling icons when AboutWindow is closed

and start cycling with current icon
This commit is contained in:
Lukas
2026-01-24 14:26:20 +01:00
parent 45525a0a85
commit a79557f521
4 changed files with 64 additions and 35 deletions

View File

@@ -117,6 +117,7 @@
Features/About/About.xib,
Features/About/AboutController.swift,
Features/About/AboutView.swift,
Features/About/AboutViewModel.swift,
Features/About/CyclingIconView.swift,
"Features/App Intents/CloseTerminalIntent.swift",
"Features/App Intents/CommandPaletteIntent.swift",

View File

@@ -5,19 +5,21 @@ import SwiftUI
class AboutController: NSWindowController, NSWindowDelegate {
static let shared: AboutController = AboutController()
private let viewModel = AboutViewModel()
override var windowNibName: NSNib.Name? { "About" }
override func windowDidLoad() {
guard let window = window else { return }
window.center()
window.isMovableByWindowBackground = true
window.contentView = NSHostingView(rootView: AboutView())
window.contentView = NSHostingView(rootView: AboutView().environmentObject(viewModel))
}
// MARK: - Functions
func show() {
window?.makeKeyAndOrderFront(nil)
viewModel.startCyclingIcons()
}
func hide() {
@@ -38,4 +40,8 @@ class AboutController: NSWindowController, NSWindowDelegate {
@objc func cancel(_ sender: Any?) {
close()
}
func windowWillClose(_ notification: Notification) {
viewModel.stopCyclingIcons()
}
}

View File

@@ -0,0 +1,40 @@
import Combine
class AboutViewModel: ObservableObject {
@Published var currentIcon: Ghostty.MacOSIcon?
@Published var isHovering: Bool = false
private var timerCancellable: AnyCancellable?
private let icons: [Ghostty.MacOSIcon] = [
.official,
.blueprint,
.chalkboard,
.microchip,
.glass,
.holographic,
.paper,
.retro,
.xray,
]
func startCyclingIcons() {
timerCancellable = Timer.publish(every: 3, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard let self, !isHovering else { return }
advanceToNextIcon()
}
}
func stopCyclingIcons() {
timerCancellable = nil
currentIcon = nil
}
func advanceToNextIcon() {
let currentIndex = currentIcon.flatMap(icons.firstIndex(of:)) ?? 0
let nextIndex = icons.indexWrapping(after: currentIndex)
currentIcon = icons[nextIndex]
}
}

View File

@@ -1,50 +1,38 @@
import SwiftUI
import GhosttyKit
import Combine
/// A view that cycles through Ghostty's official icon variants.
struct CyclingIconView: View {
@State private var currentIcon: Ghostty.MacOSIcon = .official
@State private var isHovering: Bool = false
private let icons: [Ghostty.MacOSIcon] = [
.official,
.blueprint,
.chalkboard,
.microchip,
.glass,
.holographic,
.paper,
.retro,
.xray,
]
private let timerPublisher = Timer.publish(every: 3, on: .main, in: .common)
@EnvironmentObject var viewModel: AboutViewModel
var body: some View {
ZStack {
iconView(for: currentIcon)
.id(currentIcon)
iconView(for: viewModel.currentIcon)
.id(viewModel.currentIcon)
}
.animation(.easeInOut(duration: 0.5), value: currentIcon)
.animation(.easeInOut(duration: 0.5), value: viewModel.currentIcon)
.frame(height: 128)
.onReceive(timerPublisher.autoconnect()) { _ in
if !isHovering {
advanceToNextIcon()
}
}
.onHover { hovering in
isHovering = hovering
viewModel.isHovering = hovering
}
.onTapGesture {
advanceToNextIcon()
viewModel.advanceToNextIcon()
}
.contextMenu {
if let currentIcon = viewModel.currentIcon {
Button("Copy Icon Config") {
NSPasteboard.general.setString("macos-icon = \(currentIcon.rawValue)", forType: .string)
}
}
}
.help("macos-icon = \(currentIcon.rawValue)")
.accessibilityLabel("Ghostty Application Icon")
.accessibilityHint("Click to cycle through icon variants")
}
@ViewBuilder
private func iconView(for icon: Ghostty.MacOSIcon) -> some View {
let iconImage: Image = switch icon.assetName {
private func iconView(for icon: Ghostty.MacOSIcon?) -> some View {
let iconImage: Image = switch icon?.assetName {
case let assetName?: Image(assetName)
case nil: ghosttyIconImage()
}
@@ -53,10 +41,4 @@ struct CyclingIconView: View {
.resizable()
.aspectRatio(contentMode: .fit)
}
private func advanceToNextIcon() {
let currentIndex = icons.firstIndex(of: currentIcon) ?? 0
let nextIndex = icons.indexWrapping(after: currentIndex)
currentIcon = icons[nextIndex]
}
}