From 5832009dcee728995629530449c576b4facdea26 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 17 Dec 2023 18:47:22 +0800 Subject: [PATCH] Add basic layout system support (#7) * Add AlignmentID * Add Angle and Axis test case and update docs * Add Spacing and LayoutComputer * Add AlignmentKey * Add ViewDimensions missing implementation * Update Alignment doc for OpenSwiftUI * Add empty HStack implementation --- .../Internal/Other/Defaultable.swift | 12 + .../Alignment/AlignmentID.swift | 165 +++++++++ .../Alignment/AlignmentKey.swift | 43 +++ .../Alignment/HorizontalAlignment.swift | 280 +++++++++++++++ .../Alignment/VerticalAlignment.swift | 328 ++++++++++++++++++ .../Alignment/ViewDimensions.swift | 191 ++++++++++ .../LayoutAdjustments}/Edge/Edge.swift | 53 ++- .../LayoutAdjustments}/Edge/EdgeInsets.swift | 5 +- .../LayoutFundamentals/Stack/HStack.swift | 27 ++ .../Stack/_HStackLayout.swift | 20 ++ .../internal/LayoutComputer.swift | 101 ++++++ .../LayoutFundamentals/internal/Spacing.swift | 88 +++++ .../internal/ViewGeometry.swift | 14 + .../internal/ViewOrigin.swift | 20 ++ .../internal/ViewSize.swift | 14 + .../internal/_ProposedSize.swift | 18 + .../OpenSwiftUI.docc/AppStructure/App.md | 13 - .../AppStructure/AppOrganization.md | 17 - .../AppOrganization/App-Organization.md | 28 ++ .../AppStructure/AppOrganization/App.md | 13 + .../DataAndStorage/PreferenceKey.md | 12 - .../DataAndStorage/Preferences.md | 20 -- .../Preferences/PreferenceKey.md | 12 + .../DataAndStorage/Preferences/Preferences.md | 28 ++ .../Introducing OpenSwiftUI.tutorial | 8 +- .../EventHandling/SystemEvents.md | 13 - .../SystemEvents/System-Events.md | 18 + .../Alignment/AlignmentID.md | 7 + .../Alignment/HorizontalAlignment.md | 15 + .../Alignment/VerticalAlignment.md | 19 + .../Alignment/ViewDimensions.md | 19 + .../Layout/LayoutAdjustments/Edge/Edge.Set.md | 23 ++ .../Layout/LayoutAdjustments/Edge/Edge.md | 17 + .../LayoutAdjustments/Edge/EdgeInsets.md | 21 ++ .../LayoutAdjustments/Edge/HorizontalEdge.md | 13 + .../LayoutAdjustments/Edge/VerticalEdge.md | 13 + .../LayoutAdjustments/Layout-Adjustments.md | 38 ++ .../LayoutFundamentals/Layout-Fundamentals.md | 17 + .../OpenSwiftUI.docc/OpenSwiftUI.md | 14 +- .../OpenSwiftUI.docc/Views/Animations.md | 3 +- .../OpenSwiftUI.docc/Views/Controls.md | 27 -- .../Controls-and-Indicators.md | 34 ++ .../Slider.md | 0 .../Views/Drawing-and-Graphics.md | 27 ++ .../OpenSwiftUI.docc/Views/Shapes.md | 20 ++ .../Views/Animations/TODO/Animatable.swift | 8 +- .../ControlSize/ControlSize.swift | 0 .../ControlSize/ControlSizeKey.swift | 0 .../EnabledKey.swift | 0 .../Link/Link.swift | 0 .../Link/OpenURLActionKey.swift | 0 .../Link/internal/LinkDestination.swift | 0 .../Slider/Slider.swift | 0 .../Slider/internal/AnySliderStyle.swift | 0 .../Slider/internal/SystemSliderStyle.swift | 0 .../DrawingAndGraphics}/Angle.swift | 2 +- .../Views/DrawingAndGraphics/Axis.swift | 42 +++ .../Path => Views/Shapes}/Path.swift | 3 +- .../Views/View/TODO/_VariadicView.swift | 34 ++ .../Alignment/AlignmentIDTests.swift | 40 +++ .../Slider/SliderTests.swift | 0 .../Views/DrawingAndGraphics/AngleTests.swift | 44 +++ .../Views/DrawingAndGraphics/AxisTests.swift | 26 ++ 63 files changed, 1968 insertions(+), 119 deletions(-) create mode 100644 Sources/OpenSwiftUI/Internal/Other/Defaultable.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentID.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentKey.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/VerticalAlignment.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift rename Sources/OpenSwiftUI/{UIElements => Layout/LayoutAdjustments}/Edge/Edge.swift (67%) rename Sources/OpenSwiftUI/{UIElements => Layout/LayoutAdjustments}/Edge/EdgeInsets.swift (96%) create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/HStack.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/_HStackLayout.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/LayoutComputer.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/Spacing.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift create mode 100644 Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/_ProposedSize.swift delete mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/App.md delete mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App-Organization.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App.md delete mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/PreferenceKey.md delete mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/PreferenceKey.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/Preferences.md delete mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents/System-Events.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/AlignmentID.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/VerticalAlignment.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/ViewDimensions.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.Set.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/EdgeInsets.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/HorizontalEdge.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/VerticalEdge.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Layout-Adjustments.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutFundamentals/Layout-Fundamentals.md delete mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Controls.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/ControlsAndIndicators/Controls-and-Indicators.md rename Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/{Controls => ControlsAndIndicators}/Slider.md (100%) create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Drawing-and-Graphics.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Shapes.md rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/ControlSize/ControlSize.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/ControlSize/ControlSizeKey.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/EnabledKey.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/Link/Link.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/Link/OpenURLActionKey.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/Link/internal/LinkDestination.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/Slider/Slider.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/Slider/internal/AnySliderStyle.swift (100%) rename Sources/OpenSwiftUI/Views/{Controls => ControlsAndIndicators}/Slider/internal/SystemSliderStyle.swift (100%) rename Sources/OpenSwiftUI/{UIElements/Angle => Views/DrawingAndGraphics}/Angle.swift (96%) create mode 100644 Sources/OpenSwiftUI/Views/DrawingAndGraphics/Axis.swift rename Sources/OpenSwiftUI/{UIElements/Path => Views/Shapes}/Path.swift (95%) create mode 100644 Sources/OpenSwiftUI/Views/View/TODO/_VariadicView.swift create mode 100644 Tests/OpenSwiftUITests/Layout/LayoutAdjustments/Alignment/AlignmentIDTests.swift rename Tests/OpenSwiftUITests/Views/{Controls => ControlsAndIndicators}/Slider/SliderTests.swift (100%) create mode 100644 Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AngleTests.swift create mode 100644 Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AxisTests.swift diff --git a/Sources/OpenSwiftUI/Internal/Other/Defaultable.swift b/Sources/OpenSwiftUI/Internal/Other/Defaultable.swift new file mode 100644 index 0000000..4b53b3a --- /dev/null +++ b/Sources/OpenSwiftUI/Internal/Other/Defaultable.swift @@ -0,0 +1,12 @@ +// +// Defaultable.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/16. +// Lastest Version: iOS 15.5 +// Status: Complete + +protocol Defaultable { + associatedtype Value + static var defaultValue: Value { get } +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentID.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentID.swift new file mode 100644 index 0000000..ee41437 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentID.swift @@ -0,0 +1,165 @@ +// +// AlignmentID.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/16. +// Lastest Version: iOS 15.5 +// Status: Complete + +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +/// A type that you use to create custom alignment guides. +/// +/// Every built-in alignment guide that ``VerticalAlignment`` or +/// ``HorizontalAlignment`` defines as a static property, like +/// ``VerticalAlignment/top`` or ``HorizontalAlignment/leading``, has a +/// unique alignment identifier type that produces the default offset for +/// that guide. To create a custom alignment guide, define your own alignment +/// identifier as a type that conforms to the `AlignmentID` protocol, and +/// implement the required ``AlignmentID/defaultValue(in:)`` method: +/// +/// private struct FirstThirdAlignment: AlignmentID { +/// static func defaultValue(in context: ViewDimensions) -> CGFloat { +/// context.height / 3 +/// } +/// } +/// +/// When implementing the method, calculate the guide's default offset +/// from the view's origin. If it's helpful, you can use information from the +/// ``ViewDimensions`` input in the calculation. This parameter provides context +/// about the specific view that's using the guide. The above example creates an +/// identifier called `FirstThirdAlignment` and calculates a default value +/// that's one-third of the height of the aligned view. +/// +/// Use the identifier's type to create a static property in an extension of +/// one of the alignment guide types, like ``VerticalAlignment``: +/// +/// extension VerticalAlignment { +/// static let firstThird = VerticalAlignment(FirstThirdAlignment.self) +/// } +/// +/// You can apply your custom guide like any of the built-in guides. For +/// example, you can use an ``HStack`` to align its views at one-third +/// of their height using the guide defined above: +/// +/// struct StripesGroup: View { +/// var body: some View { +/// HStack(alignment: .firstThird, spacing: 1) { +/// HorizontalStripes().frame(height: 60) +/// HorizontalStripes().frame(height: 120) +/// HorizontalStripes().frame(height: 90) +/// } +/// } +/// } +/// +/// struct HorizontalStripes: View { +/// var body: some View { +/// VStack(spacing: 1) { +/// ForEach(0..<3) { _ in Color.blue } +/// } +/// } +/// } +/// +/// Because each set of stripes has three equal, vertically stacked +/// rectangles, they align at the bottom edge of the top rectangle. This +/// corresponds in each case to a third of the overall height, as +/// measured from the origin at the top of each set of stripes: +/// +/// ![Three vertical stacks of rectangles, arranged in a row. +/// The rectangles in each stack have the same height as each other, but +/// different heights than the rectangles in the other stacks. The bottom edges +/// of the top-most rectangle in each stack are aligned with each +/// other.](AlignmentId-1-iOS) +/// +/// You can also use the ``View/alignmentGuide(_:computeValue:)-6y3u2`` view +/// modifier to alter the behavior of your custom guide for a view, as you +/// might alter a built-in guide. For example, you can change +/// one of the stacks of stripes from the previous example to align its +/// `firstThird` guide at two thirds of the height instead: +/// +/// struct StripesGroupModified: View { +/// var body: some View { +/// HStack(alignment: .firstThird, spacing: 1) { +/// HorizontalStripes().frame(height: 60) +/// HorizontalStripes().frame(height: 120) +/// HorizontalStripes().frame(height: 90) +/// .alignmentGuide(.firstThird) { context in +/// 2 * context.height / 3 +/// } +/// } +/// } +/// } +/// +/// The modified guide calculation causes the affected view to place the +/// bottom edge of its middle rectangle on the `firstThird` guide, which aligns +/// with the bottom edge of the top rectangle in the other two groups: +/// +/// ![Three vertical stacks of rectangles, arranged in a row. +/// The rectangles in each stack have the same height as each other, but +/// different heights than the rectangles in the other stacks. The bottom edges +/// of the top-most rectangle in the first two stacks are aligned with each +/// other, and with the bottom edge of the middle rectangle in the third +/// stack.](AlignmentId-2-iOS) +/// +public protocol AlignmentID { + /// Calculates a default value for the corresponding guide in the specified + /// context. + /// + /// Implement this method when you create a type that conforms to the + /// ``AlignmentID`` protocol. Use the method to calculate the default + /// offset of the corresponding alignment guide. SwiftUI interprets the + /// value that you return as an offset in the coordinate space of the + /// view that's being laid out. For example, you can use the context to + /// return a value that's one-third of the height of the view: + /// + /// private struct FirstThirdAlignment: AlignmentID { + /// static func defaultValue(in context: ViewDimensions) -> CGFloat { + /// context.height / 3 + /// } + /// } + /// + /// You can override the default value that this method returns for a + /// particular guide by adding the + /// ``View/alignmentGuide(_:computeValue:)-9mdoh`` view modifier to a + /// particular view. + /// + /// - Parameter context: The context of the view that you apply + /// the alignment guide to. The context gives you the view's dimensions, + /// as well as the values of other alignment guides that apply to the + /// view, including both built-in and custom guides. You can use any of + /// these values, if helpful, to calculate the value for your custom + /// guide. + /// + /// - Returns: The offset of the guide from the origin in the + /// view's coordinate space. + static func defaultValue(in context: ViewDimensions) -> CGFloat + + static func _combineExplicit(childValue: CGFloat, _ n: Int, into parentValue: inout CGFloat?) +} + +extension AlignmentID { + // n == 0: + // value = childValue = c0 + // parentValue = childValue = c0 + // n == 1: + // value = parentValue! = c0 + // parentValue = (c0 + c1) / 2 + // n == 2: + // value = parentValue! = (c0 + c1) / 2 + // parentValue = (c0 + c1 + c2) / 3 + public static func _combineExplicit(childValue: CGFloat, _ n: Int, into parentValue: inout CGFloat?) { + let value = (n == 0) ? childValue : parentValue! + let n = CGFloat(n) + parentValue = (value * n + childValue) / (n + 1.0) + } +} + +protocol FrameAlignment: AlignmentID {} + +extension FrameAlignment { + static func _combineExplicit(childValue _: CGFloat, _: Int, into _: inout CGFloat?) {} +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentKey.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentKey.swift new file mode 100644 index 0000000..efee25f --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/AlignmentKey.swift @@ -0,0 +1,43 @@ +// +// AlignmentKey.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: Complete +// ID: E20796D15DD3D417699102559E024115 + +@usableFromInline +@frozen +struct AlignmentKey: Hashable, Comparable { + private let bits: UInt + + @usableFromInline + static func < (lhs: AlignmentKey, rhs: AlignmentKey) -> Bool { + lhs.bits < rhs.bits + } + + @UnsafeLockedPointer + private static var typeCache = TypeCache(typeIDs: [:], types: []) + + struct TypeCache { + var typeIDs: [ObjectIdentifier: UInt] + var types: [AlignmentID.Type] + } + + init(id: AlignmentID.Type, axis _: Axis) { + let index: UInt + if let value = AlignmentKey.typeCache.typeIDs[ObjectIdentifier(id)] { + index = value + } else { + index = UInt(AlignmentKey.typeCache.types.count) + AlignmentKey.typeCache.types.append(id) + AlignmentKey.typeCache.typeIDs[ObjectIdentifier(id)] = index + } + bits = index * 2 + 3 + } + + var id: AlignmentID.Type { + AlignmentKey.typeCache.types[Int(bits / 2 - 1)] + } +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.swift new file mode 100644 index 0000000..4728083 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.swift @@ -0,0 +1,280 @@ +// +// HorizontalAlignment.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: Complete +// ID: E20796D15DD3D417699102559E024115 + +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +/// An alignment position along the horizontal axis. +/// +/// Use horizontal alignment guides to tell OpenSwiftUI how to position views +/// relative to one another horizontally, like when you place views vertically +/// in an ``VStack``. The following example demonstrates common built-in +/// horizontal alignments: +/// +/// ![Three columns of content. Each column contains a string +/// inside a box with a vertical line above and below the box. The +/// lines are aligned horizontally with the text in a different way for each +/// column. The lines for the left-most string, labeled Leading, align with +/// the left edge of the string. The lines for the middle string, labeled +/// Center, align with the center of the string. The lines for the right-most +/// string, labeled Trailing, align with the right edge of the +/// string.](HorizontalAlignment-1-iOS) +/// +/// You can generate the example above by creating a series of columns +/// implemented as vertical stacks, where you configure each stack with a +/// different alignment guide: +/// +/// private struct HorizontalAlignmentGallery: View { +/// var body: some View { +/// HStack(spacing: 30) { +/// column(alignment: .leading, text: "Leading") +/// column(alignment: .center, text: "Center") +/// column(alignment: .trailing, text: "Trailing") +/// } +/// .frame(height: 150) +/// } +/// +/// private func column(alignment: HorizontalAlignment, text: String) -> some View { +/// VStack(alignment: alignment, spacing: 0) { +/// Color.red.frame(width: 1) +/// Text(text).font(.title).border(.gray) +/// Color.red.frame(width: 1) +/// } +/// } +/// } +/// +/// During layout, OpenSwiftUI aligns the views inside each stack by bringing +/// together the specified guides of the affected views. OpenSwiftUI calculates +/// the position of a guide for a particular view based on the characteristics +/// of the view. For example, the ``HorizontalAlignment/center`` guide appears +/// at half the width of the view. You can override the guide calculation for a +/// particular view using the ``View/alignmentGuide(_:computeValue:)-9mdoh`` +/// view modifier. +/// +/// ### Layout direction +/// +/// When a user configures their device to use a left-to-right language like +/// English, the system places the leading alignment on the left and the +/// trailing alignment on the right, as the example from the previous section +/// demonstrates. However, in a right-to-left language, the system reverses +/// these. You can see this by using the ``View/environment(_:_:)`` view +/// modifier to explicitly override the ``EnvironmentValues/layoutDirection`` +/// environment value for the view defined above: +/// +/// HorizontalAlignmentGallery() +/// .environment(\.layoutDirection, .rightToLeft) +/// +/// ![Three columns of content. Each column contains a string +/// inside a box with a vertical line above and below the box. The +/// lines are aligned horizontally with the text in a different way for each +/// column. The lines for the left-most string, labeled Trailing, align with +/// the left edge of the string. The lines for the middle string, labeled +/// Center, align with the center of the string. The lines for the right-most +/// string, labeled Leading, align with the right edge of the +/// string.](HorizontalAlignment-2-iOS) +/// +/// This automatic layout adjustment makes it easier to localize your app, +/// but it's still important to test your app for the different locales that +/// you ship into. For more information about the localization process, see +/// [Localization](https://developer.apple.com/documentation/xcode/localization). +/// +/// ### Custom alignment guides +/// +/// You can create a custom horizontal alignment by creating a type that +/// conforms to the ``AlignmentID`` protocol, and then using that type to +/// initalize a new static property on `HorizontalAlignment`: +/// +/// private struct OneQuarterAlignment: AlignmentID { +/// static func defaultValue(in context: ViewDimensions) -> CGFloat { +/// context.width / 4 +/// } +/// } +/// +/// extension HorizontalAlignment { +/// static let oneQuarter = HorizontalAlignment(OneQuarterAlignment.self) +/// } +/// +/// You implement the ``AlignmentID/defaultValue(in:)`` method to calculate +/// a default value for the custom alignment guide. The method receives a +/// ``ViewDimensions`` instance that you can use to calculate an appropriate +/// value based on characteristics of the view. The example above places +/// the guide at one quarter of the width of the view, as measured from the +/// view's origin. +/// +/// You can then use the custom alignment guide like any built-in guide. For +/// example, you can use it as the `alignment` parameter to a ``VStack``, +/// or you can change it for a specific view using the +/// ``View/alignmentGuide(_:computeValue:)-9mdoh`` view modifier. +/// Custom alignment guides also automatically reverse in a right-to-left +/// environment, just like built-in guides. +/// +/// ### Composite alignment +/// +/// Combine a ``VerticalAlignment`` with a `HorizontalAlignment` to create a +/// composite ``Alignment`` that indicates both vertical and horizontal +/// positioning in one value. For example, you could combine your custom +/// `oneQuarter` horizontal alignment from the previous section with a built-in +/// ``VerticalAlignment/center`` vertical alignment to use in a ``ZStack``: +/// +/// struct LayeredVerticalStripes: View { +/// var body: some View { +/// ZStack(alignment: Alignment(horizontal: .oneQuarter, vertical: .center)) { +/// verticalStripes(color: .blue) +/// .frame(width: 300, height: 150) +/// verticalStripes(color: .green) +/// .frame(width: 180, height: 80) +/// } +/// } +/// +/// private func verticalStripes(color: Color) -> some View { +/// HStack(spacing: 1) { +/// ForEach(0..<4) { _ in color } +/// } +/// } +/// } +/// +/// The example above uses widths and heights that generate two mismatched sets +/// of four vertical stripes. The ``ZStack`` centers the two sets vertically and +/// aligns them horizontally one quarter of the way from the leading edge of +/// each set. In a left-to-right locale, this aligns the right edges of the +/// left-most stripes of each set: +/// +/// ![Two sets of four rectangles. The first set is blue. The +/// second set is green, is smaller, and is layered on top of the first set. +/// The two sets are centered vertically, but align horizontally at the right +/// edge of each set's left-most rectangle.](HorizontalAlignment-3-iOS) +@frozen +public struct HorizontalAlignment: Equatable { + /// Creates a custom horizontal alignment of the specified type. + /// + /// Use this initializer to create a custom horizontal alignment. Define + /// an ``AlignmentID`` type, and then use that type to create a new + /// static property on ``HorizontalAlignment``: + /// + /// private struct OneQuarterAlignment: AlignmentID { + /// static func defaultValue(in context: ViewDimensions) -> CGFloat { + /// context.width / 4 + /// } + /// } + /// + /// extension HorizontalAlignment { + /// static let oneQuarter = HorizontalAlignment(OneQuarterAlignment.self) + /// } + /// + /// Every horizontal alignment instance that you create needs a unique + /// identifier. For more information, see ``AlignmentID``. + /// + /// - Parameter id: The type of an identifier that uniquely identifies a + /// horizontal alignment. + public init(_ id: AlignmentID.Type) { + key = AlignmentKey(id: id, axis: .horizontal) + } + + @usableFromInline + let key: AlignmentKey + + /// A guide that marks the leading edge of the view. + /// + /// Use this guide to align the leading edges of views. + /// For a device that uses a left-to-right language, the leading edge + /// is on the left: + /// + /// ![A box that contains the word, Leading. Vertical + /// lines appear above and below the box. The lines align horizontally + /// with the left edge of the box.](HorizontalAlignment-leading-1-iOS) + /// + /// The following code generates the image above using a ``VStack``: + /// + /// struct HorizontalAlignmentLeading: View { + /// var body: some View { + /// VStack(alignment: .leading, spacing: 0) { + /// Color.red.frame(width: 1) + /// Text("Leading").font(.title).border(.gray) + /// Color.red.frame(width: 1) + /// } + /// } + /// } + /// + public static let leading = HorizontalAlignment(Leading.self) + private enum Leading: FrameAlignment { + static func defaultValue(in _: ViewDimensions) -> CGFloat { + .zero + } + } + + /// A guide that marks the horizontal center of the view. + /// + /// Use this guide to align the centers of views: + /// + /// ![A box that contains the word, Center. Vertical + /// lines appear above and below the box. The lines align horizontally + /// with the center of the box.](HorizontalAlignment-center-1-iOS) + /// + /// The following code generates the image above using a ``VStack``: + /// + /// struct HorizontalAlignmentCenter: View { + /// var body: some View { + /// VStack(alignment: .center, spacing: 0) { + /// Color.red.frame(width: 1) + /// Text("Center").font(.title).border(.gray) + /// Color.red.frame(width: 1) + /// } + /// } + /// } + /// + public static let center = HorizontalAlignment(Center.self) + private enum Center: FrameAlignment { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.width / 2 + } + } + + /// A guide that marks the trailing edge of the view. + /// + /// Use this guide to align the trailing edges of views. + /// For a device that uses a left-to-right language, the trailing edge + /// is on the right: + /// + /// ![A box that contains the word, Trailing. Vertical + /// lines appear above and below the box. The lines align horizontally + /// with the right edge of the box.](HorizontalAlignment-trailing-1-iOS) + /// + /// The following code generates the image above using a ``VStack``: + /// + /// struct HorizontalAlignmentTrailing: View { + /// var body: some View { + /// VStack(alignment: .trailing, spacing: 0) { + /// Color.red.frame(width: 1) + /// Text("Trailing").font(.title).border(.gray) + /// Color.red.frame(width: 1) + /// } + /// } + /// } + /// + public static let trailing = HorizontalAlignment(Trailing.self) + private enum Trailing: FrameAlignment { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.width + } + } + + static let leadingText = HorizontalAlignment(LeadingText.self) + private enum LeadingText: FrameAlignment { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height + } + + static func _combineExplicit(childValue: CGFloat, _: Int, into parentValue: inout CGFloat?) { + parentValue = min(childValue, parentValue ?? .infinity) + } + } +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/VerticalAlignment.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/VerticalAlignment.swift new file mode 100644 index 0000000..b2c4cdc --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/VerticalAlignment.swift @@ -0,0 +1,328 @@ +// +// VerticalAlignment.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: Complete +// ID: E20796D15DD3D417699102559E024115 + +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +/// An alignment position along the vertical axis. +/// +/// Use vertical alignment guides to position views +/// relative to one another vertically, like when you place views side-by-side +/// in an ``HStack`` or when you create a row of views in a ``Grid`` using +/// ``GridRow``. The following example demonstrates common built-in +/// vertical alignments: +/// +/// ![Five rows of content. Each row contains text inside +/// a box with horizontal lines to the left and the right of the box. The +/// lines are aligned vertically with the text in a different way for each +/// row, corresponding to the content of the text in that row. The text strings +/// are, in order, top, center, bottom, first text baseline, and last text +/// baseline.](VerticalAlignment-1-iOS) +/// +/// You can generate the example above by creating a series of rows +/// implemented as horizontal stacks, where you configure each stack with a +/// different alignment guide: +/// +/// private struct VerticalAlignmentGallery: View { +/// var body: some View { +/// VStack(spacing: 30) { +/// row(alignment: .top, text: "Top") +/// row(alignment: .center, text: "Center") +/// row(alignment: .bottom, text: "Bottom") +/// row(alignment: .firstTextBaseline, text: "First Text Baseline") +/// row(alignment: .lastTextBaseline, text: "Last Text Baseline") +/// } +/// } +/// +/// private func row(alignment: VerticalAlignment, text: String) -> some View { +/// HStack(alignment: alignment, spacing: 0) { +/// Color.red.frame(height: 1) +/// Text(text).font(.title).border(.gray) +/// Color.red.frame(height: 1) +/// } +/// } +/// } +/// +/// During layout, OpenSwiftUI aligns the views inside each stack by bringing +/// together the specified guides of the affected views. OpenSwiftUI calculates +/// the position of a guide for a particular view based on the characteristics +/// of the view. For example, the ``VerticalAlignment/center`` guide appears +/// at half the height of the view. You can override the guide calculation for a +/// particular view using the ``View/alignmentGuide(_:computeValue:)-6y3u2`` +/// view modifier. +/// +/// ### Text baseline alignment +/// +/// Use the ``VerticalAlignment/firstTextBaseline`` or +/// ``VerticalAlignment/lastTextBaseline`` guide to match the bottom of either +/// the top- or bottom-most line of text that a view contains, respectively. +/// Text baseline alignment excludes the parts of characters that descend +/// below the baseline, like the tail on lower case g and j: +/// +/// row(alignment: .firstTextBaseline, text: "fghijkl") +/// +/// If you use a text baseline alignment on a view that contains no text, +/// OpenSwiftUI applies the equivalent of ``VerticalAlignment/bottom`` alignment +/// instead. For the row in the example above, OpenSwiftUI matches the bottom of +/// the horizontal lines with the baseline of the text: +/// +/// ![A string containing the lowercase letters f, g, h, i, j, and +/// k. The string is inside a box, and horizontal lines appear to the left and +/// to the right of the box. The lines align with the bottom of the text, +/// excluding the descenders of letters g and j, which extend below the +/// baseline.](VerticalAlignment-2-iOS) +/// +/// Aligning a text view to its baseline rather than to the bottom of its frame +/// produces the best layout effect in many cases, like when creating forms. +/// For example, you can align the baseline of descriptive text in +/// one ``GridRow`` cell with the baseline of a text field, or the label +/// of a checkbox, in another cell in the same row. +/// +/// ### Custom alignment guides +/// +/// You can create a custom vertical alignment guide by first creating a type +/// that conforms to the ``AlignmentID`` protocol, and then using that type to +/// initalize a new static property on `VerticalAlignment`: +/// +/// private struct FirstThirdAlignment: AlignmentID { +/// static func defaultValue(in context: ViewDimensions) -> CGFloat { +/// context.height / 3 +/// } +/// } +/// +/// extension VerticalAlignment { +/// static let firstThird = VerticalAlignment(FirstThirdAlignment.self) +/// } +/// +/// You implement the ``AlignmentID/defaultValue(in:)`` method to calculate +/// a default value for the custom alignment guide. The method receives a +/// ``ViewDimensions`` instance that you can use to calculate a +/// value based on characteristics of the view. The example above places +/// the guide at one-third of the height of the view as measured from the +/// view's origin. +/// +/// You can then use the custom alignment guide like any built-in guide. For +/// example, you can use it as the `alignment` parameter to an ``HStack``, +/// or to alter the guide calculation for a specific view using the +/// ``View/alignmentGuide(_:computeValue:)-6y3u2`` view modifier. +/// +/// ### Composite alignment +/// +/// Combine a `VerticalAlignment` with a ``HorizontalAlignment`` to create a +/// composite ``Alignment`` that indicates both vertical and horizontal +/// positioning in one value. For example, you could combine your custom +/// `firstThird` vertical alignment from the previous section with a built-in +/// ``HorizontalAlignment/center`` horizontal alignment to use in a ``ZStack``: +/// +/// struct LayeredHorizontalStripes: View { +/// var body: some View { +/// ZStack(alignment: Alignment(horizontal: .center, vertical: .firstThird)) { +/// horizontalStripes(color: .blue) +/// .frame(width: 180, height: 90) +/// horizontalStripes(color: .green) +/// .frame(width: 70, height: 60) +/// } +/// } +/// +/// private func horizontalStripes(color: Color) -> some View { +/// VStack(spacing: 1) { +/// ForEach(0..<3) { _ in color } +/// } +/// } +/// } +/// +/// The example above uses widths and heights that generate two mismatched +/// sets of three vertical stripes. The ``ZStack`` centers the two sets +/// horizontally and aligns them vertically one-third from the top +/// of each set. This aligns the bottom edges of the top stripe from each set: +/// +/// ![Two sets of three vertically stacked rectangles. The first +/// set is blue. The second set of rectangles are green, smaller, and layered +/// on top of the first set. The two sets are centered horizontally, but align +/// vertically at the bottom edge of each set's top-most +/// rectangle.](VerticalAlignment-3-iOS) +@frozen +public struct VerticalAlignment: Equatable { + /// Creates a custom vertical alignment of the specified type. + /// + /// Use this initializer to create a custom vertical alignment. Define + /// an ``AlignmentID`` type, and then use that type to create a new + /// static property on ``VerticalAlignment``: + /// + /// private struct FirstThirdAlignment: AlignmentID { + /// static func defaultValue(in context: ViewDimensions) -> CGFloat { + /// context.height / 3 + /// } + /// } + /// + /// extension VerticalAlignment { + /// static let firstThird = VerticalAlignment(FirstThirdAlignment.self) + /// } + /// + /// Every vertical alignment instance that you create needs a unique + /// identifier. For more information, see ``AlignmentID``. + /// + /// - Parameter id: The type of an identifier that uniquely identifies a + /// vertical alignment. + public init(_ id: AlignmentID.Type) { + key = AlignmentKey(id: id, axis: .vertical) + } + + @usableFromInline + let key: AlignmentKey + + /// A guide that marks the top edge of the view. + /// + /// Use this guide to align the top edges of views: + /// + /// ![A box that contains the word, Top. A horizontal + /// line appears on either side of the box. The lines align vertically + /// with the top edge of the box.](VerticalAlignment-top-1-iOS) + /// + /// The following code generates the image above using an ``HStack``: + /// + /// struct VerticalAlignmentTop: View { + /// var body: some View { + /// HStack(alignment: .top, spacing: 0) { + /// Color.red.frame(height: 1) + /// Text("Top").font(.title).border(.gray) + /// Color.red.frame(height: 1) + /// } + /// } + /// } + /// + public static let top = VerticalAlignment(Top.self) + private enum Top: FrameAlignment { + static func defaultValue(in _: ViewDimensions) -> CGFloat { + .zero + } + } + + /// A guide that marks the vertical center of the view. + /// + /// Use this guide to align the centers of views: + /// + /// ![A box that contains the word, Center. A horizontal + /// line appears on either side of the box. The lines align vertically + /// with the center of the box.](VerticalAlignment-center-1-iOS) + /// + /// The following code generates the image above using an ``HStack``: + /// + /// struct VerticalAlignmentCenter: View { + /// var body: some View { + /// HStack(alignment: .center, spacing: 0) { + /// Color.red.frame(height: 1) + /// Text("Center").font(.title).border(.gray) + /// Color.red.frame(height: 1) + /// } + /// } + /// } + /// + public static let center = VerticalAlignment(Center.self) + private enum Center: FrameAlignment { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height / 2 + } + } + + /// A guide that marks the bottom edge of the view. + /// + /// Use this guide to align the bottom edges of views: + /// + /// ![A box that contains the word, Bottom. A horizontal + /// line appears on either side of the box. The lines align vertically + /// with the bottom edge of the box.](VerticalAlignment-bottom-1-iOS) + /// + /// The following code generates the image above using an ``HStack``: + /// + /// struct VerticalAlignmentBottom: View { + /// var body: some View { + /// HStack(alignment: .bottom, spacing: 0) { + /// Color.red.frame(height: 1) + /// Text("Bottom").font(.title).border(.gray) + /// Color.red.frame(height: 1) + /// } + /// } + /// } + /// + public static let bottom = VerticalAlignment(Bottom.self) + private enum Bottom: FrameAlignment { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height + } + } + + /// A guide that marks the top-most text baseline in a view. + /// + /// Use this guide to align with the baseline of the top-most text in a + /// view. The guide aligns with the bottom of a view that contains no text: + /// + /// ![A box that contains the text, First Text Baseline. + /// A horizontal line appears on either side of the box. The lines align + /// vertically with the baseline of the first line of + /// text.](VerticalAlignment-firstTextBaseline-1-iOS) + /// + /// The following code generates the image above using an ``HStack``: + /// + /// struct VerticalAlignmentFirstTextBaseline: View { + /// var body: some View { + /// HStack(alignment: .firstTextBaseline, spacing: 0) { + /// Color.red.frame(height: 1) + /// Text("First Text Baseline").font(.title).border(.gray) + /// Color.red.frame(height: 1) + /// } + /// } + /// } + public static let firstTextBaseline = VerticalAlignment(FirstTextBaseline.self) + private enum FirstTextBaseline: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height + } + + static func _combineExplicit(childValue: CGFloat, _: Int, into parentValue: inout CGFloat?) { + parentValue = min(childValue, parentValue ?? .infinity) + } + } + + /// A guide that marks the bottom-most text baseline in a view. + /// + /// Use this guide to align with the baseline of the bottom-most text in a + /// view. The guide aligns with the bottom of a view that contains no text. + /// + /// ![A box that contains the text, Last Text Baseline. + /// A horizontal line appears on either side of the box. The lines align + /// vertically with the baseline of the last line of + /// text.](VerticalAlignment-lastTextBaseline-1-iOS) + /// + /// The following code generates the image above using an ``HStack``: + /// + /// struct VerticalAlignmentLastTextBaseline: View { + /// var body: some View { + /// HStack(alignment: .lastTextBaseline, spacing: 0) { + /// Color.red.frame(height: 1) + /// Text("Last Text Baseline").font(.title).border(.gray) + /// Color.red.frame(height: 1) + /// } + /// } + /// } + /// + public static let lastTextBaseline = VerticalAlignment(LastTextBaseline.self) + private enum LastTextBaseline: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context.height + } + + static func _combineExplicit(childValue: CGFloat, _: Int, into parentValue: inout CGFloat?) { + parentValue = max(childValue, parentValue ?? -.infinity) + } + } +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift new file mode 100644 index 0000000..65864a0 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift @@ -0,0 +1,191 @@ +// +// ViewDimensions.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/16. +// Lastest Version: iOS 15.5 +// Status: Complete + +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +/// A view's size and alignment guides in its own coordinate space. +/// +/// This structure contains the size and alignment guides of a view. +/// You receive an instance of this structure to use in a variety of +/// layout calculations, like when you: +/// +/// * Define a default value for a custom alignment guide; +/// see ``AlignmentID/defaultValue(in:)``. +/// * Modify an alignment guide on a view; +/// see ``View/alignmentGuide(_:computeValue:)-9mdoh``. +/// * Ask for the dimensions of a subview of a custom view layout; +/// see ``LayoutSubview/dimensions(in:)``. +/// +/// ### Custom alignment guides +/// +/// You receive an instance of this structure as the `context` parameter to +/// the ``AlignmentID/defaultValue(in:)`` method that you implement to produce +/// the default offset for an alignment guide, or as the first argument to the +/// closure you provide to the ``View/alignmentGuide(_:computeValue:)-6y3u2`` +/// view modifier to override the default calculation for an alignment guide. +/// In both cases you can use the instance, if helpful, to calculate the +/// offset for the guide. For example, you could compute a default offset +/// for a custom ``VerticalAlignment`` as a fraction of the view's ``height``: +/// +/// private struct FirstThirdAlignment: AlignmentID { +/// static func defaultValue(in context: ViewDimensions) -> CGFloat { +/// context.height / 3 +/// } +/// } +/// +/// extension VerticalAlignment { +/// static let firstThird = VerticalAlignment(FirstThirdAlignment.self) +/// } +/// +/// As another example, you could use the view dimensions instance to look +/// up the offset of an existing guide and modify it: +/// +/// struct ViewDimensionsOffset: View { +/// var body: some View { +/// VStack(alignment: .leading) { +/// Text("Default") +/// Text("Indented") +/// .alignmentGuide(.leading) { context in +/// context[.leading] - 10 +/// } +/// } +/// } +/// } +/// +/// The example above indents the second text view because the subtraction +/// moves the second text view's leading guide in the negative x direction, +/// which is to the left in the view's coordinate space. As a result, +/// SwiftUI moves the second text view to the right, relative to the first +/// text view, to keep their leading guides aligned: +/// +/// ![A screenshot of two strings. The first says Default and the second, +/// which appears below the first, says Indented. The left side of the second +/// string appears horizontally offset to the right from the left side of the +/// first string by about the width of one character.](ViewDimensions-1-iOS) +/// +/// ### Layout direction +/// +/// The discussion above describes a left-to-right language environment, +/// but you don't change your guide calculation to operate in a right-to-left +/// environment. SwiftUI moves the view's origin from the left to the right side +/// of the view and inverts the positive x direction. As a result, +/// the existing calculation produces the same effect, but in the opposite +/// direction. +/// +/// You can see this if you use the ``View/environment(_:_:)`` +/// modifier to set the ``EnvironmentValues/layoutDirection`` property for the +/// view that you defined above: +/// +/// ViewDimensionsOffset() +/// .environment(\.layoutDirection, .rightToLeft) +/// +/// With no change in your guide, this produces the desired effect --- +/// it indents the second text view's right side, relative to the +/// first text view's right side. The leading edge is now on the right, +/// and the direction of the offset is reversed: +/// +/// ![A screenshot of two strings. The first says Default and the second, +/// which appears below the first, says Indented. The right side of the second +/// string appears horizontally offset to the left from the right side of the +/// first string by about the width of one character.](ViewDimensions-2-iOS) +public struct ViewDimensions { + let guideComputer: LayoutComputer + var size: ViewSize + + /// The view's width. + public var width: CGFloat { size.value.width } + + /// The view's height. + public var height: CGFloat { size.value.height } + + /// Gets the value of the given horizontal guide. + /// + /// Find the offset of a particular guide in the corresponding view by + /// using that guide as an index to read from the context: + /// + /// .alignmentGuide(.leading) { context in + /// context[.leading] - 10 + /// } + /// + /// For information about using subscripts in Swift to access member + /// elements of a collection, list, or, sequence, see + /// [Subscripts](https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html) + /// in _The Swift Programming Language_. + public subscript(guide: HorizontalAlignment) -> CGFloat { + self[guide.key] + } + + /// Gets the value of the given vertical guide. + /// + /// Find the offset of a particular guide in the corresponding view by + /// using that guide as an index to read from the context: + /// + /// .alignmentGuide(.top) { context in + /// context[.top] - 10 + /// } + /// + /// For information about using subscripts in Swift to access member + /// elements of a collection, list, or, sequence, see + /// [Subscripts](https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html) + /// in _The Swift Programming Language_. + public subscript(guide: VerticalAlignment) -> CGFloat { + self[guide.key] + } + + subscript(key: AlignmentKey) -> CGFloat { + self[explicit: key] ?? key.id.defaultValue(in: self) + } + + /// Gets the explicit value of the given horizontal alignment guide. + /// + /// Find the horizontal offset of a particular guide in the corresponding + /// view by using that guide as an index to read from the context: + /// + /// .alignmentGuide(.leading) { context in + /// context[.leading] - 10 + /// } + /// + /// This subscript returns `nil` if no value exists for the guide. + /// + /// For information about using subscripts in Swift to access member + /// elements of a collection, list, or, sequence, see + /// [Subscripts](https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html) + /// in _The Swift Programming Language_. + public subscript(explicit guide: HorizontalAlignment) -> CGFloat? { + self[explicit: guide.key] + } + + /// Gets the explicit value of the given vertical alignment guide + /// + /// Find the vertical offset of a particular guide in the corresponding + /// view by using that guide as an index to read from the context: + /// + /// .alignmentGuide(.top) { context in + /// context[.top] - 10 + /// } + /// + /// This subscript returns `nil` if no value exists for the guide. + /// + /// For information about using subscripts in Swift to access member + /// elements of a collection, list, or, sequence, see + /// [Subscripts](https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html) + /// in _The Swift Programming Language_. + public subscript(explicit guide: VerticalAlignment) -> CGFloat? { + self[explicit: guide.key] + } + + subscript(explicit key: AlignmentKey) -> CGFloat? { + guideComputer.delegate.explicitAlignment(key, at: size) + } +} + +extension ViewDimensions: Equatable {} diff --git a/Sources/OpenSwiftUI/UIElements/Edge/Edge.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Edge/Edge.swift similarity index 67% rename from Sources/OpenSwiftUI/UIElements/Edge/Edge.swift rename to Sources/OpenSwiftUI/Layout/LayoutAdjustments/Edge/Edge.swift index 6f42820..42cc67c 100644 --- a/Sources/OpenSwiftUI/UIElements/Edge/Edge.swift +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Edge/Edge.swift @@ -8,17 +8,24 @@ // MARK: - Edge +/// An enumeration to indicate one edge of a rectangle. @frozen public enum Edge: Int8, CaseIterable { - case top, leading, bottom, trailing + case top + case leading + case bottom + case trailing + + /// An efficient set of Edges. @frozen public struct Set: OptionSet { public typealias Element = Set public let rawValue: Int8 - @inline(__always) - public init(rawValue: Int8) { self.rawValue = rawValue } + public init(rawValue: Int8) { + self.rawValue = rawValue + } public static let top = Set(.top) public static let leading = Set(.leading) @@ -28,10 +35,9 @@ public enum Edge: Int8, CaseIterable { public static let horizontal: Set = [.leading, .trailing] public static let vertical: Set = [.top, .bottom] - @inline(__always) + /// Creates an instance containing just e public init(_ e: Edge) { self.init(rawValue: 1 << e.rawValue) } - @inline(__always) func contains(_ edge: Edge) -> Bool { contains(.init(edge)) } @@ -50,18 +56,37 @@ extension Edge: CodableByProxy { // MARK: - HorizontalEdge and VerticalEdge +/// An edge on the horizontal axis. +/// +/// Use a horizontal edge for tasks like setting a swipe action with the +/// ``View/swipeActions(edge:allowsFullSwipe:content:)`` +/// view modifier. The positions of the leading and trailing edges +/// depend on the locale chosen by the user. @frozen public enum HorizontalEdge: Int8, CaseIterable, Codable { + /// The leading edge. case leading + + /// The trailing edge. case trailing - @frozen public struct Set: OptionSet { + + /// An efficient set of `HorizontalEdge`s. + @frozen + public struct Set: OptionSet { public typealias Element = Set public let rawValue: Int8 public init(rawValue: Int8) { self.rawValue = rawValue } + + /// A set containing only the leading horizontal edge. public static let leading = Set(.leading) + + /// A set containing only the trailing horizontal edge. public static let trailing = Set(.trailing) + + /// A set containing the leading and trailing horizontal edges. public static let all: Set = [.leading, .trailing] + /// Creates an instance containing just `e`. public init(_ e: HorizontalEdge) { self.init(rawValue: 1 << e.rawValue) } @inline(__always) @@ -69,18 +94,32 @@ public enum HorizontalEdge: Int8, CaseIterable, Codable { } } +/// An edge on the vertical axis. @frozen public enum VerticalEdge: Int8, CaseIterable, Codable { + /// The top edge. case top + + /// The bottom edge. case bottom - @frozen public struct Set: OptionSet { + + /// An efficient set of `VerticalEdge`s. + @frozen + public struct Set: OptionSet { public typealias Element = Set public let rawValue: Int8 public init(rawValue: Int8) { self.rawValue = rawValue } + + /// A set containing only the top vertical edge. public static let top = Set(.top) + + /// A set containing only the bottom vertical edge. public static let bottom = Set(.bottom) + + /// A set containing the top and bottom vertical edges. public static let all: Set = [.top, .bottom] + /// Creates an instance containing just `e` public init(_ e: VerticalEdge) { self.init(rawValue: 1 << e.rawValue) } @inline(__always) diff --git a/Sources/OpenSwiftUI/UIElements/Edge/EdgeInsets.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Edge/EdgeInsets.swift similarity index 96% rename from Sources/OpenSwiftUI/UIElements/Edge/EdgeInsets.swift rename to Sources/OpenSwiftUI/Layout/LayoutAdjustments/Edge/EdgeInsets.swift index 5de53c6..da6853a 100644 --- a/Sources/OpenSwiftUI/UIElements/Edge/EdgeInsets.swift +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Edge/EdgeInsets.swift @@ -12,7 +12,9 @@ import CoreGraphics import Foundation #endif -@frozen public struct EdgeInsets: Equatable { +/// The inset distances for the sides of a rectangle. +@frozen +public struct EdgeInsets: Equatable { public var top: CGFloat public var leading: CGFloat public var bottom: CGFloat @@ -75,6 +77,7 @@ import UIKit import AppKit #endif extension EdgeInsets { + /// Create edge insets from the equivalent NSDirectionalEdgeInsets. @inline(__always) @available(watchOS, unavailable) public init(_ nsEdgeInsets: NSDirectionalEdgeInsets) { diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/HStack.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/HStack.swift new file mode 100644 index 0000000..d0b796b --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/HStack.swift @@ -0,0 +1,27 @@ +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +@frozen +public struct HStack: PrimitiveView { + @inlinable + public init( + alignment: VerticalAlignment = .center, + spacing: CGFloat? = nil, + @ViewBuilder content: () -> Content + ) { + _tree = .init( + root: _HStackLayout(alignment: alignment, spacing: spacing), + content: content() + ) + } + + @usableFromInline + var _tree: _VariadicView.Tree<_HStackLayout, Content> + + public static func _makeView(view _: _GraphValue>, inputs _: _ViewInputs) -> _ViewOutputs { + fatalError("TODO") + } +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/_HStackLayout.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/_HStackLayout.swift new file mode 100644 index 0000000..af79366 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/Stack/_HStackLayout.swift @@ -0,0 +1,20 @@ +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +@frozen +public struct _HStackLayout { + public var alignment: VerticalAlignment + public var spacing: CGFloat? + + @inlinable + public init(alignment: VerticalAlignment = .center, spacing: CGFloat? = nil) { + self.alignment = alignment + self.spacing = spacing + } + + public typealias AnimatableData = EmptyAnimatableData + public typealias Body = Swift.Never +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/LayoutComputer.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/LayoutComputer.swift new file mode 100644 index 0000000..2896e1a --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/LayoutComputer.swift @@ -0,0 +1,101 @@ +// +// LayoutComputer.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: WIP + +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +// TODO: +struct LayoutComputer: Equatable { + static func == (lhs: LayoutComputer, rhs: LayoutComputer) -> Bool { + lhs.seed == rhs.seed /*&& lhs.delegate == rhs.delegate*/ + } + + var seed: Int + var delegate: Delegate +} + +extension LayoutComputer: Defaultable { + static var defaultValue: LayoutComputer { + LayoutComputer(seed: 0, delegate: LayoutComputer.defaultDelegate) + } +} + +// MARK: LayoutComputer + Delegate + +extension LayoutComputer { + class Delegate: LayoutComputerDelegate { + func layoutPriority() -> Double { .zero } + func ignoresAutomaticPadding() -> Bool { false } + func requiresSpacingProjection() -> Bool { false } + func spacing() -> Spacing { fatalError() } + func lengthThatFits(_ size: _ProposedSize, in axis: Axis) -> CGFloat { + let result = sizeThatFits(size) + return switch axis { + case .horizontal: result.width + case .vertical: result.height + } + } + + func sizeThatFits(_: _ProposedSize) -> CGSize { fatalError() } + func childGeometries(at _: ViewSize, origin _: CGPoint) -> [ViewGeometry] { fatalError() } + func explicitAlignment(_: AlignmentKey, at _: ViewSize) -> CGFloat? { nil } + } +} + +protocol LayoutComputerDelegate: LayoutComputer.Delegate {} + +// MARK: LayoutComputer + DefaultDelegate + +extension LayoutComputer { + static let defaultDelegate = DefaultDelegate() + + class DefaultDelegate: Delegate { + override func layoutPriority() -> Double { + .zero + } + + override func ignoresAutomaticPadding() -> Bool { + false + } + + override func spacing() -> Spacing { + Spacing(minima: [ + .init(category: .edgeBelowText, edge: .top) : .zero, + .init(category: .edgeAboveText, edge: .bottom) : .zero, + ]) + } + + override func sizeThatFits(_ size: _ProposedSize) -> CGSize { + CGSize(width: size.width ?? 10, height: size.height ?? 10) + } + + override func childGeometries(at _: ViewSize, origin _: CGPoint) -> [ViewGeometry] { + [] + } + } +} + +// MARK: LayoutComputer + EngineDelegate + +// TODO +extension LayoutComputer { + class EngineDelegate: Delegate {} +} + +protocol LayoutEngineProtocol { + func layoutPriority() -> Double + func ignoresAutomaticPadding() -> Bool + func requiresSpacingProjection() -> Bool + func spacing() -> Spacing + func sizeThatFits(_: _ProposedSize) -> CGSize + func childGeometries(at _: ViewSize, origin _: CGPoint) -> [ViewGeometry] + func explicitAlignment(_: AlignmentKey, at _: ViewSize) -> CGFloat? +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/Spacing.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/Spacing.swift new file mode 100644 index 0000000..593b2a9 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/Spacing.swift @@ -0,0 +1,88 @@ +// +// Spacing.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: TODO +// ID: 127A76D3C8081D0134153BE9AE746714 + +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +struct Spacing { + // TODO + static let zero = Spacing(minima: [:]) + static let zeroHorizontal = Spacing(minima: [:]) + static let zeroVertical = Spacing(minima: [:]) + + var minima: [Key: CGFloat] + + func distanceToSuccessorView(along axis: Axis, preferring spacing: Spacing) -> CGFloat? { + if minima.count >= spacing.minima.count { + switch axis { + case .horizontal: _distance(from: .leading, to: .trailing, ofViewPreferring: spacing) + case .vertical: _distance(from: .top, to: .bottom, ofViewPreferring: spacing) + } + } else { + switch axis { + case .horizontal: _distance(from: .trailing, to: .leading, ofViewPreferring: spacing) + case .vertical: _distance(from: .bottom, to: .top, ofViewPreferring: spacing) + } + } + } + + private func _distance(from: Edge, to: Edge, ofViewPreferring spacing: Spacing) -> CGFloat? { + // TODO + nil + } + + func reset(_ edge: Edge.Set) { + guard !edge.isEmpty else { + return + } + // TODO + } + + func clear(_ edge: Edge.Set) { + guard !edge.isEmpty else { + return + } + // TODO + } + + func incorporate(_ edge: Edge.Set, of spacing: Spacing) { + // TODO + } +} + +// MARK: - Spacing.Key + +extension Spacing { + struct Key: Hashable { + var category: Category? + var edge: Edge + } +} + +// MARK: - Spacing.Category + +extension Spacing { + struct Category: Hashable { + let id: ObjectIdentifier + } +} + +extension Spacing.Category { + private enum TextToText {} + private enum TextBaseline {} + private enum EdgeBelowText {} + private enum EdgeAboveText {} + static let textToText: Self = .init(id: .init(TextToText.self)) + static let textBaseline: Self = .init(id: .init(TextBaseline.self)) + static let edgeBelowText: Self = .init(id: .init(EdgeBelowText.self)) + static let edgeAboveText: Self = .init(id: .init(EdgeAboveText.self)) +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift new file mode 100644 index 0000000..24a55d0 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift @@ -0,0 +1,14 @@ +// +// ViewGeometry.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: Complete + +import Foundation + +struct ViewGeometry: Equatable { + var origin: ViewOrigin + var dimensions: ViewDimensions +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift new file mode 100644 index 0000000..1c1e3c0 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift @@ -0,0 +1,20 @@ +// +// ViewOrigin.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: Complete + +import Foundation + +struct ViewOrigin: Equatable { + var value: CGPoint +} + +extension ViewOrigin: Animatable { + var animatableData: AnimatablePair { + get { .init(value.x, value.y) } + set { value = .init(x: newValue.first, y: newValue.second) } + } +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift new file mode 100644 index 0000000..6aebd77 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift @@ -0,0 +1,14 @@ +// +// ViewSize.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/16. +// Lastest Version: iOS 15.5 +// Status: Complete + +import Foundation + +struct ViewSize: Equatable { + var value: CGSize + var _proposal: CGSize +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/_ProposedSize.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/_ProposedSize.swift new file mode 100644 index 0000000..bbb9eb3 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/_ProposedSize.swift @@ -0,0 +1,18 @@ +// +// _ProposedSize.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: Complete + +#if canImport(Darwin) +import CoreGraphics +#elseif os(Linux) +import Foundation +#endif + +struct _ProposedSize: Hashable { + var width: CGFloat? + var height: CGFloat? +} diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/App.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/App.md deleted file mode 100644 index 878fcac..0000000 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/App.md +++ /dev/null @@ -1,13 +0,0 @@ -# ``App`` - -## Topics - -### Implementing an app - -- ``OpenSwiftUI/App/body-swift.property`` -- ``OpenSwiftUI/App/Body-swift.associatedtype`` - -### Running an app - -- ``OpenSwiftUI/App/init()`` -- ``OpenSwiftUI/App/main()`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization.md deleted file mode 100644 index 4c0647f..0000000 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization.md +++ /dev/null @@ -1,17 +0,0 @@ -# App organization - -Define the entry point and top-level structure of your app. - -## Overview - -Describe your app’s structure declaratively, much like you declare a view’s appearance. Create a type that conforms to the App protocol and use it to enumerate the Scenes that represent aspects of your app’s user interface. - -OpenSwiftUI enables you to write code that works across all of Apple’s platforms, Linux and Windows. However, it also enables you to tailor your app to the specific capabilities of each platform. For example, if you need to respond to the callbacks that the system traditionally makes on a UIKit, AppKit, or WatchKit app’s delegate, define a delegate object and instantiate it in your app structure using an appropriate delegate adaptor property wrapper, like ``UIApplicationDelegateAdaptor``. - -For platform-specific design guidance, see [Platforms](https://developer.apple.com/design/human-interface-guidelines/platforms) in the Human Interface Guidelines for Apple Platform and [Windows Design Documentation](https://learn.microsoft.com/windows/apps/design) for Windows. - -## Topics - -### Creating an app - -- ``App`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App-Organization.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App-Organization.md new file mode 100644 index 0000000..66c9c56 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App-Organization.md @@ -0,0 +1,28 @@ +# App organization + +Define the entry point and top-level structure of your app. + +## Overview + +Describe your app’s structure declaratively, much like you declare a view’s +appearance. Create a type that conforms to the App protocol and use it to +enumerate the Scenes that represent aspects of your app’s user interface. + +OpenSwiftUI enables you to write code that works across all of Apple’s +platforms, Linux and Windows. However, it also enables you to tailor your app to +the specific capabilities of each platform. For example, if you need to respond +to the callbacks that the system traditionally makes on a UIKit, AppKit, or +WatchKit app’s delegate, define a delegate object and instantiate it in your app +structure using an appropriate delegate adaptor property wrapper, like +``UIApplicationDelegateAdaptor``. + +For platform-specific design guidance, see [Platforms](https://developer.apple.com/design/human-interface-guidelines/platforms) +in the Human Interface Guidelines for Apple Platform and +[Windows Design Documentation](https://learn.microsoft.com/windows/apps/design) +for Windows. + +## Topics + +### Creating an app + +- ``App`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App.md new file mode 100644 index 0000000..283520d --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/AppStructure/AppOrganization/App.md @@ -0,0 +1,13 @@ +# ``App`` + +## Topics + +### Implementing an app + +- ``body-swift.property`` +- ``Body-swift.associatedtype`` + +### Running an app + +- ``init()`` +- ``main()`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/PreferenceKey.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/PreferenceKey.md deleted file mode 100644 index b9aaec4..0000000 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/PreferenceKey.md +++ /dev/null @@ -1,12 +0,0 @@ -# ``PreferenceKey`` - -## Topics - -### Getting the default value - -- ``OpenSwiftUI/PreferenceKey/defaultValue`` -- ``OpenSwiftUI/PreferenceKey/Value-swift.associatedtype`` - -### Combining preferences - -- ``OpenSwiftUI/PreferenceKey/reduce(value:nextValue:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences.md deleted file mode 100644 index 1f3e7ae..0000000 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences.md +++ /dev/null @@ -1,20 +0,0 @@ -# Preferences - -Indicate configuration preferences from views to their container views. - -## Overview - -Whereas you use the environment to configure the subviews of a view, you use preferences to send configuration information from subviews toward their container. However, unlike configuration information that flows down a view hierarchy from one container to many subviews, a single container needs to reconcile potentially conflicting preferences flowing up from its many subviews. - -When you use the ``OpenSwiftUI/PreferenceKey`` protocol to define a custom preference, you indicate how to merge preferences from multiple subviews. You can then set a value for the preference on a view using the ``OpenSwiftUI/View/preference(key:value:)`` view modifier. Many built-in modifiers, like ``OpenSwiftUI/View/navigationTitle(_:)``, rely on preferences to send configuration information to their container. - -## Topics - -### Setting preferences - -- ``OpenSwiftUI/View/preference(key:value:)`` -- ``OpenSwiftUI/View/transformPreference(_:_:)`` - -### Creating custom preferences - -- ``OpenSwiftUI/PreferenceKey`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/PreferenceKey.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/PreferenceKey.md new file mode 100644 index 0000000..b2ca4c8 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/PreferenceKey.md @@ -0,0 +1,12 @@ +# ``PreferenceKey`` + +## Topics + +### Getting the default value + +- ``defaultValue`` +- ``Value-swift.associatedtype`` + +### Combining preferences + +- ``reduce(value:nextValue:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/Preferences.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/Preferences.md new file mode 100644 index 0000000..d2d270c --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/Preferences/Preferences.md @@ -0,0 +1,28 @@ +# Preferences + +Indicate configuration preferences from views to their container views. + +## Overview + +Whereas you use the environment to configure the subviews of a view, you use +preferences to send configuration information from subviews toward their +container. However, unlike configuration information that flows down a view +hierarchy from one container to many subviews, a single container needs to +reconcile potentially conflicting preferences flowing up from its many subviews. + +When you use the ``PreferenceKey`` protocol to define a custom preference, you +indicate how to merge preferences from multiple subviews. You can then set a +value for the preference on a view using the ``View/preference(key:value:)`` +view modifier. Many built-in modifiers, like ``View/navigationTitle(_:)``, rely +on preferences to send configuration information to their container. + +## Topics + +### Setting preferences + +- ``View/preference(key:value:)`` +- ``View/transformPreference(_:_:)`` + +### Creating custom preferences + +- ``PreferenceKey`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Essentials/Introducing OpenSwiftUI.tutorial b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Essentials/Introducing OpenSwiftUI.tutorial index 32aac1c..af4c089 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Essentials/Introducing OpenSwiftUI.tutorial +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Essentials/Introducing OpenSwiftUI.tutorial @@ -1,9 +1,13 @@ @Tutorials(name: "Introducing OpenSwiftUI") { @Intro(title: "Introducing OpenSwiftUI") { - OpenSwiftUI is a modern way to declare user interfaces for any Apple platform, Linux and Windows. Create beautiful, dynamic apps faster than ever before. + OpenSwiftUI is a modern way to declare user interfaces for any Apple + platform, Linux and Windows. Create beautiful, dynamic apps faster than + ever before. } @Chapter(name: "OpenSwiftUI Essentials") { - Learn how to use OpenSwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview. + Learn how to use OpenSwiftUI to compose rich views out of simple ones, + set up data flow, and build the navigation while watching it unfold in + Xcode’s preview. } } diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents.md deleted file mode 100644 index 96ad71b..0000000 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents.md +++ /dev/null @@ -1,13 +0,0 @@ -# System events - -React to system events, like opening a URL. - -## Overview - -Specify view and scene modifiers to indicate how your app responds to certain system events. For example, you can use the ``onOpenURL(perform:)`` view modifier to define an action to take when your app receives a universal link, or use the ``backgroundTask(_:action:)`` scene modifier to specify an asynchronous task to carry out in response to a background task event, like the completion of a background URL session. - -## Topics - -### Handling URLs - -- ``OpenURLAction`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents/System-Events.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents/System-Events.md new file mode 100644 index 0000000..78c5dab --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/EventHandling/SystemEvents/System-Events.md @@ -0,0 +1,18 @@ +# System events + +React to system events, like opening a URL. + +## Overview + +Specify view and scene modifiers to indicate how your app responds to certain +system events. For example, you can use the ``onOpenURL(perform:)`` view +modifier to define an action to take when your app receives a universal link, +or use the ``backgroundTask(_:action:)`` scene modifier to specify an +asynchronous task to carry out in response to a background task event, like the +completion of a background URL session. + +## Topics + +### Handling URLs + +- ``OpenURLAction`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/AlignmentID.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/AlignmentID.md new file mode 100644 index 0000000..df599e5 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/AlignmentID.md @@ -0,0 +1,7 @@ +# ``AlignmentID`` + +## Topics + +### Getting the default value + +- ``defaultValue(in:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.md new file mode 100644 index 0000000..55cc770 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/HorizontalAlignment.md @@ -0,0 +1,15 @@ +# ``HorizontalAlignment`` + +## Topics + +### Getting guides + +- ``leading`` + +- ``center`` + +- ``trailing`` + +### Creating a custom alignment + +- ``init(_:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/VerticalAlignment.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/VerticalAlignment.md new file mode 100644 index 0000000..98c5c95 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/VerticalAlignment.md @@ -0,0 +1,19 @@ +# ``VerticalAlignment`` + +## Topics + +### Getting guides + +- ``top`` + +- ``center`` + +- ``bottom`` + +- ``firstTextBaseline`` + +- ``lastTextBaseline`` + +### Creating a custom alignment + +- ``init(_:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/ViewDimensions.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/ViewDimensions.md new file mode 100644 index 0000000..30c1a83 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Alignment/ViewDimensions.md @@ -0,0 +1,19 @@ +# ``ViewDimensions`` + +## Topics + +### Getting dimensions + +- ``height`` + +- ``width`` + +### Accessing guide values + +- ``subscript(_:)-5mcv0`` + +- ``subscript(_:)-5qjxo`` + +- ``subscript(explicit:)-3ujqt`` + +- ``subscript(explicit:)-mdli`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.Set.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.Set.md new file mode 100644 index 0000000..5f8b962 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.Set.md @@ -0,0 +1,23 @@ +# ``Edge/Set`` + +## Topics + +### Getting edge sets + +- ``all`` + +- ``top`` + +- ``bottom`` + +- ``leading`` + +- ``trailing`` + +- ``horizontal`` + +- ``vertical`` + +### Creating an edge set + +- ``init(_:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.md new file mode 100644 index 0000000..a9eaf25 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/Edge.md @@ -0,0 +1,17 @@ +# ``Edge`` + +## Topics + +### Getting the edges + +- ``top`` + +- ``bottom`` + +- ``leading`` + +- ``trailing`` + +### Accessing sets of edges + +- ``Set`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/EdgeInsets.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/EdgeInsets.md new file mode 100644 index 0000000..37ba80c --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/EdgeInsets.md @@ -0,0 +1,21 @@ +# ``EdgeInsets`` + +## Topics + +### Getting edge insets + +- ``top`` + +- ``bottom`` + +- ``leading`` + +- ``trailing`` + +### Creating an edge inset + +- ``init()`` + +- ``init(top:leading:bottom:trailing:)`` + +- ``init(_:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/HorizontalEdge.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/HorizontalEdge.md new file mode 100644 index 0000000..9a62293 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/HorizontalEdge.md @@ -0,0 +1,13 @@ +# ``HorizontalEdge`` + +## Topics + +### Getting the edges + +- ``leading`` + +- ``trailing`` + +### Accessing sets of edges + +- ``Set`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/VerticalEdge.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/VerticalEdge.md new file mode 100644 index 0000000..09028a4 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Edge/VerticalEdge.md @@ -0,0 +1,13 @@ +# ``VerticalEdge`` + +## Topics + +### Getting the edges + +- ``top`` + +- ``bottom`` + +### Accessing sets of edges + +- ``Set`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Layout-Adjustments.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Layout-Adjustments.md new file mode 100644 index 0000000..278399e --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutAdjustments/Layout-Adjustments.md @@ -0,0 +1,38 @@ +# Layout adjustments + +Make fine adjustments to alignment, spacing, padding, and other layout +parameters. + +## Overview + +Layout containers like stacks and grids provide a great starting point for +arranging views in your app’s user interface. When you need to make fine +adjustments, use layout view modifiers. You can adjust or constrain the size, +position, and alignment of a view. You can also add padding around a view, and +indicate how the view interacts with system-defined safe areas. + +To get started with a basic layout, see . For design +guidance, see [Layout](https://developer.apple.com/design/human-interface-guidelines/foundations/layout) +in the Human Interface Guidelines. + +## Topics + +### Aligning views + +- ``HorizontalAlignment`` + +- ``VerticalAlignment`` + +- ``AlignmentID`` + +- ``ViewDimensions`` + +### Accessing edges and regions + +- ``Edge`` + +- ``HorizontalEdge`` + +- ``VerticalEdge`` + +- ``EdgeInsets`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutFundamentals/Layout-Fundamentals.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutFundamentals/Layout-Fundamentals.md new file mode 100644 index 0000000..5f0d6ad --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Layout/LayoutFundamentals/Layout-Fundamentals.md @@ -0,0 +1,17 @@ +# Layout fundamentals + +Arrange views inside built-in layout containers like stacks and grids. + +## Overview + +Use layout containers to arrange the elements of your user interface. Stacks and +grids update and adjust the positions of the subviews they contain in response +to changes in content or interface dimensions. You can nest layout containers +inside other layout containers to any depth to achieve complex layout effects. + +To finetune the position, alignment, and other elements of a layout that you +build with layout container views, see . To define +custom layout containers, see . For design guidance, see +[Layout](https://developer.apple.com/design/human-interface-guidelines/foundations/layout) in the Human Interface Guidelines. + +## Topics diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md index 5a7303a..e837fce 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md @@ -18,7 +18,7 @@ You can integrate OpenSwiftUI views with objects from the [UIKit](https://develo ### App structure -- +- ### Data and storage @@ -28,6 +28,16 @@ You can integrate OpenSwiftUI views with objects from the [UIKit](https://develo - +- + +- + +### View Layout + +- + +- + ### Event Handling -- +- diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Animations.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Animations.md index aef924e..0b67f65 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Animations.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Animations.md @@ -4,7 +4,8 @@ Create smooth visual updates in response to state changes. ## Overview -You tell OpenSwiftUI how to draw your app’s user interface for different states, and then rely on OpenSwiftUI to make interface updates when the state changes. +You tell OpenSwiftUI how to draw your app’s user interface for different states, +and then rely on OpenSwiftUI to make interface updates when the state changes. ## Topics diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Controls.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Controls.md deleted file mode 100644 index 731a287..0000000 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Controls.md +++ /dev/null @@ -1,27 +0,0 @@ -# Controls and indicators - -Display values and get user selections. - -## Overview - -OpenSwiftUI provides controls that enable user interaction specific to each platform and context. For example, people can initiate events with buttons and links, or choose among a set of discrete values with different kinds of pickers. You can also display information to the user with indicators like progress views and gauges. - -Use these built-in controls and indicators when composing custom views, and style them to match the needs of your app’s user interface. For design guidance, see [All components](https://developer.apple.com/design/human-interface-guidelines/components) in the Human Interface Guidelines. - -## Topics - -### Linking to other content - -- ``Link`` - -### Getting numeric inputs - -- ``Slider`` - -### Sizing controls - -- ``View/controlSize(_:)`` - -- ``EnvironmentValues/controlSize`` - -- ``ControlSize`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/ControlsAndIndicators/Controls-and-Indicators.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/ControlsAndIndicators/Controls-and-Indicators.md new file mode 100644 index 0000000..17546fc --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/ControlsAndIndicators/Controls-and-Indicators.md @@ -0,0 +1,34 @@ +# Controls and indicators + +Display values and get user selections. + +## Overview + +OpenSwiftUI provides controls that enable user interaction specific to each +platform and context. For example, people can initiate events with buttons and +links, or choose among a set of discrete values with different kinds of pickers. +You can also display information to the user with indicators like progress views +and gauges. + +Use these built-in controls and indicators when composing custom views, and +style them to match the needs of your app’s user interface. For design guidance, +see [All components](https://developer.apple.com/design/human-interface-guidelines/components) +in the Human Interface Guidelines. + +## Topics + +### Linking to other content + +- ``Link`` + +### Getting numeric inputs + +- ``Slider`` + +### Sizing controls + +- ``View/controlSize(_:)`` + +- ``EnvironmentValues/controlSize`` + +- ``ControlSize`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Controls/Slider.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/ControlsAndIndicators/Slider.md similarity index 100% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Controls/Slider.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/ControlsAndIndicators/Slider.md diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Drawing-and-Graphics.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Drawing-and-Graphics.md new file mode 100644 index 0000000..8092a41 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Drawing-and-Graphics.md @@ -0,0 +1,27 @@ +# Drawing and graphics + +Enhance your views with graphical effects and customized drawings. + +## Overview + +You create rich, dynamic user interfaces with the built-in views and + that OpenSwiftUI provides. To enhance any view, you can apply many +of the graphical effects typically associated with a graphics context, like +setting colors, adding masks, and creating composites. + +When you need the flexibility of immediate mode drawing in a graphics context, +use a ``Canvas`` view. This can be particularly helpful when you want to draw an +extremely large number of dynamic shapes — for example, to create particle +effects. + +For design guidance, see [Materials](https://developer.apple.com/design/human-interface-guidelines/materials) +and [Color](https://developer.apple.com/design/human-interface-guidelines/color) +in the Human Interface Guidelines. + +## Topics + +### Accessing geometric constructs + +- ``Axis`` + +- ``Angle`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Shapes.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Shapes.md new file mode 100644 index 0000000..954020f --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/Views/Shapes.md @@ -0,0 +1,20 @@ +# Shapes + +Trace and fill built-in and custom shapes with a color, gradient, or other +pattern. + +## Overview + +Draw shapes like circles and rectangles, as well as custom paths that define +shapes of your own design. Apply styles that include environment-aware colors, +rich gradients, and material effects to the foreground, background, and outline +of your shapes. + +If you need the efficiency or flexibility of immediate mode drawing — for +example, to create particle effects — use a ``Canvas`` view instead. + +## Topics + +### Drawing custom shapes + +- ``Path`` diff --git a/Sources/OpenSwiftUI/Views/Animations/TODO/Animatable.swift b/Sources/OpenSwiftUI/Views/Animations/TODO/Animatable.swift index 358c195..62ab7b5 100644 --- a/Sources/OpenSwiftUI/Views/Animations/TODO/Animatable.swift +++ b/Sources/OpenSwiftUI/Views/Animations/TODO/Animatable.swift @@ -17,7 +17,9 @@ public protocol Animatable { // MARK: - Animateble + Extension extension Animatable { - public static func _makeAnimatable(value _: inout _GraphValue, inputs _: _GraphInputs) {} + public static func _makeAnimatable(value _: inout _GraphValue, inputs _: _GraphInputs) { + // TODO + } } extension Animatable where Self: VectorArithmetic { @@ -35,7 +37,9 @@ extension Animatable where AnimatableData == EmptyAnimatableData { set {} } - public static func _makeAnimatable(value _: inout _GraphValue, inputs _: _GraphInputs) {} + public static func _makeAnimatable(value _: inout _GraphValue, inputs _: _GraphInputs) { + // TODO + } } #if canImport(Darwin) diff --git a/Sources/OpenSwiftUI/Views/Controls/ControlSize/ControlSize.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/ControlSize/ControlSize.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/ControlSize/ControlSize.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/ControlSize/ControlSize.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/ControlSize/ControlSizeKey.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/ControlSize/ControlSizeKey.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/ControlSize/ControlSizeKey.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/ControlSize/ControlSizeKey.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/EnabledKey.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/EnabledKey.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/EnabledKey.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/EnabledKey.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/Link/Link.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/Link/Link.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/Link/Link.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/Link/Link.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/Link/OpenURLActionKey.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/Link/OpenURLActionKey.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/Link/OpenURLActionKey.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/Link/OpenURLActionKey.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/Link/internal/LinkDestination.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/Link/internal/LinkDestination.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/Link/internal/LinkDestination.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/Link/internal/LinkDestination.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/Slider/Slider.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/Slider/Slider.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/Slider/Slider.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/Slider/Slider.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/Slider/internal/AnySliderStyle.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/Slider/internal/AnySliderStyle.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/Slider/internal/AnySliderStyle.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/Slider/internal/AnySliderStyle.swift diff --git a/Sources/OpenSwiftUI/Views/Controls/Slider/internal/SystemSliderStyle.swift b/Sources/OpenSwiftUI/Views/ControlsAndIndicators/Slider/internal/SystemSliderStyle.swift similarity index 100% rename from Sources/OpenSwiftUI/Views/Controls/Slider/internal/SystemSliderStyle.swift rename to Sources/OpenSwiftUI/Views/ControlsAndIndicators/Slider/internal/SystemSliderStyle.swift diff --git a/Sources/OpenSwiftUI/UIElements/Angle/Angle.swift b/Sources/OpenSwiftUI/Views/DrawingAndGraphics/Angle.swift similarity index 96% rename from Sources/OpenSwiftUI/UIElements/Angle/Angle.swift rename to Sources/OpenSwiftUI/Views/DrawingAndGraphics/Angle.swift index b77d1ad..98d7cf2 100644 --- a/Sources/OpenSwiftUI/UIElements/Angle/Angle.swift +++ b/Sources/OpenSwiftUI/Views/DrawingAndGraphics/Angle.swift @@ -49,7 +49,7 @@ extension Angle: Hashable, Comparable { } } -extension Angle: Animatable, _VectorMath { +extension Angle: _VectorMath { public var animatableData: Double { get { radians * 128.0 } set { radians = newValue / 128.0 } diff --git a/Sources/OpenSwiftUI/Views/DrawingAndGraphics/Axis.swift b/Sources/OpenSwiftUI/Views/DrawingAndGraphics/Axis.swift new file mode 100644 index 0000000..99f239e --- /dev/null +++ b/Sources/OpenSwiftUI/Views/DrawingAndGraphics/Axis.swift @@ -0,0 +1,42 @@ +// +// Axis.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/17. +// Lastest Version: iOS 15.5 +// Status: Complete + +/// The horizontal or vertical dimension in a 2D coordinate system. +@frozen +public enum Axis: Int8, CaseIterable { + /// The horizontal dimension. + case horizontal + /// The vertical dimension. + case vertical + + /// An efficient set of axes. + @frozen + public struct Set: OptionSet { + public let rawValue: Int8 + + public init(rawValue: Int8) { + self.rawValue = rawValue + } + + public static let horizontal = Set(.horizontal) + public static let vertical = Set(.vertical) + + init(_ axis: Axis) { + self.init(rawValue: 1 << axis.rawValue) + } + } +} + +extension Axis: CustomStringConvertible { + public var description: String { + switch self { + case .horizontal: "horizontal" + case .vertical: "vertical" + } + } +} diff --git a/Sources/OpenSwiftUI/UIElements/Path/Path.swift b/Sources/OpenSwiftUI/Views/Shapes/Path.swift similarity index 95% rename from Sources/OpenSwiftUI/UIElements/Path/Path.swift rename to Sources/OpenSwiftUI/Views/Shapes/Path.swift index 31fd4ac..2f8a89d 100644 --- a/Sources/OpenSwiftUI/UIElements/Path/Path.swift +++ b/Sources/OpenSwiftUI/Views/Shapes/Path.swift @@ -9,7 +9,8 @@ // MARK: - Path[Empty] -struct Path {} +/// The outline of a 2D shape. +public struct Path {} // MARK: - CodablePath[WIP] diff --git a/Sources/OpenSwiftUI/Views/View/TODO/_VariadicView.swift b/Sources/OpenSwiftUI/Views/View/TODO/_VariadicView.swift new file mode 100644 index 0000000..c19ae68 --- /dev/null +++ b/Sources/OpenSwiftUI/Views/View/TODO/_VariadicView.swift @@ -0,0 +1,34 @@ +public enum _VariadicView { + @frozen + public struct Tree { + public var root: Root + public var content: Content + @inlinable + init(root: Root, content: Content) { + self.root = root + self.content = content + } + + @inlinable public init(_ root: Root, @ViewBuilder content: () -> Content) { + self.root = root + self.content = content() + } + } +} + +public protocol _VariadicView_Root { + static var _viewListOptions: Int { get } +} + +// FIXME +extension _VariadicView_Root { + public static var _viewListOptions: Int { + 0 + } +} + +protocol _VariadicView_ViewRoot: _VariadicView_Root, View { + associatedtype Body +} + +extension _HStackLayout: _VariadicView_Root {} diff --git a/Tests/OpenSwiftUITests/Layout/LayoutAdjustments/Alignment/AlignmentIDTests.swift b/Tests/OpenSwiftUITests/Layout/LayoutAdjustments/Alignment/AlignmentIDTests.swift new file mode 100644 index 0000000..f485dcf --- /dev/null +++ b/Tests/OpenSwiftUITests/Layout/LayoutAdjustments/Alignment/AlignmentIDTests.swift @@ -0,0 +1,40 @@ +// +// AlignmentIDTests.swift +// +// +// Created by Kyle on 2023/12/16. +// + +@testable import OpenSwiftUI +import XCTest + +final class AlignmentIDTests: XCTestCase { + private struct TestAlignment: AlignmentID { + static func defaultValue(in _: ViewDimensions) -> CGFloat { .zero } + } + + func testCombineExplicitLinear() throws { + var value: CGFloat? + (0 ... 10).forEach { n in + TestAlignment._combineExplicit( + childValue: .init(n), + n, + into: &value + ) + XCTAssertEqual(value!, CGFloat(n) / 2, accuracy: 0.0001) + } + } + + func testCombineExplicitSame() throws { + var value: CGFloat? + let child = CGFloat.random(in: 0.0 ... 100.0) + (0 ... 10).forEach { n in + TestAlignment._combineExplicit( + childValue: child, + n, + into: &value + ) + XCTAssertEqual(value!, child, accuracy: 0.0001) + } + } +} diff --git a/Tests/OpenSwiftUITests/Views/Controls/Slider/SliderTests.swift b/Tests/OpenSwiftUITests/Views/ControlsAndIndicators/Slider/SliderTests.swift similarity index 100% rename from Tests/OpenSwiftUITests/Views/Controls/Slider/SliderTests.swift rename to Tests/OpenSwiftUITests/Views/ControlsAndIndicators/Slider/SliderTests.swift diff --git a/Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AngleTests.swift b/Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AngleTests.swift new file mode 100644 index 0000000..f1a0fdd --- /dev/null +++ b/Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AngleTests.swift @@ -0,0 +1,44 @@ +// +// AngleTests.swift +// +// +// Created by Kyle on 2023/12/17. +// + +import OpenSwiftUI +import XCTest + +final class AngleTests: XCTestCase { + private func helper(radians: Double, degrees: Double) { + let a1 = Angle(radians: radians) + XCTAssertEqual(a1.radians, radians) + XCTAssertEqual(a1.degrees, degrees) + XCTAssertEqual(a1.animatableData, radians * 128) + let a2 = Angle(degrees: degrees) + XCTAssertEqual(a2.radians, radians) + XCTAssertEqual(a2.degrees, degrees) + XCTAssertEqual(a1, a2) + XCTAssertEqual(a1.animatableData * 2, (a2 * 2).animatableData) + var a3 = a1 + a3.animatableData *= 2 + var a4 = a1 + a4.radians *= 2 + XCTAssertEqual(a3, a4) + } + + func testZero() { + helper(radians: .zero, degrees: .zero) + } + + func testRightAngle() { + helper(radians: .pi / 2, degrees: 90) + } + + func testHalfCircle() { + helper(radians: .pi, degrees: 180) + } + + func testCircle() { + helper(radians: .pi * 2, degrees: 360) + } +} diff --git a/Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AxisTests.swift b/Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AxisTests.swift new file mode 100644 index 0000000..f6f2849 --- /dev/null +++ b/Tests/OpenSwiftUITests/Views/DrawingAndGraphics/AxisTests.swift @@ -0,0 +1,26 @@ +// +// AxisTests.swift +// +// +// Created by Kyle on 2023/12/17. +// + +import OpenSwiftUI +import XCTest + +final class AxisTests: XCTestCase { + func testExample() { + let h = Axis.horizontal + let v = Axis.vertical + XCTAssertEqual(Axis.allCases, [h, v]) + XCTAssertEqual(h.rawValue, 0) + XCTAssertEqual(v.rawValue, 1) + + XCTAssertEqual(h.description, "horizontal") + XCTAssertEqual(v.description, "vertical") + + let hs = Axis.Set.horizontal + let vs = Axis.Set.vertical + XCTAssertFalse(hs.contains(vs)) + } +}