Skip to content

Commit

Permalink
Add preference to change what happens when clicking the menu bar icon
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Sep 30, 2021
1 parent 1b27beb commit 1ae4c58
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 37 deletions.
9 changes: 9 additions & 0 deletions Color Picker/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ struct AppMain: App {
Defaults[.preferredColorFormat] = .lch
}
}

// Preserve the old behavior for existing users.
SSApp.runOnce(identifier: "setDefaultsForMenuBarItemClickActionSetting") {
guard !SSApp.isFirstLaunch else {
return
}

Defaults[.menuBarItemClickAction] = .toggleWindow
}
}
}

Expand Down
40 changes: 34 additions & 6 deletions Color Picker/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,19 @@ final class AppState: ObservableObject {
private func createMenu() -> NSMenu {
let menu = NSMenu()

menu.addCallbackItem("Pick Color") { [self] _ in
pickColor()
if Defaults[.menuBarItemClickAction] != .showColorSampler {
menu.addCallbackItem("Pick Color") { [self] _ in
pickColor()
}
.setShortcut(for: .pickColor)
}

if Defaults[.menuBarItemClickAction] != .toggleWindow {
menu.addCallbackItem("Toggle Window") { [self] _ in
colorPanel.toggle()
}
.setShortcut(for: .toggleWindow)
}
.setShortcut(for: .pickColor)

menu.addSeparator()

Expand Down Expand Up @@ -87,12 +96,31 @@ final class AppState: ObservableObject {
$0.button!.onAction { [self] _ in
let event = NSApp.currentEvent!

if event.type == .rightMouseUp {
func showMenu() {
item.menu = createMenu()
item.button!.performClick(nil)
item.menu = nil
} else {
colorPanel.toggle()
}

switch Defaults[.menuBarItemClickAction] {
case .showMenu:
if event.type == .rightMouseUp {
pickColor()
} else {
showMenu()
}
case .showColorSampler:
if event.type == .rightMouseUp {
showMenu()
} else {
pickColor()
}
case .toggleWindow:
if event.type == .rightMouseUp {
showMenu()
} else {
colorPanel.toggle()
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Color Picker/ColorPickerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ private struct RecentlyPickedColorsButton: View {

var body: some View {
Menu {
ForEach(recentlyPickedColors.reversed(), id: \.lchColorString) { color in
ForEach(recentlyPickedColors.reversed()) { color in
Button {
appState.colorPanel.color = color
} label: {
Expand Down
27 changes: 27 additions & 0 deletions Color Picker/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extension Defaults.Keys {
// Settings
static let showInMenuBar = Key<Bool>("showInMenuBar", default: false)
static let showColorSamplerOnOpen = Key<Bool>("showColorSamplerOnOpen", default: false)
static let menuBarItemClickAction = Key<MenuBarItemClickAction>("menuBarItemClickAction", default: .showMenu)
static let preferredColorFormat = Key<ColorFormat>("preferredColorFormat", default: .hex)
static let stayOnTop = Key<Bool>("stayOnTop", default: true)
static let uppercaseHexColor = Key<Bool>("uppercaseHexColor", default: false)
Expand Down Expand Up @@ -106,3 +107,29 @@ extension CodableColorFormat: Defaults.CodableType {
extension ColorFormat: Defaults.NativeType {
typealias CodableForm = CodableColorFormat
}

enum MenuBarItemClickAction: String, CaseIterable, Defaults.Serializable {
case showMenu
case showColorSampler
case toggleWindow

var title: String {
switch self {
case .showMenu:
return "Show menu"
case .showColorSampler:
return "Show color sampler"
case .toggleWindow:
return "Toggle window"
}
}

var tip: String {
switch self {
case .showMenu:
return "Right-click to show the color sampler"
case .showColorSampler, .toggleWindow:
return "Right-click to show the menu"
}
}
}
55 changes: 25 additions & 30 deletions Color Picker/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,24 @@ import Defaults
import LaunchAtLogin
import KeyboardShortcuts

private struct ShowInMenuBarSetting: View {
@State private var isShowingTip = false
private struct MenuBarItemClickActionSetting: View {
@Default(.menuBarItemClickAction) private var menuBarItemClickAction

var body: some View {
Defaults.Toggle("Show in menu bar", key: .showInMenuBar)
.onChange {
guard $0 else {
return
}

// isShowingTip = true

// TODO: The SwiftUI alert shows multiple times. (macOS 11.6)
DispatchQueue.main.async {
NSAlert.showModal(
title: "Tips",
message: "Click the menu bar icon to toggle the color picker window.\n\nRight-click the menu bar icon to quit the app or access the preferences."
)
}
}
.alert(isPresented: $isShowingTip) {
Alert(
title: Text("Tips"),
message: Text("Click the menu bar icon to toggle the color picker window.\n\nRight-click the menu bar icon to quit the app or access the preferences.")
)
VStack {
EnumPicker(
enumBinding: $menuBarItemClickAction,
label: Text("When clicking menu bar icon:")
.respectDisabled()
.fixedSize()
) { element, _ in
Text(element.title)
}
.fixedSize()
Text(menuBarItemClickAction.tip)
.settingSubtitleTextStyle()
.frame(maxWidth: .infinity, alignment: .trailing)
}
}
}

Expand Down Expand Up @@ -70,15 +62,18 @@ private struct GeneralSettings: View {
var body: some View {
Form {
VStack(alignment: .leading) {
ShowInMenuBarSetting()
LaunchAtLogin.Toggle()
.disabled(!showInMenuBar)
.help(showInMenuBar ? "" : "There is really no point in launching the app at login if it is not in the menu bar. You can instead just put it in the Dock and launch it when needed.")
.padding(.leading, 19)
Defaults.Toggle("Stay on top", key: .stayOnTop)
.help("Make the color picker window stay on top of all other windows.")
.padding(.bottom, 8)
Defaults.Toggle("Show in menu bar", key: .showInMenuBar)
Group {
LaunchAtLogin.Toggle()
.help(showInMenuBar ? "" : "There is really no point in launching the app at login if it is not in the menu bar. You can instead just put it in the Dock and launch it when needed.")
MenuBarItemClickActionSetting()
}
.disabled(!showInMenuBar)
.padding(.leading, 19)
}
.offset(x: -40)
}
.padding()
.padding()
Expand Down Expand Up @@ -199,7 +194,7 @@ struct SettingsView: View {
AdvancedSettings()
.settingsTabItem(.advanced)
}
.frame(width: 400)
.frame(width: 420)
.windowLevel(.floating + 1) // Ensure it's always above the color picker.
}
}
Expand Down
35 changes: 35 additions & 0 deletions Color Picker/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2476,3 +2476,38 @@ extension Collection {
return Array(dropFirst(removeCount))
}
}


extension Collection {
var nilIfEmpty: Self? { isEmpty ? nil : self }
}


extension View {
func multilineText() -> some View {
lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}
}


extension View {
func secondaryTextStyle() -> some View {
font(.system(size: NSFont.smallSystemFontSize))
.foregroundColor(.secondary)
}
}


extension View {
/// Usually used for a verbose description of a settings item.
func settingSubtitleTextStyle() -> some View {
secondaryTextStyle()
.multilineText()
}
}


extension NSColor: Identifiable {
public var id: String { "\(rgb.hashValue) - \(colorSpace.localizedName ?? "")" }
}

0 comments on commit 1ae4c58

Please sign in to comment.