diff --git a/Example/OpenSwiftUIUITests/Layout/Separator/DividerUITests.swift b/Example/OpenSwiftUIUITests/Layout/Separator/DividerUITests.swift new file mode 100644 index 000000000..af39bf22f --- /dev/null +++ b/Example/OpenSwiftUIUITests/Layout/Separator/DividerUITests.swift @@ -0,0 +1,51 @@ +// +// DividerUITests.swift +// OpenSwiftUIUITests + +import Testing +import SnapshotTesting + +@MainActor +@Suite(.snapshots(record: .never, diffTool: diffTool)) +struct DividerUITests { + + // MARK: - Basic Spacer in HStack + + @Test("TODO: Fix colorScheme issue") + func dividerWithColorScheme() { + struct ContentView: View { + var body: some View { + HStack { + VStack { + VStack { + Color.red + Divider() + Color.blue + } + HStack { + Color.red + Divider() + Color.blue + } + }.colorScheme(.light) + VStack { + HStack { + Color.red + Divider() + Color.blue + } + VStack { + Color.red + Divider() + Color.blue + } + }.colorScheme(.dark) + } + .background(Color.green) + } + } + withKnownIssue("Path/Shape is not implemented") { + openSwiftUIAssertSnapshot(of: ContentView()) + } + } +} diff --git a/Scripts/build.sh b/Scripts/build.sh index 757314380..cd75ba75b 100755 --- a/Scripts/build.sh +++ b/Scripts/build.sh @@ -9,4 +9,10 @@ OPENSWIFTUI_ROOT="$(dirname $(dirname $(filepath $0)))" cd $OPENSWIFTUI_ROOT +# Set OPENSWIFTUI_LIB_SWIFT_PATH on Linux if swiftly is installed +if [[ "$(uname)" == "Linux" ]] && command -v swiftly &> /dev/null && [[ -z "$OPENSWIFTUI_LIB_SWIFT_PATH" ]]; then + export OPENSWIFTUI_LIB_SWIFT_PATH="$(swiftly use --print-location)/usr/lib/swift" + echo "Set OPENSWIFTUI_LIB_SWIFT_PATH=$OPENSWIFTUI_LIB_SWIFT_PATH" +fi + swift build diff --git a/Sources/OpenSwiftUI/Layout/Separator/DefaultDividerStyle.swift b/Sources/OpenSwiftUI/Layout/Separator/DefaultDividerStyle.swift new file mode 100644 index 000000000..04b39bd45 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/Separator/DefaultDividerStyle.swift @@ -0,0 +1,20 @@ +// +// DefaultDividerStyle.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: Complete + +// MARK: - DefaultDividerStyle + +extension DividerStyle where Self == DefaultDividerStyle { + static var `default`: DefaultDividerStyle { + .init() + } +} + +struct DefaultDividerStyle: DividerStyle { + func makeBody(configuration: Configuration) -> some View { + Divider().dividerStyle(.plain) + } +} diff --git a/Sources/OpenSwiftUI/Layout/Separator/Divider.swift b/Sources/OpenSwiftUI/Layout/Separator/Divider.swift new file mode 100644 index 000000000..c97b58e1f --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/Separator/Divider.swift @@ -0,0 +1,103 @@ +// +// Divider.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: Complete +// ID: A41482AADD0929733C3343B5E142E952 (SwiftUI) + +import OpenAttributeGraphShims +@_spi(ForOpenSwiftUIOnly) +public import OpenSwiftUICore + +// MARK: - Divider + +/// A visual element that can be used to separate other content. +/// +/// When contained in a stack, the divider extends across the minor axis of the +/// stack, or horizontally when not in a stack. +@available(OpenSwiftUI_v1_0, *) +public struct Divider: View, UnaryView, PrimitiveView { + + public init() { + _openSwiftUIEmptyStub() + } + + nonisolated public static func _makeView( + view: _GraphValue, + inputs: _ViewInputs + ) -> _ViewOutputs { + var newInputs = inputs + if inputs.preferences.requiresPlatformItemList { + newInputs.preferences.requiresPlatformItemList = false + newInputs.requestedTextRepresentation = nil + } + let orientation = inputs.stackOrientation + let child = Attribute( + Child( + orientation: orientation, + dynamicOrientation: orientation == nil ? inputs.dynamicStackOrientation : .init() + ) + ) + var outputs = ResolvedDivider.makeDebuggableView( + view: .init(child), + inputs: newInputs + ) + if let representation = inputs.requestedDividerRepresentation, + representation.shouldMakeRepresentation(inputs: inputs) { + representation.makeRepresentation(inputs: inputs, outputs: &outputs) + } + return outputs + } + + private struct Child: Rule { + var orientation: Axis? + @OptionalAttribute var dynamicOrientation: Axis?? + + var value: ResolvedDivider { + let axis: Axis + if let orientation { + axis = orientation + } else if let dynamicOrientation { + axis = dynamicOrientation ?? .vertical + } else { + axis = .vertical + } + return ResolvedDivider(configuration: .init(orientation: axis.otherAxis)) + } + } +} + +@available(*, unavailable) +extension Divider: Sendable {} + +// MARK: - PlatformDividerRepresentable + +package protocol PlatformDividerRepresentable { + static func shouldMakeRepresentation( + inputs: _ViewInputs + ) -> Bool + + static func makeRepresentation( + inputs: _ViewInputs, + outputs: inout _ViewOutputs + ) +} + +extension _ViewInputs { + package var requestedDividerRepresentation: (any PlatformDividerRepresentable.Type)? { + get { base.requestedDividerRepresentation } + set { base.requestedDividerRepresentation = newValue } + } +} + +extension _GraphInputs { + private struct DividerRepresentationKey: GraphInput { + static var defaultValue: (any PlatformDividerRepresentable.Type)? { nil } + } + + package var requestedDividerRepresentation: (any PlatformDividerRepresentable.Type)? { + get { self[DividerRepresentationKey.self] } + set { self[DividerRepresentationKey.self] = newValue } + } +} diff --git a/Sources/OpenSwiftUI/Layout/Separator/DividerStyle.swift b/Sources/OpenSwiftUI/Layout/Separator/DividerStyle.swift new file mode 100644 index 000000000..392bf9095 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/Separator/DividerStyle.swift @@ -0,0 +1,65 @@ +// +// DividerStyle.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: Complete + +public import OpenSwiftUICore + +// MARK: - DividerStyle + +@_spi(Private) +@available(OpenSwiftUI_v3_0, *) +@MainActor +@preconcurrency +public protocol DividerStyle { + associatedtype Body: View + + @ViewBuilder + func makeBody(configuration: Configuration) -> Body + + typealias Configuration = DividerStyleConfiguration +} + +// MARK: - DividerStyleConfiguration + +@_spi(Private) +@available(OpenSwiftUI_v3_0, *) +public struct DividerStyleConfiguration { + public var orientation: Axis +} + +@_spi(Private) +@available(*, unavailable) +extension DividerStyleConfiguration: Sendable {} + +// MARK: - ResolvedDivider + +struct ResolvedDivider: StyleableView { + static var defaultStyleModifier: DividerStyleModifier = .init(style: .default) + + var configuration: DividerStyleConfiguration +} + +// MARK: - DividerStyleModifier + +@_spi(Private) +@available(OpenSwiftUI_v3_0, *) +extension View { + nonisolated public func dividerStyle(_ style: S) -> some View where S: DividerStyle { + modifier(DividerStyleModifier(style: style)) + } +} + +struct DividerStyleModifier: StyleModifier where S: DividerStyle { + var style: S + + init(style: S) { + self.style = style + } + + func styleBody(configuration: DividerStyleConfiguration) -> some View { + style.makeBody(configuration: configuration) + } +} diff --git a/Sources/OpenSwiftUI/Layout/Separator/PlainDividerStyle.swift b/Sources/OpenSwiftUI/Layout/Separator/PlainDividerStyle.swift new file mode 100644 index 000000000..3193478e8 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/Separator/PlainDividerStyle.swift @@ -0,0 +1,81 @@ +// +// PlainDividerStyle.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: Blocked by Shape +// ID: 4E7A7B805FC8F5CEEF99A1E333CA7AA7 (SwiftUI) + +import OpenCoreGraphicsShims +@_spi(Private) +import OpenSwiftUICore + +// MARK: - PlainDividerStyle + +extension DividerStyle where Self == PlainDividerStyle { + static var plain: PlainDividerStyle { + .init() + } +} + +struct PlainDividerStyle: DividerStyle { + @Environment(\.dividerThickness) + private var thickness: CGFloat + + func makeBody(configuration: DividerStyleConfiguration) -> some View { + // TODO: Shape is not implemented yet + Color(provider: PlainDividerShapeStyle()) +// DividerShape(base: Rectangle()) +// .fill(PlainDividerShapeStyle()) + .frame( + width: configuration.orientation == .horizontal ? nil : thickness, + height: configuration.orientation == .horizontal ? thickness : nil + ) + } +} + +// MARK: - PlainDividerShapeStyle + +struct PlainDividerShapeStyle: ShapeStyle, ColorProvider { + private static let sharedColor = Color(provider: PlainDividerShapeStyle()) + + func _apply(to shape: inout _ShapeStyle_Shape) { + if shape.environment.isVisionEnabled { + SeparatorShapeStyle()._apply(to: &shape) + } else { + let color: Color + if shape.environment.backgroundMaterial != nil { + color = Color.quaternary + } else { + color = Self.sharedColor + } + color._apply(to: &shape) + } + } + + func resolve(in environment: EnvironmentValues) -> Color.Resolved { + if environment.colorScheme == .dark { + Color.Resolved(red: 84 / 255, green: 84 / 255, blue: 88 / 255, opacity: 0.6) + } else { + Color.Resolved(red: 60 / 255, green: 60 / 255, blue: 67 / 255, opacity: 0.29) + } + } +} + +// MARK: - DividerShape + +private struct DividerShape: Shape where S: Shape { + var base: S + + nonisolated func path(in rect: CGRect) -> Path { + base.path(in: rect) + } + + nonisolated static var role: ShapeRole { + .separator + } + + nonisolated var layoutDirectionBehavior: LayoutDirectionBehavior { + base.layoutDirectionBehavior + } +} diff --git a/Sources/OpenSwiftUICore/Layout/Separator/Divider.swift b/Sources/OpenSwiftUICore/Layout/Separator/Divider.swift deleted file mode 100644 index 040f349b8..000000000 --- a/Sources/OpenSwiftUICore/Layout/Separator/Divider.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Divider.swift -// OpenSwiftUI -// -// Audited for 3.5.2 -// Status: TODO - -/// A visual element that can be used to separate other content. -/// -/// When contained in a stack, the divider extends across the minor axis of the -/// stack, or horizontally when not in a stack. -public struct Divider: UnaryView, PrimitiveView { - public init() {} -} diff --git a/Sources/OpenSwiftUICore/Layout/Separator/Spacer.swift b/Sources/OpenSwiftUICore/Layout/Separator/Spacer.swift index 034855771..34aed4eda 100644 --- a/Sources/OpenSwiftUICore/Layout/Separator/Spacer.swift +++ b/Sources/OpenSwiftUICore/Layout/Separator/Spacer.swift @@ -289,7 +289,6 @@ extension PrimitiveSpacer { ) -> _ViewOutputs { var outputs = _ViewOutputs() if inputs.requestsLayoutComputer { - let computer = if let orientation = axis ?? inputs.stackOrientation { SpacerLayoutComputer( spacer: view.value, diff --git a/Sources/OpenSwiftUICore/Shape/ShapeView.swift b/Sources/OpenSwiftUICore/Shape/ShapeView.swift index e384dd8c3..dc7b8ae79 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeView.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeView.swift @@ -136,7 +136,10 @@ public struct _ShapeView: View, UnaryView, ShapeStyledLeafView, self.fillStyle = fillStyle } - nonisolated public static func _makeView(view: _GraphValue<_ShapeView>, inputs: _ViewInputs) -> _ViewOutputs { + nonisolated public static func _makeView( + view: _GraphValue, + inputs: _ViewInputs + ) -> _ViewOutputs { _openSwiftUIUnimplementedFailure() } diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift index fe9ab4b32..1b025ced0 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift @@ -211,7 +211,7 @@ public struct _ViewInputs { @available(*, unavailable) extension _ViewInputs: Sendable {} -// MARK: - DynamicStackOrientation [6.0.87] +// MARK: - DynamicStackOrientation [6.5.4] package struct DynamicStackOrientation: ViewInput { package static let defaultValue: OptionalAttribute = .init() @@ -219,7 +219,7 @@ package struct DynamicStackOrientation: ViewInput { extension _ViewInputs { @inline(__always) - var dynamicStackOrientation: OptionalAttribute { + package var dynamicStackOrientation: OptionalAttribute { get { self[DynamicStackOrientation.self] } set { self[DynamicStackOrientation.self] = newValue } } diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+PlatformRepresentation.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+PlatformRepresentation.swift new file mode 100644 index 000000000..974ef1e59 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+PlatformRepresentation.swift @@ -0,0 +1,62 @@ +// +// Text+PlatformRepresentation.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: 03CAEBF34B5290A85C0CA97765182271 (SwiftUICore) + +package import Foundation +package import OpenAttributeGraphShims + +package protocol PlatformTextRepresentable { + static func shouldMakeRepresentation( + inputs: _ViewInputs + ) -> Bool + + static func representationOptions( + inputs: _ViewInputs + ) -> RepresentationOptions + + static func makeRepresentation( + inputs: _ViewInputs, + context: Attribute, + outputs: inout _ViewOutputs + ) + + typealias Context = PlatformTextRepresentableContext + + typealias RepresentationOptions = PlatformTextRepresentationOptions +} + +package struct PlatformTextRepresentableContext { + package var text: NSAttributedString? +} + +package struct PlatformTextRepresentationOptions: OptionSet { + package let rawValue: Int + + package init(rawValue: Int) { + self.rawValue = rawValue + } + + package static let includeStyledText: PlatformTextRepresentationOptions = .init(rawValue: 1 << 0) +} + +extension _ViewInputs { + package var requestedTextRepresentation: (any PlatformTextRepresentable.Type)? { + get { base.requestedTextRepresentation } + set { base.requestedTextRepresentation = newValue } + } +} + +extension _GraphInputs { + private struct TextRepresentationKey: GraphInput { + static var defaultValue: (any PlatformTextRepresentable.Type)? { nil } + } + + package var requestedTextRepresentation: (any PlatformTextRepresentable.Type)? { + get { self[TextRepresentationKey.self] } + set { self[TextRepresentationKey.self] = newValue } + } +}