
























@@ -8,6 +8,7 @@ struct SettingsRootView: View {
88@State private var monitoringPermissions = false
99@State private var selectedTab: SettingsTab = .general
1010@State private var cachedTabs: Set<SettingsTab>
11+@State private var columnVisibility: NavigationSplitViewVisibility = .all
1112@State private var snapshotPaths: (configPath: String?, stateDir: String?) = (nil, nil)
1213let updater: UpdaterProviding?
1314private let isPreview = ProcessInfo.processInfo.isPreview
@@ -22,17 +23,25 @@ struct SettingsRootView: View {
2223}
23242425var body: some View {
25-HStack(spacing: 0) {
26-SettingsSidebar(
27- groups: self.visibleGroups,
28- selectedTab: self.$selectedTab)
29-.frame(width: SettingsLayout.sidebarWidth)
30-26+NavigationSplitView(columnVisibility: self.animatedColumnVisibility) {
27+List(selection: self.sidebarSelection) {
28+ForEach(self.visibleGroups) { group in
29+Section(group.title) {
30+ForEach(group.tabs) { tab in
31+Label(tab.title, systemImage: tab.systemImage)
32+.tag(tab as SettingsTab?)
33+}
34+}
35+}
36+}
37+.listStyle(.sidebar)
38+.navigationSplitViewColumnWidth(SettingsLayout.sidebarWidth)
39+} detail: {
3140self.detailContainer
3241}
42+.navigationSplitViewStyle(.balanced)
3343.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading)
3444.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
35-.background(SettingsWindowChromeConfigurator())
3645.onReceive(NotificationCenter.default.publisher(for: .openclawSelectSettingsTab)) { note in
3746if let tab = note.object as? SettingsTab {
3847withAnimation(.spring(response: 0.32, dampingFraction: 0.85)) {
@@ -75,6 +84,25 @@ struct SettingsRootView: View {
7584SettingsTabGroup.defaultGroups(showDebug: self.state.debugPaneEnabled)
7685}
778687+private var sidebarSelection: Binding<SettingsTab?> {
88+Binding(
89+ get: { self.selectedTab },
90+ set: { tab in
91+guard let tab else { return }
92+self.selectedTab = self.validTab(for: tab)
93+})
94+}
95+96+private var animatedColumnVisibility: Binding<NavigationSplitViewVisibility> {
97+Binding(
98+ get: { self.columnVisibility },
99+ set: { visibility in
100+withAnimation(.easeInOut(duration: 0.22)) {
101+self.columnVisibility = visibility
102+}
103+})
104+}
105+78106private var detailContainer: some View {
79107VStack(alignment: .leading, spacing: 14) {
80108if self.isNixMode {
@@ -201,77 +229,6 @@ struct SettingsRootView: View {
201229}
202230}
203231204-private struct SettingsSidebar: View {
205-let groups: [SettingsTabGroup]
206-@Binding var selectedTab: SettingsTab
207-208-var body: some View {
209-ZStack(alignment: .topLeading) {
210-VisualEffectView(material: .sidebar)
211-.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
212-.overlay {
213-RoundedRectangle(cornerRadius: 16, style: .continuous)
214-.strokeBorder(.white.opacity(0.09), lineWidth: 1)
215-}
216-.shadow(color: .black.opacity(0.16), radius: 18, x: 0, y: 12)
217-218-ScrollView(.vertical) {
219-VStack(alignment: .leading, spacing: 18) {
220-ForEach(self.groups) { group in
221-VStack(alignment: .leading, spacing: 6) {
222-Text(group.title)
223-.font(.caption.weight(.semibold))
224-.foregroundStyle(.secondary)
225-.padding(.horizontal, 8)
226-227-ForEach(group.tabs) { tab in
228-SettingsSidebarRow(
229- tab: tab,
230- selected: self.selectedTab == tab)
231-{
232-self.selectedTab = tab
233-}
234-}
235-}
236-.frame(maxWidth: .infinity, alignment: .leading)
237-}
238-}
239-.frame(maxWidth: .infinity, alignment: .leading)
240-.padding(.horizontal, 10)
241-.padding(.vertical, 10)
242-}
243-}
244-.padding(.leading, 12)
245-.padding(.vertical, 10)
246-}
247-}
248-249-private struct SettingsSidebarRow: View {
250-let tab: SettingsTab
251-let selected: Bool
252-let select: () -> Void
253-254-var body: some View {
255-Label(self.tab.title, systemImage: self.tab.systemImage)
256-.font(.body.weight(.medium))
257-.labelStyle(.titleAndIcon)
258-.frame(maxWidth: .infinity, alignment: .leading)
259-.padding(.horizontal, 10)
260-.padding(.vertical, 8)
261-.frame(maxWidth: .infinity, alignment: .leading)
262-.background {
263-RoundedRectangle(cornerRadius: 10, style: .continuous)
264-.fill(self.selected ? Color.white.opacity(0.13) : Color.clear)
265-}
266-.contentShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
267-.onTapGesture(perform: self.select)
268-.accessibilityElement(children: .combine)
269-.accessibilityLabel(self.tab.title)
270-.accessibilityAddTraits(self.selected ? [.isButton, .isSelected] : .isButton)
271-.accessibilityAction { self.select() }
272-}
273-}
274-275232private struct SettingsTabGroup: Identifiable {
276233let title: String
277234let tabs: [SettingsTab]
@@ -344,28 +301,6 @@ enum SettingsTab: CaseIterable, Identifiable, Hashable {
344301}
345302}
346303347-private struct SettingsWindowChromeConfigurator: NSViewRepresentable {
348-func makeNSView(context: Context) -> NSView {
349-let view = NSView(frame: .zero)
350-self.configureWindow(for: view)
351-return view
352-}
353-354-func updateNSView(_ nsView: NSView, context: Context) {
355-self.configureWindow(for: nsView)
356-}
357-358-private func configureWindow(for view: NSView) {
359-DispatchQueue.main.async {
360-guard let window = view.window else { return }
361- window.styleMask.remove(.fullSizeContentView)
362- window.titleVisibility = .visible
363- window.titlebarAppearsTransparent = true
364- window.toolbarStyle = .unifiedCompact
365-}
366-}
367-}
368-369304@MainActor
370305enum SettingsTabRouter {
371306private static var pending: SettingsTab?
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。