fix: persist window centers and preserve them across tool size changes
This commit is contained in:
@@ -19,7 +19,6 @@ import Testing
|
||||
#expect(keyword != name)
|
||||
#expect(comment != keyword)
|
||||
#expect((attributes("**not bold**")[.font] as? NSFont)?.isFixedPitch == true)
|
||||
#expect((attributes("After")[.font] as? NSFont)?.isFixedPitch == false)
|
||||
#expect(storage.string == text)
|
||||
editor.string = "```python\ndef main():\n pass"
|
||||
MarkdownHighlighting.apply(to: editor)
|
||||
|
||||
@@ -48,17 +48,21 @@ func launcherAlignmentKeepsFractionalDisplayCentersOnAStableWindowGrid() {
|
||||
}
|
||||
|
||||
@Test
|
||||
func launcherPositionRestoresVisibleOriginsAndFallsBackForMissingOrOffscreenPositions() {
|
||||
func launcherPositionRestoresCentersAcrossSizesAndFallsBackForMissingOrOffscreenPositions() {
|
||||
let display = CGRect(x: -1920, y: 40, width: 1920, height: 1040)
|
||||
let size = CGSize(width: 750, height: 474)
|
||||
let origin = CGPoint(x: -1500, y: 450)
|
||||
#expect(LauncherAlignment.restoredFrame(savedOrigin: origin, windowSize: size, visibleFrame: display)
|
||||
== CGRect(origin: origin, size: size))
|
||||
for invalid: CGPoint? in [nil, CGPoint(x: 0, y: 450), CGPoint(x: -1500, y: 39), CGPoint(x: CGFloat.nan, y: 450)] {
|
||||
#expect(LauncherAlignment.restoredFrame(savedOrigin: invalid, windowSize: size, visibleFrame: display)
|
||||
let center = CGPoint(x: -1125, y: 687)
|
||||
for windowSize in [size, CGSize(width: 570, height: 404), CGSize(width: 840, height: 620)] {
|
||||
let frame = LauncherAlignment.restoredFrame(savedCenter: center, windowSize: windowSize, visibleFrame: display)
|
||||
#expect(CGPoint(x: frame.midX, y: frame.midY) == center)
|
||||
#expect(frame.size == windowSize)
|
||||
}
|
||||
for invalid: CGPoint? in [nil, CGPoint(x: 0, y: 687), CGPoint(x: -1125, y: 276),
|
||||
CGPoint(x: CGFloat.nan, y: 687), CGPoint(x: -1125, y: CGFloat.infinity)] {
|
||||
#expect(LauncherAlignment.restoredFrame(savedCenter: invalid, windowSize: size, visibleFrame: display)
|
||||
== CGRect(x: -1335, y: 398, width: 750, height: 474))
|
||||
}
|
||||
let small = CGRect(x: 0, y: 40, width: 800, height: 500)
|
||||
#expect(LauncherAlignment.restoredFrame(savedOrigin: nil, windowSize: size, visibleFrame: small)
|
||||
#expect(LauncherAlignment.restoredFrame(savedCenter: nil, windowSize: size, visibleFrame: small)
|
||||
== CGRect(x: 25, y: 40, width: 750, height: 474))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import AppKit
|
||||
import ConjureKit
|
||||
import Testing
|
||||
@testable import Conjure
|
||||
|
||||
@Test @MainActor
|
||||
func launcherPositionKeepsTheDraggedCenterAcrossCommandsSizesAndRelaunch() throws {
|
||||
_ = NSApplication.shared
|
||||
let suite = "LauncherPosition.\(UUID().uuidString)"
|
||||
let defaults = try #require(UserDefaults(suiteName: suite))
|
||||
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(suite)
|
||||
defer {
|
||||
defaults.removePersistentDomain(forName: suite)
|
||||
try? FileManager.default.removeItem(at: directory)
|
||||
}
|
||||
func makeLauncher() -> LauncherWindowController {
|
||||
LauncherWindowController(
|
||||
history: LaunchHistory(defaults: defaults, now: { Date(timeIntervalSinceReferenceDate: 12_000) }),
|
||||
defaults: defaults, notesDirectory: directory)
|
||||
}
|
||||
func center(_ frame: CGRect) -> CGPoint { CGPoint(x: frame.midX, y: frame.midY) }
|
||||
let launcher = makeLauncher()
|
||||
let window = try #require(launcher.window as? LauncherWindow)
|
||||
defer { launcher.dismiss(restoringFocus: false) }
|
||||
let display = try #require(window.screen).visibleFrame
|
||||
let initialCenter = CGPoint(x: display.midX.rounded(.down), y: display.midY.rounded(.down))
|
||||
window.setFrameOrigin(CGPoint(x: initialCenter.x - window.frame.width / 2,
|
||||
y: initialCenter.y - window.frame.height / 2))
|
||||
// Open from the launcher rather than presenting again (which restores the saved position).
|
||||
func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) }
|
||||
let content = try #require(window.contentView)
|
||||
let search = try #require(descendants(content).compactMap { $0 as? NSSearchField }.first)
|
||||
search.stringValue = "notes"
|
||||
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
|
||||
#expect(launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.insertNewline(_:))))
|
||||
try #require(center(window.frame) == initialCenter)
|
||||
|
||||
let draggedCenter = CGPoint(x: initialCenter.x + 30, y: initialCenter.y - 20)
|
||||
window.setFrameOrigin(CGPoint(x: draggedCenter.x - window.frame.width / 2,
|
||||
y: draggedCenter.y - window.frame.height / 2))
|
||||
window.dragEnded?()
|
||||
// Back must use the moved Notes center, not the launcher frame captured on entry.
|
||||
let back = try #require(descendants(content).compactMap { $0 as? NSButton }.first {
|
||||
$0.accessibilityLabel() == "Back to applications"
|
||||
})
|
||||
back.performClick(nil)
|
||||
#expect(center(window.frame) == draggedCenter)
|
||||
for command: Application.Command in [.emoji, .translate, .notes] {
|
||||
launcher.present(command: command)
|
||||
#expect(center(window.frame) == draggedCenter)
|
||||
}
|
||||
var resized = window.frame
|
||||
resized.size = CGSize(width: 620, height: 330)
|
||||
resized.origin = CGPoint(x: draggedCenter.x - resized.width / 2, y: draggedCenter.y - resized.height / 2)
|
||||
window.setFrame(resized, display: true)
|
||||
window.dragEnded?()
|
||||
launcher.dismiss(restoringFocus: false)
|
||||
|
||||
let reopened = makeLauncher()
|
||||
defer { reopened.dismiss(restoringFocus: false) }
|
||||
for command: Application.Command in [.notes, .emoji, .translate] {
|
||||
reopened.present(command: command)
|
||||
#expect(center(try #require(reopened.window).frame) == draggedCenter)
|
||||
}
|
||||
|
||||
// A compact Notes window can fit where the hidden launcher-sized frame cannot.
|
||||
reopened.present(command: .notes)
|
||||
let reopenedWindow = try #require(reopened.window as? LauncherWindow)
|
||||
let edgeCenter = CGPoint(x: display.minX + 320, y: initialCenter.y)
|
||||
reopenedWindow.setFrameOrigin(CGPoint(x: edgeCenter.x - reopenedWindow.frame.width / 2,
|
||||
y: edgeCenter.y - reopenedWindow.frame.height / 2))
|
||||
reopenedWindow.dragEnded?()
|
||||
reopened.dismiss(restoringFocus: false)
|
||||
reopened.present(command: .notes)
|
||||
#expect(center(reopenedWindow.frame) == edgeCenter)
|
||||
}
|
||||
Reference in New Issue
Block a user