mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-29 08:04:32 +00:00
Dynamic Popup/Dropdown shortcut
This commit is contained in:
parent
5321352184
commit
e29745f8f4
6 changed files with 139 additions and 19 deletions
|
|
@ -450,8 +450,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
ClipboardManager.shared.startMonitoring()
|
ClipboardManager.shared.startMonitoring()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if window mode is enabled
|
// Determine display mode based on user preference and context
|
||||||
if Defaults[.clipboardWindowMode] {
|
let shouldUseWindow = self.shouldUseWindowMode()
|
||||||
|
|
||||||
|
if shouldUseWindow {
|
||||||
// Use window interface
|
// Use window interface
|
||||||
ClipboardWindowManager.shared.toggleClipboardWindow()
|
ClipboardWindowManager.shared.toggleClipboardWindow()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -622,6 +624,60 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
NSApplication.shared.terminate(nil)
|
NSApplication.shared.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Clipboard Display Mode Logic
|
||||||
|
|
||||||
|
private func shouldUseWindowMode() -> Bool {
|
||||||
|
let displayMode = Defaults[.clipboardDisplayMode]
|
||||||
|
|
||||||
|
switch displayMode {
|
||||||
|
case .window:
|
||||||
|
return true
|
||||||
|
case .popover:
|
||||||
|
return false
|
||||||
|
case .auto:
|
||||||
|
// Auto mode: use window if any app is in fullscreen, otherwise use popover
|
||||||
|
return isAnyAppInFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isAnyAppInFullscreen() -> Bool {
|
||||||
|
// Check if any running application is in fullscreen mode
|
||||||
|
let workspace = NSWorkspace.shared
|
||||||
|
|
||||||
|
// Get all visible applications
|
||||||
|
let apps = workspace.runningApplications.filter { app in
|
||||||
|
app.activationPolicy == .regular && !app.isTerminated
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each application's windows for fullscreen state
|
||||||
|
for app in apps {
|
||||||
|
if app.isActive {
|
||||||
|
// Check if the frontmost application has fullscreen windows
|
||||||
|
let windows = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as? [[String: Any]] ?? []
|
||||||
|
|
||||||
|
for window in windows {
|
||||||
|
if let ownerPID = window[kCGWindowOwnerPID as String] as? Int32,
|
||||||
|
ownerPID == app.processIdentifier,
|
||||||
|
let bounds = window[kCGWindowBounds as String] as? [String: Any],
|
||||||
|
let width = bounds["Width"] as? CGFloat,
|
||||||
|
let height = bounds["Height"] as? CGFloat {
|
||||||
|
|
||||||
|
// Check if window dimensions match screen dimensions (indicating fullscreen)
|
||||||
|
for screen in NSScreen.screens {
|
||||||
|
let screenFrame = screen.frame
|
||||||
|
if abs(width - screenFrame.width) < 10 && abs(height - screenFrame.height) < 10 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
private func showOnboardingWindow() {
|
private func showOnboardingWindow() {
|
||||||
if onboardingWindowController == nil {
|
if onboardingWindowController == nil {
|
||||||
let window = NSWindow(
|
let window = NSWindow(
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,9 @@
|
||||||
},
|
},
|
||||||
"Appearance" : {
|
"Appearance" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Auto mode intelligently chooses between popover (desktop) and window (fullscreen apps) for the best experience. You can override this with manual selection." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Automatically check for updates" : {
|
"Automatically check for updates" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -388,6 +391,9 @@
|
||||||
},
|
},
|
||||||
"Disk Write" : {
|
"Disk Write" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Display Mode" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Download" : {
|
"Download" : {
|
||||||
|
|
||||||
|
|
@ -1196,9 +1202,6 @@
|
||||||
},
|
},
|
||||||
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
||||||
|
|
||||||
},
|
|
||||||
"Window Mode shows clipboard in a moveable window like Raycast with tabs for history and favorites. When disabled, clipboard appears in a popover attached to the Dynamic Island." : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"With %lld graphs enabled, the Dynamic Island will expand horizontally to accommodate all graphs in a single row." : {
|
"With %lld graphs enabled, the Dynamic Island will expand horizontally to accommodate all graphs in a single row." : {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,25 @@ struct DynamicIslandHeader: View {
|
||||||
|
|
||||||
if Defaults[.enableClipboardManager] && Defaults[.showClipboardIcon] {
|
if Defaults[.enableClipboardManager] && Defaults[.showClipboardIcon] {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
// Use the same smart logic as the keyboard shortcut
|
||||||
|
let displayMode = Defaults[.clipboardDisplayMode]
|
||||||
|
let shouldUseWindow: Bool
|
||||||
|
|
||||||
|
switch displayMode {
|
||||||
|
case .window:
|
||||||
|
shouldUseWindow = true
|
||||||
|
case .popover:
|
||||||
|
shouldUseWindow = false
|
||||||
|
case .auto:
|
||||||
|
// For header button clicks, prefer popover since user is already interacting with notch
|
||||||
|
shouldUseWindow = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldUseWindow {
|
||||||
|
ClipboardWindowManager.shared.toggleClipboardWindow()
|
||||||
|
} else {
|
||||||
showClipboardPopover.toggle()
|
showClipboardPopover.toggle()
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(.black)
|
.fill(.black)
|
||||||
|
|
|
||||||
|
|
@ -1589,7 +1589,7 @@ struct ClipboardSettings: View {
|
||||||
@Default(.enableClipboardManager) var enableClipboardManager
|
@Default(.enableClipboardManager) var enableClipboardManager
|
||||||
@Default(.clipboardHistorySize) var clipboardHistorySize
|
@Default(.clipboardHistorySize) var clipboardHistorySize
|
||||||
@Default(.showClipboardIcon) var showClipboardIcon
|
@Default(.showClipboardIcon) var showClipboardIcon
|
||||||
@Default(.clipboardWindowMode) var clipboardWindowMode
|
@Default(.clipboardDisplayMode) var clipboardDisplayMode
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
|
|
@ -1612,7 +1612,24 @@ struct ClipboardSettings: View {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Show Clipboard Icon", key: .showClipboardIcon)
|
Defaults.Toggle("Show Clipboard Icon", key: .showClipboardIcon)
|
||||||
|
|
||||||
Defaults.Toggle("Window Mode", key: .clipboardWindowMode)
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
Text("Display Mode")
|
||||||
|
Spacer()
|
||||||
|
Picker("Display Mode", selection: $clipboardDisplayMode) {
|
||||||
|
ForEach(ClipboardDisplayMode.allCases, id: \.self) { mode in
|
||||||
|
Text(mode.displayName).tag(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
|
.frame(width: 140)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(clipboardDisplayMode.description)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.leading, 0)
|
||||||
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("History Size")
|
Text("History Size")
|
||||||
|
|
@ -1650,7 +1667,7 @@ struct ClipboardSettings: View {
|
||||||
} header: {
|
} header: {
|
||||||
Text("Settings")
|
Text("Settings")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Window Mode shows clipboard in a moveable window like Raycast with tabs for history and favorites. When disabled, clipboard appears in a popover attached to the Dynamic Island.")
|
Text("Auto mode intelligently chooses between popover (desktop) and window (fullscreen apps) for the best experience. You can override this with manual selection.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@ class ClipboardWindowManager: ObservableObject {
|
||||||
|
|
||||||
func showClipboardWindow() {
|
func showClipboardWindow() {
|
||||||
if let existingWindow = clipboardWindow {
|
if let existingWindow = clipboardWindow {
|
||||||
|
// Ensure window appears above fullscreen apps
|
||||||
|
existingWindow.level = .screenSaver
|
||||||
existingWindow.makeKeyAndOrderFront(nil)
|
existingWindow.makeKeyAndOrderFront(nil)
|
||||||
|
existingWindow.orderFrontRegardless() // Force window to front even above fullscreen
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -32,24 +35,24 @@ class ClipboardWindowManager: ObservableObject {
|
||||||
|
|
||||||
window.title = "Clipboard Manager"
|
window.title = "Clipboard Manager"
|
||||||
window.isMovableByWindowBackground = true
|
window.isMovableByWindowBackground = true
|
||||||
window.level = .floating
|
window.level = .screenSaver // Use screenSaver level to appear above fullscreen apps
|
||||||
window.isReleasedWhenClosed = false
|
window.isReleasedWhenClosed = false
|
||||||
window.hasShadow = true
|
window.hasShadow = true
|
||||||
window.titlebarAppearsTransparent = true
|
window.titlebarAppearsTransparent = true
|
||||||
window.styleMask.insert(.fullSizeContentView)
|
window.styleMask.insert(.fullSizeContentView)
|
||||||
|
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] // Allow on all spaces and above fullscreen
|
||||||
|
|
||||||
// Set minimum and maximum sizes
|
// Set minimum and maximum sizes
|
||||||
window.minSize = NSSize(width: 350, height: 250)
|
window.minSize = NSSize(width: 350, height: 250)
|
||||||
window.maxSize = NSSize(width: 600, height: 500)
|
window.maxSize = NSSize(width: 600, height: 500)
|
||||||
|
|
||||||
// Center the window
|
// Center the window on the current screen (important for fullscreen apps)
|
||||||
if let screen = NSScreen.main {
|
let currentScreen = NSScreen.main ?? NSScreen.screens.first!
|
||||||
let screenFrame = screen.visibleFrame
|
let screenFrame = currentScreen.frame // Use full frame instead of visibleFrame for fullscreen compatibility
|
||||||
let windowFrame = window.frame
|
let windowFrame = window.frame
|
||||||
let x = (screenFrame.width - windowFrame.width) / 2 + screenFrame.minX
|
let x = (screenFrame.width - windowFrame.width) / 2 + screenFrame.minX
|
||||||
let y = (screenFrame.height - windowFrame.height) / 2 + screenFrame.minY
|
let y = (screenFrame.height - windowFrame.height) / 2 + screenFrame.minY
|
||||||
window.setFrameOrigin(NSPoint(x: x, y: y))
|
window.setFrameOrigin(NSPoint(x: x, y: y))
|
||||||
}
|
|
||||||
|
|
||||||
// Set the content view
|
// Set the content view
|
||||||
let contentView = ClipboardWindow()
|
let contentView = ClipboardWindow()
|
||||||
|
|
@ -63,6 +66,7 @@ class ClipboardWindowManager: ObservableObject {
|
||||||
|
|
||||||
self.clipboardWindow = window
|
self.clipboardWindow = window
|
||||||
window.makeKeyAndOrderFront(nil)
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
window.orderFrontRegardless() // Force window to front even above fullscreen apps
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,28 @@ enum CalendarSelectionState: Codable, Defaults.Serializable {
|
||||||
case selected(Set<String>)
|
case selected(Set<String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable {
|
||||||
|
case auto = "auto" // Automatically choose: popover on desktop, window in fullscreen
|
||||||
|
case popover = "popover" // Always use popover (attached to notch)
|
||||||
|
case window = "window" // Always use standalone window
|
||||||
|
|
||||||
|
var displayName: String {
|
||||||
|
switch self {
|
||||||
|
case .auto: return "Auto (Smart)"
|
||||||
|
case .popover: return "Popover"
|
||||||
|
case .window: return "Window"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .auto: return "Automatically chooses the best display mode based on context"
|
||||||
|
case .popover: return "Always shows clipboard attached to the Dynamic Island"
|
||||||
|
case .window: return "Always shows clipboard in a standalone moveable window"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum HideNotchOption: String, Defaults.Serializable {
|
enum HideNotchOption: String, Defaults.Serializable {
|
||||||
case always
|
case always
|
||||||
case nowPlayingOnly
|
case nowPlayingOnly
|
||||||
|
|
@ -170,7 +192,7 @@ extension Defaults.Keys {
|
||||||
static let enableClipboardManager = Key<Bool>("enableClipboardManager", default: true)
|
static let enableClipboardManager = Key<Bool>("enableClipboardManager", default: true)
|
||||||
static let clipboardHistorySize = Key<Int>("clipboardHistorySize", default: 3)
|
static let clipboardHistorySize = Key<Int>("clipboardHistorySize", default: 3)
|
||||||
static let showClipboardIcon = Key<Bool>("showClipboardIcon", default: true)
|
static let showClipboardIcon = Key<Bool>("showClipboardIcon", default: true)
|
||||||
static let clipboardWindowMode = Key<Bool>("clipboardWindowMode", default: true)
|
static let clipboardDisplayMode = Key<ClipboardDisplayMode>("clipboardDisplayMode", default: .auto)
|
||||||
|
|
||||||
// MARK: Keyboard Shortcuts
|
// MARK: Keyboard Shortcuts
|
||||||
static let enableShortcuts = Key<Bool>("enableShortcuts", default: true)
|
static let enableShortcuts = Key<Bool>("enableShortcuts", default: true)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue