From 88a1546677b57014b01cbf486f42d62c4fb91503 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 16 Oct 2026 16:01:11 -0400 Subject: [PATCH] Update leading toolbar buttons --- .../Common/NavigationSplitViewWrapper.swift | 5 +- Xcodes/Frontend/MainWindow.swift | 6 +- Xcodes/Frontend/XcodeList/MainToolbar.swift | 104 ++++++++++-------- 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift b/Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift index 92dafff3..da93c3aa 100644 --- a/Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift +++ b/Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift @@ -10,17 +10,20 @@ import SwiftUI struct NavigationSplitViewWrapper: View where Sidebar: View, Detail: View { private var sidebar: Sidebar private var detail: Detail + @Binding private var columnVisibility: NavigationSplitViewVisibility init( + columnVisibility: Binding, @ViewBuilder sidebar: () -> Sidebar, @ViewBuilder detail: () -> Detail ) { + self._columnVisibility = columnVisibility self.sidebar = sidebar() self.detail = detail() } var body: some View { - NavigationSplitView { + NavigationSplitView(columnVisibility: $columnVisibility) { if #available(macOS 14, *) { sidebar .navigationSplitViewColumnWidth(min: 290, ideal: 290) diff --git a/Xcodes/Frontend/MainWindow.swift b/Xcodes/Frontend/MainWindow.swift index b108f053..0ef082b6 100644 --- a/Xcodes/Frontend/MainWindow.swift +++ b/Xcodes/Frontend/MainWindow.swift @@ -8,6 +8,7 @@ struct MainWindow: View { @EnvironmentObject var appState: AppState @State private var selectedXcodeID: Xcode.ID? @State private var searchText: String = "" + @State private var columnVisibility: NavigationSplitViewVisibility = .automatic @AppStorage("lastUpdated") private var lastUpdated: Double? // These two properties should be per-scene state managed by @SceneStorage property wrappers. // There's currently a bug with @SceneStorage on macOS, though, where quitting the app will discard the values, which removes a lot of its utility. @@ -19,7 +20,7 @@ struct MainWindow: View { @AppStorage("isInstalledOnly") private var isInstalledOnly = false var body: some View { - NavigationSplitViewWrapper { + NavigationSplitViewWrapper(columnVisibility: $columnVisibility) { XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly, architecture: architecture) .layoutPriority(1) .alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in @@ -33,7 +34,8 @@ struct MainWindow: View { category: $category, isInstalledOnly: $isInstalledOnly, isShowingInfoPane: $isShowingInfoPane, - architecture: $architecture + architecture: $architecture, + isSidebarCollapsed: columnVisibility == .detailOnly ) } detail: { Group { diff --git a/Xcodes/Frontend/XcodeList/MainToolbar.swift b/Xcodes/Frontend/XcodeList/MainToolbar.swift index f2f645f0..a00ba728 100644 --- a/Xcodes/Frontend/XcodeList/MainToolbar.swift +++ b/Xcodes/Frontend/XcodeList/MainToolbar.swift @@ -6,60 +6,74 @@ struct MainToolbarModifier: ViewModifier { @Binding var isInstalledOnly: Bool @Binding var isShowingInfoPane: Bool @Binding var architectures: XcodeListArchitecture + var isSidebarCollapsed = false func body(content: Content) -> some View { content .toolbar { toolbar } } + @ToolbarContentBuilder private var toolbar: some ToolbarContent { - ToolbarItemGroup { - ProgressButton( - isInProgress: appState.isUpdating, - action: appState.update - ) { - Label("Refresh", systemImage: "arrow.clockwise") - } - .keyboardShortcut(KeyEquivalent("r")) - .help("RefreshDescription") - - Spacer() - - let isFiltering = isInstalledOnly || category != .all || !architectures.isCurrentMachineDefault - Menu("Filter", systemImage: "line.horizontal.3.decrease.circle") { - Section { - Toggle("Installed Only", systemImage: "arrow.down.app", isOn: $isInstalledOnly) .labelStyle(.titleAndIcon) + if !isSidebarCollapsed { + ToolbarItemGroup { + ProgressButton( + isInProgress: appState.isUpdating, + action: appState.update + ) { + Label("Refresh", systemImage: "arrow.clockwise") } - .help("FilterInstalledDescription") - - Section { - Picker("Category", selection: $category) { - Label("All", systemImage: "line.horizontal.3.decrease.circle") - .tag(XcodeListCategory.all) - Label("ReleaseOnly", systemImage: "line.horizontal.3.decrease.circle.fill") - .tag(XcodeListCategory.release) - Label("BetaOnly", systemImage: "line.horizontal.3.decrease.circle.fill") - .tag(XcodeListCategory.beta) + .keyboardShortcut(KeyEquivalent("r")) + .help("RefreshDescription") + + Spacer() + + let isFiltering = isInstalledOnly || category != .all || !architectures.isCurrentMachineDefault + Menu("Filter", systemImage: filterSymbolName) { + Section { + Toggle("Installed Only", systemImage: "arrow.down.app", isOn: $isInstalledOnly) .labelStyle(.titleAndIcon) } - } - .help("FilterAvailableDescription") - .disabled(category.isManaged) - - Section { - Picker("Architecture", selection: $architectures) { - Label(architecturesLabel(for: .universal), systemImage: "cpu.fill") - .tag(XcodeListArchitecture.universal) - Label(architecturesLabel(for: .appleSilicon), systemImage: "m4.button.horizontal") - .foregroundColor(.accentColor) - .tag(XcodeListArchitecture.appleSilicon) + .help("FilterInstalledDescription") + + Section { + Picker("Category", selection: $category) { + Label("All", systemImage: "line.horizontal.3.decrease.circle") + .tag(XcodeListCategory.all) + Label("ReleaseOnly", systemImage: "line.horizontal.3.decrease.circle.fill") + .tag(XcodeListCategory.release) + Label("BetaOnly", systemImage: "line.horizontal.3.decrease.circle.fill") + .tag(XcodeListCategory.beta) + } } - .help("FilterArchitecturesDescription") - .disabled(architectures.isManaged) + .help("FilterAvailableDescription") + .disabled(category.isManaged) + + Section { + Picker("Architecture", selection: $architectures) { + Label(architecturesLabel(for: .universal), systemImage: "cpu.fill") + .tag(XcodeListArchitecture.universal) + Label(architecturesLabel(for: .appleSilicon), systemImage: "m4.button.horizontal") + .foregroundColor(.accentColor) + .tag(XcodeListArchitecture.appleSilicon) + } + .help("FilterArchitecturesDescription") + .disabled(architectures.isManaged) + } + .labelStyle(.titleAndIcon) } - .labelStyle(.titleAndIcon) + .pickerStyle(.inline) + .menuIndicator(.hidden) + .symbolVariant(isFiltering ? .fill : .none) } - .pickerStyle(.inline) - .symbolVariant(isFiltering ? .fill : .none) + } + } + + /// On macOS 26+ toolbar items already get their own glass background, so the circled symbol is redundant. + private var filterSymbolName: String { + if #available(macOS 26, *) { + return "line.3.horizontal.decrease" + } else { + return "line.horizontal.3.decrease.circle" } } @@ -73,14 +87,16 @@ extension View { category: Binding, isInstalledOnly: Binding, isShowingInfoPane: Binding, - architecture: Binding + architecture: Binding, + isSidebarCollapsed: Bool = false ) -> some View { modifier( MainToolbarModifier( category: category, isInstalledOnly: isInstalledOnly, isShowingInfoPane: isShowingInfoPane, - architectures: architecture + architectures: architecture, + isSidebarCollapsed: isSidebarCollapsed ) ) }