mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-24 03:18:38 +00:00
refactor(macos): dedupe UI, pairing, and runtime helpers
This commit is contained in:
126
apps/macos/Sources/OpenClaw/OverlayPanelFactory.swift
Normal file
126
apps/macos/Sources/OpenClaw/OverlayPanelFactory.swift
Normal file
@@ -0,0 +1,126 @@
|
||||
import AppKit
|
||||
import QuartzCore
|
||||
|
||||
enum OverlayPanelFactory {
|
||||
@MainActor
|
||||
static func makePanel(
|
||||
contentRect: NSRect,
|
||||
level: NSWindow.Level,
|
||||
hasShadow: Bool,
|
||||
acceptsMouseMovedEvents: Bool = false) -> NSPanel
|
||||
{
|
||||
let panel = NSPanel(
|
||||
contentRect: contentRect,
|
||||
styleMask: [.nonactivatingPanel, .borderless],
|
||||
backing: .buffered,
|
||||
defer: false)
|
||||
panel.isOpaque = false
|
||||
panel.backgroundColor = .clear
|
||||
panel.hasShadow = hasShadow
|
||||
panel.level = level
|
||||
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .transient]
|
||||
panel.hidesOnDeactivate = false
|
||||
panel.isMovable = false
|
||||
panel.isFloatingPanel = true
|
||||
panel.becomesKeyOnlyIfNeeded = true
|
||||
panel.titleVisibility = .hidden
|
||||
panel.titlebarAppearsTransparent = true
|
||||
panel.acceptsMouseMovedEvents = acceptsMouseMovedEvents
|
||||
return panel
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func animatePresent(window: NSWindow, from start: NSRect, to target: NSRect, duration: TimeInterval = 0.18) {
|
||||
window.setFrame(start, display: true)
|
||||
window.alphaValue = 0
|
||||
window.orderFrontRegardless()
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = duration
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||
window.animator().setFrame(target, display: true)
|
||||
window.animator().alphaValue = 1
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func animateFrame(window: NSWindow, to frame: NSRect, duration: TimeInterval = 0.12) {
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = duration
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||
window.animator().setFrame(frame, display: true)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func applyFrame(window: NSWindow?, target: NSRect, animate: Bool) {
|
||||
guard let window else { return }
|
||||
if animate {
|
||||
self.animateFrame(window: window, to: target)
|
||||
} else {
|
||||
window.setFrame(target, display: true)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func present(
|
||||
window: NSWindow?,
|
||||
isVisible: inout Bool,
|
||||
target: NSRect,
|
||||
startOffsetY: CGFloat = -6,
|
||||
onFirstPresent: (() -> Void)? = nil,
|
||||
onAlreadyVisible: (NSWindow) -> Void)
|
||||
{
|
||||
guard let window else { return }
|
||||
if !isVisible {
|
||||
isVisible = true
|
||||
onFirstPresent?()
|
||||
let start = target.offsetBy(dx: 0, dy: startOffsetY)
|
||||
self.animatePresent(window: window, from: start, to: target)
|
||||
} else {
|
||||
onAlreadyVisible(window)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func animateDismiss(
|
||||
window: NSWindow,
|
||||
offsetX: CGFloat = 6,
|
||||
offsetY: CGFloat = 6,
|
||||
duration: TimeInterval = 0.16,
|
||||
completion: @escaping () -> Void)
|
||||
{
|
||||
let target = window.frame.offsetBy(dx: offsetX, dy: offsetY)
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = duration
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||
window.animator().setFrame(target, display: true)
|
||||
window.animator().alphaValue = 0
|
||||
} completionHandler: {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func animateDismissAndHide(
|
||||
window: NSWindow,
|
||||
offsetX: CGFloat = 6,
|
||||
offsetY: CGFloat = 6,
|
||||
duration: TimeInterval = 0.16,
|
||||
onHidden: @escaping @MainActor () -> Void)
|
||||
{
|
||||
self.animateDismiss(window: window, offsetX: offsetX, offsetY: offsetY, duration: duration) {
|
||||
Task { @MainActor in
|
||||
window.orderOut(nil)
|
||||
onHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func clearGlobalEventMonitor(_ monitor: inout Any?) {
|
||||
if let current = monitor {
|
||||
NSEvent.removeMonitor(current)
|
||||
monitor = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user