diff --git a/TablePro/Views/Settings/SettingsView.swift b/TablePro/Views/Settings/SettingsView.swift index 4bb4f4078..bc07dd103 100644 --- a/TablePro/Views/Settings/SettingsView.swift +++ b/TablePro/Views/Settings/SettingsView.swift @@ -10,6 +10,23 @@ import SwiftUI /// Settings tab identifiers for programmatic navigation enum SettingsTab: String { case general, appearance, editor, dataGrid, keyboard, history, ai, plugins, sync, license + + static let fixedWidth: CGFloat = 720 + + var preferredHeight: CGFloat { + switch self { + case .general: 380 + case .appearance: 500 + case .editor: 300 + case .dataGrid: 380 + case .keyboard: 500 + case .history: 320 + case .ai: 520 + case .plugins: 500 + case .sync: 420 + case .license: 280 + } + } } /// Main settings view with tab-based navigation (macOS Settings style) @@ -18,6 +35,10 @@ struct SettingsView: View { @Environment(UpdaterBridge.self) var updaterBridge @AppStorage("selectedSettingsTab") private var selectedTab: String = SettingsTab.general.rawValue + private var currentTab: SettingsTab { + SettingsTab(rawValue: selectedTab) ?? .general + } + var body: some View { TabView(selection: $selectedTab) { GeneralSettingsView(settings: $settingsManager.general, updaterBridge: updaterBridge) @@ -81,7 +102,8 @@ struct SettingsView: View { } .tag(SettingsTab.license.rawValue) } - .frame(width: 720, height: 500) + .frame(width: SettingsTab.fixedWidth, height: currentTab.preferredHeight) + .background(SettingsWindowResizer(size: CGSize(width: SettingsTab.fixedWidth, height: currentTab.preferredHeight))) } } diff --git a/TablePro/Views/Settings/SettingsWindowResizer.swift b/TablePro/Views/Settings/SettingsWindowResizer.swift new file mode 100644 index 000000000..0fc2b22a1 --- /dev/null +++ b/TablePro/Views/Settings/SettingsWindowResizer.swift @@ -0,0 +1,39 @@ +// +// SettingsWindowResizer.swift +// TablePro +// + +import AppKit +import SwiftUI + +/// Resizes the Settings window height to match the selected tab's preferred size. +/// Width stays fixed to keep all tab items visible. Uses AppKit's +/// `NSWindow.setFrame(_:display:animate:)` for smooth height transitions, +/// keeping the top-left corner pinned (standard macOS preferences behavior). +struct SettingsWindowResizer: NSViewRepresentable { + var size: CGSize + + func makeNSView(context: Context) -> NSView { + _SettingsWindowSizeView() + } + + func updateNSView(_ nsView: NSView, context: Context) { + guard let window = nsView.window else { return } + let newFrameSize = window.frameRect(forContentRect: NSRect(origin: .zero, size: size)).size + guard window.frame.size != newFrameSize else { return } + // Defer to next run loop tick to avoid reentrant layout during SwiftUI rendering + DispatchQueue.main.async { + var frame = window.frame + frame.origin.y += frame.size.height - newFrameSize.height + frame.size = newFrameSize + window.setFrame(frame, display: true, animate: window.isVisible) + } + } +} + +private final class _SettingsWindowSizeView: NSView { + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + needsLayout = true + } +}