diff --git a/README.md b/README.md index 31c7baa..0220997 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Conjure searches the system and user Applications folders. Hover over a result t Conjure runs without a Dock icon. Dismissing the launcher leaves it running; reopen it from Finder or use your configured global shortcut. -The launcher opens without a window animation; Settings is constructed only when needed, not before the launcher appears. +The launcher, emoji picker, and translation tool open immediately without window animations or content fades, including from global shortcuts. Shortcut destinations are prepared before the window appears. Settings is constructed only when needed, not before the launcher appears. ### Controls diff --git a/Sources/Conjure/LauncherWindowController.swift b/Sources/Conjure/LauncherWindowController.swift index 1de6c18..b4847f7 100644 --- a/Sources/Conjure/LauncherWindowController.swift +++ b/Sources/Conjure/LauncherWindowController.swift @@ -132,6 +132,10 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe searchField.stringValue = "" restorePosition() updateResults() + if let command { + showCommand(command) + history.recordLaunch(of: Application(command: command)) + } window?.deminiaturize(nil) showWindow(nil) NSApp.activate() @@ -140,10 +144,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe (window as? LauncherPanel)?.sampleNumberHints() (searchField.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent refreshApplications() - if let command { - showCommand(command) - history.recordLaunch(of: Application(command: command)) - } } func toggle(command: Application.Command? = nil) { @@ -869,7 +869,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe view.setAccessibilityHidden(true) } controller.view.translatesAutoresizingMaskIntoConstraints = false - controller.view.alphaValue = 0 content.addSubview(controller.view) NSLayoutConstraint.activate([ controller.view.leadingAnchor.constraint(equalTo: content.leadingAnchor), @@ -877,10 +876,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe controller.view.topAnchor.constraint(equalTo: searchField.bottomAnchor, constant: 16), controller.view.bottomAnchor.constraint(equalTo: content.bottomAnchor), ]) - NSAnimationContext.runAnimationGroup { context in - context.duration = NSWorkspace.shared.accessibilityDisplayShouldReduceMotion ? 0 : 0.18 - controller.view.animator().alphaValue = 1 - } window?.makeFirstResponder(searchField) } } diff --git a/TODO.md b/TODO.md index b530959..1855944 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ - the window should scroll with the current selection - [x] make escape close the window that was opened, be it the application launcher, the emoji picker, or something else. - clicking the "back" chevron should still take you back to the application launcher view though (as it does today). -- [ ] remove window open/fade in/transition animation when opening conjure from keybindings +- [x] remove window open/fade in/transition animation when opening conjure from keybindings - this is already the case for the application launcher window. - evidently not the case for the emoji picker (though it should be ubiquitous) - [ ] add a notes app inspired by raycast notes diff --git a/Tests/ConjureKitTests/ShortcutDestinationTests.swift b/Tests/ConjureKitTests/ShortcutDestinationTests.swift index 4269509..724962f 100644 --- a/Tests/ConjureKitTests/ShortcutDestinationTests.swift +++ b/Tests/ConjureKitTests/ShortcutDestinationTests.swift @@ -32,3 +32,38 @@ import Testing #expect(!descendants(content).contains { $0 is NSCollectionView }) #expect(search.accessibilityLabel() == "Search applications, calculate, or convert units and currencies") } + +@Test(arguments: Application.Command.allCases, [true, false]) +@MainActor func commandsAreFullyVisibleImmediately(_ command: Application.Command, _ fromShortcut: Bool) throws { + _ = NSApplication.shared + let suite = "ConjurePresentation.\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: suite)! + defer { defaults.removePersistentDomain(forName: suite) } + let launcher = LauncherWindowController( + history: LaunchHistory(defaults: defaults, now: { Date(timeIntervalSinceReferenceDate: 12_000) }), + defaults: defaults) + let window = try #require(launcher.window) + defer { window.orderOut(nil) } + let content = try #require(window.contentView) + func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) } + if fromShortcut { + launcher.toggle(command: command) + } else { + launcher.present() + let search = try #require(descendants(content).compactMap { $0 as? NSSearchField }.first) + search.stringValue = command.rawValue + launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search)) + _ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.insertNewline(_:))) + } + let tool = try #require(content.subviews.first { view in + descendants(view).contains { + switch command { + case .emoji: $0 is NSCollectionView + case .translate: $0.accessibilityLabel() == "Japanese translation" + } + } + }) + #expect(window.isVisible) + #expect(!tool.isHidden) + #expect(tool.alphaValue == 1) +}