From d5cf4d94edbe72d53fb417bf32105c2258bce924 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 14 Jun 2026 16:02:13 +0800
Subject: [PATCH] Fix horizontal App hover linkage
---
C3.tools/round1-app-shelf-fixture-qa.sh | 1
C3.tools/round1-horizontal-waterfall-fixture-qa.sh | 35 +++++++++++++++++
C1.source/Sources/Aligner/QuickSwitchRootView.swift | 46 +++++++++++++++++++----
C1.source/Resources/Aligner-Info.plist | 4 +-
C3.tools/round1-three-zone-linkage-fixture-qa.sh | 1
5 files changed, 77 insertions(+), 10 deletions(-)
diff --git a/C1.source/Resources/Aligner-Info.plist b/C1.source/Resources/Aligner-Info.plist
index 03cfa10..5c260c2 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.66</string>
+ <string>0.0.67</string>
<key>CFBundleVersion</key>
- <string>20260613.2043</string>
+ <string>20260614.1559</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 c04faf7..40611ae 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -3340,6 +3340,14 @@
)
}
+ private var appIconHoverLinkedAppGroupIndex: Int? {
+ if let debugHoveredAppGroupIndex {
+ return debugHoveredAppGroupIndex
+ }
+ guard case .app(let appGroupIndex) = hoverTarget else { return nil }
+ return appGroupIndex
+ }
+
private var appHoverAssociatedSpaceLabels: [String] {
guard !appHoverAssociatedSpaceIDs.isEmpty else { return [] }
return currentViewModel?.displays
@@ -3767,10 +3775,13 @@
let bounds = card.containerLayer.bounds
let isSelected = card.item.window.id == effectiveSelection?.windowID
let isHovered = card.item.window.id == hoveredWindowID
+ let isAppLinked = isHorizontalMasonryMode
+ && card.item.appGroupIndex == appIconHoverLinkedAppGroupIndex
applyWaterfallCardVisualState(
card,
selected: shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered),
- hovered: isHovered
+ hovered: isHovered,
+ appLinked: isAppLinked
)
let outerInset: CGFloat = 0
@@ -3833,12 +3844,19 @@
)
}
- private func applyWaterfallCardVisualState(_ card: WaterfallCardLayers, selected: Bool, hovered: Bool) {
+ private func applyWaterfallCardVisualState(
+ _ card: WaterfallCardLayers,
+ selected: Bool,
+ hovered: Bool,
+ appLinked: Bool
+ ) {
let isSpaceFocused = isWaterfallCardSpaceFocused(card)
card.containerLayer.backgroundColor = (selected
? NSColor.windowBackgroundColor.withAlphaComponent(0.94)
: hovered
? accentTintedWindowBackground(fraction: 0.09, alpha: 0.95)
+ : appLinked
+ ? accentTintedWindowBackground(fraction: 0.08, alpha: 0.93)
: isSpaceFocused
? accentTintedWindowBackground(fraction: 0.10, alpha: 0.94)
: NSColor.windowBackgroundColor.withAlphaComponent(0.82)
@@ -3847,27 +3865,31 @@
? NSColor.controlAccentColor.withAlphaComponent(0.52)
: hovered
? NSColor.controlAccentColor.withAlphaComponent(0.44)
+ : appLinked
+ ? NSColor.controlAccentColor.withAlphaComponent(0.38)
: isSpaceFocused
? NSColor.controlAccentColor.withAlphaComponent(0.42)
: NSColor.separatorColor.withAlphaComponent(0.28)
).cgColor
- card.containerLayer.borderWidth = (selected || hovered || isSpaceFocused) ? 1.5 : 1
+ card.containerLayer.borderWidth = (selected || hovered || appLinked || isSpaceFocused) ? 1.5 : 1
card.containerLayer.shadowColor = NSColor.black.cgColor
- card.containerLayer.shadowOpacity = selected ? 0.24 : hovered ? 0.14 : isSpaceFocused ? 0.20 : 0
- card.containerLayer.shadowRadius = selected ? 22 : hovered ? 17 : isSpaceFocused ? 24 : 0
+ card.containerLayer.shadowOpacity = selected ? 0.24 : hovered ? 0.14 : appLinked ? 0.16 : isSpaceFocused ? 0.20 : 0
+ card.containerLayer.shadowRadius = selected ? 22 : hovered ? 17 : appLinked ? 18 : isSpaceFocused ? 24 : 0
card.containerLayer.shadowOffset = selected
? CGSize(width: 0, height: -6)
: hovered
? CGSize(width: 0, height: -2.5)
+ : appLinked
+ ? CGSize(width: 0, height: -3)
: isSpaceFocused
? CGSize(width: 0, height: -5)
: .zero
- card.containerLayer.zPosition = selected ? 20 : hovered ? 10 : isSpaceFocused ? 8 : 0
+ card.containerLayer.zPosition = selected ? 20 : hovered ? 12 : appLinked ? 10 : isSpaceFocused ? 8 : 0
let focusedScale: CGFloat = selected ? 1.015 : isSpaceFocused ? 1.012 : 1.008
card.containerLayer.transform = (selected || hovered || isSpaceFocused)
? CATransform3DMakeScale(focusedScale, focusedScale, 1)
: CATransform3DIdentity
- card.containerLayer.shadowPath = (selected || hovered || isSpaceFocused)
+ card.containerLayer.shadowPath = (selected || hovered || appLinked || isSpaceFocused)
? CGPath(
roundedRect: card.containerLayer.bounds,
cornerWidth: card.containerLayer.cornerRadius,
@@ -3875,7 +3897,9 @@
transform: nil
)
: nil
- card.titleBarLayer.backgroundColor = NSColor.clear.cgColor
+ card.titleBarLayer.backgroundColor = appLinked
+ ? spaceLaneOccupiedColor.withAlphaComponent(0.18).cgColor
+ : NSColor.clear.cgColor
card.titleBarLayer.borderColor = NSColor.clear.cgColor
card.titleBarLayer.borderWidth = 0
card.shineLayer.opacity = selected ? 1 : 0
@@ -5002,6 +5026,7 @@
"shadowRadius": Double(card.containerLayer.shadowRadius),
"thumbnailFrame": dictionary(from: card.thumbnailLayer.frame),
"titleBarFrame": dictionary(from: card.titleBarLayer.frame),
+ "titleBarBackgroundColor": colorDictionary(from: card.titleBarLayer.backgroundColor),
"titleBarBorderWidth": Double(card.titleBarLayer.borderWidth),
"titleFrame": dictionary(from: card.titleLayer.frame),
"metaFrame": dictionary(from: card.metaLayer.frame),
@@ -5109,6 +5134,8 @@
var states: [String] = []
let isSelected = card.item.window.id == effectiveSelection?.windowID
let isHovered = card.item.window.id == hoveredWindowID
+ let isAppLinked = isHorizontalMasonryMode
+ && card.item.appGroupIndex == appIconHoverLinkedAppGroupIndex
if shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered) {
states.append("selected")
} else if isSelected {
@@ -5117,6 +5144,9 @@
if isHovered {
states.append("hover")
}
+ if isAppLinked {
+ states.append("appLinked")
+ }
if isWaterfallCardSpaceFocused(card) {
states.append("spaceFocused")
}
diff --git a/C3.tools/round1-app-shelf-fixture-qa.sh b/C3.tools/round1-app-shelf-fixture-qa.sh
index bd4716f..59446ee 100755
--- a/C3.tools/round1-app-shelf-fixture-qa.sh
+++ b/C3.tools/round1-app-shelf-fixture-qa.sh
@@ -214,6 +214,7 @@
--round01-open-quick-switch
--round01-fixture-app-count="$app_count"
--round01-disable-screenshot-refresh
+ --round01-waterfall-view-mode=vertical
--round01-debug-hover-app-index="$hover_index"
--round01-quick-switch-report="$report"
)
diff --git a/C3.tools/round1-horizontal-waterfall-fixture-qa.sh b/C3.tools/round1-horizontal-waterfall-fixture-qa.sh
index a441016..5463421 100755
--- a/C3.tools/round1-horizontal-waterfall-fixture-qa.sh
+++ b/C3.tools/round1-horizontal-waterfall-fixture-qa.sh
@@ -122,6 +122,7 @@
require(root.get("horizontalMasonryMaxScrollOffset", 0) > 0, "horizontal masonry must expose positive vertical max scroll offset")
require(root.get("horizontalMasonryScrollOffset", 0) > 0, "hovering a later App must anchor masonry vertically")
require(root.get("appShelfHoveredIndex") == hover_app_index, "debug hover must mark the requested App Shelf item")
+require(root.get("hoveredAppGroupIndex") == hover_app_index, "horizontal masonry must keep the hovered App group available to Waterfall visuals")
require(cards, "horizontal masonry fixture must expose cards")
widths = [round(card.get("frame", {}).get("width", 0), 1) for card in cards]
@@ -161,6 +162,37 @@
hover_visible = hover_first_card.get("visibleFrame", {})
require(hover_visible.get("y", -9999) < waterfall_height + 1, "hovered App first card should be brought into vertical viewport")
require(hover_visible.get("y", 0) + hover_visible.get("height", 0) > -1, "hovered App first card should not be fully below viewport")
+hover_cards = hover_column.get("cards", [])
+non_hover_cards = [
+ card
+ for column in columns
+ if column.get("appGroupIndex") != hover_app_index
+ for card in column.get("cards", [])
+]
+blue_reference = next(
+ (
+ color
+ for segment in root.get("spaceLaneSegments", [])
+ for color in segment.get("windowBlockColors", [])
+ if color.get("alpha", 0) > 0
+ ),
+ None
+)
+require(blue_reference is not None, "fixture must expose a Space Lane blue reference color")
+require(all("appLinked" in card.get("visualStates", []) for card in hover_cards), "all cards for the hovered App must expose appLinked visual state")
+require(all("appLinked" not in card.get("visualStates", []) for card in non_hover_cards), "non-hovered App cards must not expose appLinked visual state")
+require(all(card.get("shadowOpacity", 0) >= 0.15 for card in hover_cards), "hovered App cards must float with visible shadow opacity")
+require(all(card.get("shadowRadius", 0) >= 18 for card in hover_cards), "hovered App cards must float with visible shadow radius")
+require(all(card.get("shadowOpacity", 1) <= 0.01 for card in non_hover_cards if "spaceFocused" not in card.get("visualStates", []) and "hover" not in card.get("visualStates", []) and "selected" not in card.get("visualStates", [])), "normal non-hovered cards must not inherit App hover shadow")
+for card in hover_cards:
+ title_fill = card.get("titleBarBackgroundColor", {})
+ require(title_fill.get("alpha", 0) >= 0.16, "hovered App card title bar must get a visible blue fill")
+ for channel in ("red", "green", "blue"):
+ require(
+ abs(title_fill.get(channel, -1) - blue_reference.get(channel, -2)) <= 0.002,
+ f"hovered App card title bar must use the Space Lane blue reference for {channel}"
+ )
+require(all(card.get("titleBarBackgroundColor", {}).get("alpha", 1) <= 0.01 for card in non_hover_cards), "non-hovered App card title bars must stay clear")
print(json.dumps({
"mode": root.get("waterfallViewMode"),
@@ -170,6 +202,9 @@
"cardHeight": heights[0],
"thumbnailHeight": thumbnail_heights[0],
"cardStep": expected_card_step,
+ "hoveredAppGroupIndex": root.get("hoveredAppGroupIndex"),
+ "hoveredAppLinkedCardCount": len(hover_cards),
+ "hoveredAppTitleBarFill": hover_cards[0].get("titleBarBackgroundColor"),
"horizontalMasonryScrollOffset": root.get("horizontalMasonryScrollOffset"),
"horizontalMasonryMaxScrollOffset": root.get("horizontalMasonryMaxScrollOffset")
}, indent=2, ensure_ascii=False))
diff --git a/C3.tools/round1-three-zone-linkage-fixture-qa.sh b/C3.tools/round1-three-zone-linkage-fixture-qa.sh
index 8486406..af79f00 100755
--- a/C3.tools/round1-three-zone-linkage-fixture-qa.sh
+++ b/C3.tools/round1-three-zone-linkage-fixture-qa.sh
@@ -131,6 +131,7 @@
--round01-debug-overlay-width="$OVERLAY_WIDTH"
--round01-disable-screenshot-refresh
--round01-debug-window-activation
+ --round01-waterfall-view-mode=vertical
--round01-debug-mouse-sequence="$sequence"
--round01-quick-switch-report="$report"
)
--
Gitblit v1.9.3