diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json index c73e880cf703..a044049e258e 100644 --- a/apps/.i18n/native-source.json +++ b/apps/.i18n/native-source.json @@ -32171,7 +32171,7 @@ }, { "kind": "conditional-branch", - "line": 898, + "line": 888, "path": "apps/macos/Sources/OpenClaw/DashboardWindowController.swift", "source": "[\\(host)]", "surface": "apple", diff --git a/apps/macos/Sources/OpenClaw/DashboardWindowController.swift b/apps/macos/Sources/OpenClaw/DashboardWindowController.swift index fcbb0b8a7348..fd1572cff761 100644 --- a/apps/macos/Sources/OpenClaw/DashboardWindowController.swift +++ b/apps/macos/Sources/OpenClaw/DashboardWindowController.swift @@ -24,16 +24,6 @@ private final class DashboardWindow: NSWindow { } } -private final class DashboardWindowDragRegionView: NSView { - override var mouseDownCanMoveWindow: Bool { - true - } - - override func mouseDown(with event: NSEvent) { - window?.performDrag(with: event) - } -} - private final class DashboardLinkSplitView: NSSplitView { var onDividerDragEnded: (() -> Void)? @@ -597,7 +587,7 @@ final class DashboardWindowController: NSWindowController, WKNavigationDelegate, else { return } - window.performDrag(with: event) + DashboardWindowDragGesture.handle(event, in: window) } static func isWindowDragRequest(_ body: Any) -> Bool { diff --git a/apps/macos/Sources/OpenClaw/DashboardWindowDragRegionView.swift b/apps/macos/Sources/OpenClaw/DashboardWindowDragRegionView.swift new file mode 100644 index 000000000000..25d96e3b1e7d --- /dev/null +++ b/apps/macos/Sources/OpenClaw/DashboardWindowDragRegionView.swift @@ -0,0 +1,25 @@ +import AppKit + +enum DashboardWindowDragGesture { + @MainActor + static func handle(_ event: NSEvent, in window: NSWindow) { + // These custom surfaces replace AppKit's titlebar hit region. Preserve + // its maximize gesture instead of consuming the second click as a drag. + if event.type == .leftMouseDown, event.clickCount == 2 { + window.performZoom(nil) + return + } + window.performDrag(with: event) + } +} + +final class DashboardWindowDragRegionView: NSView { + override var mouseDownCanMoveWindow: Bool { + true + } + + override func mouseDown(with event: NSEvent) { + guard let window else { return } + DashboardWindowDragGesture.handle(event, in: window) + } +} diff --git a/apps/macos/Tests/OpenClawIPCTests/DashboardWindowSmokeTests.swift b/apps/macos/Tests/OpenClawIPCTests/DashboardWindowSmokeTests.swift index ed124271033c..c345db3c6567 100644 --- a/apps/macos/Tests/OpenClawIPCTests/DashboardWindowSmokeTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/DashboardWindowSmokeTests.swift @@ -41,9 +41,55 @@ private final class DashboardBrowserImportGate { } } +private final class DashboardWindowGestureSpy: NSWindow { + private(set) var dragCount = 0 + private(set) var zoomCount = 0 + + override func performDrag(with _: NSEvent) { + self.dragCount += 1 + } + + override func performZoom(_: Any?) { + self.zoomCount += 1 + } +} + @Suite(.serialized) @MainActor struct DashboardWindowSmokeTests { + @Test func `dashboard frame routes single click to drag and double click to zoom`() throws { + let window = DashboardWindowGestureSpy( + contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), + styleMask: [.titled, .resizable], + backing: .buffered, + defer: false) + let dragRegion = DashboardWindowDragRegionView( + frame: NSRect(x: 0, y: 0, width: 300, height: 12)) + window.contentView = dragRegion + let mouseDownEvent: (Int) -> NSEvent? = { clickCount in + NSEvent.mouseEvent( + with: .leftMouseDown, + location: NSPoint(x: 100, y: 6), + modifierFlags: [], + timestamp: 0, + windowNumber: window.windowNumber, + context: nil, + eventNumber: clickCount, + clickCount: clickCount, + pressure: 1) + } + + dragRegion.mouseDown(with: try #require(mouseDownEvent(1))) + + #expect(window.dragCount == 1) + #expect(window.zoomCount == 0) + + dragRegion.mouseDown(with: try #require(mouseDownEvent(2))) + + #expect(window.dragCount == 1) + #expect(window.zoomCount == 1) + } + @Test func `dashboard window controller shows and closes`() throws { let url = try #require(URL(string: "http://127.0.0.1:18789/control/#token=device-token")) let controller = DashboardWindowController(