From 3168585c144f26bbc139b16e3553f9c2d9507696 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 25 Sep 2026 15:31:04 +0800 Subject: [PATCH 1/6] feat: complete content shape kinds --- .../Event/Responder/ContentShapeKinds.swift | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Sources/OpenSwiftUICore/Event/Responder/ContentShapeKinds.swift b/Sources/OpenSwiftUICore/Event/Responder/ContentShapeKinds.swift index 8d45a91cc..22958f28a 100644 --- a/Sources/OpenSwiftUICore/Event/Responder/ContentShapeKinds.swift +++ b/Sources/OpenSwiftUICore/Event/Responder/ContentShapeKinds.swift @@ -1,8 +1,8 @@ // // ContentShapeKinds.swift -// OpenSwiftUI +// OpenSwiftUICore // -// Audited for 6.0.87 +// Audited for 6.5.4 // Status: Complete import Foundation @@ -11,6 +11,7 @@ import Foundation /// /// The kind is used by the system to influence various effects, hit-testing, /// and more. +@available(OpenSwiftUI_v3_0, *) public struct ContentShapeKinds: OptionSet, Sendable { /// The corresponding value of the raw type. @@ -70,6 +71,15 @@ public struct ContentShapeKinds: OptionSet, Sendable { @available(watchOS, unavailable) public static let hoverEffect: ContentShapeKinds = ContentShapeKinds(rawValue: 1 << 3) + /// The kind for focus effects. + @available(iOS, unavailable) + @available(tvOS, unavailable) + @available(visionOS, unavailable) + public static let focusEffect: ContentShapeKinds = ContentShapeKinds(rawValue: 1 << 4) + + @available(tvOS, unavailable) + package static let _focusEffect: ContentShapeKinds = ContentShapeKinds(rawValue: 1 << 4) + /// The kind for accessibility visuals and sorting. /// /// Setting a content shape with this kind causes the accessibility frame @@ -81,5 +91,21 @@ public struct ContentShapeKinds: OptionSet, Sendable { /// shape of the view. /// /// To control the shape for accessibility and hit-testing, use the `interaction` kind. - public static let accessibility: ContentShapeKinds = ContentShapeKinds(rawValue: 1 << 4) + @available(OpenSwiftUI_v5_0, *) + public static let accessibility: ContentShapeKinds = ContentShapeKinds(rawValue: 1 << 6) + + package var description: String { + var result = "[ " + for (bit, name) in [ + (0, "interaction"), + (1, "dragPreview"), + (2, "contextMenuPreview"), + (3, "hoverEffect"), + (4, "focusEffect"), + (6, "accessibility"), + ] where rawValue & (1 << bit) != 0 { + result += name + " " + } + return result + "]" + } } From 4b915ed858cd2226033562aed8111554b6327b2e Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 25 Sep 2026 21:37:31 +0800 Subject: [PATCH 2/6] chore: add accessibility api --- .../Accessibility/AccessibilityGeometry.swift | 14 ++++++++++++++ .../Accessibility/AccessibilityNodeList.swift | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Sources/OpenSwiftUI/Accessibility/AccessibilityGeometry.swift b/Sources/OpenSwiftUI/Accessibility/AccessibilityGeometry.swift index 287d9f661..f00a8d0ff 100644 --- a/Sources/OpenSwiftUI/Accessibility/AccessibilityGeometry.swift +++ b/Sources/OpenSwiftUI/Accessibility/AccessibilityGeometry.swift @@ -6,9 +6,23 @@ // Status: WIP // ID: EE68159C4F54001FA5A3813EBA5DD945 (SwiftUI) +import OpenAttributeGraphShims @_spi(Private) import OpenSwiftUICore +// MARK: - ViewModifier + Accessibility Geometry [TBA] + +extension ViewModifier { + nonisolated static func makeAccessibilityGeometryTransform( + for nodeList: Attribute?, + kind: Attribute?, + inputs: _ViewInputs, + outputs: _ViewOutputs + ) -> Attribute { + _openSwiftUIUnimplementedFailure() + } +} + // MARK: - IgnoreViewRespondersModifier private struct IgnoreViewRespondersModifier: PrimitiveViewModifier, ViewInputsModifier { diff --git a/Sources/OpenSwiftUI/Accessibility/AccessibilityNodeList.swift b/Sources/OpenSwiftUI/Accessibility/AccessibilityNodeList.swift index d2370cf75..9e3686333 100644 --- a/Sources/OpenSwiftUI/Accessibility/AccessibilityNodeList.swift +++ b/Sources/OpenSwiftUI/Accessibility/AccessibilityNodeList.swift @@ -32,6 +32,20 @@ struct AccessibilityNodesKey: PreferenceKey { } } +extension PreferencesInputs { + @inline(__always) + var requiresAccessibilityNodes: Bool { + get { contains(AccessibilityNodesKey.self) } + set { + if newValue { + add(AccessibilityNodesKey.self) + } else { + remove(AccessibilityNodesKey.self) + } + } + } +} + extension _ViewOutputs { @inline(__always) var accessibilityNodes: Attribute? { From 30b25563f9bfde81598e0154476fc334924b6fc8 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 25 Sep 2026 21:40:11 +0800 Subject: [PATCH 3/6] feat: implement content shape kind modifier --- .../Responder/ContentShapeKindModifier.swift | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift diff --git a/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift b/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift new file mode 100644 index 000000000..0703728de --- /dev/null +++ b/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift @@ -0,0 +1,191 @@ +// +// ContentShapeKindModifier.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: Complete +// ID: 14064F4AE362A5AD4C34ADD19A80790A (SwiftUI) + +import Foundation +import OpenAttributeGraphShims +@_spi(ForOpenSwiftUIOnly) +public import OpenSwiftUICore + +// MARK: - View + contentShape + +@available(OpenSwiftUI_v3_0, *) +extension View { + /// Sets the content shape for the specified kinds of interactions. + /// + /// - Parameters: + /// - kind: The interactions that use this shape. + /// - shape: The shape to use for those interactions. + /// - eoFill: Whether to use the even-odd fill rule. + @inlinable + nonisolated public func contentShape( + _ kind: ContentShapeKinds, + _ shape: S, + eoFill: Bool = false + ) -> some View where S: Shape { + modifier( + _ContentShapeKindModifier( + shape: shape, + eoFill: eoFill, + kind: kind + ) + ) + } +} + +// MARK: - _ContentShapeKindModifier + +@available(OpenSwiftUI_v3_0, *) +@frozen +public struct _ContentShapeKindModifier: ViewModifier, MultiViewModifier, PrimitiveViewModifier where ContentShape: Shape { + public var shape: ContentShape + public var eoFill: Bool + public var kind: ContentShapeKinds + + @inlinable + public init(shape: ContentShape, eoFill: Bool = false, kind: ContentShapeKinds) { + self.shape = shape + self.eoFill = eoFill + self.kind = kind + } + + nonisolated public static func _makeView( + modifier: _GraphValue, + inputs: _ViewInputs, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs + ) -> _ViewOutputs { + var outputs = body(_Graph(), inputs) + if inputs.preferences.requiresViewResponders { + outputs[ViewRespondersKey.self] = Attribute(ContentShapeKindResponderFilter( + modifier: modifier.value, + position: inputs.animatedPosition(), + size: inputs.animatedSize(), + transform: inputs.transform, + children: outputs.viewResponders(), + responder: ContentShapeKindResponder(inputs: inputs) + )) + if inputs.preferences.requiresAccessibilityNodes { + outputs.accessibilityNodes = makeAccessibilityGeometryTransform( + for: nil, + kind: modifier[offset: { .of(&$0.kind) }].value, + inputs: inputs, + outputs: outputs + ) + } + } + return outputs + } +} + +@available(*, unavailable) +extension _ContentShapeKindModifier: Sendable {} + +extension _ContentShapeKindModifier: ContentResponder { + package func contains(points: UnsafeBufferPointer, size: CGSize) -> BitVector64 { + shape.effectivePath(in: CGRect(origin: .zero, size: size)) + .contains(points: points, eoFill: eoFill) + } + + package func contentPath(size: CGSize) -> Path { + kind.contains(.interaction) + ? shape.effectivePath(in: CGRect(origin: .zero, size: size)) + : Path() + } + + package func contentPath(size: CGSize, kind: ContentShapeKinds) -> Path { + !kind.intersection(self.kind).isEmpty + ? shape.effectivePath(in: CGRect(origin: .zero, size: size)) + : Path() + } +} + +// MARK: - ContentShapeKindResponder + +private class ContentShapeKindResponder: DefaultLayoutViewResponder where ContentShape: Shape { + var kind: ContentShapeKinds = .interaction + var helper = ContentResponderHelper<_ContentShapeKindModifier>() + + override func containsGlobalPoints( + _ points: [PlatformPoint], + cacheKey: UInt32?, + options: ContainsPointsOptions + ) -> ContainsPointsResult { + guard kind.contains(.interaction) else { + return super.containsGlobalPoints(points, cacheKey: cacheKey, options: options) + } + let priority = isLinkedOnOrAfter(.v6) + ? super.containsGlobalPoints(points, cacheKey: cacheKey, options: options).priority + : -1 + var result = helper.containsGlobalPoints( + points, + cacheKey: cacheKey, + options: options, + children: children + ) + result.priority.formMax(priority) + return result + } + + override func addContentPath( + to path: inout Path, + kind: ContentShapeKinds, + in space: CoordinateSpace, + observer: (any ContentPathObserver)? + ) { + var remaining = kind + if !self.kind.intersection(kind).isEmpty { + helper.addContentPath( + to: &path, + kind: self.kind.intersection(kind), + in: space, + observer: observer + ) + remaining.remove(self.kind) + } + if remaining != [] { + super.addContentPath(to: &path, kind: remaining, in: space, observer: observer) + } + } + + override var descriptionName: String { + let size = helper.size + return "ContentShapeKindResponder<\(ContentShape.self)>\(kind.description) (\(size.width), \(size.height))\"" + } + + override func extendPrintTree(string: inout String) { + let position = helper.globalPosition + string += "[\(helper.size.width), \(helper.size.height)] @\((position.x, position.y))" + } +} + +// MARK: - ContentShapeKindResponderFilter + +private struct ContentShapeKindResponderFilter: StatefulRule where ContentShape: Shape { + @Attribute var modifier: _ContentShapeKindModifier + @Attribute var position: CGPoint + @Attribute var size: ViewSize + @Attribute var transform: ViewTransform + @Attribute var children: [ViewResponder] + let responder: ContentShapeKindResponder + + typealias Value = [ViewResponder] + + func updateValue() { + responder.helper.update( + data: $modifier.changedValue(), + size: $size.changedValue(), + position: $position.changedValue(), + transform: $transform.changedValue(), + parent: responder + ) + responder.kind = modifier.kind + responder.updateChildren($children.changedValue()) + if !hasValue { + value = [responder] + } + } +} From 40e6c3187ef9aad7921c3d165cc3fd1f0b901504 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 25 Sep 2026 21:57:30 +0800 Subject: [PATCH 4/6] feat: implement content shape modifier --- .../Responder/ContentShapeModifier.swift | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift diff --git a/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift b/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift new file mode 100644 index 000000000..af90c8a2e --- /dev/null +++ b/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift @@ -0,0 +1,163 @@ +// +// ContentShapeModifier.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: Complete +// ID: DEE7A9A341D7A5DA4357011E578980C2 (SwiftUI) + +import Foundation +import OpenAttributeGraphShims +@_spi(ForOpenSwiftUIOnly) +public import OpenSwiftUICore + +// MARK: - _ContentShapeModifier + +@available(OpenSwiftUI_v1_0, *) +@frozen +public struct _ContentShapeModifier: ViewModifier, MultiViewModifier, PrimitiveViewModifier where ContentShape: Shape { + public var shape: ContentShape + public var eoFill: Bool + + @inlinable + public init(shape: ContentShape, eoFill: Bool = false) { + self.shape = shape + self.eoFill = eoFill + } + + nonisolated public static func _makeView( + modifier: _GraphValue, + inputs: _ViewInputs, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs + ) -> _ViewOutputs { + var outputs = body(_Graph(), inputs) + if inputs.preferences.contains(ViewRespondersKey.self) { + let filter = ContentShapeResponderFilter( + modifier: modifier.value, + position: inputs.animatedPosition(), + size: inputs.animatedSize(), + transform: inputs.transform, + children: outputs.viewResponders(), + inputs: inputs, + viewSubgraph: Subgraph.current! + ) + outputs[ViewRespondersKey.self] = Attribute(filter) + if inputs.preferences.requiresAccessibilityNodes { + outputs.accessibilityNodes = makeAccessibilityGeometryTransform( + for: nil, + kind: nil, + inputs: inputs, + outputs: outputs + ) + } + } + return outputs + } +} + +@available(*, unavailable) +extension _ContentShapeModifier: Sendable {} + +// MARK: - View + contentShape + +@available(OpenSwiftUI_v1_0, *) +extension View { + /// Sets the shape used to test whether a point is inside this view. + /// + /// - Parameters: + /// - shape: The shape to use for hit testing. + /// - eoFill: Whether to use the even-odd fill rule. + @inlinable + nonisolated public func contentShape(_ shape: S, eoFill: Bool = false) -> some View where S: Shape { + modifier(_ContentShapeModifier(shape: shape, eoFill: eoFill)) + } +} + +// MARK: - _ContentShapeModifier + ContentResponder + +extension _ContentShapeModifier: ContentResponder { + package func contains(points: UnsafeBufferPointer, size: CGSize) -> BitVector64 { + shape.effectivePath(in: CGRect(origin: .zero, size: size)) + .contains(points: points, eoFill: eoFill) + } + + package func contentPath(size: CGSize) -> Path { + shape.effectivePath(in: CGRect(origin: .zero, size: size)) + } +} + +// MARK: - ContentShapeResponder + +private class ContentShapeResponder: DefaultLayoutViewResponder where ContentShape: Shape { + var helper = ContentResponderHelper<_ContentShapeModifier>() + + override func containsGlobalPoints( + _ points: [PlatformPoint], + cacheKey: UInt32?, + options: ContainsPointsOptions + ) -> ContainsPointsResult { + let priority = isLinkedOnOrAfter(.v6) + ? super.containsGlobalPoints(points, cacheKey: cacheKey, options: options).priority + : -1 + var result = helper.containsGlobalPoints( + points, + cacheKey: cacheKey, + options: options, + children: children + ) + result.priority.formMax(priority) + return result + } + + override func addContentPath( + to path: inout Path, + kind: ContentShapeKinds, + in space: CoordinateSpace, + observer: (any ContentPathObserver)? + ) { + helper.addContentPath(to: &path, kind: kind, in: space, observer: observer) + } + + override var descriptionName: String { + let size = helper.size + return "ContentShapeResponder<\(ContentShape.self)> (\(size.width), \(size.height))\"" + } + + override func extendPrintTree(string: inout String) { + let position = helper.globalPosition + string += "[\(helper.size.width), \(helper.size.height)] @\((position.x, position.y))" + } +} + +// MARK: - ContentShapeResponderFilter + +private struct ContentShapeResponderFilter: StatefulRule where ContentShape: Shape { + @Attribute var modifier: _ContentShapeModifier + @Attribute var position: CGPoint + @Attribute var size: ViewSize + @Attribute var transform: ViewTransform + @Attribute var children: [ViewResponder] + var inputs: _ViewInputs + var viewSubgraph: Subgraph + lazy var responder: ContentShapeResponder = { + viewSubgraph.apply { + ContentShapeResponder(inputs: inputs) + } + }() + + typealias Value = [ViewResponder] + + mutating func updateValue() { + responder.helper.update( + data: $modifier.changedValue(), + size: $size.changedValue(), + position: $position.changedValue(), + transform: $transform.changedValue(), + parent: responder + ) + responder.updateChildren($children.changedValue()) + if !hasValue { + value = [responder] + } + } +} From e905863209b0104685c99c3e9cda9ff1621f2545 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 25 Sep 2026 22:46:39 +0800 Subject: [PATCH 5/6] docs: document content shape modifiers --- .../Event/Responder/ContentShapeKindModifier.swift | 7 +++++++ .../OpenSwiftUI/Event/Responder/ContentShapeModifier.swift | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift b/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift index 0703728de..6e22e7e90 100644 --- a/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift +++ b/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift @@ -39,11 +39,18 @@ extension View { // MARK: - _ContentShapeKindModifier +/// A modifier that defines the content shape for a view. @available(OpenSwiftUI_v3_0, *) @frozen public struct _ContentShapeKindModifier: ViewModifier, MultiViewModifier, PrimitiveViewModifier where ContentShape: Shape { + /// The content shape. public var shape: ContentShape + + /// If true, the shape is interpreted using the even-odd winding + /// number rule, otherwise using the non-zero winding number rule. public var eoFill: Bool + + /// The kind assigned for the given shape. public var kind: ContentShapeKinds @inlinable diff --git a/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift b/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift index af90c8a2e..824fee2e5 100644 --- a/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift +++ b/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift @@ -13,10 +13,16 @@ public import OpenSwiftUICore // MARK: - _ContentShapeModifier +/// View subtype to define the hit-region for a content view as an +/// arbitrary shape. @available(OpenSwiftUI_v1_0, *) @frozen public struct _ContentShapeModifier: ViewModifier, MultiViewModifier, PrimitiveViewModifier where ContentShape: Shape { + /// The content shape. public var shape: ContentShape + + /// If true, the shape is interpreted using the even-odd winding + /// number rule, otherwise using the non-zero winding number rule. public var eoFill: Bool @inlinable From 24f60c4ec993951f68b5a3efc58a1b254d2951b1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 26 Sep 2026 01:06:33 +0800 Subject: [PATCH 6/6] fix: correct content shape Foundation import visibility --- .../OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift | 2 +- Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift b/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift index 6e22e7e90..55a986bbf 100644 --- a/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift +++ b/Sources/OpenSwiftUI/Event/Responder/ContentShapeKindModifier.swift @@ -6,7 +6,7 @@ // Status: Complete // ID: 14064F4AE362A5AD4C34ADD19A80790A (SwiftUI) -import Foundation +package import Foundation import OpenAttributeGraphShims @_spi(ForOpenSwiftUIOnly) public import OpenSwiftUICore diff --git a/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift b/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift index 824fee2e5..d68c2a6b6 100644 --- a/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift +++ b/Sources/OpenSwiftUI/Event/Responder/ContentShapeModifier.swift @@ -6,7 +6,7 @@ // Status: Complete // ID: DEE7A9A341D7A5DA4357011E578980C2 (SwiftUI) -import Foundation +package import Foundation import OpenAttributeGraphShims @_spi(ForOpenSwiftUIOnly) public import OpenSwiftUICore