Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions Sources/DefiCore/Overview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,113 @@ public func interpolateOverviewViewport(
)
}

public func interpolateOverviewProjection(
from: OverviewProjection,
to: OverviewProjection,
progress: Double,
foregroundWindowID: WindowID? = nil
) -> OverviewProjection {
guard from.monitorID == to.monitorID else { return to }
let progress = min(max(progress, 0), 1)
let sourceWorkspaces = Dictionary(
uniqueKeysWithValues: from.workspaces.map { ($0.workspaceID, $0) }
)
let sourceWindows = Dictionary(
uniqueKeysWithValues: from.workspaces.flatMap(\.windows).map { ($0.windowID, $0) }
)
let sourceWorkspaceID = from.workspaces.first(where: { workspace in
workspace.windows.contains(where: { $0.windowID == foregroundWindowID })
})?.workspaceID
let targetWorkspaceID = to.workspaces.first(where: { workspace in
workspace.windows.contains(where: { $0.windowID == foregroundWindowID })
})?.workspaceID
let overlayWindowID = sourceWorkspaceID != targetWorkspaceID
? foregroundWindowID
: nil
return OverviewProjection(
monitorID: to.monitorID,
workspaces: to.workspaces.map { targetWorkspace in
guard let sourceWorkspace = sourceWorkspaces[targetWorkspace.workspaceID]
else { return targetWorkspace }
var windows = targetWorkspace.windows.map { targetWindow in
guard let sourceWindow = sourceWindows[targetWindow.windowID]
else { return targetWindow }
return OverviewWindowProjection(
windowID: targetWindow.windowID,
frame: interpolateOverviewRect(
from: sourceWindow.frame,
to: targetWindow.frame,
progress: progress
),
layer: targetWindow.layer,
isNativeFullscreen: targetWindow.isNativeFullscreen,
canDrag: targetWindow.canDrag
)
}
if let foregroundWindowID,
let index = windows.firstIndex(where: { $0.windowID == foregroundWindowID })
{
windows.append(windows.remove(at: index))
}
return OverviewWorkspaceProjection(
workspaceID: targetWorkspace.workspaceID,
frame: interpolateOverviewRect(
from: sourceWorkspace.frame,
to: targetWorkspace.frame,
progress: progress
),
windows: windows,
hiddenTiledWindowCountBefore: targetWorkspace.hiddenTiledWindowCountBefore,
hiddenTiledWindowCountAfter: targetWorkspace.hiddenTiledWindowCountAfter
)
},
overlayWindowID: overlayWindowID
)
}

private func interpolateOverviewRect(
from: Rect,
to: Rect,
progress: Double
) -> Rect {
Rect(
x: from.x + (to.x - from.x) * progress,
y: from.y + (to.y - from.y) * progress,
width: from.width + (to.width - from.width) * progress,
height: from.height + (to.height - from.height) * progress
)
}

public struct OverviewProjection: Equatable, Sendable {
public let monitorID: MonitorID
public let workspaces: [OverviewWorkspaceProjection]
public let overlayWindowID: WindowID?

public init(
monitorID: MonitorID,
workspaces: [OverviewWorkspaceProjection]
workspaces: [OverviewWorkspaceProjection],
overlayWindowID: WindowID? = nil
) {
self.monitorID = monitorID
self.workspaces = workspaces
self.overlayWindowID = overlayWindowID
}

public func hitTest(_ point: OverviewPoint) -> OverviewHit? {
if let overlayWindowID,
let workspace = workspaces.first(where: { workspace in
workspace.windows.contains(where: { $0.windowID == overlayWindowID })
}),
let window = workspace.windows.first(where: {
$0.windowID == overlayWindowID && $0.frame.contains(point)
})
{
return .window(
windowID: window.windowID,
monitorID: monitorID,
workspaceID: workspace.workspaceID
)
}
for workspace in workspaces.reversed() {
for window in workspace.windows.reversed()
where window.frame.contains(point) && workspace.frame.contains(point) {
Expand Down Expand Up @@ -207,6 +301,12 @@ public func projectOverview(
)
let workspaceHeight = stride - workspaceGap
let contentScale = workspaceHeight / monitorFrame.height
let projectionBounds = Rect(
x: bounds.x,
y: bounds.y - stride,
width: bounds.width,
height: bounds.height + stride * 2
)
let windows = Array(snapshot.windows.values)
var projected: [OverviewWorkspaceProjection] = []

Expand All @@ -219,7 +319,7 @@ public func projectOverview(
width: workspaceWidth,
height: workspaceHeight
)
guard workspaceFrame.intersects(bounds) else { continue }
guard workspaceFrame.intersects(projectionBounds) else { continue }

var workspace = originalWorkspace
workspace.scrollOffset = viewport.horizontalOffsets[workspace.id]
Expand Down
4 changes: 3 additions & 1 deletion Sources/DefiDaemon/DaemonDesktop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ extension Daemon {
var hiddenWindowIDs = Set<WindowID>()
var outOfScopeWindowIDs = Set<WindowID>()
let allPhysicalMonitorFrames = latestMonitors.map(\.physicalFrame)
let overviewParksWindows = overviewController?.isOpen == true
&& overviewController?.usesWorkspaceParking == true
let liveMonitorIDs = Set(state.monitors.map(\.id))
layoutPlansByMonitor = layoutPlansByMonitor.filter {
liveMonitorIDs.contains($0.key)
Expand Down Expand Up @@ -118,7 +120,7 @@ extension Daemon {
excludingWindowIDs: state.nativeFullscreenWindowIDs
)
let sizedFrames = layout.frames.map(preserveIntrinsicSize)
if workspace.id == monitor.activeWorkspace {
if workspace.id == monitor.activeWorkspace && !overviewParksWindows {
let strip = continuousStripFramesForActiveWorkspace(
sizedFrames,
viewport: viewport,
Expand Down
19 changes: 18 additions & 1 deletion Sources/DefiDaemon/DaemonDesktopSynchronization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import DefiModel
import DefiRuntime
import Foundation

func shouldCloseOverviewAfterNativeFocusChange(
nativeFocusChanged: Bool,
overviewOpenedAt: TimeInterval?,
mouseFocusIntentTimestamp: TimeInterval?,
keyboardFocusIntentTimestamp: TimeInterval?
) -> Bool {
guard nativeFocusChanged else { return false }
guard let overviewOpenedAt else { return true }
return max(mouseFocusIntentTimestamp ?? 0, keyboardFocusIntentTimestamp ?? 0)
> overviewOpenedAt
}

@MainActor
extension Daemon {
func synchronizeDesktop(
Expand Down Expand Up @@ -64,7 +76,12 @@ extension Daemon {
}
}
let snapshotCompletedAt = ProcessInfo.processInfo.systemUptime
if snapshot.nativeFocusChanged {
if shouldCloseOverviewAfterNativeFocusChange(
nativeFocusChanged: snapshot.nativeFocusChanged,
overviewOpenedAt: overviewOpenedAt,
mouseFocusIntentTimestamp: snapshot.mouseFocusIntentTimestamp,
keyboardFocusIntentTimestamp: snapshot.keyboardFocusIntentTimestamp
) {
overviewController?.close()
}
nextPeriodicWindowRefreshAt = boundedSnapshotRefreshDeadline(
Expand Down
18 changes: 16 additions & 2 deletions Sources/DefiDaemon/DaemonOverview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,22 @@ extension Daemon {
self?.activeMonitorID = monitorID
},
openStateChanged: { [weak self] isOpen in
self?.hotKeys?.setOverviewModeEnabled(isOpen)
self?.platform.setWindowBordersSuppressed(isOpen)
guard let self else { return }
let parksWindows = overviewController?.usesWorkspaceParking == true
overviewOpenedAt = isOpen && parksWindows
? ProcessInfo.processInfo.systemUptime
: nil
hotKeys?.setOverviewModeEnabled(isOpen)
platform.setWindowBordersSuppressed(isOpen)
if parksWindows {
applyCurrentLayout(
asynchronousPositions: true,
updateVisibility: true,
positionTimeoutSeconds: 0.05,
stagesVisibleBeforeParking: !isOpen,
source: isOpen ? "overview-park" : "overview-restore"
)
}
}
)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/DefiDaemon/DefiDaemon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ final class Daemon: NSObject {
var placementSaveWorkItem: DispatchWorkItem?
var hotKeys: HotKeyManager?
var overviewController: OverviewController?
var overviewOpenedAt: TimeInterval?
var menuBar: MenuBarController?
var lastPublishedWorkspaceState: WorkspaceStateSnapshot?
var lastWorkspacePublishState: RuntimeState?
Expand Down
19 changes: 14 additions & 5 deletions Sources/DefiMacOS/HotKeyModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,29 @@ public enum OverviewKeyAction: Equatable, Sendable {
case right
case up
case down
case moveUp
case moveDown
case select
case cancel
}

func overviewKeyAction(
keyCode: CGKeyCode,
modifierBits: UInt64,
isConfiguredBinding: Bool = false
configuredCommand: String? = nil
) -> OverviewKeyAction? {
let commandName = configuredCommand?.split(whereSeparator: \.isWhitespace).first
let navigatesOverview =
modifierBits == 0
|| commandName == "focus-column"
|| commandName == "focus-window"
return switch keyCode {
case 123 where modifierBits == 0 || isConfiguredBinding: .left
case 124 where modifierBits == 0 || isConfiguredBinding: .right
case 125 where modifierBits == 0 || isConfiguredBinding: .down
case 126 where modifierBits == 0 || isConfiguredBinding: .up
case 125 where commandName == "move-window": .moveDown
case 126 where commandName == "move-window": .moveUp
case 123 where navigatesOverview: .left
case 124 where navigatesOverview: .right
case 125 where navigatesOverview: .down
case 126 where navigatesOverview: .up
case 36 where modifierBits == 0: .select
case 76 where modifierBits == 0: .select
case 53 where modifierBits == 0: .cancel
Expand Down
2 changes: 1 addition & 1 deletion Sources/DefiMacOS/HotKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ private final class HotKeyTapContext: @unchecked Sendable {
let action = overviewKeyAction(
keyCode: code,
modifierBits: key.modifierBits,
isConfiguredBinding: bindings[key] != nil
configuredCommand: bindings[key]
)
{
lock.lock()
Expand Down
Loading