From c9fde9ea03c8c61b97f56b5d91c5d5ae54810c93 Mon Sep 17 00:00:00 2001 From: tomholford Date: Fri, 7 Aug 2026 08:28:27 -0700 Subject: [PATCH 1/2] size settings window to content and scroll long tabs settings tabs claimed unbounded height via maxHeight infinity and trailing spacers, so the settings window opened at full screen height regardless of content. size the window to content, wrap each tab in a scroll view for short screens, and keep a minimum window height. --- BitDream/BitDreamApp.swift | 3 +- .../Settings/macOSGeneralSettingsTab.swift | 252 +++++++++--------- .../macOS/Settings/macOSSettingsView.swift | 30 ++- 3 files changed, 145 insertions(+), 140 deletions(-) diff --git a/BitDream/BitDreamApp.swift b/BitDream/BitDreamApp.swift index 4238ef1..8867ab1 100644 --- a/BitDream/BitDreamApp.swift +++ b/BitDream/BitDreamApp.swift @@ -322,10 +322,11 @@ private extension BitDreamApp { SettingsView(store: store) #endif } - .frame(minWidth: 500, idealWidth: 550, maxWidth: 650) + .frame(minWidth: 500, idealWidth: 550, maxWidth: 650, minHeight: 320) .environmentObject(themeManager) // Pass the ThemeManager to the Settings view .immediateTheme(manager: themeManager) } + .windowResizability(.contentSize) } #else var iOSScene: some Scene { diff --git a/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift b/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift index a5a3c2e..e6fa348 100644 --- a/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift +++ b/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift @@ -41,173 +41,175 @@ struct macOSGeneralSettingsTab: View { #endif var body: some View { - VStack(alignment: .leading, spacing: 20) { - GroupBox { - VStack(alignment: .leading, spacing: 16) { - settingsSection("Appearance") { - HStack { - Text("Theme") - Spacer() - Picker("", selection: $themeManager.themeMode) { - ForEach(ThemeMode.allCases, id: \.self) { mode in - Text(mode.rawValue).tag(mode) + ScrollView { + VStack(alignment: .leading, spacing: 20) { + GroupBox { + VStack(alignment: .leading, spacing: 16) { + settingsSection("Appearance") { + HStack { + Text("Theme") + Spacer() + Picker("", selection: $themeManager.themeMode) { + ForEach(ThemeMode.allCases, id: \.self) { mode in + Text(mode.rawValue).tag(mode) + } } + .pickerStyle(.menu) + } + + HStack { + Text("Accent Color") + Spacer() + Picker("", selection: $themeManager.currentAccentColorOption) { + ForEach(AccentColorOption.allCases) { option in + HStack { + Circle() + .fill(option.color) + .frame(width: 12, height: 12) + Text(option.name) + } + .tag(option) + } + } + .pickerStyle(.menu) } - .pickerStyle(.menu) - } - HStack { - Text("Accent Color") - Spacer() - Picker("", selection: $themeManager.currentAccentColorOption) { + HStack(spacing: 12) { ForEach(AccentColorOption.allCases) { option in - HStack { - Circle() + VStack { + RoundedRectangle(cornerRadius: 8) .fill(option.color) - .frame(width: 12, height: 12) - Text(option.name) + .frame(width: 40, height: 40) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(themeManager.currentAccentColorOption == option ? Color.primary : Color.clear, lineWidth: 2) + ) + Text(option.rawValue) + .font(.caption2) + .foregroundColor(.secondary) + } + .onTapGesture { + themeManager.setAccentColor(option) } - .tag(option) } } - .pickerStyle(.menu) + .padding(.top, 8) + + Toggle("Show file type icons", isOn: $showContentTypeIcons) } - HStack(spacing: 12) { - ForEach(AccentColorOption.allCases) { option in - VStack { - RoundedRectangle(cornerRadius: 8) - .fill(option.color) - .frame(width: 40, height: 40) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(themeManager.currentAccentColorOption == option ? Color.primary : Color.clear, lineWidth: 2) - ) - Text(option.rawValue) - .font(.caption2) - .foregroundColor(.secondary) - } - .onTapGesture { - themeManager.setAccentColor(option) + divider + + settingsSection("Menu Bar") { + Toggle("Show BitDream in menu bar", isOn: $menuBarTransferWidgetEnabled) + + VStack(alignment: .leading, spacing: 12) { + Toggle("Show active torrent count", isOn: $menuBarShowActiveCount) + + HStack { + Text("Sort torrents by") + Spacer() + Picker("", selection: menuBarSortMode) { + ForEach(MenuBarSortMode.allCases, id: \.self) { mode in + Text(mode.label).tag(mode) + } + } + .pickerStyle(.menu) } } + .padding(.leading, 20) + .disabled(!menuBarTransferWidgetEnabled) } - .padding(.top, 8) - Toggle("Show file type icons", isOn: $showContentTypeIcons) - } + divider - divider + settingsSection("Dock Badge") { + Toggle("Show completed torrents count", isOn: $dockShowCompletedBadge) + Toggle("Show download rate", isOn: $dockShowDownloadSpeed) + Toggle("Show upload rate", isOn: $dockShowUploadSpeed) + } - settingsSection("Menu Bar") { - Toggle("Show BitDream in menu bar", isOn: $menuBarTransferWidgetEnabled) + divider - VStack(alignment: .leading, spacing: 12) { - Toggle("Show active torrent count", isOn: $menuBarShowActiveCount) + settingsSection("Connection Settings") { + HStack { + Text("Startup connection") + Spacer() + Picker("", selection: .fromRawValue(rawValue: $startupBehaviorRaw, defaultValue: AppDefaults.startupConnectionBehavior)) { + Text("Last used server").tag(StartupConnectionBehavior.lastUsed) + Text("Default server").tag(StartupConnectionBehavior.defaultServer) + } + .pickerStyle(.menu) + } + .help("Choose which server BitDream connects to when it launches.") HStack { - Text("Sort torrents by") + Text("Auto-refresh interval") Spacer() - Picker("", selection: menuBarSortMode) { - ForEach(MenuBarSortMode.allCases, id: \.self) { mode in - Text(mode.label).tag(mode) + Picker("", selection: $store.pollInterval) { + ForEach(SettingsView.pollIntervalOptions, id: \.self) { interval in + Text(SettingsView.formatInterval(interval)).tag(interval) } } .pickerStyle(.menu) } } - .padding(.leading, 20) - .disabled(!menuBarTransferWidgetEnabled) - } - divider + #if canImport(Sparkle) + divider + settingsSection("Updates") { + Toggle("Automatically check for updates", isOn: automaticallyChecksForUpdates) - settingsSection("Dock Badge") { - Toggle("Show completed torrents count", isOn: $dockShowCompletedBadge) - Toggle("Show download rate", isOn: $dockShowDownloadSpeed) - Toggle("Show upload rate", isOn: $dockShowUploadSpeed) - } + HStack(alignment: .firstTextBaseline) { + Button("Check for Updates…", action: appUpdater.checkForUpdates) + .disabled(!appUpdater.canCheckForUpdates) - divider + Spacer() - settingsSection("Connection Settings") { - HStack { - Text("Startup connection") - Spacer() - Picker("", selection: .fromRawValue(rawValue: $startupBehaviorRaw, defaultValue: AppDefaults.startupConnectionBehavior)) { - Text("Last used server").tag(StartupConnectionBehavior.lastUsed) - Text("Default server").tag(StartupConnectionBehavior.defaultServer) - } - .pickerStyle(.menu) - } - .help("Choose which server BitDream connects to when it launches.") - - HStack { - Text("Auto-refresh interval") - Spacer() - Picker("", selection: $store.pollInterval) { - ForEach(SettingsView.pollIntervalOptions, id: \.self) { interval in - Text(SettingsView.formatInterval(interval)).tag(interval) - } + Text("Last checked: \(lastUpdateCheckText)") + .font(.caption2) + .foregroundStyle(.tertiary) } - .pickerStyle(.menu) } - } + #endif - #if canImport(Sparkle) - divider - settingsSection("Updates") { - Toggle("Automatically check for updates", isOn: automaticallyChecksForUpdates) + divider - HStack(alignment: .firstTextBaseline) { - Button("Check for Updates…", action: appUpdater.checkForUpdates) - .disabled(!appUpdater.canCheckForUpdates) + settingsSection("Notifications") { + Toggle("Show notifications for completed torrents", isOn: .constant(false)) + .disabled(true) - Spacer() - - Text("Last checked: \(lastUpdateCheckText)") + Text("Advanced settings coming soon") .font(.caption2) - .foregroundStyle(.tertiary) + .foregroundColor(.orange) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.orange.opacity(0.2)) + .cornerRadius(4) } - } - #endif - divider - - settingsSection("Notifications") { - Toggle("Show notifications for completed torrents", isOn: .constant(false)) - .disabled(true) - - Text("Advanced settings coming soon") - .font(.caption2) - .foregroundColor(.orange) - .padding(.horizontal, 6) - .padding(.vertical, 2) - .background(Color.orange.opacity(0.2)) - .cornerRadius(4) - } - - divider - - settingsSection("Reset") { - Button("Reset All Settings") { - SettingsView.resetAllSettings( - store: store, - themeManager: themeManager, - userDefaults: userDefaults - ) - #if canImport(Sparkle) - appUpdater.resetToDefaults() - #endif + divider + + settingsSection("Reset") { + Button("Reset All Settings") { + SettingsView.resetAllSettings( + store: store, + themeManager: themeManager, + userDefaults: userDefaults + ) + #if canImport(Sparkle) + appUpdater.resetToDefaults() + #endif + } } } + .padding(16) } - .padding(16) } - Spacer() + .padding(20) + .frame(maxWidth: .infinity, alignment: .topLeading) } - .padding(20) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .scrollBounceBehavior(.basedOnSize) } @ViewBuilder diff --git a/BitDream/Views/macOS/Settings/macOSSettingsView.swift b/BitDream/Views/macOS/Settings/macOSSettingsView.swift index a3ad9c7..2b816f4 100644 --- a/BitDream/Views/macOS/Settings/macOSSettingsView.swift +++ b/BitDream/Views/macOS/Settings/macOSSettingsView.swift @@ -68,22 +68,24 @@ private struct SettingsServerTab: View { let content: (TransmissionSessionResponseArguments, SettingsViewModel) -> Content var body: some View { - VStack(alignment: .leading, spacing: 20) { - if let config { - content(config, editModel) - SettingsSaveStateView(state: editModel.saveState) - Spacer() - } else { - ContentUnavailableView( - "No Server Connected", - systemImage: unavailableSystemImage, - description: Text(unavailableDescription) - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) + ScrollView { + VStack(alignment: .leading, spacing: 20) { + if let config { + content(config, editModel) + SettingsSaveStateView(state: editModel.saveState) + } else { + ContentUnavailableView( + "No Server Connected", + systemImage: unavailableSystemImage, + description: Text(unavailableDescription) + ) + .frame(maxWidth: .infinity, minHeight: 250) + } } + .padding(20) + .frame(maxWidth: .infinity, alignment: .topLeading) } - .padding(20) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .scrollBounceBehavior(.basedOnSize) .bindSettingsViewModel(editModel, to: store) } } From f4d1f9386c2cc2d2a6b7d94ef08dd42fff19a257 Mon Sep 17 00:00:00 2001 From: tomholford Date: Sun, 9 Aug 2026 12:44:52 -0700 Subject: [PATCH 2/2] bump settings window min height and drop windowResizability --- BitDream/BitDreamApp.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BitDream/BitDreamApp.swift b/BitDream/BitDreamApp.swift index 8867ab1..63f6fa6 100644 --- a/BitDream/BitDreamApp.swift +++ b/BitDream/BitDreamApp.swift @@ -322,11 +322,10 @@ private extension BitDreamApp { SettingsView(store: store) #endif } - .frame(minWidth: 500, idealWidth: 550, maxWidth: 650, minHeight: 320) + .frame(minWidth: 500, idealWidth: 550, maxWidth: 650, minHeight: 650) .environmentObject(themeManager) // Pass the ThemeManager to the Settings view .immediateTheme(manager: themeManager) } - .windowResizability(.contentSize) } #else var iOSScene: some Scene {