From c4a978b07aa1ada5bd6817b2194fa8f853bbff5e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 14 Jun 2025 13:49:58 -0700 Subject: [PATCH 1/2] macos: set toolbar title `isBordered` to avoid glass view This was recommended by the WWDC25 session on AppKit updates. My hack was not the right approach. --- .../TitlebarTabsTahoeTerminalWindow.swift | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift index 145c37c59..9381f7329 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift @@ -21,18 +21,6 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool } } - override var toolbar: NSToolbar? { - didSet{ - guard toolbar != nil else { return } - - // When a toolbar is added, remove the Liquid Glass look to have a cleaner - // appearance for our custom titlebar tabs. - if let glass = titlebarContainer?.firstDescendant(withClassName: "NSGlassContainerView") { - glass.isHidden = true - } - } - } - override func awakeFromNib() { super.awakeFromNib() @@ -222,6 +210,11 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool item.view = NSHostingView(rootView: TitleItem(viewModel: viewModel)) item.visibilityPriority = .user item.isEnabled = true + + // This is the documented way to avoid the glass view on an item. + // We don't want glass on our title. + item.isBordered = false + return item default: return NSToolbarItem(itemIdentifier: itemIdentifier) From 202020cd7d10bb6fa7b61c5d67033ddb5565b52c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 14 Jun 2025 14:21:40 -0700 Subject: [PATCH 2/2] macos: menu item symbols for Tahoe This is recommended for macOS Tahoe and all standard menu items now have associated images. This makes our app look more polished and native for macOS Tahoe. For icon choice, I tried to copy other native macOS apps as much as possible, mostly from Xcode. It looks like a lot of apps aren't updated yet. I'm absolutely open to suggestions for better icons but I think these are a good starting point. One menu change is I moved "reset font size" above "increase font size" which better matches other apps (e.g. Terminal.app). --- macos/Ghostty.xcodeproj/project.pbxproj | 4 ++ macos/Sources/App/macOS/AppDelegate.swift | 40 ++++++++++++++++++- macos/Sources/App/macOS/MainMenu.xib | 16 ++++---- .../Sources/Ghostty/SurfaceView_AppKit.swift | 25 ++++++++---- .../Extensions/NSMenuItem+Extension.swift | 11 +++++ 5 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 macos/Sources/Helpers/Extensions/NSMenuItem+Extension.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 4943f2f4d..a5663202b 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ A5A6F72A2CC41B8900B232A5 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A6F7292CC41B8700B232A5 /* AppInfo.swift */; }; A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; }; A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; }; + A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */; }; A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; }; A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; }; A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; }; @@ -210,6 +211,7 @@ A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; }; A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = ""; }; + A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenuItem+Extension.swift"; sourceTree = ""; }; A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = ""; }; A5CA378D2D31D6C100931030 /* Weak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = ""; }; A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = ""; }; @@ -478,6 +480,7 @@ A599CDAF2CF103F20049FA26 /* NSAppearance+Extension.swift */, A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */, A54B0CEA2D0CFB4A00CBEFF8 /* NSImage+Extension.swift */, + A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */, A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */, AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */, @@ -763,6 +766,7 @@ A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */, A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */, A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */, + A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */, A5874D992DAD751B00E83852 /* CGS.swift in Sources */, A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */, A51544FE2DFB111C009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift in Sources */, diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 7fb52a025..f460017f5 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -166,7 +166,7 @@ class AppDelegate: NSObject, // This registers the Ghostty => Services menu to exist. NSApp.servicesMenu = menuServices - + // Setup a local event monitor for app-level keyboard shortcuts. See // localEventHandler for more info why. _ = NSEvent.addLocalMonitorForEvents( @@ -242,6 +242,9 @@ class AppDelegate: NSObject, ghostty_app_set_color_scheme(app, scheme) } + + // Setup our menu + setupMenuImages() } func applicationDidBecomeActive(_ notification: Notification) { @@ -392,6 +395,41 @@ class AppDelegate: NSObject, return dockMenu } + /// Setup all the images for our menu items. + private func setupMenuImages() { + // Note: This COULD Be done all in the xib file, but I find it easier to + // modify this stuff as code. + self.menuNewWindow?.setImageIfDesired(systemSymbolName: "macwindow.badge.plus") + self.menuNewTab?.setImageIfDesired(systemSymbolName: "macwindow") + self.menuSplitRight?.setImageIfDesired(systemSymbolName: "rectangle.righthalf.inset.filled") + self.menuSplitLeft?.setImageIfDesired(systemSymbolName: "rectangle.leadinghalf.inset.filled") + self.menuSplitUp?.setImageIfDesired(systemSymbolName: "rectangle.tophalf.inset.filled") + self.menuSplitDown?.setImageIfDesired(systemSymbolName: "rectangle.bottomhalf.inset.filled") + self.menuClose?.setImageIfDesired(systemSymbolName: "xmark") + self.menuIncreaseFontSize?.setImageIfDesired(systemSymbolName: "textformat.size.larger") + self.menuResetFontSize?.setImageIfDesired(systemSymbolName: "textformat.size") + self.menuDecreaseFontSize?.setImageIfDesired(systemSymbolName: "textformat.size.smaller") + self.menuCommandPalette?.setImageIfDesired(systemSymbolName: "filemenu.and.selection") + self.menuQuickTerminal?.setImageIfDesired(systemSymbolName: "apple.terminal") + self.menuChangeTitle?.setImageIfDesired(systemSymbolName: "pencil.line") + self.menuTerminalInspector?.setImageIfDesired(systemSymbolName: "scope") + self.menuToggleFullScreen?.setImageIfDesired(systemSymbolName: "square.arrowtriangle.4.outward") + self.menuToggleVisibility?.setImageIfDesired(systemSymbolName: "eye") + self.menuZoomSplit?.setImageIfDesired(systemSymbolName: "arrow.up.left.and.arrow.down.right") + self.menuPreviousSplit?.setImageIfDesired(systemSymbolName: "chevron.backward.2") + self.menuNextSplit?.setImageIfDesired(systemSymbolName: "chevron.forward.2") + self.menuEqualizeSplits?.setImageIfDesired(systemSymbolName: "inset.filled.topleft.topright.bottomleft.bottomright.rectangle") + self.menuSelectSplitLeft?.setImageIfDesired(systemSymbolName: "arrow.left") + self.menuSelectSplitRight?.setImageIfDesired(systemSymbolName: "arrow.right") + self.menuSelectSplitAbove?.setImageIfDesired(systemSymbolName: "arrow.up") + self.menuSelectSplitBelow?.setImageIfDesired(systemSymbolName: "arrow.down") + self.menuMoveSplitDividerUp?.setImageIfDesired(systemSymbolName: "arrow.up.to.line") + self.menuMoveSplitDividerDown?.setImageIfDesired(systemSymbolName: "arrow.down.to.line") + self.menuMoveSplitDividerLeft?.setImageIfDesired(systemSymbolName: "arrow.left.to.line") + self.menuMoveSplitDividerRight?.setImageIfDesired(systemSymbolName: "arrow.right.to.line") + self.menuFloatOnTop?.setImageIfDesired(systemSymbolName: "square.3.layers.3d.top.filled") + } + /// Sync all of our menu item keyboard shortcuts with the Ghostty configuration. private func syncMenuShortcuts(_ config: Ghostty.Config) { guard ghostty.readiness == .ready else { return } diff --git a/macos/Sources/App/macOS/MainMenu.xib b/macos/Sources/App/macOS/MainMenu.xib index 7130d544e..c9bff8b4a 100644 --- a/macos/Sources/App/macOS/MainMenu.xib +++ b/macos/Sources/App/macOS/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -251,18 +251,18 @@ - - - - - - + + + + + + diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index e4f6f507c..3e87176fc 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -1281,6 +1281,10 @@ extension Ghostty { let menu = NSMenu() + // We just use a floating var so we can easily setup metadata on each item + // in a row without storing it all. + var item: NSMenuItem + // If we have a selection, add copy if self.selectedRange().length > 0 { menu.addItem(withTitle: "Copy", action: #selector(copy(_:)), keyEquivalent: "") @@ -1288,16 +1292,23 @@ extension Ghostty { menu.addItem(withTitle: "Paste", action: #selector(paste(_:)), keyEquivalent: "") menu.addItem(.separator()) - menu.addItem(withTitle: "Split Right", action: #selector(splitRight(_:)), keyEquivalent: "") - menu.addItem(withTitle: "Split Left", action: #selector(splitLeft(_:)), keyEquivalent: "") - menu.addItem(withTitle: "Split Down", action: #selector(splitDown(_:)), keyEquivalent: "") - menu.addItem(withTitle: "Split Up", action: #selector(splitUp(_:)), keyEquivalent: "") + item = menu.addItem(withTitle: "Split Right", action: #selector(splitRight(_:)), keyEquivalent: "") + item.setImageIfDesired(systemSymbolName: "rectangle.righthalf.inset.filled") + item = menu.addItem(withTitle: "Split Left", action: #selector(splitLeft(_:)), keyEquivalent: "") + item.setImageIfDesired(systemSymbolName: "rectangle.leadinghalf.inset.filled") + item = menu.addItem(withTitle: "Split Down", action: #selector(splitDown(_:)), keyEquivalent: "") + item.setImageIfDesired(systemSymbolName: "rectangle.bottomhalf.inset.filled") + item = menu.addItem(withTitle: "Split Up", action: #selector(splitUp(_:)), keyEquivalent: "") + item.setImageIfDesired(systemSymbolName: "rectangle.tophalf.inset.filled") menu.addItem(.separator()) - menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "") - menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "") + item = menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "") + item.setImageIfDesired(systemSymbolName: "arrow.trianglehead.2.clockwise") + item = menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "") + item.setImageIfDesired(systemSymbolName: "scope") menu.addItem(.separator()) - menu.addItem(withTitle: "Change Title...", action: #selector(changeTitle(_:)), keyEquivalent: "") + item = menu.addItem(withTitle: "Change Title...", action: #selector(changeTitle(_:)), keyEquivalent: "") + item.setImageIfDesired(systemSymbolName: "pencil.line") return menu } diff --git a/macos/Sources/Helpers/Extensions/NSMenuItem+Extension.swift b/macos/Sources/Helpers/Extensions/NSMenuItem+Extension.swift new file mode 100644 index 000000000..e512904ef --- /dev/null +++ b/macos/Sources/Helpers/Extensions/NSMenuItem+Extension.swift @@ -0,0 +1,11 @@ +import AppKit + +extension NSMenuItem { + /// Sets the image property from a symbol if we want images on our menu items. + func setImageIfDesired(systemSymbolName symbol: String) { + // We only set on macOS 26 when icons on menu items became the norm. + if #available(macOS 26, *) { + image = NSImage(systemSymbolName: symbol, accessibilityDescription: title) + } + } +}