Files
OpenStats/Packages/OpenStatsKit/Sources/OpenStatsUI/Design/PanelContainer.swift
T

79 lines
3.0 KiB
Swift

import AppKit
import SwiftUI
/// 弹窗窗口的底板:圆角裁切的普通容器,背景由 SwiftUI 内容绘制
@MainActor
enum PanelContainer {
/// 容器必须从内容的尺寸开始:若从 0 开始,窗口把它撑满时自动调整会把内容再放大一倍
static func make(containing content: NSView, cornerRadius: CGFloat) -> NSView {
let container = NSView(frame: content.frame)
container.wantsLayer = true
container.layer?.cornerRadius = cornerRadius
container.layer?.cornerCurve = .continuous
container.layer?.masksToBounds = true
content.autoresizingMask = [.width, .height]
container.addSubview(content)
return container
}
}
/// 内容铺满标题栏后,标题栏区域由 SwiftUI 接收点击;放在顶栏背后,按住空白处可以拖动窗口
struct WindowDragArea: NSViewRepresentable {
final class DragView: NSView {
override func mouseDown(with event: NSEvent) {
if event.clickCount == 2 {
window?.performZoom(nil)
} else {
window?.performDrag(with: event)
}
}
}
func makeNSView(context: Context) -> DragView { DragView() }
func updateNSView(_ nsView: DragView, context: Context) {}
}
/// 滚动条只在滚动时出现:把所在的 NSScrollView 固定为浮层样式。
/// 系统设置为“始终显示滚动条”或接了鼠标时,SwiftUI 默认会一直显示一条占位的滚动条
struct OverlayScrollers: NSViewRepresentable {
final class Probe: NSView {
private var observer: NSObjectProtocol?
override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
guard window != nil else {
if let observer { NotificationCenter.default.removeObserver(observer) }
observer = nil
return
}
apply()
guard observer == nil else { return }
// 系统切换滚动条样式时会重置,收到通知后重新设置
observer = NotificationCenter.default.addObserver(forName: NSScroller.preferredScrollerStyleDidChangeNotification,
object: nil, queue: .main) { [weak self] _ in
MainActor.assumeIsolated { self?.apply() }
}
}
func apply() {
DispatchQueue.main.async { [weak self] in
MainActor.assumeIsolated {
guard let scrollView = self?.enclosingScrollView else { return }
scrollView.scrollerStyle = .overlay
scrollView.autohidesScrollers = true
}
}
}
}
func makeNSView(context: Context) -> Probe { Probe() }
func updateNSView(_ nsView: Probe, context: Context) { nsView.apply() }
}
extension View {
/// 放在 ScrollView 的内容里:滚动条改为浮层,只在滚动时显示
func overlayScrollers() -> some View {
background(OverlayScrollers().frame(width: 0, height: 0))
}
}