| | |
| | | } |
| | | .zIndex(899) |
| | | |
| | | QuickSearchOverlayView( |
| | | QuickSearchPanelPresentationView( |
| | | query: $quickSearchQuery, |
| | | results: quickSearchResults, |
| | | selectedID: quickSearchSelectedID, |
| | |
| | | selectionScrollToken: quickSearchSelectionScrollToken, |
| | | isLoading: quickSearchDocuments.isEmpty && refreshInProgress, |
| | | maxVisibleRows: quickSearchMaxVisibleRows(in: proxy.size), |
| | | panelTopY: quickSearchPanelTopY(in: proxy.size), |
| | | panelHeight: quickSearchPanelContentHeight(in: proxy.size), |
| | | errorMessage: quickSearchErrorMessage, |
| | | onCommand: handleQuickSearchCommand, |
| | | onHover: selectQuickSearchResult, |
| | | onLaunch: launchQuickSearchResult |
| | | ) |
| | | .position( |
| | | x: proxy.size.width / 2, |
| | | y: quickSearchPanelCenterY(in: proxy.size) |
| | | ) |
| | | .transition(.scale(scale: 0.98).combined(with: .opacity)) |
| | | .frame(width: 0, height: 0) |
| | | .allowsHitTesting(false) |
| | | .zIndex(900) |
| | | } |
| | | } |
| | |
| | | .animation(.easeOut(duration: 0.12), value: quickSearchVisible) |
| | | } |
| | | |
| | | private func quickSearchPanelCenterY(in size: CGSize) -> CGFloat { |
| | | let visibleRows = max(1, min(quickSearchResults.isEmpty ? 1 : quickSearchResults.count, quickSearchMaxVisibleRows(in: size))) |
| | | let estimatedPanelHeight = CGFloat(visibleRows) * 76 + 122 |
| | | return quickSearchPanelTopY(in: size) + estimatedPanelHeight / 2 |
| | | private func quickSearchPanelContentHeight(in size: CGSize) -> CGFloat { |
| | | let hasResultList = !(quickSearchDocuments.isEmpty && refreshInProgress) |
| | | && quickSearchErrorMessage == nil |
| | | && !quickSearchResults.isEmpty |
| | | let visibleRows = min(max(1, quickSearchResults.count), quickSearchMaxVisibleRows(in: size)) |
| | | return QuickSearchPanelMetrics.contentHeight( |
| | | hasResultList: hasResultList, |
| | | visibleRows: visibleRows |
| | | ) |
| | | } |
| | | |
| | | private func quickSearchPanelTopY(in size: CGSize) -> CGFloat { |
| | |
| | | |
| | | private func quickSearchMaxVisibleRows(in size: CGSize) -> Int { |
| | | let bottomClearance: CGFloat = 84 |
| | | let chromeHeight: CGFloat = 122 |
| | | let rowHeightWithSpacing: CGFloat = 76 |
| | | let chromeHeight = QuickSearchPanelMetrics.headerHeight |
| | | + QuickSearchPanelMetrics.dividerHeight |
| | | + QuickSearchPanelMetrics.resultListVerticalInset * 2 |
| | | let rowHeightWithSpacing = QuickSearchPanelMetrics.rowHeight |
| | | + QuickSearchPanelMetrics.rowSpacing |
| | | let availableHeight = max(0, size.height - quickSearchPanelTopY(in: size) - bottomClearance - chromeHeight) |
| | | return max(1, min(8, Int(floor(availableHeight / rowHeightWithSpacing)))) |
| | | } |
| | |
| | | contentInsets: isHorizontal |
| | | ? NSEdgeInsets(top: 3, left: 24, bottom: 3, right: tagPosition == "top" ? floatingControlsReservedWidth : 24) |
| | | : NSEdgeInsets(top: 12, left: 12, bottom: 12, right: 12), |
| | | dragModeActive: tagNavDragModeActive, |
| | | draggingItemID: tagNavDragItem, |
| | | onActivate: { tagID in |
| | | activateTagNavigation(tagID) |
| | | }, |
| | | onHoverChange: { tagID, active in |
| | | handleTagNavigationHover(tagID, active: active) |
| | | }, |
| | | canReorder: { tagID in |
| | | canReorderTag(tagID) |
| | | }, |
| | | onReorderBegan: { tagID in |
| | | beginTagNavReorder(tagID) |
| | | }, |
| | | onReorderMoved: { tagID, targetID in |
| | | reorderTagNavItem(fromName: tagID, to: targetID) |
| | | }, |
| | | onReorderEnded: { |
| | | endTagNavReorder() |
| | | } |
| | | ) |
| | | } |
| | |
| | | private func reorderTagNavItem(at location: CGPoint) { |
| | | guard let fromName = tagNavDragItem, |
| | | let targetName = tagNavReorderFrames.first(where: { $0.value.contains(location) })?.key, |
| | | fromName != targetName, |
| | | fromName != targetName |
| | | else { return } |
| | | reorderTagNavItem(fromName: fromName, to: targetName) |
| | | } |
| | | |
| | | private func reorderTagNavItem(fromName: String, to targetName: String) { |
| | | guard fromName != targetName, |
| | | canReorderTag(fromName), |
| | | canReorderTag(targetName), |
| | | let fromIndex = draggedTagNames.firstIndex(of: fromName), |
| | | let toIndex = draggedTagNames.firstIndex(of: targetName) |
| | | else { return } |
| | | |
| | | tagNavReorderDidMove = true |
| | | withAnimation(.spring(response: 0.22, dampingFraction: 0.82)) { |
| | | let destination = toIndex > fromIndex ? toIndex + 1 : toIndex |