From 90e2a2558e017ce71e7bd7389a35bcd078813ab6 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 7 Dec 2025 23:21:26 +0800 Subject: [PATCH] Add Anchor API implementation --- .../Layout/Geometry/Anchor+Point.swift | 88 +++++ .../Layout/Geometry/Anchor.swift | 314 +++++++++++++++++- 2 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 Sources/OpenSwiftUICore/Layout/Geometry/Anchor+Point.swift diff --git a/Sources/OpenSwiftUICore/Layout/Geometry/Anchor+Point.swift b/Sources/OpenSwiftUICore/Layout/Geometry/Anchor+Point.swift new file mode 100644 index 000000000..c9326d2b7 --- /dev/null +++ b/Sources/OpenSwiftUICore/Layout/Geometry/Anchor+Point.swift @@ -0,0 +1,88 @@ +// +// Anchor+Point.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +public import OpenCoreGraphicsShims + +extension CGPoint: AnchorProtocol { + package static var defaultAnchor: CGPoint { + .zero + } + + package func prepare(geometry: AnchorGeometry) -> CGPoint { + var point = self + point.convert(to: .global, transform: geometry.transform) + return point + } + + package static func hashValue(_ value: CGPoint, into hasher: inout Hasher) { + hasher.combine(value.x) + hasher.combine(value.y) + } +} + +extension UnitPoint: AnchorProtocol { + package static var defaultAnchor: CGPoint { + .zero + } + + package func prepare(geometry: AnchorGeometry) -> CGPoint { + let point = `in`(geometry.size) + return point.prepare(geometry: geometry) + } + + package static func hashValue(_ value: CGPoint, into hasher: inout Hasher) { + hasher.combine(value.x) + hasher.combine(value.y) + } +} + +@available(OpenSwiftUI_v1_0, *) +extension Anchor.Source where Value == CGPoint { + public static func point(_ p: CGPoint) -> Anchor.Source { + .init(anchor: p) + } + + public static func unitPoint(_ p: UnitPoint) -> Anchor.Source { + .init(anchor: p) + } + + public static var topLeading: Anchor.Source { + unitPoint(.topLeading) + } + + public static var top: Anchor.Source { + unitPoint(.top) + } + + public static var topTrailing: Anchor.Source { + unitPoint(.topTrailing) + } + + public static var leading: Anchor.Source { + unitPoint(.leading) + } + + public static var center: Anchor.Source { + unitPoint(.center) + } + + public static var trailing: Anchor.Source { + unitPoint(.trailing) + } + + public static var bottomLeading: Anchor.Source { + unitPoint(.bottomLeading) + } + + public static var bottom: Anchor.Source { + unitPoint(.bottom) + } + + public static var bottomTrailing: Anchor.Source { + unitPoint(.bottomTrailing) + } +} diff --git a/Sources/OpenSwiftUICore/Layout/Geometry/Anchor.swift b/Sources/OpenSwiftUICore/Layout/Geometry/Anchor.swift index 4a1f48a93..4281c3549 100644 --- a/Sources/OpenSwiftUICore/Layout/Geometry/Anchor.swift +++ b/Sources/OpenSwiftUICore/Layout/Geometry/Anchor.swift @@ -2,13 +2,319 @@ // Anchor.swift // OpenSwiftUICore // -// Audited for 6.0.87 -// Status: WIP +// Audited for 6.5.4 +// Status: Complete +// ID: DB1D6A7FECCB5A05E5E6B385ABD35CE6 (SwiftUICore) -package struct AnchorGeometry {} +package import OpenAttributeGraphShims +package import OpenCoreGraphicsShims + +// MARK: - AnchorGeometry + +package struct AnchorGeometry { + private var _position: Attribute + private var _size: Attribute + private var _transform: Attribute + + package init( + position: Attribute, + size: Attribute, + transform: Attribute + ) { + _position = position + _size = size + _transform = transform + } + + package var transform: ViewTransform { + _transform.value.withPosition(_position.value) + } + + package var size: CGSize { + _size.value + } +} + +// MARK: Anchor + +/// An opaque value derived from an anchor source and a particular view. +/// +/// You can convert the anchor to a `Value` in the coordinate space of a target +/// view by using a ``GeometryProxy`` to specify the target view. +@available(OpenSwiftUI_v1_0, *) +@frozen +public struct Anchor { + fileprivate let box: AnchorValueBoxBase + + package func `in`(_ context: _PositionAwarePlacementContext) -> Value { + let transform = context.transform + return convert(to: transform) + } -@frozen public struct Anchor { package func convert(to transform: ViewTransform) -> Value { + box.convert(to: transform) + } + + package var defaultValue: Value { + box.defaultValue + } + + /// A type-erased geometry value that produces an anchored value of a given + /// type. + /// + /// OpenSwiftUI passes anchored geometry values around the view tree via + /// preference keys. It then converts them back into the local coordinate + /// space using a ``GeometryProxy`` value. + @frozen + public struct Source { + private var box: AnchorBoxBase + + package func prepare(geometry: AnchorGeometry) -> Anchor { + Anchor(box: box.prepare(geometry: geometry)) + } + + package init(box: AnchorBoxBase) { + self.box = box + } + } +} + +@available(OpenSwiftUI_v1_0, *) +extension Anchor.Source: Sendable where Value: Sendable {} + +@available(OpenSwiftUI_v1_0, *) +extension Anchor: Sendable where Value: Sendable {} + +@available(OpenSwiftUI_v3_0, *) +extension Anchor: Equatable where Value: Equatable { + public static func == (lhs: Anchor, rhs: Anchor) -> Bool { + lhs.box.isEqual(to: rhs.box) + } +} + +@available(OpenSwiftUI_v3_0, *) +extension Anchor: Hashable where Value: Hashable { + public func hash(into hasher: inout Hasher) { + box.hash(into: &hasher) + } +} + +// MARK: - AnchorBoxBase + +@available(OpenSwiftUI_v1_0, *) +@usableFromInline +package class AnchorBoxBase { + func prepare(geometry: AnchorGeometry) -> AnchorValueBoxBase { + _openSwiftUIBaseClassAbstractMethod() + } +} + +@available(OpenSwiftUI_v1_0, *) +extension AnchorBoxBase: @unchecked Sendable where T: Sendable {} + +// MARK: - AnchorValueBoxBase + +@available(OpenSwiftUI_v1_0, *) +@usableFromInline +internal class AnchorValueBoxBase { + var defaultValue: T { + _openSwiftUIBaseClassAbstractMethod() + } + + func convert(to transform: ViewTransform) -> T { + _openSwiftUIBaseClassAbstractMethod() + } + + func isEqual(to other: AnchorValueBoxBase) -> Bool { + _openSwiftUIBaseClassAbstractMethod() + } + + func hash(into hasher: inout Hasher) { + _openSwiftUIBaseClassAbstractMethod() + } +} + +@available(OpenSwiftUI_v1_0, *) +extension AnchorValueBoxBase: @unchecked Sendable where T: Sendable {} + +// MARK: - AnchorProtocol + +package protocol AnchorProtocol { + associatedtype AnchorValue: ViewTransformable + + static var defaultAnchor: AnchorValue { get } + + func prepare(geometry: AnchorGeometry) -> AnchorValue + + static func valueIsEqual(lhs: AnchorValue, rhs: AnchorValue) -> Bool + + static func hashValue(_ value: AnchorValue, into hasher: inout Hasher) +} + +extension AnchorProtocol where AnchorValue: Equatable { + package static func valueIsEqual(lhs: AnchorValue, rhs: AnchorValue) -> Bool { + lhs == rhs + } +} + +extension AnchorProtocol where AnchorValue: Hashable { + package static func hashValue(_ value: AnchorValue, into hasher: inout Hasher) { + value.hash(into: &hasher) + } +} + +// MARK: - AnchorBox + +private class AnchorBox: AnchorBoxBase, @unchecked Sendable where T: AnchorProtocol { + let value: T + + init(value: T) { + self.value = value + } + + override func prepare(geometry: AnchorGeometry) -> AnchorValueBoxBase { + AnchorValueBox(value: value.prepare(geometry: geometry)) + } +} + +private class AnchorValueBox: AnchorValueBoxBase, @unchecked Sendable where T: AnchorProtocol { + let value: T.AnchorValue + + init(value: T.AnchorValue) { + self.value = value + } + + override var defaultValue: T.AnchorValue { + T.defaultAnchor + } + + override func convert(to transform: ViewTransform) -> T.AnchorValue { _openSwiftUIUnimplementedFailure() } + + override func isEqual(to other: AnchorValueBoxBase) -> Bool { + guard let other = other as? AnchorValueBox else { + return false + } + return T.valueIsEqual(lhs: value, rhs: other.value) + } + + override func hash(into hasher: inout Hasher) { + T.hashValue(value, into: &hasher) + } +} + +extension Anchor.Source { + package init(anchor value: A) where Value == A.AnchorValue, A: AnchorProtocol { + self.init(box: AnchorBox(value: value)) + } +} + +// MARK: - ArrayAnchorBox + +private class ArrayAnchorBox: AnchorBoxBase<[T]>, @unchecked Sendable { + let value: [Anchor.Source] + + init(value: [Anchor.Source]) { + self.value = value + } + + override func prepare(geometry: AnchorGeometry) -> AnchorValueBoxBase<[T]> { + ArrayAnchorValueBox(value: value.map { $0.prepare(geometry: geometry) }) + } +} + +private class ArrayAnchorValueBox: AnchorValueBoxBase<[T]>, @unchecked Sendable { + let value: [Anchor] + + init(value: [Anchor]) { + self.value = value + } + + override var defaultValue: [T] { + value.map { $0.defaultValue } + } + + override func convert(to transform: ViewTransform) -> [T] { + value.map { $0.convert(to: transform) } + } + + override func isEqual(to other: AnchorValueBoxBase<[T]>) -> Bool { + guard let other = other as? ArrayAnchorValueBox else { + return false + } + guard value.count == other.value.count else { + return false + } + for index in value.indices { + if !value[index].box.isEqual(to: other.value[index].box) { + return false + } + } + return true + } + + override func hash(into hasher: inout Hasher) { + value.forEach { $0.box.hash(into: &hasher) } + } +} + +@available(OpenSwiftUI_v1_0, *) +extension Anchor.Source { + public init(_ array: [Anchor.Source]) where Value == [T] { + self.init(box: ArrayAnchorBox(value: array)) + } +} + +// MARK: - OptionalAnchorBox + +private class OptionalAnchorBox: AnchorBoxBase, @unchecked Sendable { + let value: Anchor.Source? + + init(value: Anchor.Source?) { + self.value = value + } + + override func prepare(geometry: AnchorGeometry) -> AnchorValueBoxBase { + OptionalAnchorValueBox(value: value.map { $0.prepare(geometry: geometry) }) + } +} + +private class OptionalAnchorValueBox: AnchorValueBoxBase, @unchecked Sendable { + let value: Anchor? + + init(value: Anchor?) { + self.value = value + } + + override var defaultValue: T? { + value.map { $0.defaultValue } + } + + override func convert(to transform: ViewTransform) -> T? { + value.map { $0.convert(to: transform) } + } + + override func isEqual(to other: AnchorValueBoxBase) -> Bool { + guard let other = other as? OptionalAnchorValueBox else { + return false + } + guard let value, let otherValue = other.value else { + return value == nil && other.value == nil + } + return value.box.isEqual(to: otherValue.box) + } + + override func hash(into hasher: inout Hasher) { + if let value { + value.box.hash(into: &hasher) + } + } +} + +@available(OpenSwiftUI_v1_0, *) +extension Anchor.Source { + public init(_ anchor: Anchor.Source?) where Value == T? { + self.init(box: OptionalAnchorBox(value: anchor)) + } }