fix: align action popup background with launcher
This commit is contained in:
@@ -61,6 +61,8 @@ The launcher opens without a window animation; Settings is constructed only when
|
|||||||
|
|
||||||
Right-click the launcher background for Settings and Quit. Actions include favorites, resetting usage ranking, revealing applications in Finder, quitting them, and uninstalling them.
|
Right-click the launcher background for Settings and Quit. Actions include favorites, resetting usage ranking, revealing applications in Finder, quitting them, and uninstalling them.
|
||||||
|
|
||||||
|
The Actions surface crops the launcher’s full-size background at its own position, keeping the glows continuous without showing the results underneath.
|
||||||
|
|
||||||
### Global shortcuts and launch at login
|
### Global shortcuts and launch at login
|
||||||
|
|
||||||
In Settings, choose a **Global shortcut destination**: **Applications**, **Conjure Emoji**, or **Conjure Translate**. Choose **Record Shortcut…** and press a combination containing Command, Option, or Control. For example, select **Conjure Emoji** and record Command-Shift-Space to open the emoji picker directly. **Clear** disables only the selected destination's shortcut. No shortcuts are assigned by default; existing launcher bindings are preserved.
|
In Settings, choose a **Global shortcut destination**: **Applications**, **Conjure Emoji**, or **Conjure Translate**. Choose **Record Shortcut…** and press a combination containing Command, Option, or Control. For example, select **Conjure Emoji** and record Command-Shift-Space to open the emoji picker directly. **Clear** disables only the selected destination's shortcut. No shortcuts are assigned by default; existing launcher bindings are preserved.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ConjureKit
|
|||||||
@MainActor
|
@MainActor
|
||||||
final class ApplicationActionsController: NSViewController, NSSearchFieldDelegate {
|
final class ApplicationActionsController: NSViewController, NSSearchFieldDelegate {
|
||||||
private let search = ActionsSearchField()
|
private let search = ActionsSearchField()
|
||||||
|
private let background = LauncherBackground(frame: .zero)
|
||||||
private let entries = [
|
private let entries = [
|
||||||
ActionEntry(title: "Open", symbol: "arrow.up.forward.app", color: SunsetPalette.text),
|
ActionEntry(title: "Open", symbol: "arrow.up.forward.app", color: SunsetPalette.text),
|
||||||
ActionEntry(title: "Favorite", symbol: "star", color: SunsetPalette.text),
|
ActionEntry(title: "Favorite", symbol: "star", color: SunsetPalette.text),
|
||||||
@@ -49,11 +50,10 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
view.layer?.shadowOffset = NSSize(width: 0, height: -4)
|
view.layer?.shadowOffset = NSSize(width: 0, height: -4)
|
||||||
let content = NSVisualEffectView()
|
let content = NSVisualEffectView()
|
||||||
content.material = .hudWindow
|
content.material = .hudWindow
|
||||||
content.blendingMode = .withinWindow
|
content.blendingMode = .behindWindow
|
||||||
content.state = .active
|
content.state = .active
|
||||||
roundMaterialCorners(content, radius: 18)
|
roundMaterialCorners(content, radius: 18)
|
||||||
content.layer?.borderWidth = 0.5
|
content.layer?.borderWidth = 0.5
|
||||||
let background = LauncherBackground(frame: .zero)
|
|
||||||
background.setAccessibilityElement(false)
|
background.setAccessibilityElement(false)
|
||||||
content.addSubview(background)
|
content.addSubview(background)
|
||||||
applicationLabel.textColor = SunsetPalette.secondaryText
|
applicationLabel.textColor = SunsetPalette.secondaryText
|
||||||
@@ -108,10 +108,6 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
content.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
content.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
content.topAnchor.constraint(equalTo: view.topAnchor),
|
content.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
content.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
content.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
background.leadingAnchor.constraint(equalTo: content.leadingAnchor),
|
|
||||||
background.trailingAnchor.constraint(equalTo: content.trailingAnchor),
|
|
||||||
background.topAnchor.constraint(equalTo: content.topAnchor),
|
|
||||||
background.bottomAnchor.constraint(equalTo: content.bottomAnchor),
|
|
||||||
applicationLabel.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 16),
|
applicationLabel.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 16),
|
||||||
applicationLabel.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -16),
|
applicationLabel.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -16),
|
||||||
applicationLabel.topAnchor.constraint(equalTo: content.topAnchor, constant: 14),
|
applicationLabel.topAnchor.constraint(equalTo: content.topAnchor, constant: 14),
|
||||||
@@ -164,6 +160,13 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
view.alphaValue = 0
|
view.alphaValue = 0
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
host.addSubview(view)
|
host.addSubview(view)
|
||||||
|
// Crop the window-sized background; filtering must not rescale its glows.
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
background.leadingAnchor.constraint(equalTo: host.leadingAnchor),
|
||||||
|
background.trailingAnchor.constraint(equalTo: host.trailingAnchor),
|
||||||
|
background.topAnchor.constraint(equalTo: host.topAnchor),
|
||||||
|
background.bottomAnchor.constraint(equalTo: host.bottomAnchor)
|
||||||
|
])
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
view.trailingAnchor.constraint(equalTo: pill.trailingAnchor),
|
view.trailingAnchor.constraint(equalTo: pill.trailingAnchor),
|
||||||
view.bottomAnchor.constraint(equalTo: pill.bottomAnchor)
|
view.bottomAnchor.constraint(equalTo: pill.bottomAnchor)
|
||||||
|
|||||||
@@ -2,5 +2,12 @@
|
|||||||
- the window should scroll with the current selection
|
- 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.
|
- [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).
|
- 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
|
||||||
|
- 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
|
- [ ] add a notes app inspired by raycast notes
|
||||||
|
- support multiple different notes
|
||||||
|
- support switching between notes
|
||||||
|
- highlight markdown syntax
|
||||||
|
- allow images
|
||||||
- [ ] improve on the conjure translation command
|
- [ ] improve on the conjure translation command
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import AppKit
|
||||||
|
import ConjureKit
|
||||||
|
import Testing
|
||||||
|
@testable import Conjure
|
||||||
|
|
||||||
|
@Test @MainActor func actionsBackgroundKeepsWindowCoordinates() throws {
|
||||||
|
_ = NSApplication.shared
|
||||||
|
let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 750, height: 480),
|
||||||
|
styleMask: [.borderless], backing: .buffered, defer: false)
|
||||||
|
let host = try #require(window.contentView)
|
||||||
|
let anchor = NSView(frame: NSRect(x: 700, y: 20, width: 30, height: 30))
|
||||||
|
host.addSubview(anchor)
|
||||||
|
let actions = ApplicationActionsController()
|
||||||
|
defer { actions.close(animated: false) }
|
||||||
|
func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) }
|
||||||
|
for width in [750.0, 900.0] {
|
||||||
|
window.setContentSize(NSSize(width: width, height: 480))
|
||||||
|
actions.showEmoji(named: "Smile", relativeTo: anchor)
|
||||||
|
let background = try #require(descendants(host).compactMap { $0 as? LauncherBackground }.first)
|
||||||
|
let search = try #require(descendants(actions.view).compactMap { $0 as? NSSearchField }.first)
|
||||||
|
for query in ["", "Copy Emoji", "no matching action"] {
|
||||||
|
search.stringValue = query
|
||||||
|
actions.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
|
||||||
|
host.layoutSubtreeIfNeeded()
|
||||||
|
let rect = background.convert(background.bounds, to: host)
|
||||||
|
#expect(rect == host.bounds)
|
||||||
|
#expect(background.bounds.size != actions.view.bounds.size)
|
||||||
|
}
|
||||||
|
actions.close(animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user