macos: open dragged windows where they are dropped

This commit is contained in:
Mitchell Hashimoto
2025-12-29 10:19:51 -08:00
parent 5ecd26727e
commit 29edbbbc86
3 changed files with 27 additions and 5 deletions

View File

@@ -734,6 +734,9 @@ class BaseTerminalController: NSWindowController,
// it is already a single split.
guard surfaceTree.isSplit else { return }
// Extract the drop position from the notification
let dropPoint = notification.userInfo?[Notification.Name.ghosttySurfaceDragEndedNoTargetPointKey] as? NSPoint
// Remove the surface from our tree
let removedTree = surfaceTree.remove(targetNode)
@@ -748,7 +751,7 @@ class BaseTerminalController: NSWindowController,
}
replaceSurfaceTree(removedTree, moveFocusFrom: focusedSurface)
_ = TerminalController.newWindow(ghostty, tree: newTree)
_ = TerminalController.newWindow(ghostty, tree: newTree, position: dropPoint)
}
// MARK: Local Events

View File

@@ -277,9 +277,15 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
/// Create a new window with an existing split tree.
/// The window will be sized to match the tree's current view bounds if available.
/// - Parameters:
/// - ghostty: The Ghostty app instance.
/// - tree: The split tree to use for the new window.
/// - position: Optional screen position (top-left corner) for the new window.
/// If nil, the window will cascade from the last cascade point.
static func newWindow(
_ ghostty: Ghostty.App,
tree: SplitTree<Ghostty.SurfaceView>
tree: SplitTree<Ghostty.SurfaceView>,
position: NSPoint? = nil
) -> TerminalController {
let c = TerminalController.init(ghostty, withSurfaceTree: tree)
@@ -295,7 +301,12 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
}
if !window.styleMask.contains(.fullScreen) {
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
if let position {
window.setFrameTopLeftPoint(position)
window.constrainToScreen()
} else {
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
}
}
}

View File

@@ -177,7 +177,11 @@ extension Ghostty {
}
onDragStateChanged?(true)
beginDraggingSession(with: [item], event: event, source: self)
let session = beginDraggingSession(with: [item], event: event, source: self)
// We need to disable this so that endedAt happens immediately for our
// drags outside of any targets.
session.animatesToStartingPositionsOnCancelOrFail = false
}
// MARK: NSDraggingSource
@@ -229,7 +233,8 @@ extension Ghostty {
if !endsInWindow {
NotificationCenter.default.post(
name: .ghosttySurfaceDragEndedNoTarget,
object: surfaceView
object: surfaceView,
userInfo: [Foundation.Notification.Name.ghosttySurfaceDragEndedNoTargetPointKey: screenPoint]
)
}
}
@@ -245,4 +250,7 @@ extension Notification.Name {
/// released outside a valid drop target) and was not cancelled by the user
/// pressing escape. The notification's object is the SurfaceView that was dragged.
static let ghosttySurfaceDragEndedNoTarget = Notification.Name("ghosttySurfaceDragEndedNoTarget")
/// Key for the screen point where the drag ended in the userInfo dictionary.
static let ghosttySurfaceDragEndedNoTargetPointKey = "endedAtPoint"
}