From 3d8d37306e39db5c8eb41fafd95a13112d573f62 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 11 Jun 2026 22:24:39 +0800
Subject: [PATCH] Center filtered Waterfall columns

---
 C3.tools/round1-space-filter-fixture-qa.sh          |    8 ++++++++
 C1.source/Sources/Aligner/QuickSwitchRootView.swift |   28 +++++++++++++++++++++++++---
 C1.source/Resources/Aligner-Info.plist              |    4 ++--
 3 files changed, 35 insertions(+), 5 deletions(-)

diff --git a/C1.source/Resources/Aligner-Info.plist b/C1.source/Resources/Aligner-Info.plist
index 5b423f8..6026e5d 100644
--- a/C1.source/Resources/Aligner-Info.plist
+++ b/C1.source/Resources/Aligner-Info.plist
@@ -17,9 +17,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-    <string>0.0.55</string>
+    <string>0.0.56</string>
 	<key>CFBundleVersion</key>
-    <string>20260611.2113</string>
+    <string>20260611.2211</string>
 	<key>LSMinimumSystemVersion</key>
 	<string>26.0</string>
 	<key>NSHighResolutionCapable</key>
diff --git a/C1.source/Sources/Aligner/QuickSwitchRootView.swift b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
index 0b4e062..4f8f371 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -1668,6 +1668,11 @@
             return
         }
 
+        if waterfallShouldKeepFilteredColumnsCentered {
+            ensureWaterfallCardVisibleVertically(selection)
+            return
+        }
+
         switch horizontalIntent {
         case .reveal:
             ensureWaterfallColumnVisible(column)
@@ -1751,6 +1756,10 @@
     }
 
     private var waterfallAlignmentScrollRange: ClosedRange<CGFloat> {
+        if waterfallShouldKeepFilteredColumnsCentered {
+            return 0...0
+        }
+
         var minimumOffset: CGFloat = 0
         var maximumOffset = waterfallMaxScrollOffset
 
@@ -1775,6 +1784,10 @@
     private func clampedWaterfallAlignmentOffset(_ offset: CGFloat) -> CGFloat {
         let range = waterfallAlignmentScrollRange
         return min(max(range.lowerBound, offset), range.upperBound)
+    }
+
+    private var waterfallShouldKeepFilteredColumnsCentered: Bool {
+        currentViewModel?.lockedSpaceID != nil && waterfallMaxScrollOffset <= 0.5
     }
 
     private func setWaterfallScrollOffset(_ offset: CGFloat, animated: Bool) {
@@ -3019,9 +3032,9 @@
         }
 
         let columnWidth = waterfallColumnWidth()
-        let totalWidth = WaterfallMetrics.contentHorizontalPadding * 2
-            + CGFloat(waterfallColumns.count) * columnWidth
+        let columnsWidth = CGFloat(waterfallColumns.count) * columnWidth
             + CGFloat(max(0, waterfallColumns.count - 1)) * WaterfallMetrics.gap
+        let totalWidth = WaterfallMetrics.contentHorizontalPadding * 2 + columnsWidth
         waterfallContentWidth = max(waterfallLayer.bounds.width, totalWidth)
         waterfallScrollOffset = clampedWaterfallAlignmentOffset(waterfallScrollOffset)
         waterfallContentLayer.frame = CGRect(
@@ -3031,7 +3044,7 @@
             height: waterfallLayer.bounds.height
         )
 
-        var x = WaterfallMetrics.contentHorizontalPadding
+        var x = waterfallColumnStartX(columnsWidth: columnsWidth)
         for column in waterfallColumns {
             column.containerLayer.frame = CGRect(
                 x: x,
@@ -3044,6 +3057,15 @@
         }
     }
 
+    private func waterfallColumnStartX(columnsWidth: CGFloat) -> CGFloat {
+        guard currentViewModel?.lockedSpaceID != nil else {
+            return WaterfallMetrics.contentHorizontalPadding
+        }
+
+        let centeredStart = (waterfallContentWidth - columnsWidth) / 2
+        return max(WaterfallMetrics.contentHorizontalPadding, centeredStart)
+    }
+
     private func layoutWaterfallColumn(_ column: WaterfallColumnLayers) {
         let bounds = column.containerLayer.bounds
         let isSpaceFocused = spaceFocusWindowCount(for: column.column.appGroupIndex) > 0
diff --git a/C3.tools/round1-space-filter-fixture-qa.sh b/C3.tools/round1-space-filter-fixture-qa.sh
index 5780deb..9ca4e13 100755
--- a/C3.tools/round1-space-filter-fixture-qa.sh
+++ b/C3.tools/round1-space-filter-fixture-qa.sh
@@ -257,6 +257,11 @@
 segments = root.get("spaceLaneSegments", [])
 cards = [card for column in columns for card in column.get("cards", [])]
 segment_by_id = {segment.get("spaceID"): segment for segment in segments}
+visible_frames = [column.get("visibleFrame", {}) for column in columns]
+visible_min_x = min(frame.get("x", 0) for frame in visible_frames)
+visible_max_x = max(frame.get("x", 0) + frame.get("width", 0) for frame in visible_frames)
+left_blank_width = max(0, visible_min_x)
+right_blank_width = max(0, root.get("waterfallVisibleWidth", 0) - visible_max_x)
 
 require(report.get("snapshotLoaded") is True, "snapshot must load")
 require(report.get("quickSwitchVisible") is True, "Quick Switch must stay visible after filtering")
@@ -268,6 +273,7 @@
 require(report.get("appShelfNames") == ["Alpha Space App", "Beta Space App"], "App Shelf must only include A1 apps")
 require(root.get("appShelfNames") == ["Alpha Space App", "Beta Space App"], "root App Shelf must only include A1 apps")
 require(root.get("waterfallColumnNames") == ["Alpha Space App", "Beta Space App"], "Waterfall must only include A1 columns")
+require(abs(left_blank_width - right_blank_width) <= 2.0, "filtered Waterfall columns must be centered as one content block")
 require(report.get("windowCount") == 3, "A1 must expose exactly three windows")
 require(len(cards) == 3, "A1 card count must be three")
 require(all(card.get("primarySpaceID") == 1 for card in cards), "every visible card must belong to A1")
@@ -299,6 +305,8 @@
     "lockedBorderWidth": segment_by_id[1].get("borderWidth"),
     "lockedShadowOpacity": segment_by_id[1].get("shadowOpacity"),
     "lockedShadowRadius": segment_by_id[1].get("shadowRadius"),
+    "waterfallLeftBlankWidth": left_blank_width,
+    "waterfallRightBlankWidth": right_blank_width,
     "fadeOutLayerCount": root.get("projectionTransitionFadeOutLayerCount"),
     "transitionDurationMilliseconds": root.get("projectionTransitionDurationMilliseconds")
 }, indent=2, ensure_ascii=False))

--
Gitblit v1.9.3