feat: beautify macOS command palette (#7179)
Some checks failed
Nix / check-zig-cache-hash (push) Has been cancelled
Test / test (push) Has been cancelled
Test / zig-fmt (push) Has been cancelled
Test / prettier (push) Has been cancelled
Test / alejandra (push) Has been cancelled
Test / typos (push) Has been cancelled
Test / translations (push) Has been cancelled
Test / blueprint-compiler (push) Has been cancelled
Test / flatpak-check-zig-cache (push) Has been cancelled
Nix / Required Checks: Nix (push) Has been cancelled
Test / Required Checks: Test (push) Has been cancelled
Test / build-bench (push) Has been cancelled
Test / build-flatpak (push) Has been cancelled
Test / build-linux (namespace-profile-ghostty-md) (push) Has been cancelled
Test / build-linux (namespace-profile-ghostty-md-arm64) (push) Has been cancelled
Test / build-linux-libghostty (push) Has been cancelled
Test / build-nix (namespace-profile-ghostty-md) (push) Has been cancelled
Test / build-nix (namespace-profile-ghostty-md-arm64) (push) Has been cancelled
Test / build-dist (push) Has been cancelled
Test / build-macos (push) Has been cancelled
Test / build-macos-matrix (push) Has been cancelled
Test / build-snap (namespace-profile-ghostty-snap) (push) Has been cancelled
Test / build-snap (namespace-profile-ghostty-snap-arm64) (push) Has been cancelled
Test / build-windows (push) Has been cancelled
Test / build-windows-cross (namespace-profile-ghostty-md, x86-windows-gnu) (push) Has been cancelled
Test / build-windows-cross (namespace-profile-ghostty-md, x86_64-windows-gnu) (push) Has been cancelled
Test / GTK x11=false wayland=false (push) Has been cancelled
Test / GTK x11=true wayland=false (push) Has been cancelled
Test / GTK x11=false wayland=true (push) Has been cancelled
Test / GTK x11=true wayland=true (push) Has been cancelled
Test / Build -Dsentry=false (push) Has been cancelled
Test / Build -Dsentry=true (push) Has been cancelled
Test / test-macos (push) Has been cancelled
Test / Test pkg/wuffs (push) Has been cancelled
Test / Test build on Debian 12 (push) Has been cancelled
Test / Flatpak (map[arch:aarch64 runner:namespace-profile-ghostty-md-arm64]) (push) Has been cancelled
Test / Flatpak (map[arch:x86_64 runner:namespace-profile-ghostty-md]) (push) Has been cancelled
Update iTerm2 colorschemes / update-iterm2-schemes (push) Has been cancelled

Resolve 1. of #7173
<img width="1126" alt="image"
src="https://github.com/user-attachments/assets/8904b09f-42f6-4f26-a722-c92dad8e2933"
/>

Changes made:
1. Change shortcut from `String` to `[String]` so its easier to iterate
over it.
2. Overlay background color on top of an `ultraThinMaterial` for better
aesthetic.
3. Reorganize and beautify the spacings and paddings.
4. Unhide the scrollbar.
5. Reorder the modifier keys to Control, Option, Shift and then Command.
<https://leancrew.com/all-this/2017/11/modifier-key-order/>
6. Style shortcut keys to resemble macOS menu bar items, using
corresponding symbols and fixed-width for alignment.
This commit is contained in:
Mitchell Hashimoto 2025-04-25 12:01:01 -07:00 committed by GitHub
commit 38445dca2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 42 deletions

View File

@ -4,7 +4,7 @@ struct CommandOption: Identifiable, Hashable {
let id = UUID()
let title: String
let description: String?
let shortcut: String?
let symbols: [String]?
let action: () -> Void
static func == (lhs: CommandOption, rhs: CommandOption) -> Bool {
@ -44,6 +44,12 @@ struct CommandPaletteView: View {
}
var body: some View {
let scheme: ColorScheme = if OSColor(backgroundColor).isLightColor {
.light
} else {
.dark
}
VStack(alignment: .leading, spacing: 0) {
CommandPaletteQuery(query: $query) { event in
switch (event) {
@ -89,7 +95,6 @@ struct CommandPaletteView: View {
}
Divider()
.padding(.bottom, 4)
CommandTable(
options: filteredOptions,
@ -101,15 +106,23 @@ struct CommandPaletteView: View {
}
.frame(maxWidth: 500)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(backgroundColor)
.shadow(color: .black.opacity(0.4), radius: 10, x: 0, y: 10)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.black.opacity(0.1), lineWidth: 1)
)
ZStack {
Rectangle()
.fill(.ultraThinMaterial)
Rectangle()
.fill(backgroundColor)
.blendMode(.color)
}
.compositingGroup()
)
.clipShape(RoundedRectangle(cornerRadius: 10))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(nsColor: .tertiaryLabelColor).opacity(0.75))
)
.shadow(radius: 32, x: 0, y: 12)
.padding()
.environment(\.colorScheme, scheme)
}
}
@ -147,8 +160,9 @@ fileprivate struct CommandPaletteQuery: View {
TextField("Execute a command…", text: $query)
.padding()
.font(.system(size: 14))
.textFieldStyle(PlainTextFieldStyle())
.font(.system(size: 20, weight: .light))
.frame(height: 48)
.textFieldStyle(.plain)
.focused($isTextFieldFocused)
.onAppear {
isTextFieldFocused = true
@ -178,7 +192,7 @@ fileprivate struct CommandTable: View {
.padding()
} else {
ScrollViewReader { proxy in
ScrollView(showsIndicators: false) {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
ForEach(Array(options.enumerated()), id: \.1.id) { index, option in
CommandRow(
@ -198,6 +212,7 @@ fileprivate struct CommandTable: View {
}
}
}
.padding(10)
}
.frame(maxHeight: 200)
.onChange(of: selectedIndex) { _ in
@ -223,20 +238,12 @@ fileprivate struct CommandRow: View {
HStack {
Text(option.title)
Spacer()
if let shortcut = option.shortcut {
Text(shortcut)
.font(.system(.body, design: .monospaced))
.kerning(1.5)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(Color.gray.opacity(0.2))
)
if let symbols = option.symbols {
ShortcutSymbolsView(symbols: symbols)
.foregroundStyle(.secondary)
}
}
.padding(.horizontal, 6)
.padding(.vertical, 8)
.padding(8)
.background(
isSelected
? Color.accentColor.opacity(0.2)
@ -244,14 +251,26 @@ fileprivate struct CommandRow: View {
? Color.secondary.opacity(0.2)
: Color.clear)
)
.cornerRadius(6)
.cornerRadius(5)
}
.help(option.description ?? "")
.buttonStyle(PlainButtonStyle())
.buttonStyle(.plain)
.onHover { hovering in
hoveredID = hovering ? option.id : nil
}
.padding(.horizontal, 4)
.padding(.vertical, 1)
}
}
/// A row of Text representing a shortcut.
fileprivate struct ShortcutSymbolsView: View {
let symbols: [String]
var body: some View {
HStack(spacing: 1) {
ForEach(symbols, id: \.self) { symbol in
Text(symbol)
.frame(minWidth: 13)
}
}
}
}

View File

@ -40,7 +40,7 @@ struct TerminalCommandPaletteView: View {
return CommandOption(
title: String(cString: c.title),
description: String(cString: c.description),
shortcut: ghosttyConfig.keyboardShortcut(for: action)?.description
symbols: ghosttyConfig.keyboardShortcut(for: action)?.keyList
) {
onAction(action)
}

View File

@ -1,12 +1,9 @@
import SwiftUI
extension KeyboardShortcut: @retroactive CustomStringConvertible {
public var description: String {
var result = ""
public var keyList: [String] {
var result: [String] = []
if modifiers.contains(.command) {
result.append("")
}
if modifiers.contains(.control) {
result.append("")
}
@ -16,6 +13,9 @@ extension KeyboardShortcut: @retroactive CustomStringConvertible {
if modifiers.contains(.shift) {
result.append("")
}
if modifiers.contains(.command) {
result.append("")
}
let keyString: String
switch key {
@ -24,14 +24,14 @@ extension KeyboardShortcut: @retroactive CustomStringConvertible {
case .delete: keyString = ""
case .space: keyString = ""
case .tab: keyString = ""
case .upArrow: keyString = ""
case .downArrow: keyString = ""
case .leftArrow: keyString = ""
case .rightArrow: keyString = ""
case .pageUp: keyString = "PgUp"
case .pageDown: keyString = "PgDown"
case .end: keyString = "End"
case .home: keyString = "Home"
case .upArrow: keyString = ""
case .downArrow: keyString = ""
case .leftArrow: keyString = ""
case .rightArrow: keyString = ""
case .pageUp: keyString = ""
case .pageDown: keyString = ""
case .home: keyString = ""
case .end: keyString = ""
default:
keyString = String(key.character.uppercased())
}
@ -39,6 +39,10 @@ extension KeyboardShortcut: @retroactive CustomStringConvertible {
result.append(keyString)
return result
}
public var description: String {
return self.keyList.joined()
}
}
// This is available in macOS 14 so this only applies to early macOS versions.