diff --git a/LilAgents/DockVisibility.swift b/LilAgents/DockVisibility.swift index 05de766..291ca36 100644 --- a/LilAgents/DockVisibility.swift +++ b/LilAgents/DockVisibility.swift @@ -1,6 +1,27 @@ +import AppKit import CoreGraphics +import Foundation + +enum AppPreferences { + static let showOnAllDesktopsKey = "showOnAllDesktops" + + static var showOnAllDesktops: Bool { + get { + UserDefaults.standard.bool(forKey: showOnAllDesktopsKey) + } + set { + UserDefaults.standard.set(newValue, forKey: showOnAllDesktopsKey) + } + } +} enum DockVisibility { + static func collectionBehavior(showOnAllDesktops: Bool = AppPreferences.showOnAllDesktops) -> NSWindow.CollectionBehavior { + showOnAllDesktops + ? [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary] + : [.moveToActiveSpace, .stationary] + } + static func screenHasVisibleDockReservedArea( screenFrame: CGRect, visibleFrame: CGRect diff --git a/LilAgents/LilAgentsApp.swift b/LilAgents/LilAgentsApp.swift index 42f2dbd..a813422 100644 --- a/LilAgents/LilAgentsApp.swift +++ b/LilAgents/LilAgentsApp.swift @@ -96,11 +96,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { let displayItem = NSMenuItem(title: "Display", action: nil, keyEquivalent: "") let displayMenu = NSMenu() displayMenu.delegate = self + let allDesktopsItem = NSMenuItem(title: "Show on All Desktops", action: #selector(toggleShowOnAllDesktops(_:)), keyEquivalent: "") + allDesktopsItem.state = AppPreferences.showOnAllDesktops ? .on : .off + displayMenu.addItem(allDesktopsItem) + displayMenu.addItem(NSMenuItem.separator()) let autoItem = NSMenuItem(title: "Auto (Main Display)", action: #selector(switchDisplay(_:)), keyEquivalent: "") autoItem.tag = -1 autoItem.state = .on displayMenu.addItem(autoItem) - displayMenu.addItem(NSMenuItem.separator()) for (i, screen) in NSScreen.screens.enumerated() { let name = screen.localizedName let item = NSMenuItem(title: name, action: #selector(switchDisplay(_:)), keyEquivalent: "") @@ -210,6 +213,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + @objc func toggleShowOnAllDesktops(_ sender: NSMenuItem) { + let enabled = !AppPreferences.showOnAllDesktops + AppPreferences.showOnAllDesktops = enabled + sender.state = enabled ? .on : .off + syncWindowCollectionBehaviors() + } + @objc func toggleChar1(_ sender: NSMenuItem) { guard let chars = controller?.characters, chars.count > 0 else { return } let char = chars[0] @@ -242,6 +252,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { @objc func quitApp() { NSApp.terminate(nil) } + + private func syncWindowCollectionBehaviors() { + let behavior = DockVisibility.collectionBehavior() + + controller?.characters.forEach { character in + character.window.collectionBehavior = behavior + character.popoverWindow?.collectionBehavior = behavior + character.thinkingBubbleWindow?.collectionBehavior = behavior + } + } } extension AppDelegate: NSMenuDelegate {} diff --git a/LilAgents/LilAgentsController.swift b/LilAgents/LilAgentsController.swift index ec6fce9..03a263e 100644 --- a/LilAgents/LilAgentsController.swift +++ b/LilAgents/LilAgentsController.swift @@ -184,6 +184,10 @@ class LilAgentsController { } private func shouldShowCharacters(on screen: NSScreen) -> Bool { + if AppPreferences.showOnAllDesktops { + return true + } + // User explicitly pinned to this screen — always show if pinnedScreenIndex >= 0, pinnedScreenIndex < NSScreen.screens.count { return true diff --git a/LilAgents/WalkerCharacter.swift b/LilAgents/WalkerCharacter.swift index fa30824..357712c 100644 --- a/LilAgents/WalkerCharacter.swift +++ b/LilAgents/WalkerCharacter.swift @@ -160,7 +160,7 @@ class WalkerCharacter { window.hasShadow = false window.level = .statusBar window.ignoresMouseEvents = false - window.collectionBehavior = [.moveToActiveSpace, .stationary] + window.collectionBehavior = windowCollectionBehavior let hostView = CharacterContentView(frame: CGRect(x: 0, y: 0, width: displayWidth, height: displayHeight)) hostView.character = self @@ -405,6 +405,10 @@ class WalkerCharacter { (themeOverride ?? PopoverTheme.current).withCharacterColor(characterColor).withCustomFont() } + private var windowCollectionBehavior: NSWindow.CollectionBehavior { + DockVisibility.collectionBehavior() + } + func createPopoverWindow() { let t = resolvedTheme let popoverWidth: CGFloat = 420 @@ -420,7 +424,7 @@ class WalkerCharacter { win.backgroundColor = .clear win.hasShadow = true win.level = NSWindow.Level(rawValue: NSWindow.Level.statusBar.rawValue + 10) - win.collectionBehavior = [.moveToActiveSpace, .stationary] + win.collectionBehavior = windowCollectionBehavior let brightness = t.popoverBg.redComponent * 0.299 + t.popoverBg.greenComponent * 0.587 + t.popoverBg.blueComponent * 0.114 win.appearance = NSAppearance(named: brightness < 0.5 ? .darkAqua : .aqua) @@ -792,7 +796,7 @@ class WalkerCharacter { win.hasShadow = true win.level = NSWindow.Level(rawValue: NSWindow.Level.statusBar.rawValue + 5) win.ignoresMouseEvents = true - win.collectionBehavior = [.moveToActiveSpace, .stationary] + win.collectionBehavior = windowCollectionBehavior let container = NSView(frame: NSRect(x: 0, y: 0, width: w, height: h)) container.wantsLayer = true diff --git a/Tests/AppPreferencesTests.swift b/Tests/AppPreferencesTests.swift new file mode 100644 index 0000000..c7e1cc3 --- /dev/null +++ b/Tests/AppPreferencesTests.swift @@ -0,0 +1,22 @@ +import Foundation + +func runAppPreferencesTests() { + let defaults = UserDefaults.standard + let key = AppPreferences.showOnAllDesktopsKey + let originalValue = defaults.object(forKey: key) + + defer { + defaults.removeObject(forKey: key) + if let originalValue { + defaults.set(originalValue, forKey: key) + } + } + + defaults.removeObject(forKey: key) + expect(AppPreferences.showOnAllDesktops == false, "show on all desktops defaults to off") + + AppPreferences.showOnAllDesktops = true + expect(defaults.bool(forKey: key) == true, "show on all desktops persists on") + + print("AppPreferences tests passed") +} diff --git a/Tests/DockVisibilityTests.swift b/Tests/DockVisibilityTests.swift index 52a22ea..612fe82 100644 --- a/Tests/DockVisibilityTests.swift +++ b/Tests/DockVisibilityTests.swift @@ -1,16 +1,8 @@ import Foundation import CoreGraphics +import AppKit func runDockVisibilityTests() { - func expect( - _ condition: @autoclosure () -> Bool, - _ message: String - ) { - if !condition() { - fputs("FAIL: \(message)\n", stderr) - exit(1) - } - } let screenFrame = CGRect(x: 0, y: 0, width: 1440, height: 900) @@ -74,5 +66,15 @@ func runDockVisibilityTests() { "keeps characters hidden on non-main screens when only the menu bar is visible" ) + expect( + DockVisibility.collectionBehavior(showOnAllDesktops: false) == [.moveToActiveSpace, .stationary], + "uses active-space behavior when show on all desktops is off" + ) + + expect( + DockVisibility.collectionBehavior(showOnAllDesktops: true) == [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary], + "uses all-spaces behavior when show on all desktops is on" + ) + print("DockVisibility tests passed") } diff --git a/Tests/main.swift b/Tests/main.swift index 5a0c1d4..fbcdc88 100644 --- a/Tests/main.swift +++ b/Tests/main.swift @@ -1 +1,11 @@ +import Foundation + +func expect(_ condition: @autoclosure () -> Bool, _ message: String) { + if !condition() { + fputs("FAIL: \(message)\n", stderr) + exit(1) + } +} + runDockVisibilityTests() +runAppPreferencesTests()