diff --git a/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift b/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift index b437a6b85..8254cb5d6 100644 --- a/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift +++ b/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift @@ -10,7 +10,7 @@ extension WorkspaceSwipePresentation { controller.motionPolicy.animationsEnabled, controller.settings.gestures.workspaceSwipeEnabled, !controller.isOverviewOpen(), let monitor = controller.monitorForInteraction(), - let preparation = makePreparation(monitorId: monitor.id) + let preparation = makePreparation(monitorId: monitor.id, liveFrames: false) else { return } previewSurface(controller).warm( source: preparation.source.items, @@ -32,10 +32,10 @@ extension WorkspaceSwipePresentation { } } - func makePreparation(monitorId: Monitor.ID) -> Preparation? { + func makePreparation(monitorId: Monitor.ID, liveFrames: Bool = true) -> Preparation? { guard let controller, let monitor = controller.workspaceManager.monitor(byId: monitorId), let current = controller.workspaceManager.activeWorkspaceOrFirst(on: monitorId), - let source = makeWorkspace(current.id, monitor: monitor, active: true) + let source = makeWorkspace(current.id, monitor: monitor, active: true, liveFrames: liveFrames) else { return nil } let navigation = controller.workspaceNavigationHandler let previous = navigation.swipeTargetWorkspace( @@ -53,35 +53,40 @@ extension WorkspaceSwipePresentation { ) } - func makeWorkspace(_ id: WorkspaceDescriptor.ID, monitor: Monitor, active: Bool) -> Workspace? { + func makeWorkspace( + _ id: WorkspaceDescriptor.ID, monitor: Monitor, active: Bool, liveFrames: Bool = true + ) -> Workspace? { guard let controller, let refreshController else { return nil } let entries = controller.workspaceManager.entries(in: id).filter { !controller.workspaceManager.isWindowSuppressedByMacOS($0.token) } guard entries.allSatisfy({ $0.layoutReason == .standard }) else { return nil } - let frames: [WindowToken: CGRect] = controller.workspaceManager.withEngineMutationScope { - if controller.workspaceManager.activeLayoutKind(for: id) == .niri { - return refreshController.niriHandler.settledFrames(in: id, visibleOnly: true) ?? [:] - } - guard let engine = controller.dwindleEngine, + let frames: [WindowToken: CGRect] + if controller.workspaceManager.activeLayoutKind(for: id) == .niri { + frames = refreshController.niriHandler.settledFrames(in: id, visibleOnly: true) ?? [:] + } else if let engine = controller.dwindleEngine, let snapshot = refreshController.dwindleHandler.makeWorkspaceSnapshot( workspaceId: id, monitor: monitor, resolveConstraints: false, isActiveWorkspace: active - ) else { return [:] } + ) + { let plan = refreshController.dwindleHandler.buildOnDemandLayoutPlan(snapshot: snapshot, engine: engine) - return Dictionary( + frames = Dictionary( plan.diff.frameChanges.map { ($0.token, $0.frame) }, uniquingKeysWith: { _, last in last } ) + } else { + frames = [:] } + let live = active && liveFrames var items: [WorkspaceSwipePreview.Item] = [] for entry in entries { guard let handle = controller.workspaceManager.handle(for: entry.token) else { return nil } let frame: CGRect? if entry.mode == .floating { - frame = active ? controller.liveFrame(for: entry) + frame = live ? controller.liveFrame(for: entry) : controller.workspaceManager.resolvedFloatingFrame(for: entry.token, preferredMonitor: monitor) } else if let projected = frames[entry.token] { - frame = active ? (controller.liveFrame(for: entry) ?? projected) : projected + frame = live ? (controller.liveFrame(for: entry) ?? projected) : projected } else { continue } diff --git a/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift b/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift index 5ad13ee02..7b5d25b37 100644 --- a/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift +++ b/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift @@ -484,6 +484,27 @@ final class WorkspaceSwipePresentationTests: XCTestCase { XCTAssertFalse(fixture.swipe.hasPresentation) } + func testWarmingPreviewsUsesSettledFramesWithoutCommittingWorldEvents() async throws { + let fixture = try previewFixture() + fixture.controller.hasStartedServices = true + fixture.controller.settings.gestures.workspaceSwipeEnabled = true + let liveWidth = try XCTUnwrap( + fixture.swipe.makePreparation(monitorId: fixture.monitor.id)?.source.items.first?.frame.width + ) + let settledWidth = try XCTUnwrap( + fixture.swipe.makePreparation(monitorId: fixture.monitor.id, liveFrames: false)?.source.items.first? + .frame.width + ) + XCTAssertEqual(liveWidth, Self.sourceFrame.width) + XCTAssertNotEqual(settledWidth, liveWidth) + + let seq = fixture.controller.workspaceManager.worldSeq + fixture.swipe.warmPreviews() + XCTAssertEqual(fixture.controller.workspaceManager.worldSeq, seq) + try await fixture.driver.waitForStarts(1) + XCTAssertEqual(fixture.driver.streams[0].request.pixelWidth, Int(ceil(settledWidth))) + } + func testTurningSwipeOrAnimationsOffReleasesThePreviewAndItsCache() async throws { let disablers: [@MainActor (WMController) -> Void] = [ { $0.settings.gestures.workspaceSwipeEnabled = false },