| | |
| | | } |
| | | } |
| | | |
| | | func canPreviewEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool) -> Bool { |
| | | guard let coordinator, |
| | | coordinator.displayStyle != .flat |
| | | else { return false } |
| | | return !coordinator.shouldCancelEmptyDropForActiveReorder( |
| | | path: path, |
| | | screenPoint: screenPoint, |
| | | copy: copy |
| | | ) |
| | | } |
| | | |
| | | private func setup() { |
| | | wantsLayer = true |
| | | layer?.backgroundColor = NSColor.clear.cgColor |
| | |
| | | detailScrollView.borderType = .noBorder |
| | | detailScrollView.documentView = detailLabel |
| | | |
| | | closeButton.action = { [weak self] in self?.hideUsageTips() } |
| | | closeButton.action = { [weak self] in self?.requestHideUsageTips() } |
| | | previousButton.action = { [weak self] in self?.selectUsageTip(offset: -1) } |
| | | nextButton.action = { [weak self] in self?.selectUsageTip(offset: 1) } |
| | | |
| | |
| | | applyCoordinatorState() |
| | | } |
| | | |
| | | private func hideUsageTips() { |
| | | private func requestHideUsageTips() { |
| | | Diagnostics.log("usageTips.hud.close") |
| | | clearHoverState() |
| | | isHidden = true |
| | | coordinator?.onHideUsageTips() |
| | | } |
| | | |
| | |
| | | let closeHitOutset: CGFloat = 8 |
| | | if closeButton.frame.insetBy(dx: -closeHitOutset, dy: -closeHitOutset).contains(point) { |
| | | closeButton.performPressFeedback() |
| | | hideUsageTips() |
| | | requestHideUsageTips() |
| | | return true |
| | | } |
| | | let hitOutset: CGFloat = 6 |
| | |
| | | |
| | | private struct LayoutItem { |
| | | let index: Int |
| | | let frame: NSRect |
| | | var frame: NSRect |
| | | } |
| | | |
| | | private struct LayoutPlan { |
| | |
| | | let columnWidth = floor((availableWidth - gap * CGFloat(columnCount - 1)) / CGFloat(columnCount)) |
| | | var columnHeights = Array(repeating: outerPadding, count: columnCount) |
| | | var items: [LayoutItem] = [] |
| | | var lastItemIndexForColumn = Array<Int?>(repeating: nil, count: columnCount) |
| | | let bottomPinnedGroups = groups.enumerated() |
| | | .filter { bottomPinnedPriority(for: $0.element.containerID) != nil } |
| | | .sorted { |
| | | let lhsPriority = bottomPinnedPriority(for: $0.element.containerID) ?? Int.max |
| | | let rhsPriority = bottomPinnedPriority(for: $1.element.containerID) ?? Int.max |
| | | if lhsPriority != rhsPriority { return lhsPriority < rhsPriority } |
| | | return $0.offset < $1.offset |
| | | } |
| | | let bottomPinnedIndexes = Set(bottomPinnedGroups.map { $0.offset }) |
| | | |
| | | for (index, group) in groups.enumerated() { |
| | | if bottomPinnedIndexes.contains(index) { continue } |
| | | |
| | | let columnIndex = columnHeights.indices.min { columnHeights[$0] < columnHeights[$1] } ?? 0 |
| | | let x = outerPadding + CGFloat(columnIndex) * (columnWidth + gap) |
| | | let y = columnHeights[columnIndex] |
| | |
| | | frame: NSRect(x: x, y: y, width: columnWidth, height: height) |
| | | ) |
| | | ) |
| | | lastItemIndexForColumn[columnIndex] = items.count - 1 |
| | | columnHeights[columnIndex] = y + height + gap |
| | | } |
| | | |
| | | let tallest = columnHeights.max() ?? outerPadding |
| | | let contentHeight = (groups.isEmpty ? outerPadding * 2 : tallest - gap + outerPadding) |
| | | var contentBottom = columnHeights.max() ?? outerPadding |
| | | if !bottomPinnedGroups.isEmpty { |
| | | let alignedBottom = max(outerPadding, contentBottom - gap) |
| | | for itemIndex in lastItemIndexForColumn.compactMap({ $0 }) { |
| | | let currentFrame = items[itemIndex].frame |
| | | let alignedHeight = alignedBottom - currentFrame.minY |
| | | if alignedHeight > currentFrame.height { |
| | | items[itemIndex].frame.size.height = alignedHeight |
| | | } |
| | | } |
| | | } |
| | | for (index, group) in bottomPinnedGroups { |
| | | let y = max(outerPadding, contentBottom) |
| | | let height = AppGridCollectionMetrics.cardHeight( |
| | | appCount: group.apps.count, |
| | | width: availableWidth, |
| | | iconSize: iconSize |
| | | ) |
| | | items.append( |
| | | LayoutItem( |
| | | index: index, |
| | | frame: NSRect(x: outerPadding, y: y, width: availableWidth, height: height) |
| | | ) |
| | | ) |
| | | contentBottom = y + height + gap |
| | | } |
| | | |
| | | let contentHeight = (items.isEmpty ? outerPadding * 2 : contentBottom - gap + outerPadding) |
| | | + max(0, bottomContentPadding) |
| | | return LayoutPlan( |
| | | items: items, |
| | |
| | | ) |
| | | } |
| | | |
| | | private static func bottomPinnedPriority(for containerID: String) -> Int? { |
| | | switch containerID { |
| | | case AppContainerID.uncategorized: |
| | | return 0 |
| | | case AppContainerID.appleBuiltIn: |
| | | return 1 |
| | | default: |
| | | return nil |
| | | } |
| | | } |
| | | |
| | | private struct RowItem { |
| | | let index: Int |
| | | let width: CGFloat |