mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-05-05 23:43:00 +00:00
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
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:
commit
38445dca2a
@ -4,7 +4,7 @@ struct CommandOption: Identifiable, Hashable {
|
|||||||
let id = UUID()
|
let id = UUID()
|
||||||
let title: String
|
let title: String
|
||||||
let description: String?
|
let description: String?
|
||||||
let shortcut: String?
|
let symbols: [String]?
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
static func == (lhs: CommandOption, rhs: CommandOption) -> Bool {
|
static func == (lhs: CommandOption, rhs: CommandOption) -> Bool {
|
||||||
@ -44,6 +44,12 @@ struct CommandPaletteView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let scheme: ColorScheme = if OSColor(backgroundColor).isLightColor {
|
||||||
|
.light
|
||||||
|
} else {
|
||||||
|
.dark
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
CommandPaletteQuery(query: $query) { event in
|
CommandPaletteQuery(query: $query) { event in
|
||||||
switch (event) {
|
switch (event) {
|
||||||
@ -89,7 +95,6 @@ struct CommandPaletteView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.padding(.bottom, 4)
|
|
||||||
|
|
||||||
CommandTable(
|
CommandTable(
|
||||||
options: filteredOptions,
|
options: filteredOptions,
|
||||||
@ -101,15 +106,23 @@ struct CommandPaletteView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: 500)
|
.frame(maxWidth: 500)
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 12)
|
ZStack {
|
||||||
|
Rectangle()
|
||||||
|
.fill(.ultraThinMaterial)
|
||||||
|
Rectangle()
|
||||||
.fill(backgroundColor)
|
.fill(backgroundColor)
|
||||||
.shadow(color: .black.opacity(0.4), radius: 10, x: 0, y: 10)
|
.blendMode(.color)
|
||||||
|
}
|
||||||
|
.compositingGroup()
|
||||||
|
)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.stroke(Color.black.opacity(0.1), lineWidth: 1)
|
.stroke(Color(nsColor: .tertiaryLabelColor).opacity(0.75))
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
.shadow(radius: 32, x: 0, y: 12)
|
||||||
.padding()
|
.padding()
|
||||||
|
.environment(\.colorScheme, scheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +160,9 @@ fileprivate struct CommandPaletteQuery: View {
|
|||||||
|
|
||||||
TextField("Execute a command…", text: $query)
|
TextField("Execute a command…", text: $query)
|
||||||
.padding()
|
.padding()
|
||||||
.font(.system(size: 14))
|
.font(.system(size: 20, weight: .light))
|
||||||
.textFieldStyle(PlainTextFieldStyle())
|
.frame(height: 48)
|
||||||
|
.textFieldStyle(.plain)
|
||||||
.focused($isTextFieldFocused)
|
.focused($isTextFieldFocused)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
isTextFieldFocused = true
|
isTextFieldFocused = true
|
||||||
@ -178,7 +192,7 @@ fileprivate struct CommandTable: View {
|
|||||||
.padding()
|
.padding()
|
||||||
} else {
|
} else {
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(Array(options.enumerated()), id: \.1.id) { index, option in
|
ForEach(Array(options.enumerated()), id: \.1.id) { index, option in
|
||||||
CommandRow(
|
CommandRow(
|
||||||
@ -198,6 +212,7 @@ fileprivate struct CommandTable: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(10)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: 200)
|
.frame(maxHeight: 200)
|
||||||
.onChange(of: selectedIndex) { _ in
|
.onChange(of: selectedIndex) { _ in
|
||||||
@ -223,20 +238,12 @@ fileprivate struct CommandRow: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Text(option.title)
|
Text(option.title)
|
||||||
Spacer()
|
Spacer()
|
||||||
if let shortcut = option.shortcut {
|
if let symbols = option.symbols {
|
||||||
Text(shortcut)
|
ShortcutSymbolsView(symbols: symbols)
|
||||||
.font(.system(.body, design: .monospaced))
|
.foregroundStyle(.secondary)
|
||||||
.kerning(1.5)
|
|
||||||
.padding(.horizontal, 6)
|
|
||||||
.padding(.vertical, 2)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 6)
|
|
||||||
.fill(Color.gray.opacity(0.2))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 6)
|
.padding(8)
|
||||||
.padding(.vertical, 8)
|
|
||||||
.background(
|
.background(
|
||||||
isSelected
|
isSelected
|
||||||
? Color.accentColor.opacity(0.2)
|
? Color.accentColor.opacity(0.2)
|
||||||
@ -244,14 +251,26 @@ fileprivate struct CommandRow: View {
|
|||||||
? Color.secondary.opacity(0.2)
|
? Color.secondary.opacity(0.2)
|
||||||
: Color.clear)
|
: Color.clear)
|
||||||
)
|
)
|
||||||
.cornerRadius(6)
|
.cornerRadius(5)
|
||||||
}
|
}
|
||||||
.help(option.description ?? "")
|
.help(option.description ?? "")
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(.plain)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
hoveredID = hovering ? option.id : nil
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ struct TerminalCommandPaletteView: View {
|
|||||||
return CommandOption(
|
return CommandOption(
|
||||||
title: String(cString: c.title),
|
title: String(cString: c.title),
|
||||||
description: String(cString: c.description),
|
description: String(cString: c.description),
|
||||||
shortcut: ghosttyConfig.keyboardShortcut(for: action)?.description
|
symbols: ghosttyConfig.keyboardShortcut(for: action)?.keyList
|
||||||
) {
|
) {
|
||||||
onAction(action)
|
onAction(action)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension KeyboardShortcut: @retroactive CustomStringConvertible {
|
extension KeyboardShortcut: @retroactive CustomStringConvertible {
|
||||||
public var description: String {
|
public var keyList: [String] {
|
||||||
var result = ""
|
var result: [String] = []
|
||||||
|
|
||||||
if modifiers.contains(.command) {
|
|
||||||
result.append("⌘")
|
|
||||||
}
|
|
||||||
if modifiers.contains(.control) {
|
if modifiers.contains(.control) {
|
||||||
result.append("⌃")
|
result.append("⌃")
|
||||||
}
|
}
|
||||||
@ -16,6 +13,9 @@ extension KeyboardShortcut: @retroactive CustomStringConvertible {
|
|||||||
if modifiers.contains(.shift) {
|
if modifiers.contains(.shift) {
|
||||||
result.append("⇧")
|
result.append("⇧")
|
||||||
}
|
}
|
||||||
|
if modifiers.contains(.command) {
|
||||||
|
result.append("⌘")
|
||||||
|
}
|
||||||
|
|
||||||
let keyString: String
|
let keyString: String
|
||||||
switch key {
|
switch key {
|
||||||
@ -24,14 +24,14 @@ extension KeyboardShortcut: @retroactive CustomStringConvertible {
|
|||||||
case .delete: keyString = "⌫"
|
case .delete: keyString = "⌫"
|
||||||
case .space: keyString = "␣"
|
case .space: keyString = "␣"
|
||||||
case .tab: keyString = "⇥"
|
case .tab: keyString = "⇥"
|
||||||
case .upArrow: keyString = "↑"
|
case .upArrow: keyString = "▲"
|
||||||
case .downArrow: keyString = "↓"
|
case .downArrow: keyString = "▼"
|
||||||
case .leftArrow: keyString = "←"
|
case .leftArrow: keyString = "◀"
|
||||||
case .rightArrow: keyString = "→"
|
case .rightArrow: keyString = "▶"
|
||||||
case .pageUp: keyString = "PgUp"
|
case .pageUp: keyString = "↑"
|
||||||
case .pageDown: keyString = "PgDown"
|
case .pageDown: keyString = "↓"
|
||||||
case .end: keyString = "End"
|
case .home: keyString = "⤒"
|
||||||
case .home: keyString = "Home"
|
case .end: keyString = "⤓"
|
||||||
default:
|
default:
|
||||||
keyString = String(key.character.uppercased())
|
keyString = String(key.character.uppercased())
|
||||||
}
|
}
|
||||||
@ -39,6 +39,10 @@ extension KeyboardShortcut: @retroactive CustomStringConvertible {
|
|||||||
result.append(keyString)
|
result.append(keyString)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return self.keyList.joined()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is available in macOS 14 so this only applies to early macOS versions.
|
// This is available in macOS 14 so this only applies to early macOS versions.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user