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

---
 C1.source/Sources/Aligner/QuickSwitchRootView.swift |  107 +++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 86 insertions(+), 21 deletions(-)

diff --git a/C1.source/Sources/Aligner/QuickSwitchRootView.swift b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
index 4ea3319..40611ae 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -1347,6 +1347,15 @@
             if let card = waterfallCard(appGroupIndex: appGroupIndex, windowIndex: windowIndex) {
                 clickWindowCard(card)
             }
+        case "click-card-id" where parts.count == 2:
+            guard let windowID = UInt32(parts[1]) else {
+                recordMouseCommand("invalid:\(command)")
+                return
+            }
+            recordMouseCommand("click-card-id:\(windowID)")
+            if let card = waterfallCard(windowID: windowID) {
+                clickWindowCard(card)
+            }
         case "hover-card-close" where parts.count == 3:
             guard let appGroupIndex = Int(parts[1]), let windowIndex = Int(parts[2]) else {
                 recordMouseCommand("invalid:\(command)")
@@ -3085,15 +3094,25 @@
             .first(where: { $0.item.windowIndex == windowIndex })
     }
 
+    private func waterfallCard(windowID: UInt32) -> WaterfallCardLayers? {
+        waterfallColumns
+            .lazy
+            .flatMap(\.cards)
+            .first(where: { $0.item.window.id == windowID })
+    }
+
     private func layoutSpaceLaneSegmentContents(_ segment: SpaceLaneSegmentLayers) {
         let bounds = segment.containerLayer.bounds
+        let splitAppNames = splitViewAppNames(for: segment.space)
+        let isSplitView = splitAppNames.count >= 2
         segment.labelLayer.frame = CGRect(x: 10, y: bounds.height - 29, width: max(1, bounds.width - 48), height: 20)
         segment.countLayer.frame = CGRect(x: bounds.width - 34, y: bounds.height - 29, width: 24, height: 20)
+        segment.countLayer.string = isSplitView ? "" : "\(segment.space.windowCount)"
+        segment.countLayer.isHidden = isSplitView
         if segment.space.type == .fullscreen {
             let markerFrame = fullscreenMarkerFrame(in: bounds)
-            let splitAppNames = splitViewAppNames(for: segment.space)
-            segment.appLayer.isHidden = splitAppNames.count >= 2
-            if splitAppNames.count >= 2 {
+            segment.appLayer.isHidden = isSplitView
+            if isSplitView {
                 let labelY = markerFrame.midY - 8.5
                 let halfWidth = markerFrame.width / 2
                 for (index, splitAppLayer) in segment.splitAppLayers.enumerated() {
@@ -3128,7 +3147,7 @@
             layoutFullscreenMarkerLayer(
                 fullscreenMarkerLayer,
                 in: bounds,
-                isSplitView: splitViewAppNames(for: segment.space).count >= 2
+                isSplitView: isSplitView
             )
         }
 
@@ -3319,6 +3338,14 @@
                 .cards
                 .compactMap(\.item.primarySpaceID) ?? []
         )
+    }
+
+    private var appIconHoverLinkedAppGroupIndex: Int? {
+        if let debugHoveredAppGroupIndex {
+            return debugHoveredAppGroupIndex
+        }
+        guard case .app(let appGroupIndex) = hoverTarget else { return nil }
+        return appGroupIndex
     }
 
     private var appHoverAssociatedSpaceLabels: [String] {
@@ -3748,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
@@ -3814,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)
@@ -3828,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,
@@ -3856,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
@@ -4740,17 +4783,11 @@
 
         let bounds = shapeLayer.bounds
         if isSplitView {
-            let insetBounds = bounds.insetBy(dx: 0.6, dy: 0.6)
-            let radius = min(7, max(5, insetBounds.height * 0.22))
             let path = CGMutablePath()
-            path.addRoundedRect(
-                in: insetBounds,
-                cornerWidth: radius,
-                cornerHeight: radius
-            )
-            path.move(to: CGPoint(x: bounds.midX, y: insetBounds.minY + 1.5))
-            path.addLine(to: CGPoint(x: bounds.midX, y: insetBounds.maxY - 1.5))
+            path.move(to: CGPoint(x: bounds.midX, y: bounds.minY))
+            path.addLine(to: CGPoint(x: bounds.midX, y: bounds.maxY))
             shapeLayer.path = path
+            shapeLayer.lineCap = .butt
             return
         }
 
@@ -4774,6 +4811,7 @@
         path.addLine(to: CGPoint(x: 0, y: bounds.maxY - cornerLength))
 
         shapeLayer.path = path
+        shapeLayer.lineCap = .round
     }
 
     private func fullscreenMarkerFrame(in segmentBounds: CGRect) -> CGRect {
@@ -4988,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),
@@ -5095,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 {
@@ -5102,6 +5143,9 @@
         }
         if isHovered {
             states.append("hover")
+        }
+        if isAppLinked {
+            states.append("appLinked")
         }
         if isWaterfallCardSpaceFocused(card) {
             states.append("spaceFocused")
@@ -5176,6 +5220,10 @@
                         markerLayer: segment.fullscreenMarkerLayer,
                         splitAppNames: splitAppNames
                     ),
+                    "fullscreenMarkerPathStyle": fullscreenMarkerPathStyle(
+                        markerLayer: segment.fullscreenMarkerLayer,
+                        splitAppNames: splitAppNames
+                    ),
                     "fullscreenMarkerFrame": fullscreenMarkerFrame.map { dictionary(from: $0) } ?? NSNull(),
                     "fullscreenMarkerCenterRatio": fullscreenMarkerCenterRatio ?? NSNull(),
                     "splitViewAppNames": splitAppNames,
@@ -5187,6 +5235,8 @@
                     "windowBlockColors": segment.windowBlocks.map { colorDictionary(from: $0.backgroundColor) },
                     "windowBlockFrames": segment.windowBlocks.map { dictionary(from: $0.frame) },
                     "labelColor": colorDictionary(from: segment.labelLayer.foregroundColor),
+                    "countText": string(from: segment.countLayer.string),
+                    "countVisible": !segment.countLayer.isHidden && segment.countLayer.opacity > 0,
                     "countColor": colorDictionary(from: segment.countLayer.foregroundColor),
                     "appColor": colorDictionary(from: segment.appLayer.foregroundColor),
                     "fullscreenMarkerStrokeColor": colorDictionary(
@@ -5229,6 +5279,11 @@
     private func fullscreenMarkerKind(markerLayer: CALayer?, splitAppNames: [String]) -> String {
         guard markerLayer != nil else { return "none" }
         return splitAppNames.count >= 2 ? "splitViewPair" : "cornerBracket"
+    }
+
+    private func fullscreenMarkerPathStyle(markerLayer: CALayer?, splitAppNames: [String]) -> String {
+        guard markerLayer != nil else { return "none" }
+        return splitAppNames.count >= 2 ? "centerDividerOnly" : "cornerBracket"
     }
 
     private func visualStates(for space: QuickSwitchSpaceViewModel) -> [String] {
@@ -5282,6 +5337,16 @@
         ]
     }
 
+    private func string(from textLayerValue: Any?) -> String {
+        if let string = textLayerValue as? String {
+            return string
+        }
+        if let attributedString = textLayerValue as? NSAttributedString {
+            return attributedString.string
+        }
+        return ""
+    }
+
     private func colorDictionary(from color: CGColor?) -> [String: Double] {
         guard let color,
               let nsColor = NSColor(cgColor: color)?.usingColorSpace(.deviceRGB)

--
Gitblit v1.9.3