Bladeren bron

Panel quick action hover highlight

Made-with: Cursor
huzaifahayat12 3 maanden geleden
bovenliggende
commit
edb355eb13
1 gewijzigde bestanden met toevoegingen van 124 en 13 verwijderingen
  1. 124 13
      google_apps/LauncherRootView.swift

+ 124 - 13
google_apps/LauncherRootView.swift

@@ -614,6 +614,26 @@ private struct PanelAppRowView: View {
 
     @State private var isHovering = false
 
+    private var quickActions: [WidgetAction] {
+        let variants = WidgetTemplates.variants(for: app)
+        let chosen = variants.first(where: { $0.size == .medium }) ??
+        variants.first(where: { $0.size == .small }) ??
+        variants.first
+        guard let chosen else { return [] }
+
+        let maxActions: Int
+        switch chosen.size {
+        case .small: maxActions = 3
+        case .medium: maxActions = 4
+        case .large: maxActions = 8
+        }
+        return Array(chosen.actions.prefix(maxActions))
+    }
+
+    private var shouldShowQuickActions: Bool {
+        !app.isCreateNew && app.webURL != nil && !quickActions.isEmpty
+    }
+
     private var pinnedIDs: Set<UUID> {
         decodeUUIDSetGlobal(from: pinnedTileIDsData)
     }
@@ -632,20 +652,18 @@ private struct PanelAppRowView: View {
 
     var body: some View {
         ZStack(alignment: .topTrailing) {
-            Button(action: { onAppTap(app) }) {
-                HStack(spacing: 14) {
-                    AppIconGlyph(app: app)
-                    Text(app.name)
-                        .font(.system(size: 18, weight: .medium))
-                        .foregroundStyle(.white.opacity(0.94))
-                        .lineLimit(1)
-                        .truncationMode(.tail)
-                    Spacer(minLength: 0)
-                }
-                .frame(maxWidth: .infinity, alignment: .leading)
-                .contentShape(Rectangle())
+            HStack(spacing: 14) {
+                AppIconGlyph(app: app)
+                Text(app.name)
+                    .font(.system(size: 18, weight: .medium))
+                    .foregroundStyle(.white.opacity(0.94))
+                    .lineLimit(1)
+                    .truncationMode(.tail)
+                Spacer(minLength: 0)
             }
-            .buttonStyle(.plain)
+            .frame(maxWidth: .infinity, alignment: .leading)
+            .contentShape(Rectangle())
+            .onTapGesture { onAppTap(app) }
 
             if isPinned && !app.isCreateNew {
                 Image(systemName: "pin.fill")
@@ -666,6 +684,21 @@ private struct PanelAppRowView: View {
                     .allowsHitTesting(false)
             }
 
+            if shouldShowQuickActions {
+                HStack(spacing: 8) {
+                    ForEach(quickActions) { action in
+                        PanelQuickActionButton(
+                            action: action,
+                            isRowHovering: isHovering,
+                            onTap: { openWidgetAction(action) }
+                        )
+                    }
+                }
+                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
+                .padding(.trailing, 34)
+                .padding(.top, 18)
+            }
+
             if !app.isCreateNew {
                 Menu {
                     Button(isPinned ? "Unpin" : "Pin") { togglePin() }
@@ -783,6 +816,25 @@ private struct PanelAppRowView: View {
         )
     }
 
+    private func openWidgetAction(_ action: WidgetAction) {
+        guard let base = app.webURL else { return }
+        let target = resolvedURL(base: base, actionPath: action.urlPath)
+        InAppBrowserWindowManager.shared.open(url: target, title: app.name)
+    }
+
+    private func resolvedURL(base: URL, actionPath: String?) -> URL {
+        guard let actionPath, !actionPath.isEmpty else { return base }
+        if actionPath.hasPrefix("http://") || actionPath.hasPrefix("https://") {
+            return URL(string: actionPath) ?? base
+        }
+        if actionPath.hasPrefix("/") {
+            var c = URLComponents(url: base, resolvingAgainstBaseURL: false)
+            c?.path = actionPath
+            return c?.url ?? base
+        }
+        return URL(string: actionPath, relativeTo: base)?.absoluteURL ?? base
+    }
+
     private func addToDesktop() {
         guard let webURL = app.webURL else {
             showAlert(title: "Unavailable", message: "Desktop shortcuts need a web address.")
@@ -801,6 +853,65 @@ private struct PanelAppRowView: View {
     }
 }
 
+private struct PanelQuickActionButton: View {
+    let action: WidgetAction
+    let isRowHovering: Bool
+    let onTap: () -> Void
+
+    @State private var isHovering = false
+
+    // Only highlight the hovered icon (not the whole row).
+    private var highlight: Bool { isHovering }
+
+    var body: some View {
+        Button(action: onTap) {
+            Image(systemName: action.systemImage)
+                .font(.system(size: 12, weight: .semibold))
+                .foregroundStyle(.white.opacity(0.82))
+                .frame(width: 24, height: 24)
+                .background(
+                    Circle()
+                        .fill(Color.black.opacity(highlight ? 0.48 : 0.22))
+                )
+                .overlay(
+                    Circle()
+                        .stroke(Color.white.opacity(highlight ? 0.18 : 0.10), lineWidth: 1)
+                )
+                .shadow(color: .black.opacity(highlight ? 0.28 : 0.20), radius: highlight ? 12 : 8, x: 0, y: 4)
+        }
+        .buttonStyle(.plain)
+        .scaleEffect(highlight ? 1.08 : 1.0)
+        .animation(.spring(response: 0.28, dampingFraction: 0.85), value: highlight)
+        .overlay(alignment: .top) {
+            Text(action.title)
+                .font(.system(size: 10, weight: .semibold))
+                .foregroundStyle(.white.opacity(0.96))
+                .lineLimit(1)
+                .fixedSize(horizontal: true, vertical: false)
+                .padding(.horizontal, 7)
+                .padding(.vertical, 3)
+                .background(
+                    Capsule(style: .continuous)
+                        .fill(Color.black.opacity(0.78))
+                )
+                .overlay(
+                    Capsule(style: .continuous)
+                        .stroke(Color.white.opacity(0.12), lineWidth: 1)
+                )
+                .shadow(color: .black.opacity(0.32), radius: 7, x: 0, y: 3)
+                // Keep the title close to the icon.
+                .offset(y: -22)
+                .opacity(isHovering ? 1 : 0)
+                .scaleEffect(isHovering ? 1 : 0.88, anchor: .bottom)
+                .offset(y: isHovering ? 0 : 5)
+                .animation(.spring(response: 0.28, dampingFraction: 0.82), value: isHovering)
+                .allowsHitTesting(false)
+        }
+        .onHover { isHovering = $0 }
+        .accessibilityLabel(action.title)
+    }
+}
+
 private struct AppIconGlyph: View {
     let app: LauncherApp