diff --git a/TablePro/ContentView.swift b/TablePro/ContentView.swift index 6e9d26dbd..14bbd13c0 100644 --- a/TablePro/ContentView.swift +++ b/TablePro/ContentView.swift @@ -257,33 +257,40 @@ struct ContentView: View { .navigationSplitViewColumnWidth(min: 200, ideal: 250, max: 600) } detail: { // MARK: - Detail (Main workspace with optional right sidebar) - MainContentView( - connection: currentSession.connection, - payload: payload, - windowTitle: $windowTitle, - tables: sessionTablesBinding, - sidebarState: SharedSidebarState.forConnection(currentSession.connection.id), - pendingTruncates: sessionPendingTruncatesBinding, - pendingDeletes: sessionPendingDeletesBinding, - tableOperationOptions: sessionTableOperationOptionsBinding, - inspectorContext: $inspectorContext, - rightPanelState: rightPanelState, - tabManager: sessionState.tabManager, - changeManager: sessionState.changeManager, - filterStateManager: sessionState.filterStateManager, - toolbarState: sessionState.toolbarState, - coordinator: sessionState.coordinator - ) - .inspector(isPresented: Bindable(rightPanelState).isPresented) { - UnifiedRightPanelView( - state: rightPanelState, - inspectorContext: inspectorContext, + HStack(spacing: 0) { + MainContentView( connection: currentSession.connection, - tables: currentSession.tables + payload: payload, + windowTitle: $windowTitle, + tables: sessionTablesBinding, + sidebarState: SharedSidebarState.forConnection(currentSession.connection.id), + pendingTruncates: sessionPendingTruncatesBinding, + pendingDeletes: sessionPendingDeletesBinding, + tableOperationOptions: sessionTableOperationOptionsBinding, + inspectorContext: $inspectorContext, + rightPanelState: rightPanelState, + tabManager: sessionState.tabManager, + changeManager: sessionState.changeManager, + filterStateManager: sessionState.filterStateManager, + toolbarState: sessionState.toolbarState, + coordinator: sessionState.coordinator ) - .frame(minWidth: 280, maxWidth: 500) - .inspectorColumnWidth(min: 280, ideal: 320, max: 500) + .frame(maxWidth: .infinity) + + if rightPanelState.isPresented { + PanelResizeHandle(panelWidth: Bindable(rightPanelState).panelWidth) + Divider() + UnifiedRightPanelView( + state: rightPanelState, + inspectorContext: inspectorContext, + connection: currentSession.connection, + tables: currentSession.tables + ) + .frame(width: rightPanelState.panelWidth) + .transition(.move(edge: .trailing)) + } } + .animation(.easeInOut(duration: 0.2), value: rightPanelState.isPresented) } .navigationTitle(windowTitle) .navigationSubtitle(currentSession.connection.name) diff --git a/TablePro/Models/UI/RightPanelState.swift b/TablePro/Models/UI/RightPanelState.swift index d7afdf805..4c0888168 100644 --- a/TablePro/Models/UI/RightPanelState.swift +++ b/TablePro/Models/UI/RightPanelState.swift @@ -12,8 +12,13 @@ import os @MainActor @Observable final class RightPanelState { private static let isPresentedKey = "com.TablePro.rightPanel.isPresented" + private static let panelWidthKey = "com.TablePro.rightPanel.width" private static let isPresentedChangedNotification = Notification.Name("com.TablePro.rightPanel.isPresentedChanged") private var isSyncing = false + + static let minWidth: CGFloat = 280 + static let maxWidth: CGFloat = 500 + static let defaultWidth: CGFloat = 320 @ObservationIgnored private let _didTeardown = OSAllocatedUnfairLock(initialState: false) var isPresented: Bool { @@ -26,6 +31,14 @@ import os } } + var panelWidth: CGFloat { + didSet { + let clamped = min(max(panelWidth, Self.minWidth), Self.maxWidth) + if panelWidth != clamped { panelWidth = clamped } + UserDefaults.standard.set(Double(clamped), forKey: Self.panelWidthKey) + } + } + var activeTab: RightPanelTab = .details // Save closure — set by MainContentCommandActions, called by UnifiedRightPanelView @@ -43,6 +56,8 @@ import os init() { self.isPresented = UserDefaults.standard.bool(forKey: Self.isPresentedKey) + let savedWidth = UserDefaults.standard.double(forKey: Self.panelWidthKey) + self.panelWidth = savedWidth > 0 ? min(max(savedWidth, Self.minWidth), Self.maxWidth) : Self.defaultWidth NotificationCenter.default.addObserver( self, selector: #selector(handleIsPresentedChanged(_:)), diff --git a/TablePro/Views/Components/PanelResizeHandle.swift b/TablePro/Views/Components/PanelResizeHandle.swift new file mode 100644 index 000000000..6f8531c6b --- /dev/null +++ b/TablePro/Views/Components/PanelResizeHandle.swift @@ -0,0 +1,40 @@ +// +// PanelResizeHandle.swift +// TablePro +// +// Draggable resize handle for the right panel. +// + +import SwiftUI + +struct PanelResizeHandle: View { + @Binding var panelWidth: CGFloat + + @State private var isDragging = false + + var body: some View { + Rectangle() + .fill(Color.clear) + .frame(width: 5) + .contentShape(Rectangle()) + .onHover { hovering in + if hovering { + NSCursor.resizeLeftRight.push() + } else { + NSCursor.pop() + } + } + .gesture( + DragGesture(minimumDistance: 1) + .onChanged { value in + isDragging = true + // Dragging left increases panel width (handle is on the leading edge) + let newWidth = panelWidth - value.translation.width + panelWidth = min(max(newWidth, RightPanelState.minWidth), RightPanelState.maxWidth) + } + .onEnded { _ in + isDragging = false + } + ) + } +}