From 1030214551f03d50a98e7a218648d89803c4c057 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 13 Jun 2026 15:51:51 +0800
Subject: [PATCH] Native app grid usage tips
---
src/Apptag/ContentView.swift | 206 +++++++++++---------------------------------------
1 files changed, 47 insertions(+), 159 deletions(-)
diff --git a/src/Apptag/ContentView.swift b/src/Apptag/ContentView.swift
index 122addf..ed6f932 100644
--- a/src/Apptag/ContentView.swift
+++ b/src/Apptag/ContentView.swift
@@ -340,120 +340,6 @@
let tagName: String
}
-private struct AppGridUsageTip: Identifiable {
- let id: Int
- let titleKey: String
- let detailKey: String
-}
-
-private struct AppGridUsageTipsBar: View {
- static let reservedHeight: CGFloat = 64
-
- let tips: [AppGridUsageTip]
- @Binding var selectedIndex: Int
- let dragModeActive: Bool
-
- @State private var textHovered = false
-
- private var safeIndex: Int {
- guard tips.indices.contains(selectedIndex) else { return 0 }
- return selectedIndex
- }
-
- private var currentTip: AppGridUsageTip? {
- guard !tips.isEmpty else { return nil }
- return tips[safeIndex]
- }
-
- var body: some View {
- if let tip = currentTip {
- HStack(spacing: 0) {
- Text(tr(tip.titleKey))
- .font(.system(size: 14, weight: .semibold))
- .foregroundStyle(Color.primary)
- .lineLimit(1)
- .minimumScaleFactor(0.82)
- .padding(.horizontal, 14)
- .frame(width: 174, height: 42, alignment: .leading)
- .background(Color(nsColor: .controlBackgroundColor).opacity(0.96))
-
- tipDetailText(tip)
- .frame(height: 42)
- .frame(maxWidth: .infinity, alignment: .leading)
- .background(Color.black.opacity(0.88))
-
- tipsControls
- .frame(width: 86, height: 42)
- .background(Color.black.opacity(0.88))
- }
- .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
- .overlay(
- RoundedRectangle(cornerRadius: 8, style: .continuous)
- .stroke(Color.primary.opacity(0.18), lineWidth: 1)
- )
- .shadow(color: .black.opacity(0.20), radius: 14, y: 7)
- .frame(maxWidth: 980)
- .padding(.horizontal, 18)
- .padding(.bottom, 12)
- .opacity(dragModeActive ? 0.72 : 1.0)
- .allowsHitTesting(!dragModeActive)
- .accessibilityElement(children: .combine)
- .accessibilityLabel("\(tr(tip.titleKey)) \(tr(tip.detailKey))")
- }
- }
-
- private func tipDetailText(_ tip: AppGridUsageTip) -> some View {
- ScrollView(.horizontal, showsIndicators: textHovered) {
- Text(tr(tip.detailKey))
- .font(.system(size: 15, weight: .medium))
- .foregroundStyle(Color.white.opacity(0.96))
- .lineLimit(1)
- .fixedSize(horizontal: true, vertical: false)
- .padding(.horizontal, 14)
- .frame(minHeight: 42, alignment: .center)
- }
- .onHover { textHovered = $0 }
- .help(tr(tip.detailKey))
- }
-
- private var tipsControls: some View {
- VStack(spacing: 4) {
- HStack(spacing: 8) {
- usageTipNavigationButton(systemImage: "chevron.left", labelKey: "usageTips.previous") {
- selectedIndex = (safeIndex - 1 + tips.count) % tips.count
- }
- usageTipNavigationButton(systemImage: "chevron.right", labelKey: "usageTips.next") {
- selectedIndex = (safeIndex + 1) % tips.count
- }
- }
-
- HStack(spacing: 3) {
- ForEach(tips.indices, id: \.self) { index in
- Circle()
- .fill(index == safeIndex ? Color.accentColor : Color.white.opacity(0.58))
- .frame(width: 4, height: 4)
- }
- }
- }
- }
-
- private func usageTipNavigationButton(
- systemImage: String,
- labelKey: String,
- action: @escaping () -> Void
- ) -> some View {
- Button(action: action) {
- Image(systemName: systemImage)
- .font(.system(size: 15, weight: .bold))
- .foregroundStyle(Color.white.opacity(0.96))
- .frame(width: 24, height: 22)
- .contentShape(Rectangle())
- }
- .buttonStyle(.plain)
- .accessibilityLabel(tr(labelKey))
- }
-}
-
struct ContentView: View {
let hideOverlay: () -> Void
private let initialQuickSearchSource: String?
@@ -468,6 +354,7 @@
@State private var appGridScrollTargetID: String? = nil
@State private var appGridScrollRequestToken = 0
@State private var selectedUsageTipIndex = 0
+ @State private var usageTipsHovered = false
// Edit mode
@State private var editPhase: EditPhase = .none
@@ -539,7 +426,8 @@
private let floatingControlsTrailingInset: CGFloat = 20
private let floatingControlsReservedWidth: CGFloat = 120
private var appBubbleDisabled: Bool {
- appGridInteraction.appDragModeActive
+ usageTipsHovered
+ || appGridInteraction.appDragModeActive
|| appGridInteraction.pendingUncategorizedDrop != nil
|| appGridInteraction.pendingTagRemovalDrop != nil
}
@@ -1245,50 +1133,43 @@
ProgressView().scaleEffect(0.8)
Spacer()
} else {
- ZStack(alignment: .bottom) {
- AppGridCollectionView(
- groups: displayGroups,
- tagColors: tagColors,
- displayMode: displayMode,
- iconSize: iconSize,
- showNames: !hideAppNames,
- bubbleDisabled: appBubbleDisabled,
- showUncommonAppBubbles: showUncommonAppBubbles,
- highlightedGroupName: appGridHighlightedGroupName,
- bottomContentPadding: shouldShowUsageTips ? AppGridUsageTipsBar.reservedHeight : 0,
- contentRevision: groupLayoutVersion,
- scrollTargetID: appGridScrollTargetID,
- scrollRequestToken: appGridScrollRequestToken,
- onSelectApp: { app in openApp(app) },
- onBubbleHover: handleBubbleHover,
- onEditNote: beginEditingBubbleNote,
- onDropApp: { path, source, target, copy in
- dropApp(path: path, sourceTag: source, targetTag: target, copy: copy)
- },
- onDropOutsideGroup: { path, source, copy in
- dropAppOutsideGroup(path: path, sourceTag: source, copy: copy)
- },
- onReorderApps: { containerID, orderedPaths in
- reorderApps(inContainer: containerID, orderedPaths: orderedPaths)
- },
- onGroupActivate: { groupName in
- if isColorlessContainerMode {
- toggleColorlessFill(groupName)
- }
- },
- onScrollActivity: handleAppGridScrollActivity,
- onDragModeChange: { setAppDragMode($0) }
- )
-
- if shouldShowUsageTips {
- AppGridUsageTipsBar(
- tips: appGridUsageTips,
- selectedIndex: $selectedUsageTipIndex,
- dragModeActive: appGridInteraction.appDragModeActive
- )
- .zIndex(3)
- }
- }
+ AppGridCollectionView(
+ groups: displayGroups,
+ tagColors: tagColors,
+ displayMode: displayMode,
+ iconSize: iconSize,
+ showNames: !hideAppNames,
+ bubbleDisabled: appBubbleDisabled,
+ showUncommonAppBubbles: showUncommonAppBubbles,
+ highlightedGroupName: appGridHighlightedGroupName,
+ bottomContentPadding: shouldShowUsageTips ? AppGridUsageTipsMetrics.reservedHeight : 0,
+ usageTipsVisible: shouldShowUsageTips,
+ usageTips: appGridUsageTips,
+ selectedUsageTipIndex: $selectedUsageTipIndex,
+ contentRevision: groupLayoutVersion,
+ scrollTargetID: appGridScrollTargetID,
+ scrollRequestToken: appGridScrollRequestToken,
+ onSelectApp: { app in openApp(app) },
+ onBubbleHover: handleBubbleHover,
+ onEditNote: beginEditingBubbleNote,
+ onDropApp: { path, source, target, copy in
+ dropApp(path: path, sourceTag: source, targetTag: target, copy: copy)
+ },
+ onDropOutsideGroup: { path, source, copy in
+ dropAppOutsideGroup(path: path, sourceTag: source, copy: copy)
+ },
+ onReorderApps: { containerID, orderedPaths in
+ reorderApps(inContainer: containerID, orderedPaths: orderedPaths)
+ },
+ onGroupActivate: { groupName in
+ if isColorlessContainerMode {
+ toggleColorlessFill(groupName)
+ }
+ },
+ onScrollActivity: handleAppGridScrollActivity,
+ onDragModeChange: { setAppDragMode($0) },
+ onUsageTipsHoverChange: handleUsageTipsHoverChange
+ )
}
}
}
@@ -2011,6 +1892,13 @@
}
}
+ private func handleUsageTipsHoverChange(_ hovering: Bool) {
+ usageTipsHovered = hovering
+ if hovering, appGridInteraction.hoveredBubble != nil {
+ appGridInteraction.hoveredBubble = nil
+ }
+ }
+
private func handleBubbleHover(app: AppInfo, frame: CGRect, event: AppBubbleHoverEvent) {
guard !appBubbleDisabled else {
clearAppBubbleState()
--
Gitblit v1.9.3