| | |
| | | let onBubbleHover: (AppInfo, CGRect, AppBubbleHoverEvent) -> Void |
| | | let onEditNote: (AppInfo, CGRect) -> Void |
| | | let onDropApp: (String, String, String, Bool) -> Void |
| | | let onDropOutsideGroup: (String, String, Bool) -> Void |
| | | let onGroupActivate: (String) -> Void |
| | | let onScrollActivity: () -> Void |
| | | let onDragModeChange: (Bool) -> Void |
| | |
| | | onBubbleHover: onBubbleHover, |
| | | onEditNote: onEditNote, |
| | | onDropApp: onDropApp, |
| | | onDropOutsideGroup: onDropOutsideGroup, |
| | | onGroupActivate: onGroupActivate, |
| | | onScrollActivity: onScrollActivity, |
| | | onDragModeChange: onDragModeChange |
| | |
| | | var onBubbleHover: (AppInfo, CGRect, AppBubbleHoverEvent) -> Void = { _, _, _ in } |
| | | var onEditNote: (AppInfo, CGRect) -> Void = { _, _ in } |
| | | var onDropApp: (String, String, String, Bool) -> Void = { _, _, _, _ in } |
| | | var onDropOutsideGroup: (String, String, Bool) -> Void = { _, _, _ in } |
| | | var onGroupActivate: (String) -> Void = { _ in } |
| | | var onScrollActivity: () -> Void = {} |
| | | var onDragModeChange: (Bool) -> Void = { _ in } |
| | |
| | | onBubbleHover: @escaping (AppInfo, CGRect, AppBubbleHoverEvent) -> Void, |
| | | onEditNote: @escaping (AppInfo, CGRect) -> Void, |
| | | onDropApp: @escaping (String, String, String, Bool) -> Void, |
| | | onDropOutsideGroup: @escaping (String, String, Bool) -> Void, |
| | | onGroupActivate: @escaping (String) -> Void, |
| | | onScrollActivity: @escaping () -> Void, |
| | | onDragModeChange: @escaping (Bool) -> Void |
| | |
| | | self.onBubbleHover = onBubbleHover |
| | | self.onEditNote = onEditNote |
| | | self.onDropApp = onDropApp |
| | | self.onDropOutsideGroup = onDropOutsideGroup |
| | | self.onGroupActivate = onGroupActivate |
| | | self.onScrollActivity = onScrollActivity |
| | | self.onDragModeChange = onDragModeChange |
| | |
| | | iconSize: iconSize, |
| | | showNames: showNames, |
| | | showUncommonAppBubbles: showUncommonAppBubbles, |
| | | highlightedGroupName: highlightedGroupName, |
| | | contentRevision: contentRevision |
| | | ) |
| | | if nextSignature != contentSignature { |
| | |
| | | iconSize: CGFloat, |
| | | showNames: Bool, |
| | | showUncommonAppBubbles: Bool, |
| | | highlightedGroupName: String?, |
| | | contentRevision: Int |
| | | ) -> String { |
| | | let colorPart = tagColors |
| | |
| | | "\(Int(iconSize.rounded()))", |
| | | showNames ? "names" : "nonames", |
| | | showUncommonAppBubbles ? "uncommon" : "allbubbles", |
| | | highlightedGroupName ?? "", |
| | | colorPart, |
| | | "rev=\(contentRevision)" |
| | | ].joined(separator: "|") |
| | |
| | | } |
| | | } |
| | | |
| | | final class AppGridCollectionHostView: NSView { |
| | | private let scrollView = NSScrollView() |
| | | private final class AppGridScrollView: NSScrollView { |
| | | override var acceptsFirstResponder: Bool { true } |
| | | |
| | | override func scrollWheel(with event: NSEvent) { |
| | | window?.makeKeyAndOrderFront(nil) |
| | | window?.orderFrontRegardless() |
| | | super.scrollWheel(with: event) |
| | | } |
| | | } |
| | | |
| | | final class AppGridCollectionHostView: NSView, AppEmptyDropReceivingView { |
| | | private let emptyDropTargetID = UUID() |
| | | private let scrollView = AppGridScrollView() |
| | | private let collectionView = NSCollectionView() |
| | | private let gridLayout = AppGridContainerCollectionLayout() |
| | | private weak var coordinator: AppGridCollectionView.Coordinator? |
| | |
| | | private var lastLayoutSize: NSSize = .zero |
| | | private var lastReportedBoundsOrigin: NSPoint? |
| | | private var scrollUnfreezeWorkItem: DispatchWorkItem? |
| | | private var scrollActivityIsActive = false |
| | | |
| | | override var isFlipped: Bool { true } |
| | | |
| | |
| | | NotificationCenter.default.removeObserver(scrollObserver) |
| | | } |
| | | scrollUnfreezeWorkItem?.cancel() |
| | | AppDragCoordinator.shared.unregisterEmptyDropTarget(id: emptyDropTargetID) |
| | | } |
| | | |
| | | func configure(coordinator: AppGridCollectionView.Coordinator) { |
| | |
| | | } |
| | | } |
| | | |
| | | override func viewDidMoveToWindow() { |
| | | super.viewDidMoveToWindow() |
| | | if window == nil { |
| | | AppDragCoordinator.shared.unregisterEmptyDropTarget(id: emptyDropTargetID) |
| | | } else { |
| | | AppDragCoordinator.shared.registerEmptyDropTarget(id: emptyDropTargetID, view: self) |
| | | } |
| | | } |
| | | |
| | | func performEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool) { |
| | | guard let coordinator, |
| | | coordinator.displayStyle != .flat |
| | | else { return } |
| | | DispatchQueue.main.async { |
| | | coordinator.onDropOutsideGroup(path, source, copy) |
| | | } |
| | | } |
| | | |
| | | private func setup() { |
| | | wantsLayer = true |
| | | layer?.backgroundColor = NSColor.clear.cgColor |
| | |
| | | } |
| | | |
| | | private func handleScrollActivity() { |
| | | if coordinator?.setScrollBubbleDisabled(true) == true { |
| | | refreshVisibleRuntimeState() |
| | | if !scrollActivityIsActive { |
| | | scrollActivityIsActive = true |
| | | if coordinator?.setScrollBubbleDisabled(true) == true { |
| | | refreshVisibleRuntimeState() |
| | | } |
| | | coordinator?.onScrollActivity() |
| | | } |
| | | coordinator?.onScrollActivity() |
| | | |
| | | scrollUnfreezeWorkItem?.cancel() |
| | | let workItem = DispatchWorkItem { [weak self] in |
| | | guard let self else { return } |
| | | self.scrollActivityIsActive = false |
| | | self.replayPointerHover() |
| | | if self.coordinator?.setScrollBubbleDisabled(false) == true { |
| | | self.refreshVisibleRuntimeState() |
| | |
| | | guard let coordinator, let group else { return } |
| | | let displayStyle = coordinator.displayStyle |
| | | let tagColor = TagColor.nsColor(for: coordinator.tagColors[group.name] ?? 0) |
| | | let isNavigationHighlighted = coordinator.highlightedGroupName == group.name |
| | | let isColorlessActive = coordinator.isColorlessContainerMode |
| | | && (isHovered || coordinator.highlightedGroupName == group.name) |
| | | && (isHovered || isNavigationHighlighted) |
| | | |
| | | if displayStyle.usesCardSurface { |
| | | let rect = bounds.insetBy(dx: 0.5, dy: 0.5) |
| | |
| | | |
| | | private func updateCardShadow() { |
| | | guard let coordinator else { return } |
| | | let isNavigationHighlighted = coordinator.highlightedGroupName == group?.name |
| | | let isColorlessActive = coordinator.isColorlessContainerMode |
| | | && (isHovered || coordinator.highlightedGroupName == group?.name) |
| | | && (isHovered || isNavigationHighlighted) |
| | | let shouldShadow = coordinator.displayStyle.usesCardSurface |
| | | && ((coordinator.isColoredContainerMode && isHovered) || isColorlessActive) |
| | | && ((coordinator.isColoredContainerMode && (isHovered || isNavigationHighlighted)) || isColorlessActive) |
| | | layer?.shadowColor = NSColor.black.cgColor |
| | | layer?.shadowOpacity = shouldShadow ? 0.22 : 0 |
| | | layer?.shadowRadius = shouldShadow ? 8 : 0 |