| | |
| | | var tagRemovalDropSuppressFuturePrompt = false |
| | | var appDragResetToken = 0 |
| | | var bubbleDraftNote = "" |
| | | var tagNavigationHoveredGroupName: String? = nil |
| | | var tagNavigationLastHoverScrollID: String? = nil |
| | | var tagNavigationLastHoverScrollAt: Date? = nil |
| | | } |
| | | |
| | | private struct EditActionFeedback: Identifiable { |
| | |
| | | || appGridInteraction.pendingUncategorizedDrop != nil |
| | | || appGridInteraction.pendingTagRemovalDrop != nil |
| | | } |
| | | private var appGridHighlightedGroupName: String? { |
| | | appGridInteraction.tagNavigationHoveredGroupName |
| | | ?? (isColorlessContainerMode ? filledColorlessContainer : nil) |
| | | } |
| | | private let rightSidebarFloatingClearance: CGFloat = 44 |
| | | private let tagNavigationHoverScrollInterval: TimeInterval = 0.22 |
| | | |
| | | private var floatingButtonSurfaceColor: Color { |
| | | colorScheme == .dark |
| | |
| | | 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) |
| | | }, |
| | | onHover: { tagID in |
| | | handleTagNavigationHover(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() |
| | | } |
| | | ) |
| | | } |
| | |
| | | .zIndex(tagNavDragItem == tag.name ? 1 : 0) |
| | | .highPriorityGesture(tagNavReorderGesture(for: tag.name)) |
| | | .onHover { hovering in |
| | | if hovering { |
| | | handleTagNavigationHover(tag.id) |
| | | } |
| | | handleTagNavigationHover(tag.id, active: hovering) |
| | | } |
| | | } |
| | | } |
| | |
| | | .zIndex(tagNavDragItem == tag.name ? 1 : 0) |
| | | .highPriorityGesture(tagNavReorderGesture(for: tag.name)) |
| | | .onHover { hovering in |
| | | if hovering { |
| | | handleTagNavigationHover(tag.id) |
| | | } |
| | | handleTagNavigationHover(tag.id, active: hovering) |
| | | } |
| | | } |
| | | } |
| | |
| | | showNames: !hideAppNames, |
| | | bubbleDisabled: appBubbleDisabled, |
| | | showUncommonAppBubbles: showUncommonAppBubbles, |
| | | highlightedGroupName: filledColorlessContainer, |
| | | highlightedGroupName: appGridHighlightedGroupName, |
| | | contentRevision: groupLayoutVersion, |
| | | scrollTargetID: appGridScrollTargetID, |
| | | scrollRequestToken: appGridScrollRequestToken, |
| | |
| | | scrollTo(id) |
| | | } |
| | | |
| | | private func handleTagNavigationHover(_ id: String) { |
| | | private func handleTagNavigationHover(_ id: String, active: Bool) { |
| | | guard active else { |
| | | if appGridInteraction.tagNavigationHoveredGroupName == id { |
| | | appGridInteraction.tagNavigationHoveredGroupName = nil |
| | | } |
| | | return |
| | | } |
| | | |
| | | appGridInteraction.tagNavigationHoveredGroupName = id |
| | | fillColorlessContainer(id) |
| | | scrollToTagFromHover(id) |
| | | } |
| | | |
| | | private func scrollToTagFromHover(_ id: String) { |
| | | let now = Date() |
| | | if appGridInteraction.tagNavigationLastHoverScrollID == id, |
| | | let lastScrollAt = appGridInteraction.tagNavigationLastHoverScrollAt, |
| | | now.timeIntervalSince(lastScrollAt) < tagNavigationHoverScrollInterval { |
| | | return |
| | | } |
| | | appGridInteraction.tagNavigationLastHoverScrollID = id |
| | | appGridInteraction.tagNavigationLastHoverScrollAt = now |
| | | scrollTo(id) |
| | | } |
| | | |
| | |
| | | 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 |