Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
236 lines
9.5 KiB
Swift
236 lines
9.5 KiB
Swift
import AppKit
|
|
import QuartzCore
|
|
|
|
final class HoverChromeContainerView: NSView {
|
|
private let content: NSView
|
|
private let chrome: CanvasChromeOverlayView
|
|
private var tracking: NSTrackingArea?
|
|
var onClose: (() -> Void)?
|
|
|
|
init(containing content: NSView) {
|
|
self.content = content
|
|
self.chrome = CanvasChromeOverlayView(frame: .zero)
|
|
super.init(frame: .zero)
|
|
|
|
self.wantsLayer = true
|
|
self.layer?.cornerRadius = 12
|
|
self.layer?.masksToBounds = true
|
|
self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
|
|
|
|
self.content.translatesAutoresizingMaskIntoConstraints = false
|
|
self.addSubview(self.content)
|
|
|
|
self.chrome.translatesAutoresizingMaskIntoConstraints = false
|
|
self.chrome.alphaValue = 0
|
|
self.chrome.onClose = { [weak self] in self?.onClose?() }
|
|
self.addSubview(self.chrome)
|
|
|
|
NSLayoutConstraint.activate([
|
|
self.content.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
|
self.content.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
|
self.content.topAnchor.constraint(equalTo: self.topAnchor),
|
|
self.content.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
|
|
|
self.chrome.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
|
self.chrome.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
|
self.chrome.topAnchor.constraint(equalTo: self.topAnchor),
|
|
self.chrome.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
|
])
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) is not supported")
|
|
}
|
|
|
|
override func updateTrackingAreas() {
|
|
super.updateTrackingAreas()
|
|
if let tracking {
|
|
self.removeTrackingArea(tracking)
|
|
}
|
|
let area = NSTrackingArea(
|
|
rect: self.bounds,
|
|
options: [.activeAlways, .mouseEnteredAndExited, .inVisibleRect],
|
|
owner: self,
|
|
userInfo: nil)
|
|
self.addTrackingArea(area)
|
|
self.tracking = area
|
|
}
|
|
|
|
private final class CanvasDragHandleView: NSView {
|
|
override func mouseDown(with event: NSEvent) {
|
|
self.window?.performDrag(with: event)
|
|
}
|
|
|
|
override func acceptsFirstMouse(for _: NSEvent?) -> Bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
private final class CanvasResizeHandleView: NSView {
|
|
private var startPoint: NSPoint = .zero
|
|
private var startFrame: NSRect = .zero
|
|
|
|
override func acceptsFirstMouse(for _: NSEvent?) -> Bool {
|
|
true
|
|
}
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
guard let window else { return }
|
|
_ = window.makeFirstResponder(self)
|
|
self.startPoint = NSEvent.mouseLocation
|
|
self.startFrame = window.frame
|
|
super.mouseDown(with: event)
|
|
}
|
|
|
|
override func mouseDragged(with _: NSEvent) {
|
|
guard let window else { return }
|
|
let current = NSEvent.mouseLocation
|
|
let dx = current.x - self.startPoint.x
|
|
let dy = current.y - self.startPoint.y
|
|
|
|
var frame = self.startFrame
|
|
frame.size.width = max(CanvasLayout.minPanelSize.width, frame.size.width + dx)
|
|
frame.origin.y += dy
|
|
frame.size.height = max(CanvasLayout.minPanelSize.height, frame.size.height - dy)
|
|
|
|
if let screen = window.screen {
|
|
frame = CanvasWindowController.constrainFrame(frame, toVisibleFrame: screen.visibleFrame)
|
|
}
|
|
window.setFrame(frame, display: true)
|
|
}
|
|
}
|
|
|
|
private final class CanvasChromeOverlayView: NSView {
|
|
var onClose: (() -> Void)?
|
|
|
|
private let dragHandle = CanvasDragHandleView(frame: .zero)
|
|
private let resizeHandle = CanvasResizeHandleView(frame: .zero)
|
|
|
|
private final class PassthroughVisualEffectView: NSVisualEffectView {
|
|
override func hitTest(_: NSPoint) -> NSView? {
|
|
nil
|
|
}
|
|
}
|
|
|
|
private let closeBackground: NSVisualEffectView = {
|
|
let v = PassthroughVisualEffectView(frame: .zero)
|
|
v.material = .hudWindow
|
|
v.blendingMode = .withinWindow
|
|
v.state = .active
|
|
v.appearance = NSAppearance(named: .vibrantDark)
|
|
v.wantsLayer = true
|
|
v.layer?.cornerRadius = 10
|
|
v.layer?.masksToBounds = true
|
|
v.layer?.borderWidth = 1
|
|
v.layer?.borderColor = NSColor.white.withAlphaComponent(0.22).cgColor
|
|
v.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.28).cgColor
|
|
v.layer?.shadowColor = NSColor.black.withAlphaComponent(0.35).cgColor
|
|
v.layer?.shadowOpacity = 0.35
|
|
v.layer?.shadowRadius = 8
|
|
v.layer?.shadowOffset = .zero
|
|
return v
|
|
}()
|
|
|
|
private let closeButton: NSButton = {
|
|
let cfg = NSImage.SymbolConfiguration(pointSize: 8, weight: .semibold)
|
|
let img = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Close")?
|
|
.withSymbolConfiguration(cfg)
|
|
?? NSImage(size: NSSize(width: 18, height: 18))
|
|
let btn = NSButton(image: img, target: nil, action: nil)
|
|
btn.isBordered = false
|
|
btn.bezelStyle = .regularSquare
|
|
btn.imageScaling = .scaleProportionallyDown
|
|
btn.contentTintColor = NSColor.white.withAlphaComponent(0.92)
|
|
btn.toolTip = "Close"
|
|
return btn
|
|
}()
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
super.init(frame: frameRect)
|
|
|
|
self.wantsLayer = true
|
|
self.layer?.cornerRadius = 12
|
|
self.layer?.masksToBounds = true
|
|
self.layer?.borderWidth = 1
|
|
self.layer?.borderColor = NSColor.black.withAlphaComponent(0.18).cgColor
|
|
self.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.02).cgColor
|
|
|
|
self.dragHandle.translatesAutoresizingMaskIntoConstraints = false
|
|
self.dragHandle.wantsLayer = true
|
|
self.dragHandle.layer?.backgroundColor = NSColor.clear.cgColor
|
|
self.addSubview(self.dragHandle)
|
|
|
|
self.resizeHandle.translatesAutoresizingMaskIntoConstraints = false
|
|
self.resizeHandle.wantsLayer = true
|
|
self.resizeHandle.layer?.backgroundColor = NSColor.clear.cgColor
|
|
self.addSubview(self.resizeHandle)
|
|
|
|
self.closeBackground.translatesAutoresizingMaskIntoConstraints = false
|
|
self.addSubview(self.closeBackground)
|
|
|
|
self.closeButton.translatesAutoresizingMaskIntoConstraints = false
|
|
self.closeButton.target = self
|
|
self.closeButton.action = #selector(self.handleClose)
|
|
self.addSubview(self.closeButton)
|
|
|
|
NSLayoutConstraint.activate([
|
|
self.dragHandle.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
|
self.dragHandle.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
|
self.dragHandle.topAnchor.constraint(equalTo: self.topAnchor),
|
|
self.dragHandle.heightAnchor.constraint(equalToConstant: 30),
|
|
|
|
self.closeBackground.centerXAnchor.constraint(equalTo: self.closeButton.centerXAnchor),
|
|
self.closeBackground.centerYAnchor.constraint(equalTo: self.closeButton.centerYAnchor),
|
|
self.closeBackground.widthAnchor.constraint(equalToConstant: 20),
|
|
self.closeBackground.heightAnchor.constraint(equalToConstant: 20),
|
|
|
|
self.closeButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8),
|
|
self.closeButton.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
|
|
self.closeButton.widthAnchor.constraint(equalToConstant: 16),
|
|
self.closeButton.heightAnchor.constraint(equalToConstant: 16),
|
|
|
|
self.resizeHandle.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
|
self.resizeHandle.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
|
self.resizeHandle.widthAnchor.constraint(equalToConstant: 18),
|
|
self.resizeHandle.heightAnchor.constraint(equalToConstant: 18),
|
|
])
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) is not supported")
|
|
}
|
|
|
|
override func hitTest(_ point: NSPoint) -> NSView? {
|
|
// When the chrome is hidden, do not intercept any mouse events (let the WKWebView receive them).
|
|
guard self.alphaValue > 0.02 else { return nil }
|
|
|
|
if self.closeButton.frame.contains(point) { return self.closeButton }
|
|
if self.dragHandle.frame.contains(point) { return self.dragHandle }
|
|
if self.resizeHandle.frame.contains(point) { return self.resizeHandle }
|
|
return nil
|
|
}
|
|
|
|
@objc private func handleClose() {
|
|
self.onClose?()
|
|
}
|
|
}
|
|
|
|
override func mouseEntered(with _: NSEvent) {
|
|
NSAnimationContext.runAnimationGroup { ctx in
|
|
ctx.duration = 0.12
|
|
ctx.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
|
self.chrome.animator().alphaValue = 1
|
|
}
|
|
}
|
|
|
|
override func mouseExited(with _: NSEvent) {
|
|
NSAnimationContext.runAnimationGroup { ctx in
|
|
ctx.duration = 0.16
|
|
ctx.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
|
self.chrome.animator().alphaValue = 0
|
|
}
|
|
}
|
|
}
|