diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 8134341c7..0f450552f 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 2B7AC06B282452FB0082A5B8 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B7AC06A282452FB0082A5B8 /* Media.xcassets */; }; 2BE487EF28245162003F3F64 /* FinderSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE487EE28245162003F3F64 /* FinderSync.swift */; }; 2BE487F428245162003F3F64 /* OpenWithCodeEdit.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; }; 3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */; }; 3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */; }; 474397C52893AC4B00518C8C /* codeedit-midnight.json in Resources */ = {isa = PBXBuildFile; fileRef = 474397C42893AC4B00518C8C /* codeedit-midnight.json */; }; @@ -517,6 +518,7 @@ 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenWithCodeEdit.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 2BE487EE28245162003F3F64 /* FinderSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderSync.swift; sourceTree = ""; }; 2BE487F028245162003F3F64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = ""; }; 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.zsh; sourceTree = ""; }; 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.bash; sourceTree = ""; }; 474397C42893AC4B00518C8C /* codeedit-midnight.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "codeedit-midnight.json"; sourceTree = ""; }; @@ -1063,6 +1065,7 @@ 581550CB29FBD30400684881 /* OutlineView */, 286471AC27ED52950039369D /* ProjectNavigator */, 201169D52837B29600F92B46 /* SourceControlNavigator */, + 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */, B67660682AA972D400CD56B0 /* Models */, B67660692AA972DC00CD56B0 /* Views */, ); @@ -3076,6 +3079,7 @@ 6C6BD6F629CD145F00235D17 /* ExtensionInfo.swift in Sources */, 58F2EB05292FB2B0004A9BDE /* Settings.swift in Sources */, 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */, + 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */, B6E41C9429DEAE260088F9F4 /* SourceControlAccount.swift in Sources */, 2806E9022979588B000040F4 /* Contributor.swift in Sources */, 58D01C98293167DC00C5B6B4 /* String+RemoveOccurrences.swift in Sources */, diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index dd1132d1e..f3018a654 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -158,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ChimeHQ/Rearrange", "state" : { - "revision" : "0fb658e721c68495f6340c211cc6d4719e6b52d8", - "version" : "1.6.0" + "revision" : "8f97f721d8a08c6e01ab9f7460e53819bef72dfa", + "version" : "1.5.3" } }, { diff --git a/CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift b/CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift index e6fbbb888..514718677 100644 --- a/CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift +++ b/CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift @@ -111,7 +111,7 @@ class DirectoryEventStream { _ eventFlags: UnsafePointer, _ eventIds: UnsafePointer ) { - var eventPaths = eventPaths.bindMemory(to: UnsafePointer.self, capacity: numEvents) + let eventPaths = eventPaths.bindMemory(to: UnsafePointer.self, capacity: numEvents) for idx in 0..: View { @Environment(\.controlActiveState) private var activeState - var items: [Tab] - + @Binding var items: [Tab] @Binding var selection: Tab? var position: SettingsData.SidebarTabBarPosition - @State private var hasChangedLocation: Bool = false - @State private var draggingItem: SidebarDockIcon? - @State private var drugItemLocation: CGPoint? + @State private var tabLocations: [Tab: CGRect] = [:] + @State private var tabWidth: [Tab: CGFloat] = [:] + @State private var tabOffsets: [Tab: CGFloat] = [:] + + /// The tab currently being dragged. + /// + /// It will be `nil` when there is no tab dragged currently. + @State private var draggingTab: Tab? + + /// The start location of dragging. + /// + /// When there is no tab being dragged, it will be `nil`. + @State private var draggingStartLocation: CGFloat? + + /// The last location of dragging. + /// + /// This is used to determine the dragging direction. + /// - TODO: Check if I can use `value.translation` instead. + @State private var draggingLastLocation: CGFloat? var body: some View { if position == .top { @@ -63,19 +78,11 @@ struct AreaTabBar: View { layout { ForEach(items) { icon in makeIcon(tab: icon, size: size) - .opacity(draggingItem?.imageName == icon.systemImage && - hasChangedLocation && - drugItemLocation != nil ? 0.0 : 1.0) - // .onDrop( - // of: [.utf8PlainText], - // delegate: InspectorAreaDockIconDelegate( - // item: icon, - // current: $draggingItem, - // icons: $icons, - // hasChangedLocation: $hasChangedLocation, - // drugItemLocation: $drugItemLocation - // ) - // ) + .offset( + x: (position == .top) ? (tabOffsets[icon] ?? 0) : 0, + y: (position == .side) ? (tabOffsets[icon] ?? 0) : 0 + ) + .background(makeTabItemGeometryReader(tab: icon)) } if position == .side { Spacer() @@ -100,85 +107,192 @@ struct AreaTabBar: View { alignment: .center ) .help(tab.title) - // .onDrag { - // if let index = icons.firstIndex(where: { $0.imageName == named }) { - // draggingItem = icons[index] - // } - // return .init(object: NSString(string: named)) - // } preview: { - // RoundedRectangle(cornerRadius: .zero) - // .frame(width: .zero) - // } } .buttonStyle(.icon(isActive: tab == selection, size: nil)) } - private func getSafeImage(named: String, accessibilityDescription: String?) -> Image { - // We still use the NSImage init to check if a symbol with the name exists. - if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil { - return Image(systemName: named) - } else { - return Image(symbol: named) - } - } + private func makeAreaTabDragGesture(tab: Tab) -> some Gesture { + DragGesture(minimumDistance: 2, coordinateSpace: .global) + .onChanged({ value in + if draggingTab != tab { + initializeDragGesture(value: value, for: tab) + } - struct NavigatorToolbarButtonStyle: ButtonStyle { - var id: Int - var selection: Int - var activeState: ControlActiveState - var sidebarWidth: CGFloat + // Get the current cursor location + let currentLocation = (position == .top) ? value.location.x : value.location.y + guard let startLocation = draggingStartLocation, + let currentIndex = items.firstIndex(of: tab), + let currentTabWidth = tabWidth[tab], + let lastLocation = draggingLastLocation + else { return } - func makeBody(configuration: Configuration) -> some View { - configuration.label - .foregroundColor(id == selection ? .accentColor : configuration.isPressed ? .primary : .secondary) - } + let dragDifference = currentLocation - lastLocation + tabOffsets[tab] = currentLocation - startLocation + + // Check for swaps between adjacent tabs + // Left tab + swapTab( + tab: tab, + currentIndex: currentIndex, + currentLocation: currentLocation, + dragDifference: dragDifference, + currentTabWidth: currentTabWidth, + direction: .previous + ) + // Right tab + swapTab( + tab: tab, + currentIndex: currentIndex, + currentLocation: currentLocation, + dragDifference: dragDifference, + currentTabWidth: currentTabWidth, + direction: .next + ) + + // Update the last dragging location if there's enough offset + let currentLocationOnAxis = ((position == .top) ? value.location.x : value.location.y) + if draggingLastLocation == nil || abs(currentLocationOnAxis - draggingLastLocation!) >= 10 { + draggingLastLocation = (position == .top) ? value.location.x : value.location.y + } + }) + .onEnded({ _ in + draggingStartLocation = nil + draggingLastLocation = nil + withAnimation(.easeInOut(duration: 0.25)) { + tabOffsets = [:] + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + draggingTab = nil + } + }) } - private struct SidebarDockIcon: Identifiable, Equatable { - let imageName: String - let title: String - var id: Int - var disabled: Bool = false + private func initializeDragGesture(value: DragGesture.Value, for tab: Tab) { + draggingTab = tab + let initialLocation = position == .top ? value.startLocation.x : value.startLocation.y + draggingStartLocation = initialLocation + draggingLastLocation = initialLocation } - private struct NavigatorSidebarDockIconDelegate: DropDelegate { - let item: SidebarDockIcon - @Binding var current: SidebarDockIcon? - @Binding var icons: [SidebarDockIcon] - @Binding var hasChangedLocation: Bool - @Binding var drugItemLocation: CGPoint? - - func dropEntered(info: DropInfo) { - if current == nil { - current = item - drugItemLocation = info.location + enum SwapDirection { + case previous + case next + } + + // swiftlint: disable function_parameter_count + private func swapTab( + tab: Tab, + currentIndex: Int, + currentLocation: CGFloat, + dragDifference: CGFloat, + currentTabWidth: CGFloat, + direction: SwapDirection + ) { + // Determine the index to swap with based on direction + var swapIndex: Int? + if direction == .previous { + if currentIndex > 0 { + swapIndex = currentIndex - 1 + } + } else { + if currentIndex < items.count - 1 { + swapIndex = currentIndex + 1 } + } - guard item != current, let current = current, - let from = icons.firstIndex(of: current), - let toIndex = icons.firstIndex(of: item) else { return } + // Validate the drag direction + let isValidDragDir = (direction == .previous && dragDifference < 0) || + (direction == .next && dragDifference > 0) + guard let swapIndex = swapIndex, isValidDragDir else { return } - hasChangedLocation = true - drugItemLocation = info.location + // Get info about the tab to swap with + let swapTab = items[swapIndex] + guard let swapTabLocation = tabLocations[swapTab], + let swapTabWidth = tabWidth[swapTab] + else { return } - if icons[toIndex] != current { - icons.move(fromOffsets: IndexSet(integer: from), toOffset: toIndex > from ? toIndex + 1 : toIndex) - } + let isWithinBounds: Bool + if position == .top { + isWithinBounds = direction == .previous ? + isWithinPrevTopBounds(currentLocation, swapTabLocation, swapTabWidth) : + isWithinNextTopBounds(currentLocation, swapTabLocation, swapTabWidth, currentTabWidth) + } else { + isWithinBounds = direction == .previous ? + isWithinPrevBottomBounds(currentLocation, swapTabLocation, swapTabWidth) : + isWithinNextBottomBounds(currentLocation, swapTabLocation, swapTabWidth, currentTabWidth) } - func dropExited(info: DropInfo) { - drugItemLocation = nil + // Swap tab positions + if isWithinBounds { + let changing = swapTabWidth - 1 + draggingStartLocation! += direction == .previous ? -changing : changing + withAnimation { + tabOffsets[tab]! += direction == .previous ? changing : -changing + items.swapAt(currentIndex, swapIndex) + } } + } + // swiftlint: enable function_parameter_count - func dropUpdated(info: DropInfo) -> DropProposal? { - DropProposal(operation: .move) + private func isWithinPrevTopBounds( + _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat + ) -> Bool { + return curLocation < max( + swapLocation.maxX - swapWidth * 0.1, + swapLocation.minX + swapWidth * 0.9 + ) + } + + private func isWithinNextTopBounds( + _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat + ) -> Bool { + return curLocation > min( + swapLocation.minX + swapWidth * 0.1, + swapLocation.maxX - curWidth * 0.9 + ) + } + + private func isWithinPrevBottomBounds( + _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat + ) -> Bool { + return curLocation < max( + swapLocation.maxY - swapWidth * 0.1, + swapLocation.minY + swapWidth * 0.9 + ) + } + + private func isWithinNextBottomBounds( + _ curLocation: CGFloat, _ swapLocation: CGRect, _ swapWidth: CGFloat, _ curWidth: CGFloat + ) -> Bool { + return curLocation > min( + swapLocation.minY + swapWidth * 0.1, + swapLocation.maxY - curWidth * 0.9 + ) + } + + private func makeTabItemGeometryReader(tab: Tab) -> some View { + GeometryReader { geometry in + Rectangle() + .foregroundColor(.clear) + .onAppear { + self.tabWidth[tab] = geometry.size.width + self.tabLocations[tab] = geometry.frame(in: .global) + } + .onChange(of: geometry.frame(in: .global)) { newFrame in + self.tabLocations[tab] = newFrame + } + .onChange(of: geometry.size.width) { newWidth in + self.tabWidth[tab] = newWidth + } } + } - func performDrop(info: DropInfo) -> Bool { - hasChangedLocation = false - drugItemLocation = nil - current = nil - return true + private func getSafeImage(named: String, accessibilityDescription: String?) -> Image { + // We still use the NSImage init to check if a symbol with the name exists. + if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil { + return Image(systemName: named) + } else { + return Image(symbol: named) } } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 07d4e5455..0281ef005 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -19,6 +19,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs var workspace: WorkspaceDocument? var quickOpenPanel: OverlayPanel? var commandPalettePanel: OverlayPanel? + var navigatorSidebarViewModel: NavigatorSidebarViewModel? var splitViewController: NSSplitViewController! @@ -79,13 +80,18 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs let feedbackPerformer = NSHapticFeedbackManager.defaultPerformer let splitVC = CodeEditSplitViewController(workspace: workspace, feedbackPerformer: feedbackPerformer) - let navigatorView = SettingsInjector { - NavigatorAreaView(workspace: workspace) + let navigatorViewModel = NavigatorSidebarViewModel() + navigatorSidebarViewModel = navigatorViewModel + + let settingsView = SettingsInjector { + NavigatorAreaView(workspace: workspace, viewModel: navigatorViewModel) .environmentObject(workspace) .environmentObject(workspace.editorManager) } - let navigator = NSSplitViewItem(sidebarWithViewController: NSHostingController(rootView: navigatorView)) + let navigator = NSSplitViewItem( + sidebarWithViewController: NSHostingController(rootView: settingsView) + ) navigator.titlebarSeparatorStyle = .none navigator.minimumThickness = Self.minSidebarWidth navigator.collapseBehavior = .useConstraints diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift index 5a99312b5..e84e0559e 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift @@ -70,6 +70,8 @@ struct EditorTabCloseButton: View { closeButtonGestureActive = true }) .onEnded({ value in + // If the final position of the mouse is within the bounds of the + // close button then close the tab if value.location.x > 0 && value.location.x < buttonSize && value.location.y > 0 diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift index 08620f580..25a807282 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift @@ -159,22 +159,6 @@ struct EditorTabView: View { .padding(.horizontal, tabBarStyle == .native ? 28 : 23) .overlay { ZStack { - // Switch Tab Shortcut: - // Using an invisible button to contain the keyboard shortcut is simply - // because the keyboard shortcut has an unexpected bug when working with - // custom buttonStyle. This is an workaround and it works as expected. - if index < 10 { - Button( - action: switchAction, - label: { EmptyView() } - ) - .frame(width: 0, height: 0) - .keyboardShortcut( - KeyEquivalent(Character(String(index))), - modifiers: [.command] - ) - .hidden() - } // Close Button EditorTabCloseButton( isActive: isActive, diff --git a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift b/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift index 3d0eaf407..3e83bdf38 100644 --- a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift +++ b/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift @@ -18,13 +18,10 @@ struct InspectorAreaView: View { var sidebarPosition: SettingsData.SidebarTabBarPosition @State private var selection: InspectorTab? = .file + @State private var items: [InspectorTab] = [.file, .gitHistory] - private var items: [InspectorTab] { - [ - .file, - .gitHistory - ] - + extensionManager + init() { + items += extensionManager .extensions .map { ext in ext.availableFeatures.compactMap { @@ -63,7 +60,7 @@ struct InspectorAreaView: View { if sidebarPosition == .side { HStack(spacing: 0) { Divider() - AreaTabBar(items: items, selection: $selection, position: sidebarPosition) + AreaTabBar(items: $items, selection: $selection, position: sidebarPosition) } } } @@ -71,7 +68,7 @@ struct InspectorAreaView: View { if sidebarPosition == .top { VStack(spacing: 0) { Divider() - AreaTabBar(items: items, selection: $selection, position: sidebarPosition) + AreaTabBar(items: $items, selection: $selection, position: sidebarPosition) Divider() } } else { diff --git a/CodeEdit/Features/NavigatorArea/NavigatorSidebarViewModel.swift b/CodeEdit/Features/NavigatorArea/NavigatorSidebarViewModel.swift new file mode 100644 index 000000000..5d9c89f9e --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/NavigatorSidebarViewModel.swift @@ -0,0 +1,17 @@ +// +// NavigatorSidebarViewModel.swift +// CodeEdit +// +// Created by Abe Malla on 7/23/23. +// + +import Foundation + +class NavigatorSidebarViewModel: ObservableObject { + @Published var selectedTab: NavigatorTab? = .project + var items: [NavigatorTab] = [] + + func setNavigatorTab(tab newTab: NavigatorTab) { + selectedTab = newTab + } +} diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift index fcf5882b9..445890d9f 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift @@ -9,20 +9,17 @@ import SwiftUI struct NavigatorAreaView: View { @ObservedObject private var workspace: WorkspaceDocument - @ObservedObject private var extensionManager = ExtensionManager.shared + @ObservedObject public var viewModel: NavigatorSidebarViewModel @AppSettings(\.general.navigatorTabBarPosition) var sidebarPosition: SettingsData.SidebarTabBarPosition - @State private var selection: NavigatorTab? = .project - - init(workspace: WorkspaceDocument) { + init(workspace: WorkspaceDocument, viewModel: NavigatorSidebarViewModel) { self.workspace = workspace - } + self.viewModel = viewModel - private var items: [NavigatorTab] { - [.project, .sourceControl, .search] + + viewModel.items = [.project, .sourceControl, .search] + extensionManager .extensions .map { ext in @@ -38,7 +35,7 @@ struct NavigatorAreaView: View { var body: some View { VStack { - if let selection { + if let selection = viewModel.selectedTab { selection } else { NoSelectionInspectorView() @@ -47,7 +44,7 @@ struct NavigatorAreaView: View { .safeAreaInset(edge: .leading, spacing: 0) { if sidebarPosition == .side { HStack(spacing: 0) { - AreaTabBar(items: items, selection: $selection, position: sidebarPosition) + AreaTabBar(items: $viewModel.items, selection: $viewModel.selectedTab, position: sidebarPosition) Divider() } } @@ -56,7 +53,7 @@ struct NavigatorAreaView: View { if sidebarPosition == .top { VStack(spacing: 0) { Divider() - AreaTabBar(items: items, selection: $selection, position: sidebarPosition) + AreaTabBar(items: $viewModel.items, selection: $viewModel.selectedTab, position: sidebarPosition) Divider() } } else { diff --git a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift index 5364dd681..efb6d97b5 100644 --- a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift +++ b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift @@ -37,6 +37,9 @@ class UtilityAreaViewModel: ObservableObject { /// Search value to filter in drawer @Published var searchText: String = "" + /// The tab bar items for the DebugAreaView + @Published var tabItems: [UtilityAreaTab] = UtilityAreaTab.allCases + /// Returns the font for status bar items to use private(set) var toolbarFont: Font = .system(size: 11, weight: .medium) diff --git a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift index 979f8f155..2886f7afa 100644 --- a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift +++ b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift @@ -26,7 +26,7 @@ struct UtilityAreaView: View { } .safeAreaInset(edge: .leading, spacing: 0) { HStack(spacing: 0) { - AreaTabBar(items: UtilityAreaTab.allCases, selection: $selection, position: .side) + AreaTabBar(items: $model.tabItems, selection: $selection, position: .side) Divider() .overlay(Color(nsColor: colorScheme == .dark ? .black : .clear)) } diff --git a/CodeEdit/Features/WindowCommands/ViewCommands.swift b/CodeEdit/Features/WindowCommands/ViewCommands.swift index bdc0a54cf..b78ac0ed6 100644 --- a/CodeEdit/Features/WindowCommands/ViewCommands.swift +++ b/CodeEdit/Features/WindowCommands/ViewCommands.swift @@ -83,20 +83,19 @@ struct ViewCommands: Commands { } } .disabled(navigationSplitViewVisibility == nil) - .keyboardShortcut("s", modifiers: [.control, .command]) + .keyboardShortcut("0", modifiers: [.command]) Button("\(inspectorVisibility == false ? "Show" : "Hide") Inspector") { inspectorVisibility?.toggle() } .disabled(inspectorVisibility == nil) .keyboardShortcut("i", modifiers: [.control, .command]) - } else { Button("\(navigatorCollapsed ? "Show" : "Hide") Navigator") { windowController?.toggleFirstPanel() } .disabled(windowController == nil) - .keyboardShortcut("s", modifiers: [.control, .command]) + .keyboardShortcut("0", modifiers: [.command]) .onReceive(NSApp.publisher(for: \.keyWindow)) { window in windowController = window?.windowController as? CodeEditWindowController } @@ -121,6 +120,28 @@ struct ViewCommands: Commands { Divider() } + + if let model = windowController?.navigatorSidebarViewModel { + Divider() + NavigatorCommands(model: model) + } + } + } +} + +extension ViewCommands { + struct NavigatorCommands: View { + @ObservedObject var model: NavigatorSidebarViewModel + + var body: some View { + Menu("Navigators", content: { + ForEach(Array(model.items.prefix(9).enumerated()), id: \.element) { index, tab in + Button(tab.title) { + model.setNavigatorTab(tab: tab) + } + .keyboardShortcut(KeyEquivalent(Character(String(index + 1)))) + } + }) } } } diff --git a/CodeEdit/WindowSplitView.swift b/CodeEdit/WindowSplitView.swift index cc5ba5878..995607183 100644 --- a/CodeEdit/WindowSplitView.swift +++ b/CodeEdit/WindowSplitView.swift @@ -16,7 +16,7 @@ struct WindowSplitView: View { var body: some View { WindowObserver(window: window) { NavigationSplitView(columnVisibility: $visibility) { - NavigatorAreaView(workspace: workspace) + NavigatorAreaView(workspace: workspace, viewModel: NavigatorSidebarViewModel()) .toolbar { ToolbarItem { Button {