Skip to content

Commit

Permalink
Refactor application life cycle and other UI related improvements
Browse files Browse the repository at this point in the history
### Code changes

- Remove `AppDelegate` and leverage from the new SwiftUI app life cycle
- Remove `AppDelegateLaunchController`
- Refactor the UI components into smaller more manageable parts
- Move context menus and commands into their own structs
- Add new `Saloon` store which acts as the application delegate in the
 new SwiftUI life cycle
- Store group and workflow selection in user defaults
- Remove Introspect framework dependency

### Features
- Commands can now be focused, supports navigation and supports deletion
- Improve group and workflow selections
  • Loading branch information
zenangst committed Dec 14, 2020
1 parent 1ec9eec commit 6f365a1
Show file tree
Hide file tree
Showing 75 changed files with 1,617 additions and 1,352 deletions.
183 changes: 0 additions & 183 deletions App/Sources/Application/AppDelegate.swift

This file was deleted.

28 changes: 0 additions & 28 deletions App/Sources/Application/AppDelegateLaunchController.swift

This file was deleted.

76 changes: 6 additions & 70 deletions App/Sources/Application/KeyboardCowboyApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,86 +4,22 @@ import ModelKit

@main
struct KeyboardCowboyApp: App {
// swiftlint:disable weak_delegate
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var store = Saloon()
@Environment(\.scenePhase) var scenePhase
@State var content: MainView?
private var firstResponder: NSResponder? { NSApp.keyWindow?.firstResponder }

var body: some Scene {
WindowGroup {
Group {
if appDelegate.permissionController.hasPrivileges() {
content
} else {
PermissionsView()
}
}
.frame(minWidth: 800, minHeight: 520)
.onChange(of: scenePhase, perform: { phase in
if phase == .active {
content = appDelegate.mainView
}
})
.environmentObject(appDelegate.userSelection)
store.state.currentView
.frame(minWidth: 800, minHeight: 520)
.onChange(of: scenePhase, perform: store.receive(_:))
}
.windowToolbarStyle(UnifiedWindowToolbarStyle())
.commands {
CommandGroup(replacing: CommandGroupPlacement.pasteboard, addition: {

Button("Copy") {
firstResponder?.tryToPerform(#selector(NSText.copy(_:)), with: nil)
}.keyboardShortcut("c", modifiers: [.command])

Button("Paste") {
firstResponder?.tryToPerform(#selector(NSText.paste(_:)), with: nil)
}.keyboardShortcut("v", modifiers: [.command])

Button("Delete") {
firstResponder?.tryToPerform(#selector(NSText.delete(_:)), with: nil)
}.keyboardShortcut(.delete, modifiers: [])

Button("Select All") {
firstResponder?.tryToPerform(#selector(NSText.selectAll(_:)), with: nil)
}.keyboardShortcut("a", modifiers: [.command])
})

CommandGroup(replacing: CommandGroupPlacement.newItem, addition: {
Button("New Workflow") {
if let group = appDelegate.userSelection.group {
appDelegate.workflowFeatureController?.perform(.createWorkflow(in: group))
}
}.keyboardShortcut("n", modifiers: [.command])

Button("New Keyboard shortcut") {
if let workflow = appDelegate.userSelection.workflow {
appDelegate.keyboardFeatureController?.perform(.createKeyboardShortcut(ModelKit.KeyboardShortcut.empty(),
index: 999,
in: workflow))
}
}.keyboardShortcut("k", modifiers: [.command])

Button("New Command") {
if let workflow = appDelegate.userSelection.workflow {
appDelegate.commandFeatureController?.perform(.createCommand(Command.application(.empty()), in: workflow))
}
}.keyboardShortcut("n", modifiers: [.control, .option, .command])

Button("New Group") {
appDelegate.groupFeatureController?.perform(.createGroup)
}.keyboardShortcut("N", modifiers: [.command, .shift])
})

CommandGroup(after: CommandGroupPlacement.toolbar, addition: {
Button("Toggle Sidebar") {
firstResponder?.tryToPerform(
#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}.keyboardShortcut("S")
})
KeyboardCowboyCommands(store: store, keyInputSubject: Saloon.keyInputSubject)
}

Settings {
SettingsView()
KeyboardCowboySettingsView()
}
}
}
84 changes: 84 additions & 0 deletions App/Sources/Application/KeyboardCowboyCommands.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import SwiftUI
import ModelKit
import ViewKit

struct KeyboardCowboyCommands: Commands {
let store: Saloon
let keyInputSubject: KeyInputSubjectWrapper

private var firstResponder: NSResponder? { NSApp.keyWindow?.firstResponder }

var body: some Commands {
CommandGroup(replacing: CommandGroupPlacement.pasteboard, addition: {
Button("Copy") {
firstResponder?.tryToPerform(#selector(NSText.copy(_:)), with: nil)
}.keyboardShortcut("c", modifiers: [.command])

Button("Paste") {
firstResponder?.tryToPerform(#selector(NSText.paste(_:)), with: nil)
}.keyboardShortcut("v", modifiers: [.command])

keyInput(.delete, name: "Delete") {
firstResponder?.tryToPerform(#selector(NSText.delete(_:)), with: nil)
}

Button("Select All") {
firstResponder?.tryToPerform(#selector(NSText.selectAll(_:)), with: nil)
}.keyboardShortcut("a", modifiers: [.command])
})

CommandMenu("Navigation") {
keyInput(.upArrow, name: "Select Previous") {
guard let cgEvent = CGEvent(keyboardEventSource: nil, virtualKey: 126, keyDown: false) else {
return
}
let event = NSEvent.init(cgEvent: cgEvent)
firstResponder?.tryToPerform(#selector(NSApplication.keyDown(with:)), with: event)
}

keyInput(.downArrow, name: "Select Next") {
guard let cgEvent = CGEvent(keyboardEventSource: nil, virtualKey: 125, keyDown: false) else {
return
}
let event = NSEvent.init(cgEvent: cgEvent)
firstResponder?.tryToPerform(#selector(NSApplication.keyDown(with:)), with: event)
}
}

CommandGroup(replacing: CommandGroupPlacement.newItem, addition: {
if let group = store.selectedGroup {
Button("New Workflow") {
store.context.workflow.perform(.create(groupId: group.id))
}.keyboardShortcut("n", modifiers: [.command])
}

if store.selectedWorkflow != nil {
Button("New Keyboard shortcut") {
store.context.keyboardsShortcuts.perform(
.create(ModelKit.KeyboardShortcut.empty(), offset: 999, in: store.context.workflow.state))
}.keyboardShortcut("k", modifiers: [.command])

Button("New Command") {
store.context.commands.perform(.create(Command.application(.empty()), in: store.context.workflow.state))
}.keyboardShortcut("n", modifiers: [.control, .option, .command])
}
Button("New Group") {
store.context.groups.perform(.createGroup)
}.keyboardShortcut("N", modifiers: [.command])
})

CommandGroup(after: CommandGroupPlacement.toolbar, addition: {
Button("Toggle Sidebar") {
firstResponder?.tryToPerform(
#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}.keyboardShortcut("S")
})
}

func keyInput(_ key: KeyEquivalent, name: String, modifiers: EventModifiers = [],
fallbackEvent: @escaping () -> Void) -> some View {
return keyboardShortcut(key, name: name, sender: keyInputSubject,
modifiers: modifiers,
fallbackEvent: fallbackEvent)
}
}
2 changes: 2 additions & 0 deletions App/Sources/Application/LaunchArgument.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import LaunchArguments

let launchArguments = LaunchArgumentsController<LaunchArgument>()

enum LaunchArgument: String, LaunchArgumentType {
// Used to avoid running the application when running unit tests
case runningUnitTests = "-running-unit-tests"
Expand Down
Loading

0 comments on commit 6f365a1

Please sign in to comment.