Parcourir la source

Fix Widgets window layout: cap size and scale preview cards

Keep the Widgets library window from growing off-screen by bounding
maxSize to the visible display and aligning the initial frame with the
default size. Replace horizontal scrolling with a uniform layout scale
computed from the panel width so multiple wide previews fit without
changing the window. Centralize preview dimensions on WidgetVariant.

Made-with: Cursor
huzaifahayat12 il y a 3 mois
Parent
commit
9f58fabafc

+ 29 - 19
google_apps/Widgets/WidgetPreviewCard.swift

@@ -1,22 +1,10 @@
 import SwiftUI
 
-struct WidgetPreviewCard: View {
-    let app: LauncherApp
-    let variant: WidgetVariant
-
-    var body: some View {
-        DesktopWidgetView(app: app, variant: variant, isPreview: true)
-            .frame(width: previewSize.width, height: previewSize.height)
-            .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
-            .overlay(
-                RoundedRectangle(cornerRadius: 18, style: .continuous)
-                    .stroke(Color.white.opacity(0.10), lineWidth: 1)
-            )
-    }
-
-    private var previewSize: CGSize {
-        if variant.layoutMode == .interactiveMap {
-            switch variant.size {
+extension WidgetVariant {
+    /// Design-time preview dimensions (used for layout in the Widgets library and on cards).
+    var previewCardSize: CGSize {
+        if layoutMode == .interactiveMap {
+            switch size {
             case .small:
                 return CGSize(width: 280, height: 320)
             case .medium:
@@ -25,10 +13,10 @@ struct WidgetPreviewCard: View {
                 return CGSize(width: 440, height: 420)
             }
         }
-        if let override = variant.layoutMode.widgetPreviewCardSize() {
+        if let override = layoutMode.widgetPreviewCardSize() {
             return override
         }
-        switch variant.size {
+        switch size {
         case .small:
             return CGSize(width: 190, height: 190)
         case .medium:
@@ -39,6 +27,28 @@ struct WidgetPreviewCard: View {
     }
 }
 
+struct WidgetPreviewCard: View {
+    let app: LauncherApp
+    let variant: WidgetVariant
+    /// Uniform scale so multiple previews fit the library panel without widening the window.
+    var layoutScale: CGFloat = 1
+
+    private var previewSize: CGSize { variant.previewCardSize }
+
+    var body: some View {
+        let s = max(0.01, layoutScale)
+        DesktopWidgetView(app: app, variant: variant, isPreview: true)
+            .frame(width: previewSize.width, height: previewSize.height)
+            .scaleEffect(s)
+            .frame(width: previewSize.width * s, height: previewSize.height * s)
+            .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
+            .overlay(
+                RoundedRectangle(cornerRadius: 18, style: .continuous)
+                    .stroke(Color.white.opacity(0.10), lineWidth: 1)
+            )
+    }
+}
+
 #if DEBUG
 #Preview {
     ZStack {

+ 41 - 14
google_apps/Widgets/WidgetsRootView.swift

@@ -130,18 +130,7 @@ struct WidgetsRootView: View {
                     .foregroundStyle(secondaryTextColor)
                     .padding(.top, 4)
 
-                HStack(alignment: .top, spacing: 18) {
-                    ForEach(selectedVariants) { variant in
-                        WidgetPreviewAddCard(
-                            app: selectedApp,
-                            variant: variant,
-                            isAdded: isWidgetAdded(appID: selectedApp.id, variantID: variant.id),
-                            isPremiumUnlocked: premiumStore.isPremiumUnlocked,
-                            onToggle: { handleWidgetTap(app: selectedApp, variant: variant) }
-                        )
-                    }
-                    Spacer(minLength: 0)
-                }
+                widgetPreviewPickerRow(app: selectedApp, variants: selectedVariants)
             } else {
                 emptyState
             }
@@ -160,6 +149,43 @@ struct WidgetsRootView: View {
         )
     }
 
+    @ViewBuilder
+    private func widgetPreviewPickerRow(app: LauncherApp, variants: [WidgetVariant]) -> some View {
+        if variants.isEmpty {
+            Color.clear.frame(height: 0)
+        } else {
+            let rowHeightAtFullScale = variants.map(\.previewCardSize.height).max() ?? 220
+            GeometryReader { geo in
+                let spacing: CGFloat = 18
+                /// Space for floating + / − controls that sit slightly outside the card clip.
+                let horizontalChrome: CGFloat = 28
+                let count = variants.count
+                let sumWidths = variants.reduce(CGFloat(0)) { $0 + $1.previewCardSize.width }
+                let rawRowWidth = sumWidths + CGFloat(max(count - 1, 0)) * spacing
+                let available = max(geo.size.width - horizontalChrome, 50)
+                let unclampedScale = available / max(rawRowWidth, 1)
+                let minLayoutScale: CGFloat = 0.38
+                let scale = max(minLayoutScale, min(1, unclampedScale))
+
+                HStack(alignment: .top, spacing: spacing) {
+                    ForEach(variants) { variant in
+                        WidgetPreviewAddCard(
+                            app: app,
+                            variant: variant,
+                            layoutScale: scale,
+                            isAdded: isWidgetAdded(appID: app.id, variantID: variant.id),
+                            isPremiumUnlocked: premiumStore.isPremiumUnlocked,
+                            onToggle: { handleWidgetTap(app: app, variant: variant) }
+                        )
+                    }
+                    Spacer(minLength: 0)
+                }
+                .frame(width: geo.size.width, height: geo.size.height, alignment: .topLeading)
+            }
+            .frame(height: rowHeightAtFullScale)
+        }
+    }
+
     private func header(for app: LauncherApp) -> some View {
         HStack(spacing: 12) {
             AppIconView(app: app, size: 42, showAppBackground: false, iconPaddingFactor: 0.1)
@@ -345,6 +371,7 @@ private struct WidgetSidebarRow: View {
 private struct WidgetPreviewAddCard: View {
     let app: LauncherApp
     let variant: WidgetVariant
+    var layoutScale: CGFloat = 1
     let isAdded: Bool
     let isPremiumUnlocked: Bool
     let onToggle: () -> Void
@@ -354,7 +381,7 @@ private struct WidgetPreviewAddCard: View {
     var body: some View {
         ZStack(alignment: .topTrailing) {
             Button(action: onToggle) {
-                WidgetPreviewCard(app: app, variant: variant)
+                WidgetPreviewCard(app: app, variant: variant, layoutScale: layoutScale)
                     .contentShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
             }
             .buttonStyle(.plain)
@@ -374,7 +401,7 @@ private struct WidgetPreviewAddCard: View {
                     .contentShape(Circle())
             }
             .buttonStyle(.plain)
-            .offset(x: 10, y: -10)
+            .offset(x: 10 * layoutScale, y: -10 * layoutScale)
             .opacity(isAdded ? 1 : (hovering ? 1 : 0))
             .animation(.easeOut(duration: 0.12), value: hovering)
         }

+ 10 - 1
google_apps/Widgets/WidgetsWindowManager.swift

@@ -15,6 +15,7 @@ final class WidgetsWindowManager {
                 hosting.rootView = WidgetsWindowRootView(apps: apps, selectedAppID: selectedAppID)
             }
             if let window = controller.window {
+                applyScreenBoundedMaxSize(to: window)
                 resizeAndCenter(window: window)
             }
             controller.showWindow(nil)
@@ -27,7 +28,7 @@ final class WidgetsWindowManager {
         let hosting = NSHostingController(rootView: root)
 
         let window = NSWindow(
-            contentRect: NSRect(x: 0, y: 0, width: 980, height: 680),
+            contentRect: NSRect(x: 0, y: 0, width: defaultWindowSize.width, height: defaultWindowSize.height),
             styleMask: [.titled, .closable, .miniaturizable],
             backing: .buffered,
             defer: false
@@ -36,6 +37,7 @@ final class WidgetsWindowManager {
         window.contentViewController = hosting
         window.isReleasedWhenClosed = false
         window.minSize = NSSize(width: 900, height: 620)
+        applyScreenBoundedMaxSize(to: window)
         window.standardWindowButton(.zoomButton)?.isHidden = true
         resizeAndCenter(window: window)
 
@@ -58,6 +60,13 @@ final class WidgetsWindowManager {
         let frame = NSRect(x: x, y: y, width: clampedWidth, height: clampedHeight)
         window.setFrame(frame, display: true)
     }
+
+    /// Keeps the Widgets window from growing past the visible screen when hosted SwiftUI reports a wide fitting size.
+    private func applyScreenBoundedMaxSize(to window: NSWindow) {
+        let screenFrame = (window.screen ?? NSScreen.main)?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1440, height: 900)
+        window.maxSize = NSSize(width: max(window.minSize.width, screenFrame.width),
+                                height: max(window.minSize.height, screenFrame.height))
+    }
 }
 
 private struct WidgetsWindowRootView: View {