mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 10:13:54 +00:00
- window.title tracks the destination via composition-root wiring into WorkspaceWindowOpeningAdapter.bindTitle; drop the static "ShadowsocksX-NG 2.0" title - delete the 28pt shell destinationHeader; subscription and diagnostics actions move into the unified toolbar - settings save/reset stay with the form as a top action row in SettingsView; form-level commit actions read wrong in a toolbar
68 lines
3.0 KiB
Swift
68 lines
3.0 KiB
Swift
import AppKit
|
||
import Combine
|
||
import SwiftUI
|
||
|
||
/// 组合根持有的 workspace 开窗器:主窗口是 AppKit 拥有的 NSWindow,内容为
|
||
/// SwiftUI 主 workspace(NSHostingView)。此前经 SwiftUI `Window` scene +
|
||
/// `openWindow` 开窗,但该环境值在 MenuBarExtra 上下文(无论标签还是内容)
|
||
/// 实测不产生窗口,SwiftUI 对 LSUIElement 应用也不做场景启动呈现——「每次
|
||
/// 启动呈现主窗口」必须由 AppKit 直控,不依赖场景挂载时序。窗口关闭时只
|
||
/// orderOut 不释放(isReleasedWhenClosed = false),重开即复用,workspace
|
||
/// 内部导航状态得以保留。
|
||
@MainActor
|
||
final class WorkspaceWindowOpeningAdapter: WorkspaceWindowOpening {
|
||
static let workspaceWindowID = NSUserInterfaceItemIdentifier("main")
|
||
|
||
private var window: NSWindow?
|
||
/// 当前分区名(组合根经 bindTitle 接线):开窗前先缓存,开窗时应用。
|
||
private var latestTitle = ""
|
||
private var titleCancellable: AnyCancellable?
|
||
private let makeContentView: () -> NSView
|
||
|
||
/// 内容视图由组合根惰性构造:主窗口内的 SwiftUI 状态只在首次开窗时创建。
|
||
init(makeContentView: @escaping () -> NSView) {
|
||
self.makeContentView = makeContentView
|
||
}
|
||
|
||
/// 窗口标题接线(组合根一次性调用):标题跟随当前 workspace destination
|
||
/// 的分区名,随 workspace 内导航实时变化;订阅生命周期由本 adapter 持有。
|
||
func bindTitle(_ updates: AnyPublisher<String, Never>) {
|
||
titleCancellable = updates.sink { [weak self] title in
|
||
self?.latestTitle = title
|
||
self?.window?.title = title
|
||
}
|
||
}
|
||
|
||
func ensureWorkspaceVisible() {
|
||
NSApp.activate(ignoringOtherApps: true)
|
||
let window = self.window ?? makeWindow()
|
||
window.makeKeyAndOrderFront(nil)
|
||
}
|
||
|
||
private func makeWindow() -> NSWindow {
|
||
let window = NSWindow(
|
||
contentRect: NSRect(x: 0, y: 0, width: 960, height: 640),
|
||
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
|
||
backing: .buffered, defer: false)
|
||
window.title = latestTitle
|
||
window.titlebarAppearsTransparent = true
|
||
window.identifier = Self.workspaceWindowID
|
||
window.isReleasedWhenClosed = false
|
||
// SwiftUI scene 窗口的 chrome 需在此手动补齐,两件缺一不可:
|
||
// ① 空 NSToolbar + .unified:NavigationSplitView 桥接把侧栏切换按钮与
|
||
// toolbar item 装进真实工具栏,并给内容正确安全区。没有工具栏时桥接
|
||
// 只装一半——玻璃背景画进内容区顶部而内容不缩进,分区大标题被糊成
|
||
// 色块。
|
||
// ② .fullSizeContentView + titlebarAppearsTransparent:侧栏通顶、窗口
|
||
// 标题(当前分区名)呈现在内容区前缘(scene 版外观)。缺省标题栏会把
|
||
// 标题挤在红绿灯旁的窄条里。
|
||
let toolbar = NSToolbar(identifier: "workspace")
|
||
window.toolbar = toolbar
|
||
window.toolbarStyle = .unified
|
||
window.contentView = makeContentView()
|
||
window.center()
|
||
self.window = window
|
||
return window
|
||
}
|
||
}
|