Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve menu bar commands #498

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion App/Sources/Core/Models/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ extension Command {
case .application: Command.application(ApplicationCommand.empty())
case .builtIn: Command.builtIn(.init(kind: .userMode(.init(id: UUID().uuidString, name: "", isEnabled: true), .toggle), notification: false))
case .keyboard: Command.keyboard(KeyboardCommand.empty())
case .menuBar: Command.menuBar(MenuBarCommand(tokens: []))
case .menuBar: Command.menuBar(MenuBarCommand(application: nil, tokens: []))
case .mouse: Command.mouse(MouseCommand.empty())
case .open: Command.open(.init(path: "", notification: false))
case .script: Command.script(.init(name: "", kind: .appleScript, source: .path(""), notification: false))
Expand Down
8 changes: 7 additions & 1 deletion App/Sources/Core/Models/Commands/MenuBarCommand.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Apps
import Foundation

struct MenuBarCommand: MetaDataProviding {
Expand All @@ -16,12 +17,16 @@ struct MenuBarCommand: MetaDataProviding {
}

let tokens: [Token]
var application: Application?
var meta: Command.MetaData

init(id: String = UUID().uuidString, name: String = "",
init(id: String = UUID().uuidString,
name: String = "",
application: Application?,
tokens: [Token],
isEnabled: Bool = true,
notification: Bool = false) {
self.application = application
self.tokens = tokens
self.meta = Command.MetaData(id: id, name: name,
isEnabled: isEnabled,
Expand All @@ -38,5 +43,6 @@ struct MenuBarCommand: MetaDataProviding {
}

self.tokens = try container.decode([Token].self, forKey: .tokens)
self.application = try container.decodeIfPresent(Application.self, forKey: .application)
}
}
20 changes: 18 additions & 2 deletions App/Sources/Core/Runners/MenuBarCommandRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,27 @@ final class MenuBarCommandRunner {
previousMatch = nil
}

guard let frontmostApplication = NSWorkspace.shared.frontmostApplication else {
var runningApplication: NSRunningApplication?
if let application = command.application {
if let match = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == application.id }) {
runningApplication = match
} else {
NSWorkspace.shared.open(URL(filePath: application.path))
try await Task.sleep(for: .seconds(0.1))
}
} else {
runningApplication = NSWorkspace.shared.frontmostApplication
}

guard let runningApplication else {
throw MenuBarCommandRunnerError.failedToFindFrontmostApplication
}

let menuItems = try AppAccessibilityElement(frontmostApplication.processIdentifier)
if runningApplication.processIdentifier != NSWorkspace.shared.frontmostApplication?.processIdentifier {
runningApplication.activate()
}

let menuItems = try AppAccessibilityElement(runningApplication.processIdentifier)
.menuBar()
.menuItems()
let match = try recursiveSearch(command.tokens, items: menuItems)
Expand Down
4 changes: 2 additions & 2 deletions App/Sources/UI/Coordinators/DetailCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ final class DetailCoordinator {
return
case .builtIn(let newCommand):
command = .builtIn(newCommand)
case .menuBar(let tokens):
command = .menuBar(.init(id: resolvedCommandId, tokens: tokens))
case .menuBar(let tokens, let application):
command = .menuBar(.init(id: resolvedCommandId, application: application, tokens: tokens))
case .mouse(let kind):
command = .mouse(.init(meta: .init(), kind: kind))
case .keyboardShortcut(let keyShortcuts):
Expand Down
24 changes: 24 additions & 0 deletions App/Sources/UI/Views/Commands/MenuBarCommandView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ struct MenuBarCommandView: View {
} content: { _ in
ScrollView(.horizontal) {
HStack(spacing: 4) {
if let application = model.application {
Text(application.displayName)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: true)
.padding(4)
.background(
RoundedRectangle(cornerRadius: 4)
.stroke(Color(nsColor: .shadowColor).opacity(0.2), lineWidth: 1)
)
.background(
RoundedRectangle(cornerRadius: 4)
.fill(
LinearGradient(colors: [
Color(nsColor: .systemBlue).opacity(0.7),
Color(nsColor: .systemBlue.withSystemEffect(.disabled)).opacity(0.4),
], startPoint: .top, endPoint: .bottom)
)
.grayscale(0.4)
)
.compositingGroup()
.shadow(radius: 2, y: 1)
.font(.caption)
}

ForEach(model.tokens) { token in
switch token {
case .menuItem(let name):
Expand Down
2 changes: 1 addition & 1 deletion App/Sources/UI/Views/Mappers/DetailModelMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private extension Command {
case .keyboard(let keyboardCommand):
kind = .keyboard(.init(id: keyboardCommand.id, keys: keyboardCommand.keyboardShortcuts))
case .menuBar(let menubarCommand):
kind = .menuBar(.init(id: menubarCommand.id, tokens: menubarCommand.tokens))
kind = .menuBar(.init(id: menubarCommand.id, application: menubarCommand.application, tokens: menubarCommand.tokens))
case .mouse(let mouseCommand):
kind = .mouse(.init(id: mouseCommand.id, kind: mouseCommand.kind))
case .open(let openCommand):
Expand Down
7 changes: 7 additions & 0 deletions App/Sources/UI/Views/Models/CommandViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,14 @@ struct CommandViewModel: Codable, Hashable, Identifiable, Transferable {
struct MenuBarModel: Codable, Hashable, Identifiable, Sendable {
let id: String
var placeholder: String { "Click MenuBar Item …" }
var application: Application?
var tokens: [MenuBarCommand.Token]

init(id: String, application: Application? = nil, tokens: [MenuBarCommand.Token]) {
self.id = id
self.application = application
self.tokens = tokens
}
}

struct ScriptModel: Codable, Hashable, Identifiable, Sendable {
Expand Down
2 changes: 1 addition & 1 deletion App/Sources/UI/Views/Models/NewCommandPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ enum NewCommandPayload: Equatable {
case keyboardShortcut([KeyShortcut])
case text(TextCommand)
case systemCommand(kind: SystemCommand.Kind)
case menuBar(tokens: [MenuBarCommand.Token])
case menuBar(tokens: [MenuBarCommand.Token], application: Application?)
case mouse(kind: MouseCommand.Kind)
case uiElement(predicates: [UIElementCommand.Predicate])
case windowManagement(kind: WindowCommand.Kind)
Expand Down
47 changes: 36 additions & 11 deletions App/Sources/UI/Views/NewCommand/NewCommandMenuBarView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SwiftUI
import Apps
import Bonzai
import SwiftUI

@MainActor
struct NewCommandMenuBarView: View {
Expand All @@ -24,6 +25,7 @@ struct NewCommandMenuBarView: View {
@Namespace var namespace
@FocusState var focus: Focus?
@Environment(\.resetFocus) var resetFocus
@EnvironmentObject var applicationStore: ApplicationStore

@Binding private var payload: NewCommandPayload
@Binding private var validation: NewCommandValidation
Expand All @@ -33,6 +35,7 @@ struct NewCommandMenuBarView: View {

@State private var menuItem: String = ""
@State private var menuItems: (String, String) = ("","")
@State private var application: Application?

init(_ payload: Binding<NewCommandPayload>,
validation: Binding<NewCommandValidation>,
Expand All @@ -41,8 +44,9 @@ struct NewCommandMenuBarView: View {
_validation = validation
_kind = .init(initialValue: kind)

if case .menuBar(let tokens) = payload.wrappedValue {
if case .menuBar(let tokens, let application) = payload.wrappedValue {
_tokens = .init(initialValue: tokens.map(TokenContainer.init))
_application = .init(initialValue: application)
} else {
_tokens = .init(initialValue: [])
}
Expand All @@ -58,8 +62,29 @@ struct NewCommandMenuBarView: View {
label: { Image(systemName: "questionmark.circle.fill") })
.buttonStyle(.calm(color: .systemYellow, padding: .small))
}

VStack {
Menu {
Button(action: {
application = nil
updateAndValidatePayload()
},
label: { Text("Front Most Application") })
Divider()
ForEach(applicationStore.applications) { application in
Button(action: {
self.application = application
updateAndValidatePayload() },
label: { Text(application.displayName) })
}
} label: {
if let application {
Text(application.displayName)
} else {
Text("Front Most Application")
}
}
.menuStyle(.zen(.init()))

ScrollView {
ForEach(tokens) { container in
HStack {
Expand Down Expand Up @@ -106,9 +131,7 @@ struct NewCommandMenuBarView: View {
)
.padding(1)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.overlay {
.frame(maxWidth: .infinity, alignment: .topLeading)
overlay()
}
if !tokens.isEmpty {
Expand All @@ -117,10 +140,11 @@ struct NewCommandMenuBarView: View {
.matchedGeometryEffect(id: "add-buttons", in: namespace)
}
}
.padding(8)
.padding(16)
.background(
RoundedRectangle(cornerRadius: 4)
.fill(Color(.textBackgroundColor).opacity(0.5))
RoundedRectangle(cornerRadius: 8)
.fill(Color(.textBackgroundColor)
.opacity(0.5))
)
}
.onChange(of: validation, perform: { newValue in
Expand All @@ -142,7 +166,7 @@ struct NewCommandMenuBarView: View {
private func updateAndValidatePayload() -> NewCommandValidation {
guard !tokens.isEmpty else { return .invalid(reason: "You need to add at least one menu item.") }

payload = .menuBar(tokens: tokens.map { $0.token })
payload = .menuBar(tokens: tokens.map { $0.token }, application: application)

return .valid
}
Expand All @@ -151,6 +175,7 @@ struct NewCommandMenuBarView: View {
func overlay() -> some View {
if tokens.isEmpty {
VStack {
Divider()
Text("Enter the exact name of the menu command you want to add.")
.font(.caption)
.allowsHitTesting(false)
Expand Down Expand Up @@ -278,7 +303,7 @@ struct NewCommandMenuBarView_Previews: PreviewProvider {
.menuItem(name: "View"),
.menuItem(name: "Navigators"),
.menuItems(name: "Show Navigator", fallbackName: "Hide Navigator")
]),
], application: nil),
onDismiss: {},
onSave: { _, _ in })
.previewDisplayName("With instructions")
Expand Down
2 changes: 1 addition & 1 deletion App/Sources/UI/Views/Windows/NewCommandWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ struct NewCommandWindow: Scene {
case .builtIn:
return .placeholder
case .menuBar(let command):
return .menuBar(tokens: command.tokens)
return .menuBar(tokens: command.tokens, application: command.application)
case .mouse(let command):
return .mouse(kind: command.kind)
case .keyboard(let command):
Expand Down
Loading