From 1d25a31e588dc05e8e2a2d661dc96188729cebcd Mon Sep 17 00:00:00 2001 From: Abe White Date: Tue, 10 Oct 2023 17:02:00 -0500 Subject: [PATCH] Finish moving everything from ViewExtensions.swift into the appropriate file and marking unavailable where possible --- README.md | 2 +- .../SkipUI/SkipUI/Animation/Transition.swift | 65 + Sources/SkipUI/SkipUI/Commands/Menu.swift | 26 - Sources/SkipUI/SkipUI/Containers/Form.swift | 7 +- .../SkipUI/SkipUI/Containers/Navigation.swift | 26 - .../SkipUI/SkipUI/Containers/ScrollView.swift | 16 + .../SkipUI/SkipUI/Containers/TabView.swift | 4 +- .../SkipUI/SkipUI/Controls/ControlGroup.swift | 324 +--- .../SkipUI/SkipUI/Controls/DatePicker.swift | 8 +- .../SkipUI/Environment/ViewExtensions.swift | 1542 ----------------- .../SkipUI/Layout/CoordinateSpace.swift | 128 +- .../SkipUI/Layout/GlobalCoordinateSpace.swift | 20 - .../SkipUI/Layout/LocalCoordinateSpace.swift | 19 - .../Layout/MatchedGeometryProperties.swift | 82 +- .../SkipUI/Layout/NamedCoordinateSpace.swift | 23 - .../SkipUI/SkipUI/Layout/Presentation.swift | 73 + .../System/ContainerBackgroundPlacement.swift | 12 - .../System/ContentMarginPlacement.swift | 46 +- .../SkipUI/System/ContentShapeKinds.swift | 107 +- .../SkipUI/SkipUI/System/ControlSize.swift | 44 +- .../SkipUI/SkipUI/System/EventModifiers.swift | 82 +- Sources/SkipUI/SkipUI/System/HoverPhase.swift | 54 +- .../SkipUI/SkipUI/System/KeyEquivalent.swift | 74 +- .../SkipUI/System/KeyboardShortcut.swift | 119 +- .../SkipUI/System/SensoryFeedback.swift | 167 +- Sources/SkipUI/SkipUI/Text/Label.swift | 8 +- Sources/SkipUI/SkipUI/Text/Text.swift | 20 + Sources/SkipUI/SkipUI/Text/TextEditor.swift | 134 ++ Sources/SkipUI/SkipUI/View/View.swift | 152 +- 29 files changed, 679 insertions(+), 2705 deletions(-) delete mode 100644 Sources/SkipUI/SkipUI/Environment/ViewExtensions.swift delete mode 100644 Sources/SkipUI/SkipUI/Layout/GlobalCoordinateSpace.swift delete mode 100644 Sources/SkipUI/SkipUI/Layout/LocalCoordinateSpace.swift delete mode 100644 Sources/SkipUI/SkipUI/Layout/NamedCoordinateSpace.swift diff --git a/README.md b/README.md index c5455ceb..ad0e51a8 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ SkipUI contains stubs for the entire SwiftUI framework. API generally goes throu 1. The first implementation step is to move code out of `#if !SKIP` blocks so that it will be transpiled. This is helpful on its own, even if you just mark the API `@available(*, unavailable)` because you are not ready to implement it for Compose. An `unavailable` attribute will provide Skip users with a clear error message, rather than relying on the Kotlin compiler to complain about unfound API. - When moving code out of a `#if !SKIP` block, please strip Apple's extensive API comments. There is no reason for Skip to duplicate the official SwiftUI documentation, and it obscures any Skip-specific implementation comments we may add. - SwiftUI uses complex generics extensively, and the generics systems of Swift and Kotlin have significant differences. You may have to replace some generics or generic constraints with looser typing in order to transpile successfully. - - Reducing the number of Swift extensions and instead folding API into the primary declaration of a type can make Skip's internal symbol storage more efficient. This includes moving general modifier implementations from `ViewExtensions.swift` to `View.swift`. If a modifier is specific to a component - e.g. `.navigationTitle` is specific to `NavigationStack` - then use a `View` extension within the component's source file. + - Reducing the number of Swift extensions and instead folding API into the primary declaration of a type can make Skip's internal symbol storage more efficient. You may, should, however, leave `View` modifiers that are specific to a given component - e.g. `.navigationTitle` is specific to `NavigationStack` - within the component's source file. 1. Finally, we add a Compose implementation and remove any `unavailable` attribute. Note that SkipUI should remain buildable throughout this process. Being able to successfully compile SkipUI in Xcode helps us validate that our ported components still mesh with the rest of the framework. diff --git a/Sources/SkipUI/SkipUI/Animation/Transition.swift b/Sources/SkipUI/SkipUI/Animation/Transition.swift index 80072655..44b466e4 100644 --- a/Sources/SkipUI/SkipUI/Animation/Transition.swift +++ b/Sources/SkipUI/SkipUI/Animation/Transition.swift @@ -668,4 +668,69 @@ extension Transition where Self == PushTransition { public static func push(from edge: Edge) -> Self { fatalError() } } +@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) +extension View { + + /// Modifies the view to use a given transition as its method of animating + /// changes to the contents of its views. + /// + /// This modifier allows you to perform a transition that animates a change + /// within a single view. The provided ``ContentTransition`` can present an + /// opacity animation for content changes, an interpolated animation of + /// the content's paths as they change, or perform no animation at all. + /// + /// > Tip: The `contentTransition(_:)` modifier only has an effect within + /// the context of an ``Animation``. + /// + /// In the following example, a ``Button`` changes the color and font size + /// of a ``Text`` view. Since both of these properties apply to the paths of + /// the text, the ``ContentTransition/interpolate`` transition can animate a + /// gradual change to these properties through the entire transition. By + /// contrast, the ``ContentTransition/opacity`` transition would simply fade + /// between the start and end states. + /// + /// private static let font1 = Font.system(size: 20) + /// private static let font2 = Font.system(size: 45) + /// + /// @State private var color = Color.red + /// @State private var currentFont = font1 + /// + /// var body: some View { + /// VStack { + /// Text("Content transition") + /// .foregroundColor(color) + /// .font(currentFont) + /// .contentTransition(.interpolate) + /// Spacer() + /// Button("Change") { + /// withAnimation(Animation.easeInOut(duration: 5.0)) { + /// color = (color == .red) ? .green : .red + /// currentFont = (currentFont == font1) ? font2 : font1 + /// } + /// } + /// } + /// } + /// + /// This example uses an ease-in–ease-out animation with a five-second + /// duration to make it easier to see the effect of the interpolation. The + /// figure below shows the `Text` at the beginning of the animation, + /// halfway through, and at the end. + /// + /// | Time | Display | + /// | ------- | ------- | + /// | Start | ![The text Content transition in a small red font.](ContentTransition-1) | + /// | Middle | ![The text Content transition in a medium brown font.](ContentTransition-2) | + /// | End | ![The text Content transition in a large green font.](ContentTransition-3) | + /// + /// To control whether content transitions use GPU-accelerated rendering, + /// set the value of the + /// ``EnvironmentValues/contentTransitionAddsDrawingGroup`` environment + /// variable. + /// + /// - parameter transition: The transition to apply when animating the + /// content change. + public func contentTransition(_ transition: ContentTransition) -> some View { return stubView() } + +} + #endif diff --git a/Sources/SkipUI/SkipUI/Commands/Menu.swift b/Sources/SkipUI/SkipUI/Commands/Menu.swift index f7734794..e53d4064 100644 --- a/Sources/SkipUI/SkipUI/Commands/Menu.swift +++ b/Sources/SkipUI/SkipUI/Commands/Menu.swift @@ -195,32 +195,6 @@ public struct MenuActionDismissBehavior : Equatable { } -/// A control group style that presents its content as a menu when the user -/// presses the control, or as a submenu when nested within a larger menu. -/// -/// Use ``ControlGroupStyle/menu`` to construct this style. -@available(iOS 16.4, macOS 13.3, tvOS 17.0, *) -@available(watchOS, unavailable) -public struct MenuControlGroupStyle : ControlGroupStyle { - - /// Creates a menu control group style. - public init() { fatalError() } - - /// Creates a view representing the body of a control group. - /// - /// - Parameter configuration: The properties of the control group instance - /// being created. - /// - /// This method will be called for each instance of ``ControlGroup`` created - /// within a view hierarchy where this style is the current - /// `ControlGroupStyle`. - @MainActor public func makeBody(configuration: MenuControlGroupStyle.Configuration) -> some View { return stubView() } - - - /// A view representing the body of a control group. -// public typealias Body = some View -} - /// The order in which a menu presents its content. /// /// You can configure the preferred menu order using the diff --git a/Sources/SkipUI/SkipUI/Containers/Form.swift b/Sources/SkipUI/SkipUI/Containers/Form.swift index 1ec75cf3..13ddfc31 100644 --- a/Sources/SkipUI/SkipUI/Containers/Form.swift +++ b/Sources/SkipUI/SkipUI/Containers/Form.swift @@ -2,8 +2,8 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -@available(*, unavailable) public struct Form : View where Content : View { + @available(*, unavailable) public init(@ViewBuilder content: () -> Content) { } @@ -23,12 +23,15 @@ public struct FormStyle: RawRepresentable, Equatable { } public static let automatic = FormStyle(rawValue: 0) + + @available(*, unavailable) public static let columns = FormStyle(rawValue: 1) + + @available(*, unavailable) public static let grouped = FormStyle(rawValue: 2) } extension View { - @available(*, unavailable) public func formStyle(_ style: FormStyle) -> some View { return self } diff --git a/Sources/SkipUI/SkipUI/Containers/Navigation.swift b/Sources/SkipUI/SkipUI/Containers/Navigation.swift index 0425d6e5..ceff642d 100644 --- a/Sources/SkipUI/SkipUI/Containers/Navigation.swift +++ b/Sources/SkipUI/SkipUI/Containers/Navigation.swift @@ -1315,32 +1315,6 @@ extension NavigationBarItem.TitleDisplayMode : Equatable { extension NavigationBarItem.TitleDisplayMode : Hashable { } -/// The navigation control group style. -/// -/// You can also use ``ControlGroupStyle/navigation`` to construct this style. -@available(iOS 15.0, macOS 12.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -public struct NavigationControlGroupStyle : ControlGroupStyle { - - /// Creates a navigation control group style. - public init() { fatalError() } - - /// Creates a view representing the body of a control group. - /// - /// - Parameter configuration: The properties of the control group instance - /// being created. - /// - /// This method will be called for each instance of ``ControlGroup`` created - /// within a view hierarchy where this style is the current - /// `ControlGroupStyle`. - @MainActor public func makeBody(configuration: NavigationControlGroupStyle.Configuration) -> some View { return stubView() } - - - /// A view representing the body of a control group. -// public typealias Body = some View -} - /// The default navigation view style. /// /// You can also use ``NavigationViewStyle/automatic`` to construct this style. diff --git a/Sources/SkipUI/SkipUI/Containers/ScrollView.swift b/Sources/SkipUI/SkipUI/Containers/ScrollView.swift index 11dedc25..c9dbc74d 100644 --- a/Sources/SkipUI/SkipUI/Containers/ScrollView.swift +++ b/Sources/SkipUI/SkipUI/Containers/ScrollView.swift @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier #else +import struct CoreGraphics.CGFloat import struct CoreGraphics.CGRect #endif @@ -77,6 +78,21 @@ public struct ScrollTarget { } extension View { + @available(*, unavailable) + public func contentMargins(_ edges: Edge.Set = .all, _ insets: EdgeInsets, for placement: ContentMarginPlacement = .automatic) -> some View { + return self + } + + @available(*, unavailable) + public func contentMargins(_ edges: Edge.Set = .all, _ length: CGFloat?, for placement: ContentMarginPlacement = .automatic) -> some View { + return self + } + + @available(*, unavailable) + public func contentMargins(_ length: CGFloat, for placement: ContentMarginPlacement = .automatic) -> some View { + return self + } + @available(*, unavailable) public func scrollBounceBehavior(_ behavior: ScrollBounceBehavior, axes: Axis.Set = [.vertical]) -> some View { return self diff --git a/Sources/SkipUI/SkipUI/Containers/TabView.swift b/Sources/SkipUI/SkipUI/Containers/TabView.swift index df34249c..3340a867 100644 --- a/Sources/SkipUI/SkipUI/Containers/TabView.swift +++ b/Sources/SkipUI/SkipUI/Containers/TabView.swift @@ -185,6 +185,8 @@ public struct TabViewStyle: RawRepresentable, Equatable { } public static let automatic = TabViewStyle(rawValue: 0) + + @available(*, unavailable) public static let page = TabViewStyle(rawValue: 1) } @@ -197,8 +199,8 @@ extension View { #endif } - @available(*, unavailable) public func tabViewStyle(_ style: TabViewStyle) -> some View { + // We only support .automatic return self } } diff --git a/Sources/SkipUI/SkipUI/Controls/ControlGroup.swift b/Sources/SkipUI/SkipUI/Controls/ControlGroup.swift index 56b0d659..704d2d9d 100644 --- a/Sources/SkipUI/SkipUI/Controls/ControlGroup.swift +++ b/Sources/SkipUI/SkipUI/Controls/ControlGroup.swift @@ -2,48 +2,63 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI +public struct ControlGroup : View where Content : View { + @available(*, unavailable) + public init(@ViewBuilder content: () -> Content) { + } -#if !SKIP + @available(*, unavailable) + public init(@ViewBuilder content: () -> Content, @ViewBuilder label: () -> any View) /* where Content == LabeledControlGroupContent, C : View, L : View */ { + } -/// A container view that displays semantically-related controls -/// in a visually-appropriate manner for the context -/// -/// You can provide an optional label to this view that describes its children. -/// This view may be used in different ways depending on the surrounding -/// context. For example, when you place the control group in a -/// toolbar item, SkipUI uses the label when the group is moved to the -/// toolbar's overflow menu. -/// -/// ContentView() -/// .toolbar(id: "items") { -/// ToolbarItem(id: "media") { -/// ControlGroup { -/// MediaButton() -/// ChartButton() -/// GraphButton() -/// } label: { -/// Label("Plus", systemImage: "plus") -/// } -/// } -/// } -/// -@available(iOS 15.0, macOS 12.0, tvOS 17.0, *) -@available(watchOS, unavailable) -public struct ControlGroup : View where Content : View { + @available(*, unavailable) + public init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) /* where Content == LabeledControlGroupContent, C : View */ { + } - /// Creates a new ControlGroup with the specified children - /// - /// - Parameters: - /// - content: the children to display - public init(@ViewBuilder content: () -> Content) { fatalError() } + @available(*, unavailable) + public init(_ title: String, @ViewBuilder content: () -> Content) /* where Content == LabeledControlGroupContent */ { + } + + #if !SKIP + public var body: some View { + stubView() + } + #endif +} - @MainActor public var body: some View { get { return stubView() } } +// Model `ControlGroupStyle` as a struct. Kotlin does not support static members of protocols +public struct ControlGroupStyle: RawRepresentable, Equatable { + public let rawValue: Int -// public typealias Body = some View + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let automatic = ControlGroupStyle(rawValue: 0) + + @available(*, unavailable) + public static let navigation = ControlGroupStyle(rawValue: 1) + + @available(*, unavailable) + public static let palette = ControlGroupStyle(rawValue: 2) + + @available(*, unavailable) + public static let menu = ControlGroupStyle(rawValue: 3) + + @available(*, unavailable) + public static let compactMenu = ControlGroupStyle(rawValue: 4) } -@available(iOS 15.0, macOS 12.0, tvOS 17.0, *) +extension View { + public func controlGroupStyle(_ style: ControlGroupStyle) -> some View { + return self + } +} + +#if !SKIP + +// TODO: Process for use in SkipUI + @available(watchOS, unavailable) extension ControlGroup where Content == ControlGroupStyleConfiguration.Content { @@ -68,170 +83,6 @@ extension ControlGroup where Content == ControlGroupStyleConfiguration.Content { public init(_ configuration: ControlGroupStyleConfiguration) { fatalError() } } -@available(iOS 16.0, macOS 13.0, tvOS 17.0, *) -@available(watchOS, unavailable) -extension ControlGroup { - - /// Creates a new control group with the specified content and a label. - /// - /// - Parameters: - /// - content: The content to display. - /// - label: A view that describes the purpose of the group. - public init(@ViewBuilder content: () -> C, @ViewBuilder label: () -> L) where Content == LabeledControlGroupContent, C : View, L : View { fatalError() } -} - -@available(iOS 16.0, macOS 13.0, tvOS 17.0, *) -@available(watchOS, unavailable) -extension ControlGroup { - - /// Creates a new control group with the specified content that generates - /// its label from a localized string key. - /// - /// - Parameters: - /// - titleKey: The key for the group's localized title, that describes - /// the contents of the group. - /// - label: A view that describes the purpose of the group. - public init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> C) where Content == LabeledControlGroupContent, C : View { fatalError() } - - /// Creates a new control group with the specified content that generates - /// its label from a string. - /// - /// - Parameters: - /// - title: A string that describes the contents of the group. - /// - label: A view that describes the purpose of the group. - public init(_ title: S, @ViewBuilder content: () -> C) where Content == LabeledControlGroupContent, C : View, S : StringProtocol { fatalError() } -} - -/// Defines the implementation of all control groups within a view -/// hierarchy. -/// -/// To configure the current `ControlGroupStyle` for a view hierarchy, use the -/// ``View/controlGroupStyle(_:)`` modifier. -@available(iOS 15.0, macOS 12.0, tvOS 17.0, *) -@available(watchOS, unavailable) -public protocol ControlGroupStyle { - - /// A view representing the body of a control group. - associatedtype Body : View - - /// Creates a view representing the body of a control group. - /// - /// - Parameter configuration: The properties of the control group instance - /// being created. - /// - /// This method will be called for each instance of ``ControlGroup`` created - /// within a view hierarchy where this style is the current - /// `ControlGroupStyle`. - @ViewBuilder @MainActor func makeBody(configuration: Self.Configuration) -> Self.Body - - /// The properties of a `ControlGroup` instance being created. - typealias Configuration = ControlGroupStyleConfiguration -} - -@available(iOS 15.0, macOS 12.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension ControlGroupStyle where Self == NavigationControlGroupStyle { - - /// The navigation control group style. - /// - /// Use this style to group controls related to navigation, such as - /// back/forward buttons or timeline navigation controls. - /// - /// The navigation control group style can vary by platform. On iOS, it - /// renders as individual borderless buttons, while on macOS, it displays as - /// a separated momentary segmented control. - /// - /// To apply this style to a control group or to a view that contains a - /// control group, use the ``View/controlGroupStyle(_:)`` modifier. - public static var navigation: NavigationControlGroupStyle { get { fatalError() } } -} - -@available(iOS 17.0, macOS 14.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension ControlGroupStyle where Self == PaletteControlGroupStyle { - - /// A control group style that presents its content as a palette. - /// - /// - Note: When used outside of menus, this style is rendered as a - /// segmented control. - /// - /// Use this style to render a multi-select or a stateless palette. - /// The following example creates a control group that contains both type of shelves: - /// - /// Menu { - /// // A multi select palette - /// ControlGroup { - /// ForEach(ColorTags.allCases) { colorTag in - /// Toggle(isOn: $selectedColorTags[colorTag]) { - /// Label(colorTag.name, systemImage: "circle") - /// } - /// .tint(colorTag.color) - /// } - /// } - /// .controlGroupStyle(.palette) - /// .paletteSelectionEffect(.symbolVariant(.fill)) - /// - /// // A momentary / stateless palette - /// ControlGroup { - /// ForEach(Emotes.allCases) { emote in - /// Button { - /// sendEmote(emote) - /// } label: { - /// Label(emote.name, systemImage: emote.systemImage) - /// } - /// } - /// } - /// .controlGroupStyle(.palette) - /// } - /// - /// To apply this style to a control group, or to a view that contains - /// control groups, use the ``View/controlGroupStyle(_:)`` modifier. - public static var palette: PaletteControlGroupStyle { get { fatalError() } } -} - -@available(iOS 15.0, macOS 12.0, tvOS 17.0, *) -@available(watchOS, unavailable) -extension ControlGroupStyle where Self == AutomaticControlGroupStyle { - - /// The default control group style. - /// - /// The default control group style can vary by platform. By default, both - /// platforms use a momentary segmented control style that's appropriate for - /// the environment in which it is rendered. - /// - /// You can override a control group's style. To apply the default style to - /// a control group or to a view that contains a control group, use - /// the ``View/controlGroupStyle(_:)`` modifier. - public static var automatic: AutomaticControlGroupStyle { get { fatalError() } } -} - -@available(iOS 16.4, macOS 13.3, tvOS 17.0, *) -@available(watchOS, unavailable) -extension ControlGroupStyle where Self == MenuControlGroupStyle { - - /// A control group style that presents its content as a menu when the user - /// presses the control, or as a submenu when nested within a larger menu. - /// - /// To apply this style to a control group, or to a view that contains - /// control groups, use the ``View/controlGroupStyle(_:)`` modifier. - public static var menu: MenuControlGroupStyle { get { fatalError() } } -} - -@available(iOS 16.4, macOS 13.3, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension ControlGroupStyle where Self == CompactMenuControlGroupStyle { - - /// A control group style that presents its content as a compact menu when the user - /// presses the control, or as a submenu when nested within a larger menu. - /// - /// To apply this style to a control group, or to a view that contains - /// control groups, use the ``View/controlGroupStyle(_:)`` modifier. - public static var compactMenu: CompactMenuControlGroupStyle { get { fatalError() } } -} - /// The properties of a control group. @available(iOS 15.0, macOS 12.0, tvOS 17.0, *) @available(watchOS, unavailable) @@ -268,79 +119,4 @@ public struct ControlGroupStyleConfiguration { public let label: ControlGroupStyleConfiguration.Label = { fatalError() }() } -/// A control group style that presents its content as a palette. -/// -/// Use ``ControlGroupStyle/palette`` to construct this style. -@available(iOS 17.0, macOS 14.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -public struct PaletteControlGroupStyle : ControlGroupStyle { - - /// Creates a palette control group style. - public init() { fatalError() } - - /// Creates a view representing the body of a control group. - /// - /// - Parameter configuration: The properties of the control group instance - /// being created. - /// - /// This method will be called for each instance of ``ControlGroup`` created - /// within a view hierarchy where this style is the current - /// `ControlGroupStyle`. - @MainActor public func makeBody(configuration: PaletteControlGroupStyle.Configuration) -> some View { return stubView() } - - - /// A view representing the body of a control group. -// public typealias Body = some View -} - -/// A control group style that presents its content as a compact menu when the user -/// presses the control, or as a submenu when nested within a larger menu. -/// -/// Use ``ControlGroupStyle/compactMenu`` to construct this style. -@available(iOS 16.4, macOS 13.3, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -public struct CompactMenuControlGroupStyle : ControlGroupStyle { - - /// Creates a compact menu control group style. - public init() { fatalError() } - - /// Creates a view representing the body of a control group. - /// - /// - Parameter configuration: The properties of the control group instance - /// being created. - /// - /// This method will be called for each instance of ``ControlGroup`` created - /// within a view hierarchy where this style is the current - /// `ControlGroupStyle`. - @MainActor public func makeBody(configuration: CompactMenuControlGroupStyle.Configuration) -> some View { return stubView() } - - - /// A view representing the body of a control group. -// public typealias Body = some View -} - -/// The default control group style. -/// -/// You can also use ``ControlGroupStyle/automatic`` to construct this style. -@available(iOS 15.0, macOS 12.0, tvOS 17.0, *) -@available(watchOS, unavailable) -public struct AutomaticControlGroupStyle : ControlGroupStyle { - - /// Creates a view representing the body of a control group. - /// - /// - Parameter configuration: The properties of the control group instance - /// being created. - /// - /// This method will be called for each instance of ``ControlGroup`` created - /// within a view hierarchy where this style is the current - /// `ControlGroupStyle`. - @MainActor public func makeBody(configuration: AutomaticControlGroupStyle.Configuration) -> some View { return stubView() } - - - /// A view representing the body of a control group. -// public typealias Body = some View -} - #endif diff --git a/Sources/SkipUI/SkipUI/Controls/DatePicker.swift b/Sources/SkipUI/SkipUI/Controls/DatePicker.swift index 7ba248cf..8347ff04 100644 --- a/Sources/SkipUI/SkipUI/Controls/DatePicker.swift +++ b/Sources/SkipUI/SkipUI/Controls/DatePicker.swift @@ -60,14 +60,20 @@ public struct DatePickerStyle: RawRepresentable, Equatable { } public static let automatic = ButtonStyle(rawValue: 0) + + @available(*, unavailable) public static let graphical = ButtonStyle(rawValue: 1) + + @available(*, unavailable) public static let wheel = ButtonStyle(rawValue: 2) + + @available(*, unavailable) public static let compact = ButtonStyle(rawValue: 3) } extension View { - @available(*, unavailable) public func datePickerStyle(_ style: DatePickerStyle) -> some View { + // We only support .automatic return self } } diff --git a/Sources/SkipUI/SkipUI/Environment/ViewExtensions.swift b/Sources/SkipUI/SkipUI/Environment/ViewExtensions.swift deleted file mode 100644 index 23420c5b..00000000 --- a/Sources/SkipUI/SkipUI/Environment/ViewExtensions.swift +++ /dev/null @@ -1,1542 +0,0 @@ -// This is free software: you can redistribute and/or modify it -// under the terms of the GNU Lesser General Public License 3.0 -// as published by the Free Software Foundation https://fsf.org - -#if !SKIP - -// TODO: Process for use in SkipUI. Move extensions to the appropriate specific file (e.g. .buttonStyle is in Button.swift) or to View.swift - -import struct CoreGraphics.CGAffineTransform -import struct CoreGraphics.CGFloat -import struct CoreGraphics.CGPoint -import struct CoreGraphics.CGSize -import struct Foundation.URL -import struct Foundation.Locale - -import class Foundation.FileWrapper - -import protocol Combine.ObservableObject - -@available(iOS 17.0, macOS 14.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -@available(xrOS, unavailable) -extension View { - - /// Inserts an inspector at the applied position in the view hierarchy. - /// - /// Apply this modifier to declare an inspector with a context-dependent - /// presentation. For example, an inspector can present as a trailing - /// column in a horizontally regular size class, but adapt to a sheet in a - /// horizontally compact size class. - /// - /// struct ShapeEditor: View { - /// @State var presented: Bool = false - /// var body: some View { - /// MyEditorView() - /// .inspector(isPresented: $presented) { - /// TextTraitsInspectorView() - /// } - /// } - /// } - /// - /// - Parameters: - /// - isPresented: A binding to `Bool` controlling the presented state. - /// - content: The inspector content. - /// - /// - Note: Trailing column inspectors have their presentation state - /// restored by the framework. - public func inspector(isPresented: Binding, @ViewBuilder content: () -> V) -> some View where V : View { return stubView() } - - - /// Sets a flexible, preferred width for the inspector in a trailing-column - /// presentation. - /// - /// Apply this modifier on the content of a - /// ``View/inspector(isPresented:content:)`` to specify a preferred flexible - /// width for the column. Use ``View/inspectorColumnWidth(_:)`` if you need - /// to specify a fixed width. - /// - /// The following example shows an editor interface with an inspector, which - /// when presented as a trailing-column, has a preferred width of 225 - /// points, maximum of 400, and a minimum of 150 at which point it will - /// collapse, if allowed. - /// - /// MyEditorView() - /// .inspector { - /// TextTraitsInspectorView() - /// .inspectorColumnWidth(min: 150, ideal: 225, max: 400) - /// } - /// - /// Only some platforms enable flexible inspector columns. If - /// you specify a width that the current presentation environment doesn't - /// support, SkipUI may use a different width for your column. - /// - Parameters: - /// - min: The minimum allowed width for the trailing column inspector - /// - ideal: The initial width of the inspector in the absence of state - /// restoration. `ideal` influences the resulting width on macOS when a - /// user double-clicks the divider on the leading edge of the inspector. - /// clicks a divider to readjust - /// - max: The maximum allowed width for the trailing column inspector - public func inspectorColumnWidth(min: CGFloat? = nil, ideal: CGFloat, max: CGFloat? = nil) -> some View { return stubView() } - - - /// Sets a fixed, preferred width for the inspector containing this view - /// when presented as a trailing column. - /// - /// Apply this modifier on the content of a - /// ``View/inspector(isPresented:content:)`` to specify a fixed preferred - /// width for the trailing column. Use - /// ``View/navigationSplitViewColumnWidth(min:ideal:max:)`` if - /// you need to specify a flexible width. - /// - /// The following example shows an editor interface with an inspector, which - /// when presented as a trailing-column, has a fixed width of 225 - /// points. The example also uses ``View/interactiveDismissDisabled(_:)`` to - /// prevent the inspector from being collapsed by user action like dragging - /// a divider. - /// - /// MyEditorView() - /// .inspector { - /// TextTraitsInspectorView() - /// .inspectorColumnWidth(225) - /// .interactiveDismissDisabled() - /// } - /// - /// - Parameter width: The preferred fixed width for the inspector if - /// presented as a trailing column. - /// - Note: A fixed width does not prevent the user collapsing the - /// inspector on macOS. See ``View/interactiveDismissDisabled(_:)``. - public func inspectorColumnWidth(_ width: CGFloat) -> some View { return stubView() } - -} - -extension View { - - /// Sets the rename action in the environment to update focus state. - /// - /// Use this modifier in conjunction with the ``RenameButton`` to implement - /// standard rename interactions. A rename button receives its action - /// from the environment. Use this modifier to customize the action - /// provided to the rename button. - /// - /// struct RowView: View { - /// @State private var text = "" - /// @FocusState private var isFocused: Bool - /// - /// var body: some View { - /// TextField(text: $item.name) { - /// Text("Prompt") - /// } - /// .focused($isFocused) - /// .contextMenu { - /// RenameButton() - /// // ... your own custom actions - /// } - /// .renameAction($isFocused) - /// } - /// - /// When someone taps the rename button in the context menu, the rename - /// action focuses the text field by setting the `isFocused` - /// property to true. - /// - /// - Parameter isFocused: The focus binding to update when - /// activating the rename action. - /// - /// - Returns: A view that has the specified rename action. - @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) - public func renameAction(_ isFocused: FocusState.Binding) -> some View { return stubView() } - - - /// Sets a closure to run for the rename action. - /// - /// Use this modifier in conjunction with the ``RenameButton`` to implement - /// standard rename interactions. A rename button receives its action - /// from the environment. Use this modifier to customize the action - /// provided to the rename button. - /// - /// struct RowView: View { - /// @State private var text = "" - /// @FocusState private var isFocused: Bool - /// - /// var body: some View { - /// TextField(text: $item.name) { - /// Text("Prompt") - /// } - /// .focused($isFocused) - /// .contextMenu { - /// RenameButton() - /// // ... your own custom actions - /// } - /// .renameAction { isFocused = true } - /// } - /// - /// When the user taps the rename button in the context menu, the rename - /// action focuses the text field by setting the `isFocused` - /// property to true. - /// - /// - Parameter action: A closure to run when renaming. - /// - /// - Returns: A view that has the specified rename action. - @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) - public func renameAction(_ action: @escaping () -> Void) -> some View { return stubView() } - -} - -@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) -extension View { - - /// Defines a group of views with synchronized geometry using an - /// identifier and namespace that you provide. - /// - /// This method sets the geometry of each view in the group from the - /// inserted view with `isSource = true` (known as the "source" view), - /// updating the values marked by `properties`. - /// - /// If inserting a view in the same transaction that another view - /// with the same key is removed, the system will interpolate their - /// frame rectangles in window space to make it appear that there - /// is a single view moving from its old position to its new - /// position. The usual transition mechanisms define how each of - /// the two views is rendered during the transition (e.g. fade - /// in/out, scale, etc), the `matchedGeometryEffect()` modifier - /// only arranges for the geometry of the views to be linked, not - /// their rendering. - /// - /// If the number of currently-inserted views in the group with - /// `isSource = true` is not exactly one results are undefined, due - /// to it not being clear which is the source view. - /// - /// - Parameters: - /// - id: The identifier, often derived from the identifier of - /// the data being displayed by the view. - /// - namespace: The namespace in which defines the `id`. New - /// namespaces are created by adding an `@Namespace` variable - /// to a ``View`` type and reading its value in the view's body - /// method. - /// - properties: The properties to copy from the source view. - /// - anchor: The relative location in the view used to produce - /// its shared position value. - /// - isSource: True if the view should be used as the source of - /// geometry for other views in the group. - /// - /// - Returns: A new view that defines an entry in the global - /// database of views synchronizing their geometry. - /// - public func matchedGeometryEffect(id: ID, in namespace: Namespace.ID, properties: MatchedGeometryProperties = .frame, anchor: UnitPoint = .center, isSource: Bool = true) -> some View where ID : Hashable { return stubView() } - -} - - - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -extension View { - - /// Sets whether VoiceOver should always speak all punctuation in the text view. - /// - /// Use this modifier to control whether the system speaks punctuation characters - /// in the text. You might use this for code or other text where the punctuation is relevant, or where - /// you want VoiceOver to speak a verbatim transcription of the text you provide. For example, - /// given the text: - /// - /// Text("All the world's a stage, " + - /// "And all the men and women merely players;") - /// .speechAlwaysIncludesPunctuation() - /// - /// VoiceOver would speak "All the world apostrophe s a stage comma and all the men - /// and women merely players semicolon". - /// - /// By default, VoiceOver voices punctuation based on surrounding context. - /// - /// - Parameter value: A Boolean value that you set to `true` if - /// VoiceOver should speak all punctuation in the text. Defaults to `true`. - public func speechAlwaysIncludesPunctuation(_ value: Bool = true) -> some View { return stubView() } - - - /// Sets whether VoiceOver should speak the contents of the text view character by character. - /// - /// Use this modifier when you want VoiceOver to speak text as individual letters, - /// character by character. This is important for text that is not meant to be spoken together, like: - /// - An acronym that isn't a word, like APPL, spoken as "A-P-P-L". - /// - A number representing a series of digits, like 25, spoken as "two-five" rather than "twenty-five". - /// - /// - Parameter value: A Boolean value that when `true` indicates - /// VoiceOver should speak text as individual characters. Defaults - /// to `true`. - public func speechSpellsOutCharacters(_ value: Bool = true) -> some View { return stubView() } - - - /// Raises or lowers the pitch of spoken text. - /// - /// Use this modifier when you want to change the pitch of spoken text. - /// The value indicates how much higher or lower to change the pitch. - /// - /// - Parameter value: The amount to raise or lower the pitch. - /// Values between `-1` and `0` result in a lower pitch while - /// values between `0` and `1` result in a higher pitch. - /// The method clamps values to the range `-1` to `1`. - public func speechAdjustedPitch(_ value: Double) -> some View { return stubView() } - - - /// Controls whether to queue pending announcements behind existing speech rather than - /// interrupting speech in progress. - /// - /// Use this modifier when you want affect the order in which the - /// accessibility system delivers spoken text. Announcements can - /// occur automatically when the label or value of an accessibility - /// element changes. - /// - /// - Parameter value: A Boolean value that determines if VoiceOver speaks - /// changes to text immediately or enqueues them behind existing speech. - /// Defaults to `true`. - public func speechAnnouncementsQueued(_ value: Bool = true) -> some View { return stubView() } - -} - -@available(iOS 16.0, *) -@available(macOS, unavailable) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension View { - - /// Programmatically presents the find and replace interface for text - /// editor views. - /// - /// Add this modifier to a ``TextEditor``, or to a view hierarchy that - /// contains at least one text editor, to control the presentation of - /// the find and replace interface. When you set the `isPresented` binding - /// to `true`, the system shows the interface, and when you set it to - /// `false`, the system hides the interface. The following example shows - /// and hides the interface based on the state of a toolbar button: - /// - /// TextEditor(text: $text) - /// .findNavigator(isPresented: $isPresented) - /// .toolbar { - /// Toggle(isOn: $isPresented) { - /// Label("Find", systemImage: "magnifyingglass") - /// } - /// } - /// - /// The find and replace interface allows people to search for instances - /// of a specified string in the text editor, and optionally to replace - /// instances of the search string with another string. They can also - /// show and hide the interface using built-in controls, like menus and - /// keyboard shortcuts. SkipUI updates `isPresented` to reflect the - /// users's actions. - /// - /// If the text editor view isn't currently in focus, the system still - /// presents the find and replace interface when you set `isPresented` - /// to `true`. If the view hierarchy contains multiple editors, the one - /// that shows the find and replace interface is nondeterministic. - /// - /// You can disable the find and replace interface for a text editor by - /// applying the ``View/findDisabled(_:)`` modifier to the editor. If you - /// do that, setting this modifier's `isPresented` binding to `true` has - /// no effect, but only if the disabling modifier appears closer to the - /// text editor, like this: - /// - /// TextEditor(text: $text) - /// .findDisabled(isDisabled) - /// .findNavigator(isPresented: $isPresented) - /// - /// - Parameter isPresented: A binding to a Boolean value that controls the - /// presentation of the find and replace interface. - /// - /// - Returns: A view that presents the find and replace interface when - /// `isPresented` is `true`. - public func findNavigator(isPresented: Binding) -> some View { return stubView() } - - - /// Prevents find and replace operations in a text editor. - /// - /// Add this modifier to ensure that people can't activate the find - /// and replace interface for a ``TextEditor``: - /// - /// TextEditor(text: $text) - /// .findDisabled() - /// - /// When you disable the find operation, you also implicitly disable the - /// replace operation. If you want to only disable replace, use - /// ``View/replaceDisabled(_:)`` instead. - /// - /// Using this modifer also prevents programmatic find and replace - /// interface presentation using the ``View/findNavigator(isPresented:)`` - /// method. Be sure to place the disabling modifier closer to the text - /// editor for this to work: - /// - /// TextEditor(text: $text) - /// .findDisabled(isDisabled) - /// .findNavigator(isPresented: $isPresented) - /// - /// If you apply this modifer at multiple levels of a view hierarchy, - /// the call closest to the text editor takes precedence. For example, - /// people can activate find and replace for the first text editor - /// in the following example, but not the second: - /// - /// VStack { - /// TextEditor(text: $text1) - /// .findDisabled(false) - /// TextEditor(text: $text2) - /// } - /// .findDisabled(true) - /// - /// - Parameter isDisabled: A Boolean value that indicates whether to - /// disable the find and replace interface for a text editor. - /// - /// - Returns: A view that disables the find and replace interface. - public func findDisabled(_ isDisabled: Bool = true) -> some View { return stubView() } - - - /// Prevents replace operations in a text editor. - /// - /// Add this modifier to ensure that people can't activate the replace - /// feature of a find and replace interface for a ``TextEditor``: - /// - /// TextEditor(text: $text) - /// .replaceDisabled() - /// - /// If you want to disable both find and replace, use the - /// ``View/findDisabled(_:)`` modifier instead. - /// - /// Using this modifer also disables the replace feature of a find and - /// replace interface that you present programmatically using the - /// ``View/findNavigator(isPresented:)`` method. Be sure to place the - /// disabling modifier closer to the text editor for this to work: - /// - /// TextEditor(text: $text) - /// .replaceDisabled(isDisabled) - /// .findNavigator(isPresented: $isPresented) - /// - /// If you apply this modifer at multiple levels of a view hierarchy, - /// the call closest to the text editor takes precedence. For example, - /// people can activate find and replace for the first text editor - /// in the following example, but only find for the second: - /// - /// VStack { - /// TextEditor(text: $text1) - /// .replaceDisabled(false) - /// TextEditor(text: $text2) - /// } - /// .replaceDisabled(true) - /// - /// - Parameter isDisabled: A Boolean value that indicates whether text - /// replacement in the find and replace interface is disabled. - /// - /// - Returns: A view that disables the replace feature of a find and - /// replace interface. - public func replaceDisabled(_ isDisabled: Bool = true) -> some View { return stubView() } -} - -@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) -extension View { - - /// Sets an explicit type select equivalent text in a collection, such as - /// a list or table. - /// - /// By default, a type select equivalent is automatically derived from any - /// `Text` or `TextField` content in a list or table. In the below example, - /// type select can be used to select a person, even though no explicit - /// value has been set. - /// - /// List(people, selection: $selectedPersonID) { person in - /// Label { - /// Text(person.name) - /// } icon: { - /// person.avatar - /// } - /// } - /// - /// An explicit type select value should be set when there is no textual - /// content or when a different value is desired compared to what's - /// displayed in the view. Explicit values also provide a more performant - /// for complex view types. In the below example, type select is explicitly - /// set to allow selection of views that otherwise only display an image. - /// - /// List(people, selection: $selectedPersonID) { person in - /// person.avatar - /// .accessibilityLabel(person.name) - /// .typeSelectEquivalent(person.name) - /// } - /// - /// Setting an empty string value disables text selection for the view, - /// and a value of `nil` results in the view using its default value. - /// - /// - Parameter text: The explicit text value to use as a type select - /// equivalent for a view in a collection. - public func typeSelectEquivalent(_ text: Text?) -> some View { return stubView() } - - - /// Sets an explicit type select equivalent text in a collection, such as - /// a list or table. - /// - /// By default, a type select equivalent is automatically derived from any - /// `Text` or `TextField` content in a list or table. In the below example, - /// type select can be used to select a person, even though no explicit - /// value has been set. - /// - /// List(people, selection: $selectedPersonID) { person in - /// Label { - /// Text(person.name) - /// } icon: { - /// person.avatar - /// } - /// } - /// - /// An explicit type select value should be set when there is no textual - /// content or when a different value is desired compared to what's - /// displayed in the view. Explicit values also provide a more performant - /// for complex view types. In the below example, type select is explicitly - /// set to allow selection of views that otherwise only display an image. - /// - /// List(people, selection: $selectedPersonID) { person in - /// person.avatar - /// .accessibilityLabel(person.name) - /// .typeSelectEquivalent(person.name) - /// } - /// - /// Setting an empty string value disables text selection for the view, - /// and a value of `nil` results in the view using its default value. - /// - /// - Parameter stringKey: The localized string key to use as a type select - /// equivalent for a view in a collection. - //@_backDeploy(before: iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0) - public func typeSelectEquivalent(_ stringKey: LocalizedStringKey) -> some View { return stubView() } - - - /// Sets an explicit type select equivalent text in a collection, such as - /// a list or table. - /// - /// By default, a type select equivalent is automatically derived from any - /// `Text` or `TextField` content in a list or table. In the below example, - /// type select can be used to select a person, even though no explicit - /// value has been set. - /// - /// List(people, selection: $selectedPersonID) { person in - /// Label { - /// Text(person.name) - /// } icon: { - /// person.avatar - /// } - /// } - /// - /// An explicit type select value should be set when there is no textual - /// content or when a different value is desired compared to what's - /// displayed in the view. Explicit values also provide a more performant - /// for complex view types. In the below example, type select is explicitly - /// set to allow selection of views that otherwise only display an image. - /// - /// List(people, selection: $selectedPersonID) { person in - /// person.avatar - /// .accessibilityLabel(person.name) - /// .typeSelectEquivalent(person.name) - /// } - /// - /// Setting an empty string value disables text selection for the view, - /// and a value of `nil` results in the view using its default value. - /// - /// - Parameter string: The string to use as a type select equivalent for a - /// view in a collection. - //@_backDeploy(before: iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0) - public func typeSelectEquivalent(_ string: S) -> some View where S : StringProtocol { return stubView() } -} - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -extension View { - - /// Sets the content shape for this view. - /// - /// The content shape has a variety of uses. You can control the kind of the - /// content shape by specifying one in `kind`. For example, the - /// following example only sets the focus ring shape of the view, without - /// affecting its shape for hit-testing: - /// - /// MyFocusableView() - /// .contentShape(.focusEffect, Circle()) - /// - /// - Parameters: - /// - kind: The kinds to apply to this content shape. - /// - shape: The shape to use. - /// - eoFill: A Boolean that indicates whether the shape is interpreted - /// with the even-odd winding number rule. - /// - /// - Returns: A view that uses the given shape for the specified kind. - public func contentShape(_ kind: ContentShapeKinds, _ shape: S, eoFill: Bool = false) -> some View where S : Shape { return stubView() } -} - -@available(iOS 15.0, macOS 12.0, tvOS 17.0, *) -@available(watchOS, unavailable) -extension View { - - /// Sets the style for control groups within this view. - /// - /// - Parameter style: The style to apply to controls within this view. - public func controlGroupStyle(_ style: S) -> some View where S : ControlGroupStyle { return stubView() } -} - -@available(iOS 14.0, macOS 11.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension View { - - /// Defines a keyboard shortcut and assigns it to the modified control. - /// - /// Pressing the control's shortcut while the control is anywhere in the - /// frontmost window or scene, or anywhere in the macOS main menu, is - /// equivalent to direct interaction with the control to perform its primary - /// action. - /// - /// The target of a keyboard shortcut is resolved in a leading-to-trailing, - /// depth-first traversal of one or more view hierarchies. On macOS, the - /// system looks in the key window first, then the main window, and then the - /// command groups; on other platforms, the system looks in the active - /// scene, and then the command groups. - /// - /// If multiple controls are associated with the same shortcut, the first - /// one found is used. - /// - /// The default localization configuration is set to ``KeyboardShortcut/Localization-swift.struct/automatic``. - public func keyboardShortcut(_ key: KeyEquivalent, modifiers: EventModifiers = .command) -> some View { return stubView() } - - - /// Assigns a keyboard shortcut to the modified control. - /// - /// Pressing the control's shortcut while the control is anywhere in the - /// frontmost window or scene, or anywhere in the macOS main menu, is - /// equivalent to direct interaction with the control to perform its primary - /// action. - /// - /// The target of a keyboard shortcut is resolved in a leading-to-trailing - /// traversal of one or more view hierarchies. On macOS, the system looks in - /// the key window first, then the main window, and then the command groups; - /// on other platforms, the system looks in the active scene, and then the - /// command groups. - /// - /// If multiple controls are associated with the same shortcut, the first - /// one found is used. - public func keyboardShortcut(_ shortcut: KeyboardShortcut) -> some View { return stubView() } - - - /// Assigns an optional keyboard shortcut to the modified control. - /// - /// Pressing the control's shortcut while the control is anywhere in the - /// frontmost window or scene, or anywhere in the macOS main menu, is - /// equivalent to direct interaction with the control to perform its primary - /// action. - /// - /// The target of a keyboard shortcut is resolved in a leading-to-trailing - /// traversal of one or more view hierarchies. On macOS, the system looks in - /// the key window first, then the main window, and then the command groups; - /// on other platforms, the system looks in the active scene, and then the - /// command groups. - /// - /// If multiple controls are associated with the same shortcut, the first - /// one found is used. If the provided shortcut is `nil`, the modifier will - /// have no effect. - @available(iOS 15.4, macOS 12.3, *) - @available(tvOS, unavailable) - @available(watchOS, unavailable) - public func keyboardShortcut(_ shortcut: KeyboardShortcut?) -> some View { return stubView() } -} - -@available(iOS 15.0, macOS 12.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension View { - - /// Defines a keyboard shortcut and assigns it to the modified control. - /// - /// Pressing the control's shortcut while the control is anywhere in the - /// frontmost window or scene, or anywhere in the macOS main menu, is - /// equivalent to direct interaction with the control to perform its primary - /// action. - /// - /// The target of a keyboard shortcut is resolved in a leading-to-trailing, - /// depth-first traversal of one or more view hierarchies. On macOS, the - /// system looks in the key window first, then the main window, and then the - /// command groups; on other platforms, the system looks in the active - /// scene, and then the command groups. - /// - /// If multiple controls are associated with the same shortcut, the first - /// one found is used. - /// - /// ### Localization - /// - /// Provide a `localization` value to specify how this shortcut - /// should be localized. - /// Given that `key` is always defined in relation to the US-English - /// keyboard layout, it might be hard to reach on different international - /// layouts. For example the shortcut `⌘[` works well for the - /// US layout but is hard to reach for German users, where - /// `[` is available by pressing `⌥5`, making users type `⌥⌘5`. - /// The automatic keyboard shortcut remapping re-assigns the shortcut to - /// an appropriate replacement, `⌘Ö` in this case. - /// - /// Certain shortcuts carry information about directionality. For instance, - /// `⌘[` can reveal a previous view. Following the layout direction of - /// the UI, this shortcut will be automatically mirrored to `⌘]`. - /// However, this does not apply to items such as "Align Left `⌘{`", - /// which will be "left" independently of the layout direction. - /// When the shortcut shouldn't follow the directionality of the UI, but rather - /// be the same in both right-to-left and left-to-right directions, using - /// ``KeyboardShortcut/Localization-swift.struct/withoutMirroring`` - /// will prevent the system from flipping it. - /// - /// var body: some Commands { - /// CommandMenu("Card") { - /// Button("Align Left") { ... } - /// .keyboardShortcut("{", - /// modifiers: .option, - /// localization: .withoutMirroring) - /// Button("Align Right") { ... } - /// .keyboardShortcut("}", - /// modifiers: .option, - /// localization: .withoutMirroring) - /// } - /// } - /// - /// Lastly, providing the option - /// ``KeyboardShortcut/Localization-swift.struct/custom`` - /// disables - /// the automatic localization for this shortcut to tell the system that - /// internationalization is taken care of in a different way. - public func keyboardShortcut(_ key: KeyEquivalent, modifiers: EventModifiers = .command, localization: KeyboardShortcut.Localization) -> some View { return stubView() } -} - -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -extension View { - - /// Returns a new view with its inherited symbol image effects - /// either removed or left unchanged. - /// - /// The following example adds a repeating pulse effect to two - /// symbol images, but then disables the effect on one of them: - /// - /// VStack { - /// Image(systemName: "bolt.slash.fill") // does not pulse - /// .symbolEffectsRemoved() - /// Image(systemName: "folder.fill.badge.person.crop") // pulses - /// } - /// .symbolEffect(.pulse) - /// - /// - Parameter isEnabled: Whether to remove inherited symbol - /// effects or not. - /// - /// - Returns: a copy of the view with its symbol effects either - /// removed or left unchanged. - public func symbolEffectsRemoved(_ isEnabled: Bool = true) -> some View { return stubView() } -} - -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -@available(xrOS, unavailable) -extension View { - - /// Plays the specified `feedback` when the provided `trigger` value - /// changes. - /// - /// For example, you could play feedback when a state value changes: - /// - /// struct MyView: View { - /// @State private var showAccessory = false - /// - /// var body: some View { - /// ContentView() - /// .sensoryFeedback(.selection, trigger: showAccessory) - /// .onLongPressGesture { - /// showAccessory.toggle() - /// } - /// - /// if showAccessory { - /// AccessoryView() - /// } - /// } - /// } - /// - /// - Parameters: - /// - feedback: Which type of feedback to play. - /// - trigger: A value to monitor for changes to determine when to play. - public func sensoryFeedback(_ feedback: SensoryFeedback, trigger: T) -> some View where T : Equatable { return stubView() } - - - /// Plays the specified `feedback` when the provided `trigger` value changes - /// and the `condition` closure returns `true`. - /// - /// For example, you could play feedback for certain state transitions: - /// - /// struct MyView: View { - /// @State private var phase = Phase.inactive - /// - /// var body: some View { - /// ContentView(phase: $phase) - /// .sensoryFeedback(.selection, trigger: phase) { old, new in - /// old == .inactive || new == .expanded - /// } - /// } - /// - /// enum Phase { - /// case inactive - /// case preparing - /// case active - /// case expanded - /// } - /// } - /// - /// - Parameters: - /// - feedback: Which type of feedback to play. - /// - trigger: A value to monitor for changes to determine when to play. - /// - condition: A closure to determine whether to play the feedback when - /// `trigger` changes. - public func sensoryFeedback(_ feedback: SensoryFeedback, trigger: T, condition: @escaping (_ oldValue: T, _ newValue: T) -> Bool) -> some View where T : Equatable { return stubView() } - - - /// Plays feedback when returned from the `feedback` closure after the - /// provided `trigger` value changes. - /// - /// For example, you could play different feedback for different state - /// transitions: - /// - /// struct MyView: View { - /// @State private var phase = Phase.inactive - /// - /// var body: some View { - /// ContentView(phase: $phase) - /// .sensoryFeedback(trigger: phase) { old, new in - /// switch (old, new) { - /// case (.inactive, _): return .success - /// case (_, .expanded): return .impact - /// default: return nil - /// } - /// } - /// } - /// - /// enum Phase { - /// case inactive - /// case preparing - /// case active - /// case expanded - /// } - /// } - /// - /// - Parameters: - /// - trigger: A value to monitor for changes to determine when to play. - /// - feedback: A closure to determine whether to play the feedback and - /// what type of feedback to play when `trigger` changes. - public func sensoryFeedback(trigger: T, _ feedback: @escaping (_ oldValue: T, _ newValue: T) -> SensoryFeedback?) -> some View where T : Equatable { return stubView() } -} - -extension View { - - /// Configures the content margin for a provided placement. - /// - /// Use this modifier to customize the content margins of different - /// kinds of views. For example, you can use this modifier to customize - /// the margins of scrollable views like ``ScrollView``. In the - /// following example, the scroll view will automatically inset - /// its content by the safe area plus an additional 20 points - /// on the leading and trailing edge. - /// - /// ScrollView(.horizontal) { - /// // ... - /// } - /// .contentMargins(.horizontal, 20.0) - /// - /// You can provide a ``ContentMarginPlacement`` to target specific - /// parts of a view to customize. For example, provide a - /// ``ContentMargingPlacement/scrollContent`` placement to - /// inset the content of a ``TextEditor`` without affecting the - /// insets of its scroll indicators. - /// - /// TextEditor(text: $text) - /// .contentMargins(.horizontal, 20.0, for: .scrollContent) - /// - /// Similarly, you can customize the insets of scroll indicators - /// separately from scroll content. Consider doing this when applying - /// a custom clip shape that may clip the indicators. - /// - /// ScrollView { - /// // ... - /// } - /// .clipShape(.rect(cornerRadius: 20.0)) - /// .contentMargins(10.0, for: .scrollIndicators) - /// - /// When applying multiple contentMargins modifiers, modifiers with - /// the same placement will override modifiers higher up in the view - /// hierarchy. - /// - /// - Parameters: - /// - edges: The edges to add the margins to. - /// - insets: The amount of margins to add. - /// - placement: Where the margins should be added. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) - public func contentMargins(_ edges: Edge.Set = .all, _ insets: EdgeInsets, for placement: ContentMarginPlacement = .automatic) -> some View { return stubView() } - - - /// Configures the content margin for a provided placement. - /// - /// Use this modifier to customize the content margins of different - /// kinds of views. For example, you can use this modifier to customize - /// the margins of scrollable views like ``ScrollView``. In the - /// following example, the scroll view will automatically inset - /// its content by the safe area plus an additional 20 points - /// on the leading and trailing edge. - /// - /// ScrollView(.horizontal) { - /// // ... - /// } - /// .contentMargins(.horizontal, 20.0) - /// - /// You can provide a ``ContentMarginPlacement`` to target specific - /// parts of a view to customize. For example, provide a - /// ``ContentMargingPlacement/scrollContent`` placement to - /// inset the content of a ``TextEditor`` without affecting the - /// insets of its scroll indicators. - /// - /// TextEditor(text: $text) - /// .contentMargins(.horizontal, 20.0, for: .scrollContent) - /// - /// Similarly, you can customize the insets of scroll indicators - /// separately from scroll content. Consider doing this when applying - /// a custom clip shape that may clip the indicators. - /// - /// ScrollView { - /// // ... - /// } - /// .clipShape(.rect(cornerRadius: 20.0)) - /// .contentMargins(10.0, for: .scrollIndicators) - /// - /// When applying multiple contentMargins modifiers, modifiers with - /// the same placement will override modifiers higher up in the view - /// hierarchy. - /// - /// - Parameters: - /// - edges: The edges to add the margins to. - /// - length: The amount of margins to add. - /// - placement: Where the margins should be added. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) - public func contentMargins(_ edges: Edge.Set = .all, _ length: CGFloat?, for placement: ContentMarginPlacement = .automatic) -> some View { return stubView() } - - - /// Configures the content margin for a provided placement. - /// - /// Use this modifier to customize the content margins of different - /// kinds of views. For example, you can use this modifier to customize - /// the margins of scrollable views like ``ScrollView``. In the - /// following example, the scroll view will automatically inset - /// its content by the safe area plus an additional 20 points - /// on the leading and trailing edge. - /// - /// ScrollView(.horizontal) { - /// // ... - /// } - /// .contentMargins(.horizontal, 20.0) - /// - /// You can provide a ``ContentMarginPlacement`` to target specific - /// parts of a view to customize. For example, provide a - /// ``ContentMargingPlacement/scrollContent`` placement to - /// inset the content of a ``TextEditor`` without affecting the - /// insets of its scroll indicators. - /// - /// TextEditor(text: $text) - /// .contentMargins(.horizontal, 20.0, for: .scrollContent) - /// - /// Similarly, you can customize the insets of scroll indicators - /// separately from scroll content. Consider doing this when applying - /// a custom clip shape that may clip the indicators. - /// - /// ScrollView { - /// // ... - /// } - /// .clipShape(.rect(cornerRadius: 20.0)) - /// .contentMargins(10.0, for: .scrollIndicators) - /// - /// When applying multiple contentMargins modifiers, modifiers with - /// the same placement will override modifiers higher up in the view - /// hierarchy. - /// - /// - Parameters: - /// - length: The amount of margins to add on all edges. - /// - placement: Where the margins should be added. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) - public func contentMargins(_ length: CGFloat, for placement: ContentMarginPlacement = .automatic) -> some View { return stubView() } -} - -@available(iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4, *) -extension View { - - /// Sets the presentation background of the enclosing sheet using a shape - /// style. - /// - /// The following example uses the ``Material/thick`` material as the sheet - /// background: - /// - /// struct ContentView: View { - /// @State private var showSettings = false - /// - /// var body: some View { - /// Button("View Settings") { - /// showSettings = true - /// } - /// .sheet(isPresented: $showSettings) { - /// SettingsView() - /// .presentationBackground(.thickMaterial) - /// } - /// } - /// } - /// - /// The `presentationBackground(_:)` modifier differs from the - /// ``View/background(_:ignoresSafeAreaEdges:)`` modifier in several key - /// ways. A presentation background: - /// - /// * Automatically fills the entire presentation. - /// * Allows views behind the presentation to show through translucent - /// styles. - /// - /// - Parameter style: The shape style to use as the presentation - /// background. - public func presentationBackground(_ style: S) -> some View where S : ShapeStyle { return stubView() } - - - /// Sets the presentation background of the enclosing sheet to a custom - /// view. - /// - /// The following example uses a yellow view as the sheet background: - /// - /// struct ContentView: View { - /// @State private var showSettings = false - /// - /// var body: some View { - /// Button("View Settings") { - /// showSettings = true - /// } - /// .sheet(isPresented: $showSettings) { - /// SettingsView() - /// .presentationBackground { - /// Color.yellow - /// } - /// } - /// } - /// } - /// - /// The `presentationBackground(alignment:content:)` modifier differs from - /// the ``View/background(alignment:content:)`` modifier in several key - /// ways. A presentation background: - /// - /// * Automatically fills the entire presentation. - /// * Allows views behind the presentation to show through translucent - /// areas of the `content`. - /// - /// - Parameters: - /// - alignment: The alignment that the modifier uses to position the - /// implicit ``ZStack`` that groups the background views. The default is - /// ``Alignment/center``. - /// - content: The view to use as the background of the presentation. - public func presentationBackground(alignment: Alignment = .center, @ViewBuilder content: () -> V) -> some View where V : View { return stubView() } -} - -@available(iOS 15.0, macOS 10.15, watchOS 9.0, *) -@available(tvOS, unavailable) -extension View { - - /// Sets the size for controls within this view. - /// - /// Use `controlSize(_:)` to override the system default size for controls - /// in this view. In this example, a view displays several typical controls - /// at `.mini`, `.small` and `.regular` sizes. - /// - /// struct ControlSize: View { - /// var body: some View { - /// VStack { - /// MyControls(label: "Mini") - /// .controlSize(.mini) - /// MyControls(label: "Small") - /// .controlSize(.small) - /// MyControls(label: "Regular") - /// .controlSize(.regular) - /// } - /// .padding() - /// .frame(width: 450) - /// .border(Color.gray) - /// } - /// } - /// - /// struct MyControls: View { - /// var label: String - /// @State private var value = 3.0 - /// @State private var selected = 1 - /// var body: some View { - /// HStack { - /// Text(label + ":") - /// Picker("Selection", selection: $selected) { - /// Text("option 1").tag(1) - /// Text("option 2").tag(2) - /// Text("option 3").tag(3) - /// } - /// Slider(value: $value, in: 1...10) - /// Button("OK") { } - /// } - /// } - /// } - /// - /// ![A screenshot showing several controls of various - /// sizes.](SkipUI-View-controlSize.png) - /// - /// - Parameter controlSize: One of the control sizes specified in the - /// ``ControlSize`` enumeration. - @available(tvOS, unavailable) - public func controlSize(_ controlSize: ControlSize) -> some View { return stubView() } -} - -@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) -extension View { - - /// Modifies the view to use a given transition as its method of animating - /// changes to the contents of its views. - /// - /// This modifier allows you to perform a transition that animates a change - /// within a single view. The provided ``ContentTransition`` can present an - /// opacity animation for content changes, an interpolated animation of - /// the content's paths as they change, or perform no animation at all. - /// - /// > Tip: The `contentTransition(_:)` modifier only has an effect within - /// the context of an ``Animation``. - /// - /// In the following example, a ``Button`` changes the color and font size - /// of a ``Text`` view. Since both of these properties apply to the paths of - /// the text, the ``ContentTransition/interpolate`` transition can animate a - /// gradual change to these properties through the entire transition. By - /// contrast, the ``ContentTransition/opacity`` transition would simply fade - /// between the start and end states. - /// - /// private static let font1 = Font.system(size: 20) - /// private static let font2 = Font.system(size: 45) - /// - /// @State private var color = Color.red - /// @State private var currentFont = font1 - /// - /// var body: some View { - /// VStack { - /// Text("Content transition") - /// .foregroundColor(color) - /// .font(currentFont) - /// .contentTransition(.interpolate) - /// Spacer() - /// Button("Change") { - /// withAnimation(Animation.easeInOut(duration: 5.0)) { - /// color = (color == .red) ? .green : .red - /// currentFont = (currentFont == font1) ? font2 : font1 - /// } - /// } - /// } - /// } - /// - /// This example uses an ease-in–ease-out animation with a five-second - /// duration to make it easier to see the effect of the interpolation. The - /// figure below shows the `Text` at the beginning of the animation, - /// halfway through, and at the end. - /// - /// | Time | Display | - /// | ------- | ------- | - /// | Start | ![The text Content transition in a small red font.](ContentTransition-1) | - /// | Middle | ![The text Content transition in a medium brown font.](ContentTransition-2) | - /// | End | ![The text Content transition in a large green font.](ContentTransition-3) | - /// - /// To control whether content transitions use GPU-accelerated rendering, - /// set the value of the - /// ``EnvironmentValues/contentTransitionAddsDrawingGroup`` environment - /// variable. - /// - /// - parameter transition: The transition to apply when animating the - /// content change. - public func contentTransition(_ transition: ContentTransition) -> some View { return stubView() } - -} - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -extension View { - - /// Shows the specified content above or below the modified view. - /// - /// The `content` view is anchored to the specified - /// vertical edge in the parent view, aligning its horizontal axis - /// to the specified alignment guide. The modified view is inset by - /// the height of `content`, from `edge`, with its safe area - /// increased by the same amount. - /// - /// struct ScrollableViewWithBottomBar: View { - /// var body: some View { - /// ScrollView { - /// ScrolledContent() - /// } - /// .safeAreaInset(edge: .bottom, spacing: 0) { - /// BottomBarContent() - /// } - /// } - /// } - /// - /// - Parameters: - /// - edge: The vertical edge of the view to inset by the height of - /// `content`, to make space for `content`. - /// - spacing: Extra distance placed between the two views, or - /// nil to use the default amount of spacing. - /// - alignment: The alignment guide used to position `content` - /// horizontally. - /// - content: A view builder function providing the view to - /// display in the inset space of the modified view. - /// - /// - Returns: A new view that displays both `content` above or below the - /// modified view, - /// making space for the `content` view by vertically insetting - /// the modified view, adjusting the safe area of the result to match. - public func safeAreaInset(edge: VerticalEdge, alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> V) -> some View where V : View { return stubView() } - - - /// Shows the specified content beside the modified view. - /// - /// The `content` view is anchored to the specified - /// horizontal edge in the parent view, aligning its vertical axis - /// to the specified alignment guide. The modified view is inset by - /// the width of `content`, from `edge`, with its safe area - /// increased by the same amount. - /// - /// struct ScrollableViewWithSideBar: View { - /// var body: some View { - /// ScrollView { - /// ScrolledContent() - /// } - /// .safeAreaInset(edge: .leading, spacing: 0) { - /// SideBarContent() - /// } - /// } - /// } - /// - /// - Parameters: - /// - edge: The horizontal edge of the view to inset by the width of - /// `content`, to make space for `content`. - /// - spacing: Extra distance placed between the two views, or - /// nil to use the default amount of spacing. - /// - alignment: The alignment guide used to position `content` - /// vertically. - /// - content: A view builder function providing the view to - /// display in the inset space of the modified view. - /// - /// - Returns: A new view that displays `content` beside the modified view, - /// making space for the `content` view by horizontally insetting - /// the modified view. - public func safeAreaInset(edge: HorizontalEdge, alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> V) -> some View where V : View { return stubView() } - -} - -extension View { - - /// Adds the provided insets into the safe area of this view. - /// - /// Use this modifier when you would like to add a fixed amount - /// of space to the safe area a view sees. - /// - /// ScrollView(.horizontal) { - /// HStack(spacing: 10.0) { - /// ForEach(items) { item in - /// ItemView(item) - /// } - /// } - /// } - /// .safeAreaPadding(.horizontal, 20.0) - /// - /// See the ``View/safeAreaInset(edge:alignment:spacing:content)`` - /// modifier for adding to the safe area based on the size of a - /// view. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) - public func safeAreaPadding(_ insets: EdgeInsets) -> some View { return stubView() } - - - /// Adds the provided insets into the safe area of this view. - /// - /// Use this modifier when you would like to add a fixed amount - /// of space to the safe area a view sees. - /// - /// ScrollView(.horizontal) { - /// HStack(spacing: 10.0) { - /// ForEach(items) { item in - /// ItemView(item) - /// } - /// } - /// } - /// .safeAreaPadding(.horizontal, 20.0) - /// - /// See the ``View/safeAreaInset(edge:alignment:spacing:content)`` - /// modifier for adding to the safe area based on the size of a - /// view. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) - public func safeAreaPadding(_ edges: Edge.Set = .all, _ length: CGFloat? = nil) -> some View { return stubView() } - - - /// Adds the provided insets into the safe area of this view. - /// - /// Use this modifier when you would like to add a fixed amount - /// of space to the safe area a view sees. - /// - /// ScrollView(.horizontal) { - /// HStack(spacing: 10.0) { - /// ForEach(items) { item in - /// ItemView(item) - /// } - /// } - /// } - /// .safeAreaPadding(.horizontal, 20.0) - /// - /// See the ``View/safeAreaInset(edge:alignment:spacing:content)`` - /// modifier for adding to the safe area based on the size of a - /// view. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) - public func safeAreaPadding(_ length: CGFloat) -> some View { return stubView() } - -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension View { - - /// Sets the specified style to render backgrounds within the view. - /// - /// The following example uses this modifier to set the - /// ``EnvironmentValues/backgroundStyle`` environment value to a - /// ``ShapeStyle/blue`` color that includes a subtle ``Color/gradient``. - /// SkipUI fills the ``Circle`` shape that acts as a background element - /// with this style: - /// - /// Image(systemName: "swift") - /// .padding() - /// .background(in: Circle()) - /// .backgroundStyle(.blue.gradient) - /// - /// ![An image of the Swift logo inside a circle that's blue with a slight - /// linear gradient. The blue color is slightly lighter at the top of the - /// circle and slightly darker at the bottom.](View-backgroundStyle-1-iOS) - /// - /// To restore the default background style, set the - /// ``EnvironmentValues/backgroundStyle`` environment value to - /// `nil` using the ``View/environment(_:_:)`` modifer: - /// - /// .environment(\.backgroundStyle, nil) - /// - @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) - public func backgroundStyle(_ style: S) -> some View where S : ShapeStyle { return stubView() } -} - -extension View { - - /// Adds an action to perform when the pointer enters, moves within, and - /// exits the view's bounds. - /// - /// Call this method to define a region for detecting pointer movement with - /// the size and position of this view. - /// The following example updates `hoverLocation` and `isHovering` to be - /// based on the phase provided to the closure: - /// - /// @State private var hoverLocation: CGPoint = .zero - /// @State private var isHovering = false - /// - /// var body: some View { - /// VStack { - /// Color.red - /// .frame(width: 400, height: 400) - /// .onContinuousHover { phase in - /// switch phase { - /// case .active(let location): - /// hoverLocation = location - /// isHovering = true - /// case .ended: - /// isHovering = false - /// } - /// } - /// .overlay { - /// Rectangle() - /// .frame(width: 50, height: 50) - /// .foregroundColor(isHovering ? .green : .blue) - /// .offset(x: hoverLocation.x, y: hoverLocation.y) - /// } - /// } - /// } - /// - /// - Parameters: - /// - coordinateSpace: The coordinate space for the - /// location values. Defaults to ``CoordinateSpace/local``. - /// - action: The action to perform whenever the pointer enters, - /// moves within, or exits the view's bounds. The `action` closure - /// passes the ``HoverPhase/active(_:)`` phase with the pointer's - /// coordinates if the pointer is in the view's bounds; otherwise, it - /// passes ``HoverPhase/ended``. - /// - /// - Returns: A view that calls `action` when the pointer enters, - /// moves within, or exits the view's bounds. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, *) - @available(watchOS, unavailable) - public func onContinuousHover(coordinateSpace: some CoordinateSpaceProtocol = .local, perform action: @escaping (HoverPhase) -> Void) -> some View { return stubView() } -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension View { - - /// Binds a view's identity to the given proxy value. - /// - /// When the proxy value specified by the `id` parameter changes, the - /// identity of the view — for example, its state — is reset. - public func id(_ id: ID) -> some View where ID : Hashable { return stubView() } -} - -extension View { - - /// Sets the container background of the enclosing container using a view. - /// - /// The following example uses a ``LinearGradient`` as a background: - /// - /// struct ContentView: View { - /// var body: some View { - /// NavigationStack { - /// List { - /// NavigationLink("Blue") { - /// Text("Blue") - /// .containerBackground(.blue.gradient, for: .navigation) - /// } - /// NavigationLink("Red") { - /// Text("Red") - /// .containerBackground(.red.gradient, for: .navigation) - /// } - /// } - /// } - /// } - /// } - /// - /// The `.containerBackground(_:for:)` modifier differs from the - /// ``View/background(_:ignoresSafeAreaEdges:)`` modifier by automatically - /// filling an entire parent container. ``ContainerBackgroundPlacement`` - /// describes the available containers. - /// - /// - Parameters - /// - style: The shape style to use as the container background. - /// - container: The container that will use the background. - @available(iOS 17.0, tvOS 17.0, macOS 14.0, watchOS 10.0, *) - public func containerBackground(_ style: S, for container: ContainerBackgroundPlacement) -> some View where S : ShapeStyle { return stubView() } - - - /// Sets the container background of the enclosing container using a view. - /// - /// The following example uses a custom ``View`` as a background: - /// - /// struct ContentView: View { - /// var body: some View { - /// NavigationStack { - /// List { - /// NavigationLink("Image") { - /// Text("Image") - /// .containerBackground(for: .navigation) { - /// Image(name: "ImageAsset") - /// } - /// } - /// } - /// } - /// } - /// } - /// - /// The `.containerBackground(for:alignment:content:)` modifier differs from - /// the ``View/background(_:ignoresSafeAreaEdges:)`` modifier by - /// automatically filling an entire parent container. - /// ``ContainerBackgroundPlacement`` describes the available containers. - /// - /// - Parameters: - /// - alignment: The alignment that the modifier uses to position the - /// implicit ``ZStack`` that groups the background views. The default is - /// ``Alignment/center``. - /// - container: The container that will use the background. - /// - content: The view to use as the background of the container. - @available(iOS 17.0, tvOS 17.0, macOS 14.0, watchOS 10.0, *) - public func containerBackground(for container: ContainerBackgroundPlacement, alignment: Alignment = .center, @ViewBuilder content: () -> V) -> some View where V : View { return stubView() } -} - -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -extension View { - - /// Assigns a name to the view's coordinate space, so other code can operate - /// on dimensions like points and sizes relative to the named space. - /// - /// Use `coordinateSpace(_:)` to allow another function to find and - /// operate on a view and operate on dimensions relative to that view. - /// - /// The example below demonstrates how a nested view can find and operate on - /// its enclosing view's coordinate space: - /// - /// struct ContentView: View { - /// @State private var location = CGPoint.zero - /// - /// var body: some View { - /// VStack { - /// Color.red.frame(width: 100, height: 100) - /// .overlay(circle) - /// Text("Location: \(Int(location.x)), \(Int(location.y))") - /// } - /// .coordinateSpace(.named("stack")) - /// } - /// - /// var circle: some View { - /// Circle() - /// .frame(width: 25, height: 25) - /// .gesture(drag) - /// .padding(5) - /// } - /// - /// var drag: some Gesture { - /// DragGesture(coordinateSpace: .named("stack")) - /// .onChanged { info in location = info.location } - /// } - /// } - /// - /// Here, the ``VStack`` in the `ContentView` named “stack” is composed of a - /// red frame with a custom ``Circle`` view ``View/overlay(_:alignment:)`` - /// at its center. - /// - /// The `circle` view has an attached ``DragGesture`` that targets the - /// enclosing VStack's coordinate space. As the gesture recognizer's closure - /// registers events inside `circle` it stores them in the shared `location` - /// state variable and the ``VStack`` displays the coordinates in a ``Text`` - /// view. - /// - /// ![A screenshot showing an example of finding a named view and tracking - /// relative locations in that view.](SkipUI-View-coordinateSpace.png) - /// - /// - Parameter name: A name used to identify this coordinate space. - public func coordinateSpace(_ name: NamedCoordinateSpace) -> some View { return stubView() } - -} - -@available(iOS 14.0, macOS 11.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension View { - - /// Specifies a modifier indicating the Scene this View - /// is in can handle matching incoming External Events. - /// - /// If no modifier is set in any Views within a Scene, the behavior - /// is platform dependent. On macOS, a new Scene will be created to - /// use for the External Event. On iOS, the system will choose an - /// existing Scene to use. - /// - /// On platforms that only allow a single Window/Scene, this method is - /// ignored, and incoming External Events are always routed to the - /// existing single Scene. - /// - /// - Parameter preferring: A Set of Strings that are checked to see - /// if they are contained in the targetContentIdenfifier to see if - /// the Scene this View is in prefers to handle the Exernal Event. - /// The empty Set and empty Strings never match. The String value - /// "*" always matches. The String comparisons are case/diacritic - /// insensitive - /// - /// - Parameter allowing: A Set of Strings that are checked to see - /// if they are contained in the targetContentIdenfifier to see if - /// the Scene this View is in allows handling the External Event. - /// The empty Set and empty Strings never match. The String value - /// "*" always matches. - public func handlesExternalEvents(preferring: Set, allowing: Set) -> some View { return stubView() } - -} - -#endif diff --git a/Sources/SkipUI/SkipUI/Layout/CoordinateSpace.swift b/Sources/SkipUI/SkipUI/Layout/CoordinateSpace.swift index 3f4f593a..65eee95d 100644 --- a/Sources/SkipUI/SkipUI/Layout/CoordinateSpace.swift +++ b/Sources/SkipUI/SkipUI/Layout/CoordinateSpace.swift @@ -2,113 +2,63 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - -#if !SKIP - -/// A resolved coordinate space created by `CoordinateSpaceProtocol`. -/// -/// You don't typically use `CoordinateSpace` directly. Instead, use the static -/// properties and functions of `CoordinateSpaceProtocol` such as `.global`, -/// `.local`, and `.named(_:)`. -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -public enum CoordinateSpace { - - /// The global coordinate space at the root of the view hierarchy. +public enum CoordinateSpace: Hashable { case global - - /// The local coordinate space of the current view. case local - - /// A named reference to a view's local coordinate space. case named(AnyHashable) -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension CoordinateSpace { - public var isGlobal: Bool { get { fatalError() } } + public var isGlobal: Bool { + return self == .global + } - public var isLocal: Bool { get { fatalError() } } + public var isLocal: Bool { + return self == .local + } } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension CoordinateSpace : Equatable, Hashable { +// Model `CoordinateSpaceProtocol` as a class. Kotlin does not support static members of protocols +public class CoordinateSpaceProtocol { + public var coordinateSpace: CoordinateSpace { + return .global + } + public static func scrollView(axis: Axis) -> NamedCoordinateSpace { + return named("_scrollView_axis_\(axis.rawValue)_") + } -} + public static var scrollView: NamedCoordinateSpace { + return named("_scrollView_") + } -/// A frame of reference within the layout system. -/// -/// All geometric properties of a view, including size, position, and -/// transform, are defined within the local coordinate space of the view's -/// parent. These values can be converted into other coordinate spaces -/// by passing types conforming to this protocol into functions such as -/// `GeometryProxy.frame(in:)`. -/// -/// For example, a named coordinate space allows you to convert the frame -/// of a view into the local coordinate space of an ancestor view by defining -/// a named coordinate space using the `coordinateSpace(_:)` modifier, then -/// passing that same named coordinate space into the `frame(in:)` function. -/// -/// VStack { -/// GeometryReader { geometryProxy in -/// let distanceFromTop = geometryProxy.frame(in: "container").origin.y -/// Text("This view is \(distanceFromTop) points from the top of the VStack") -/// } -/// .padding() -/// } -/// .coordinateSpace(.named("container")) -/// -/// You don't typically create types conforming to this protocol yourself. -/// Instead, use the system-provided `.global`, `.local`, and `.named(_:)` -/// implementations. -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -public protocol CoordinateSpaceProtocol { + public static func named(_ name: some Hashable) -> NamedCoordinateSpace { + return NamedCoordinateSpace(coordinateSpace: .named(name)) + } - /// The resolved coordinate space. - var coordinateSpace: CoordinateSpace { get } + public static let local = LocalCoordinateSpace() + public static let global = GlobalCoordinateSpace() } -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -extension CoordinateSpaceProtocol where Self == NamedCoordinateSpace { - - /// The named coordinate space that is added by the system for the innermost - /// containing scroll view that allows scrolling along the provided axis. - public static func scrollView(axis: Axis) -> Self { fatalError() } +public class NamedCoordinateSpace : CoordinateSpaceProtocol, Equatable { + public override var coordinateSpace: CoordinateSpace { + return _coordinateSpace + } - /// The named coordinate space that is added by the system for the innermost - /// containing scroll view. - public static var scrollView: NamedCoordinateSpace { get { fatalError() } } -} + private let _coordinateSpace: CoordinateSpace -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -extension CoordinateSpaceProtocol where Self == NamedCoordinateSpace { + init(coordinateSpace: CoordinateSpace) { + _coordinateSpace = coordinateSpace + } - /// Creates a named coordinate space using the given value. - /// - /// Use the `coordinateSpace(_:)` modifier to assign a name to the local - /// coordinate space of a parent view. Child views can then refer to that - /// coordinate space using `.named(_:)`. - /// - /// - Parameter name: A unique value that identifies the coordinate space. - /// - /// - Returns: A named coordinate space identified by the given value. - public static func named(_ name: some Hashable) -> NamedCoordinateSpace { fatalError() } + public static func ==(lhs: NamedCoordinateSpace, rhs: NamedCoordinateSpace) -> Bool { + return lhs.coordinateSpace == rhs.coordinateSpace + } } -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -extension CoordinateSpaceProtocol where Self == LocalCoordinateSpace { - - /// The local coordinate space of the current view. - public static var local: LocalCoordinateSpace { get { fatalError() } } +public class LocalCoordinateSpace : CoordinateSpaceProtocol { + public override var coordinateSpace: CoordinateSpace { + return .local + } } -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -extension CoordinateSpaceProtocol where Self == GlobalCoordinateSpace { - - /// The global coordinate space at the root of the view hierarchy. - public static var global: GlobalCoordinateSpace { get { fatalError() } } +public class GlobalCoordinateSpace : CoordinateSpaceProtocol { } - -#endif diff --git a/Sources/SkipUI/SkipUI/Layout/GlobalCoordinateSpace.swift b/Sources/SkipUI/SkipUI/Layout/GlobalCoordinateSpace.swift deleted file mode 100644 index d032969e..00000000 --- a/Sources/SkipUI/SkipUI/Layout/GlobalCoordinateSpace.swift +++ /dev/null @@ -1,20 +0,0 @@ -// This is free software: you can redistribute and/or modify it -// under the terms of the GNU Lesser General Public License 3.0 -// as published by the Free Software Foundation https://fsf.org - -// TODO: Process for use in SkipUI - -#if !SKIP - -/// The global coordinate space at the root of the view hierarchy. -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -public struct GlobalCoordinateSpace : CoordinateSpaceProtocol { - - public init() { fatalError() } - - /// The resolved coordinate space. - public var coordinateSpace: CoordinateSpace { get { fatalError() } } -} - - -#endif diff --git a/Sources/SkipUI/SkipUI/Layout/LocalCoordinateSpace.swift b/Sources/SkipUI/SkipUI/Layout/LocalCoordinateSpace.swift deleted file mode 100644 index d736330c..00000000 --- a/Sources/SkipUI/SkipUI/Layout/LocalCoordinateSpace.swift +++ /dev/null @@ -1,19 +0,0 @@ -// This is free software: you can redistribute and/or modify it -// under the terms of the GNU Lesser General Public License 3.0 -// as published by the Free Software Foundation https://fsf.org - -// TODO: Process for use in SkipUI - -#if !SKIP - -/// The local coordinate space of the current view. -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -public struct LocalCoordinateSpace : CoordinateSpaceProtocol { - - public init() { fatalError() } - - /// The resolved coordinate space. - public var coordinateSpace: CoordinateSpace { get { fatalError() } } -} - -#endif diff --git a/Sources/SkipUI/SkipUI/Layout/MatchedGeometryProperties.swift b/Sources/SkipUI/SkipUI/Layout/MatchedGeometryProperties.swift index d94ffa86..52cdfbe1 100644 --- a/Sources/SkipUI/SkipUI/Layout/MatchedGeometryProperties.swift +++ b/Sources/SkipUI/SkipUI/Layout/MatchedGeometryProperties.swift @@ -2,80 +2,14 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI +public struct MatchedGeometryProperties : OptionSet, Sendable { + public let rawValue: Int -#if !SKIP + public init(rawValue: Int) { + self.rawValue = rawValue + } -/// A set of view properties that may be synchronized between views -/// using the `View.matchedGeometryEffect()` function. -@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) -@frozen public struct MatchedGeometryProperties : OptionSet { - - /// The corresponding value of the raw type. - /// - /// A new instance initialized with `rawValue` will be equivalent to this - /// instance. For example: - /// - /// enum PaperSize: String { - /// case A4, A5, Letter, Legal - /// } - /// - /// let selectedSize = PaperSize.Letter - /// print(selectedSize.rawValue) - /// // Prints "Letter" - /// - /// print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!) - /// // Prints "true" - public let rawValue: UInt32 - - /// Creates a new option set from the given raw value. - /// - /// This initializer always succeeds, even if the value passed as `rawValue` - /// exceeds the static properties declared as part of the option set. This - /// example creates an instance of `ShippingOptions` with a raw value beyond - /// the highest element, with a bit mask that effectively contains all the - /// declared static members. - /// - /// let extraOptions = ShippingOptions(rawValue: 255) - /// print(extraOptions.isStrictSuperset(of: .all)) - /// // Prints "true" - /// - /// - Parameter rawValue: The raw value of the option set to create. Each bit - /// of `rawValue` potentially represents an element of the option set, - /// though raw values may include bits that are not defined as distinct - /// values of the `OptionSet` type. - @inlinable public init(rawValue: UInt32) { fatalError() } - - /// The view's position, in window coordinates. - public static let position: MatchedGeometryProperties = { fatalError() }() - - /// The view's size, in local coordinates. - public static let size: MatchedGeometryProperties = { fatalError() }() - - /// Both the `position` and `size` properties. - public static let frame: MatchedGeometryProperties = { fatalError() }() - - /// The type of the elements of an array literal. - public typealias ArrayLiteralElement = MatchedGeometryProperties - - /// The element type of the option set. - /// - /// To inherit all the default implementations from the `OptionSet` protocol, - /// the `Element` type must be `Self`, the default. - public typealias Element = MatchedGeometryProperties - - /// The raw type that can be used to represent all values of the conforming - /// type. - /// - /// Every distinct value of the conforming type has a corresponding unique - /// value of the `RawValue` type, but there may be values of the `RawValue` - /// type that don't have a corresponding value of the conforming type. - public typealias RawValue = UInt32 -} - -@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) -extension MatchedGeometryProperties : Sendable { + public static let position = MatchedGeometryProperties(rawValue: 1 << 0) + public static let size = MatchedGeometryProperties(rawValue: 1 << 1) + public static let frame = MatchedGeometryProperties(rawValue: 1 << 2) } - - -#endif diff --git a/Sources/SkipUI/SkipUI/Layout/NamedCoordinateSpace.swift b/Sources/SkipUI/SkipUI/Layout/NamedCoordinateSpace.swift deleted file mode 100644 index 3d262d83..00000000 --- a/Sources/SkipUI/SkipUI/Layout/NamedCoordinateSpace.swift +++ /dev/null @@ -1,23 +0,0 @@ -// This is free software: you can redistribute and/or modify it -// under the terms of the GNU Lesser General Public License 3.0 -// as published by the Free Software Foundation https://fsf.org - -// TODO: Process for use in SkipUI - -#if !SKIP - -/// A named coordinate space. -/// -/// Use the `coordinateSpace(_:)` modifier to assign a name to the local -/// coordinate space of a parent view. Child views can then refer to that -/// coordinate space using `.named(_:)`. -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -public struct NamedCoordinateSpace : CoordinateSpaceProtocol, Equatable { - - /// The resolved coordinate space. - public var coordinateSpace: CoordinateSpace { get { fatalError() } } - - -} - -#endif diff --git a/Sources/SkipUI/SkipUI/Layout/Presentation.swift b/Sources/SkipUI/SkipUI/Layout/Presentation.swift index e1668f9b..11063d9e 100644 --- a/Sources/SkipUI/SkipUI/Layout/Presentation.swift +++ b/Sources/SkipUI/SkipUI/Layout/Presentation.swift @@ -377,4 +377,77 @@ extension View { } +@available(iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4, *) +extension View { + + /// Sets the presentation background of the enclosing sheet using a shape + /// style. + /// + /// The following example uses the ``Material/thick`` material as the sheet + /// background: + /// + /// struct ContentView: View { + /// @State private var showSettings = false + /// + /// var body: some View { + /// Button("View Settings") { + /// showSettings = true + /// } + /// .sheet(isPresented: $showSettings) { + /// SettingsView() + /// .presentationBackground(.thickMaterial) + /// } + /// } + /// } + /// + /// The `presentationBackground(_:)` modifier differs from the + /// ``View/background(_:ignoresSafeAreaEdges:)`` modifier in several key + /// ways. A presentation background: + /// + /// * Automatically fills the entire presentation. + /// * Allows views behind the presentation to show through translucent + /// styles. + /// + /// - Parameter style: The shape style to use as the presentation + /// background. + public func presentationBackground(_ style: S) -> some View where S : ShapeStyle { return stubView() } + + + /// Sets the presentation background of the enclosing sheet to a custom + /// view. + /// + /// The following example uses a yellow view as the sheet background: + /// + /// struct ContentView: View { + /// @State private var showSettings = false + /// + /// var body: some View { + /// Button("View Settings") { + /// showSettings = true + /// } + /// .sheet(isPresented: $showSettings) { + /// SettingsView() + /// .presentationBackground { + /// Color.yellow + /// } + /// } + /// } + /// } + /// + /// The `presentationBackground(alignment:content:)` modifier differs from + /// the ``View/background(alignment:content:)`` modifier in several key + /// ways. A presentation background: + /// + /// * Automatically fills the entire presentation. + /// * Allows views behind the presentation to show through translucent + /// areas of the `content`. + /// + /// - Parameters: + /// - alignment: The alignment that the modifier uses to position the + /// implicit ``ZStack`` that groups the background views. The default is + /// ``Alignment/center``. + /// - content: The view to use as the background of the presentation. + public func presentationBackground(alignment: Alignment = .center, @ViewBuilder content: () -> V) -> some View where V : View { return stubView() } +} + #endif diff --git a/Sources/SkipUI/SkipUI/System/ContainerBackgroundPlacement.swift b/Sources/SkipUI/SkipUI/System/ContainerBackgroundPlacement.swift index 32cfb9c4..2cb8269d 100644 --- a/Sources/SkipUI/SkipUI/System/ContainerBackgroundPlacement.swift +++ b/Sources/SkipUI/SkipUI/System/ContainerBackgroundPlacement.swift @@ -2,17 +2,5 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - -#if !SKIP - -/// The placement of a container background. -/// -/// This method controls where to place a background that you specify with the -/// ``View/containerBackground(_:for:)`` or -/// ``View/containerBackground(for:alignment:content:)`` modifier. -@available(iOS 17.0, tvOS 17.0, macOS 14.0, watchOS 10.0, *) public struct ContainerBackgroundPlacement : Sendable, Hashable { } - -#endif diff --git a/Sources/SkipUI/SkipUI/System/ContentMarginPlacement.swift b/Sources/SkipUI/SkipUI/System/ContentMarginPlacement.swift index 32b37173..ff22abeb 100644 --- a/Sources/SkipUI/SkipUI/System/ContentMarginPlacement.swift +++ b/Sources/SkipUI/SkipUI/System/ContentMarginPlacement.swift @@ -2,46 +2,8 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - -#if !SKIP - -/// The placement of margins. -/// -/// Different views can support customizating margins that appear in -/// different parts of that view. Use values of this type to customize -/// those margins of a particular placement. -/// -/// For example, use a ``ContentMarginPlacement/scrollIndicators`` -/// placement to customize the margins of scrollable view's scroll -/// indicators separately from the margins of a scrollable view's -/// content. -/// -/// Use this type in conjunction with the ``View/contentMargin(_:for:)`` -/// modifier. -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -public struct ContentMarginPlacement { - - /// The automatic placement. - /// - /// Views that support margin customization can automatically use - /// margins with this placement. For example, a ``ScrollView`` will - /// use this placement to automatically inset both its content and - /// scroll indicators by the specified amount. - public static var automatic: ContentMarginPlacement { get { fatalError() } } - - /// The scroll content placement. - /// - /// Scrollable views like ``ScrollView`` will use this placement to - /// inset their content, but not their scroll indicators. - public static var scrollContent: ContentMarginPlacement { get { fatalError() } } - - /// The scroll indicators placement. - /// - /// Scrollable views like ``ScrollView`` will use this placement to - /// inset their scroll indicators, but not their content. - public static var scrollIndicators: ContentMarginPlacement { get { fatalError() } } +public enum ContentMarginPlacement { + case automatic + case scrollContent + case scrollIndicators } - - -#endif diff --git a/Sources/SkipUI/SkipUI/System/ContentShapeKinds.swift b/Sources/SkipUI/SkipUI/System/ContentShapeKinds.swift index 39e3b145..81d82d70 100644 --- a/Sources/SkipUI/SkipUI/System/ContentShapeKinds.swift +++ b/Sources/SkipUI/SkipUI/System/ContentShapeKinds.swift @@ -2,105 +2,16 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - -#if !SKIP - -/// A kind for the content shape of a view. -/// -/// The kind is used by the system to influence various effects, hit-testing, -/// and more. -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) public struct ContentShapeKinds : OptionSet, Sendable { + public let rawValue: Int - /// The corresponding value of the raw type. - /// - /// A new instance initialized with `rawValue` will be equivalent to this - /// instance. For example: - /// - /// enum PaperSize: String { - /// case A4, A5, Letter, Legal - /// } - /// - /// let selectedSize = PaperSize.Letter - /// print(selectedSize.rawValue) - /// // Prints "Letter" - /// - /// print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!) - /// // Prints "true" - public var rawValue: Int { get { fatalError() } } - - /// Creates a content shape kind. - public init(rawValue: Int) { fatalError() } - - /// The kind for hit-testing and accessibility. - /// - /// Setting a content shape with this kind causes the view to hit-test - /// using the specified shape. - public static let interaction: ContentShapeKinds = { fatalError() }() - - /// The kind for drag and drop previews. - /// - /// When using this kind, only the preview shape is affected. To control the - /// shape used to hit-test and start the drag preview, use the `interaction` - /// kind. - @available(watchOS, unavailable) - @available(tvOS, unavailable) - public static let dragPreview: ContentShapeKinds = { fatalError() }() - - /// The kind for context menu previews. - /// - /// When using this kind, only the preview shape will be affected. To - /// control the shape used to hit-test and start the context menu - /// presentation, use the `.interaction` kind. - @available(tvOS 17.0, *) - @available(macOS, unavailable) - @available(watchOS, unavailable) - public static let contextMenuPreview: ContentShapeKinds = { fatalError() }() + public init(rawValue: Int) { + self.rawValue = rawValue + } - /// The kind for hover effects. - /// - /// When using this kind, only the preview shape is affected. To control - /// the shape used to hit-test and start the effect, use the `interaction` - /// kind. - /// - /// This kind does not affect the `onHover` modifier. - @available(macOS, unavailable) - @available(watchOS, unavailable) - @available(tvOS, unavailable) - public static let hoverEffect: ContentShapeKinds = { fatalError() }() - - /// The kind for accessibility visuals and sorting. - /// - /// Setting a content shape with this kind causes the accessibility frame - /// and path of the view's underlying accessibility element to match the - /// shape without adjusting the hit-testing shape, updating the visual focus - /// ring that assistive apps, such as VoiceOver, draw, as well as how the - /// element is sorted. Updating the accessibility shape is only required if - /// the shape or size used to hit-test significantly diverges from the visual - /// shape of the view. - /// - /// To control the shape for accessibility and hit-testing, use the `interaction` kind. - @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) - public static let accessibility: ContentShapeKinds = { fatalError() }() - - /// The type of the elements of an array literal. - public typealias ArrayLiteralElement = ContentShapeKinds - - /// The element type of the option set. - /// - /// To inherit all the default implementations from the `OptionSet` protocol, - /// the `Element` type must be `Self`, the default. - public typealias Element = ContentShapeKinds - - /// The raw type that can be used to represent all values of the conforming - /// type. - /// - /// Every distinct value of the conforming type has a corresponding unique - /// value of the `RawValue` type, but there may be values of the `RawValue` - /// type that don't have a corresponding value of the conforming type. - public typealias RawValue = Int + public static let interaction = ContentShapeKinds(rawValue: 1 << 0) + public static let dragPreview = ContentShapeKinds(rawValue: 1 << 1) + public static let contextMenuPreview = ContentShapeKinds(rawValue: 1 << 2) + public static let hoverEffect = ContentShapeKinds(rawValue: 1 << 3) + public static let accessibility = ContentShapeKinds(rawValue: 1 << 4) } - - -#endif diff --git a/Sources/SkipUI/SkipUI/System/ControlSize.swift b/Sources/SkipUI/SkipUI/System/ControlSize.swift index df0d920e..6d880081 100644 --- a/Sources/SkipUI/SkipUI/System/ControlSize.swift +++ b/Sources/SkipUI/SkipUI/System/ControlSize.swift @@ -2,52 +2,10 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - -#if !SKIP - -/// The size classes, like regular or small, that you can apply to controls -/// within a view. -@available(iOS 15.0, macOS 10.15, watchOS 9.0, *) -@available(tvOS, unavailable) -public enum ControlSize : CaseIterable, Sendable { - - /// A control version that is minimally sized. +public enum ControlSize : CaseIterable, Hashable, Sendable { case mini - - /// A control version that is proportionally smaller size for space-constrained views. case small - - /// A control version that is the default size. case regular - - /// A control version that is prominently sized. - @available(macOS 11.0, *) case large - - @available(iOS 17.0, macOS 14.0, watchOS 10.0, xrOS 1.0, *) case extraLarge - - /// A collection of all values of this type. - public static var allCases: [ControlSize] { get { fatalError() } } - - - - - /// A type that can represent a collection of all values of this type. - public typealias AllCases = [ControlSize] - -} - -@available(iOS 15.0, macOS 10.15, watchOS 9.0, *) -@available(tvOS, unavailable) -extension ControlSize : Equatable { } - -@available(iOS 15.0, macOS 10.15, watchOS 9.0, *) -@available(tvOS, unavailable) -extension ControlSize : Hashable { -} - - -#endif diff --git a/Sources/SkipUI/SkipUI/System/EventModifiers.swift b/Sources/SkipUI/SkipUI/System/EventModifiers.swift index bcd76614..c1cefb11 100644 --- a/Sources/SkipUI/SkipUI/System/EventModifiers.swift +++ b/Sources/SkipUI/SkipUI/System/EventModifiers.swift @@ -2,72 +2,18 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - -#if !SKIP - -/// A set of key modifiers that you can add to a gesture. -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -@frozen public struct EventModifiers : OptionSet { - - /// The raw value. - public let rawValue: Int = { fatalError() }() - - /// Creates a new set from a raw value. - /// - /// - Parameter rawValue: The raw value with which to create the key - /// modifier. - public init(rawValue: Int) { fatalError() } - - /// The Caps Lock key. - public static let capsLock: EventModifiers = { fatalError() }() - - /// The Shift key. - public static let shift: EventModifiers = { fatalError() }() - - /// The Control key. - public static let control: EventModifiers = { fatalError() }() - - /// The Option key. - public static let option: EventModifiers = { fatalError() }() - - /// The Command key. - public static let command: EventModifiers = { fatalError() }() - - /// Any key on the numeric keypad. - public static let numericPad: EventModifiers = { fatalError() }() - - /// The Function key. - @available(iOS, deprecated: 15.0, message: "Function modifier is reserved for system applications") - @available(macOS, deprecated: 12.0, message: "Function modifier is reserved for system applications") - @available(tvOS, deprecated: 15.0, message: "Function modifier is reserved for system applications") - @available(watchOS, deprecated: 8.0, message: "Function modifier is reserved for system applications") - @available(xrOS, deprecated: 1.0, message: "Function modifier is reserved for system applications") - public static let function: EventModifiers = { fatalError() }() - - /// All possible modifier keys. - public static let all: EventModifiers = { fatalError() }() - - /// The type of the elements of an array literal. - public typealias ArrayLiteralElement = EventModifiers - - /// The element type of the option set. - /// - /// To inherit all the default implementations from the `OptionSet` protocol, - /// the `Element` type must be `Self`, the default. - public typealias Element = EventModifiers - - /// The raw type that can be used to represent all values of the conforming - /// type. - /// - /// Every distinct value of the conforming type has a corresponding unique - /// value of the `RawValue` type, but there may be values of the `RawValue` - /// type that don't have a corresponding value of the conforming type. - public typealias RawValue = Int -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension EventModifiers : Sendable { +@frozen public struct EventModifiers : OptionSet, Hashable, Sendable { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let capsLock = EventModifiers(rawValue: 1) + public static let shift = EventModifiers(rawValue: 2) + public static let control = EventModifiers(rawValue: 4) + public static let option = EventModifiers(rawValue: 8) + public static let command = EventModifiers(rawValue: 16) + public static let numericPad = EventModifiers(rawValue: 32) + public static let all = EventModifiers(rawValue: 63) } - -#endif diff --git a/Sources/SkipUI/SkipUI/System/HoverPhase.swift b/Sources/SkipUI/SkipUI/System/HoverPhase.swift index 8b53f1e9..f677da10 100644 --- a/Sources/SkipUI/SkipUI/System/HoverPhase.swift +++ b/Sources/SkipUI/SkipUI/System/HoverPhase.swift @@ -2,61 +2,11 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - #if !SKIP - import struct CoreGraphics.CGPoint +#endif -/// The current hovering state and value of the pointer. -/// -/// When you use the ``View/onContinuousHover(coordinateSpace:perform:)`` -/// modifier, you can handle the hovering state using the `action` closure. -/// SkipUI calls the closure with a phase value to indicate the current -/// hovering state. The following example updates `hoverLocation` and -/// `isHovering` based on the phase provided to the closure: -/// -/// @State private var hoverLocation: CGPoint = .zero -/// @State private var isHovering = false -/// -/// var body: some View { -/// VStack { -/// Color.red -/// .frame(width: 400, height: 400) -/// .onContinuousHover { phase in -/// switch phase { -/// case .active(let location): -/// hoverLocation = location -/// isHovering = true -/// case .ended: -/// isHovering = false -/// } -/// } -/// .overlay { -/// Rectangle() -/// .frame(width: 50, height: 50) -/// .foregroundColor(isHovering ? .green : .blue) -/// .offset(x: hoverLocation.x, y: hoverLocation.y) -/// } -/// } -/// } -/// -@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) -@available(watchOS, unavailable) -@frozen public enum HoverPhase : Equatable { - - /// The pointer's location moved to the specified point within the view. +public enum HoverPhase : Equatable, Sendable { case active(CGPoint) - - /// The pointer exited the view. case ended - - -} - -@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) -@available(watchOS, unavailable) -extension HoverPhase : Sendable { } - -#endif diff --git a/Sources/SkipUI/SkipUI/System/KeyEquivalent.swift b/Sources/SkipUI/SkipUI/System/KeyEquivalent.swift index 2cfc4812..934cfd33 100644 --- a/Sources/SkipUI/SkipUI/System/KeyEquivalent.swift +++ b/Sources/SkipUI/SkipUI/System/KeyEquivalent.swift @@ -2,92 +2,62 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI +public struct KeyEquivalent : Hashable, Sendable { + public let character: Character -#if !SKIP - -/// Key equivalents consist of a letter, punctuation, or function key that can -/// be combined with an optional set of modifier keys to specify a keyboard -/// shortcut. -/// -/// Key equivalents are used to establish keyboard shortcuts to app -/// functionality. Any key can be used as a key equivalent as long as pressing -/// it produces a single character value. Key equivalents are typically -/// initialized using a single-character string literal, with constants for -/// unprintable or hard-to-type values. -/// -/// The modifier keys necessary to type a key equivalent are factored in to the -/// resulting keyboard shortcut. That is, a key equivalent whose raw value is -/// the capitalized string "A" corresponds with the keyboard shortcut -/// Command-Shift-A. The exact mapping may depend on the keyboard layout—for -/// example, a key equivalent with the character value "}" produces a shortcut -/// equivalent to Command-Shift-] on ANSI keyboards, but would produce a -/// different shortcut for keyboard layouts where punctuation characters are in -/// different locations. -@available(iOS 14.0, macOS 11.0, tvOS 17.0, *) -@available(watchOS, unavailable) -public struct KeyEquivalent : Sendable { + public init(_ character: Character) { + self.character = character + } /// Up Arrow (U+F700) - public static let upArrow: KeyEquivalent = { fatalError() }() + public static let upArrow = KeyEquivalent("\u{F700}") /// Down Arrow (U+F701) - public static let downArrow: KeyEquivalent = { fatalError() }() + public static let downArrow = KeyEquivalent("\u{F701}") /// Left Arrow (U+F702) - public static let leftArrow: KeyEquivalent = { fatalError() }() + public static let leftArrow = KeyEquivalent("\u{F702}") /// Right Arrow (U+F703) - public static let rightArrow: KeyEquivalent = { fatalError() }() + public static let rightArrow = KeyEquivalent("\u{F703}") /// Escape (U+001B) - public static let escape: KeyEquivalent = { fatalError() }() + public static let escape = KeyEquivalent("\u{001B}") /// Delete (U+0008) - public static let delete: KeyEquivalent = { fatalError() }() + public static let delete = KeyEquivalent("\u{0008}") /// Delete Forward (U+F728) - public static let deleteForward: KeyEquivalent = { fatalError() }() + public static let deleteForward = KeyEquivalent("\u{F728}") /// Home (U+F729) - public static let home: KeyEquivalent = { fatalError() }() + public static let home = KeyEquivalent("\u{F729}") /// End (U+F72B) - public static let end: KeyEquivalent = { fatalError() }() + public static let end = KeyEquivalent("\u{F72B}") /// Page Up (U+F72C) - public static let pageUp: KeyEquivalent = { fatalError() }() + public static let pageUp = KeyEquivalent("\u{F72C}") /// Page Down (U+F72D) - public static let pageDown: KeyEquivalent = { fatalError() }() + public static let pageDown = KeyEquivalent("\u{F72D}") /// Clear (U+F739) - public static let clear: KeyEquivalent = { fatalError() }() + public static let clear = KeyEquivalent("\u{F739}") /// Tab (U+0009) - public static let tab: KeyEquivalent = { fatalError() }() + public static let tab = KeyEquivalent("\u{0009}") /// Space (U+0020) - public static let space: KeyEquivalent = { fatalError() }() + public static let space = KeyEquivalent("\u{0020}") /// Return (U+000D) - public static let `return`: KeyEquivalent = { fatalError() }() - - /// The character value that the key equivalent represents. - public var character: Character { get { fatalError() } } - - /// Creates a new key equivalent from the given character value. - public init(_ character: Character) { fatalError() } + public static let `return` = KeyEquivalent("\u{000D}") } -@available(iOS 17.0, macOS 14.0, tvOS 17.0, *) -@available(watchOS, unavailable) -extension KeyEquivalent : Hashable { - - - +#if !SKIP -} +// TODO: Process for use in SkipUI @available(iOS 14.0, macOS 11.0, tvOS 17.0, *) @available(watchOS, unavailable) diff --git a/Sources/SkipUI/SkipUI/System/KeyboardShortcut.swift b/Sources/SkipUI/SkipUI/System/KeyboardShortcut.swift index 85a1ae3b..9afe2e2b 100644 --- a/Sources/SkipUI/SkipUI/System/KeyboardShortcut.swift +++ b/Sources/SkipUI/SkipUI/System/KeyboardShortcut.swift @@ -2,112 +2,23 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI - -#if !SKIP - - -/// Keyboard shortcuts describe combinations of keys on a keyboard that the user -/// can press in order to activate a button or toggle. -@available(iOS 14.0, macOS 11.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -public struct KeyboardShortcut : Sendable { - - /// Options for how a keyboard shortcut participates in automatic localization. - /// - /// A shortcut's `key` that is defined on an US-English keyboard - /// layout might not be reachable on international layouts. - /// For example the shortcut `⌘[` works well for the US layout but is - /// hard to reach for German users. - /// On the German keyboard layout, pressing `⌥5` will produce - /// `[`, which causes the shortcut to become `⌥⌘5`. - /// If configured, which is the default behavior, automatic shortcut - /// remapping will convert it to `⌘Ö`. - /// - /// In addition to that, some keyboard shortcuts carry information - /// about directionality. - /// Right-aligning a block of text or seeking forward in context of music - /// playback are such examples. These kinds of shortcuts benefit from the option - /// ``KeyboardShortcut/Localization-swift.struct/withoutMirroring`` - /// to tell the system that they won't be flipped when running in a - /// right-to-left context. - @available(iOS 15.0, macOS 12.0, *) - @available(tvOS, unavailable) - @available(watchOS, unavailable) - public struct Localization : Sendable { - - /// Remap shortcuts to their international counterparts, mirrored for - /// right-to-left usage if appropriate. - /// - /// This is the default configuration. - public static let automatic: KeyboardShortcut.Localization = { fatalError() }() - - /// Don't mirror shortcuts. - /// - /// Use this for shortcuts that always have a specific directionality, like - /// aligning something on the right. - /// - /// Don't use this option for navigational shortcuts like "Go Back" because navigation - /// is flipped in right-to-left contexts. - public static let withoutMirroring: KeyboardShortcut.Localization = { fatalError() }() - - /// Don't use automatic shortcut remapping. - /// - /// When you use this mode, you have to take care of international use-cases separately. - public static let custom: KeyboardShortcut.Localization = { fatalError() }() +public struct KeyboardShortcut : Hashable, Sendable { + public enum Localization : Hashable, Sendable { + case automatic + case withoutMirroring + case custom } - /// The standard keyboard shortcut for the default button, consisting of - /// the Return (↩) key and no modifiers. - /// - /// On macOS, the default button is designated with special coloration. If - /// more than one control is assigned this shortcut, only the first one is - /// emphasized. - public static let defaultAction: KeyboardShortcut = { fatalError() }() - - /// The standard keyboard shortcut for cancelling the in-progress action - /// or dismissing a prompt, consisting of the Escape (⎋) key and no - /// modifiers. - public static let cancelAction: KeyboardShortcut = { fatalError() }() - - /// The key equivalent that the user presses in conjunction with any - /// specified modifier keys to activate the shortcut. - public var key: KeyEquivalent { get { fatalError() } } - - /// The modifier keys that the user presses in conjunction with a key - /// equivalent to activate the shortcut. - public var modifiers: EventModifiers { get { fatalError() } } - - /// The localization strategy to apply to this shortcut. - @available(iOS 15.0, macOS 12.0, *) - @available(tvOS, unavailable) - @available(watchOS, unavailable) - public var localization: KeyboardShortcut.Localization { get { fatalError() } } - - /// Creates a new keyboard shortcut with the given key equivalent and set of - /// modifier keys. - /// - /// The localization configuration defaults to ``KeyboardShortcut/Localization-swift.struct/automatic``. - public init(_ key: KeyEquivalent, modifiers: EventModifiers = .command) { fatalError() } - - /// Creates a new keyboard shortcut with the given key equivalent and set of - /// modifier keys. - /// - /// Use the `localization` parameter to specify a localization strategy - /// for this shortcut. - @available(iOS 15.0, macOS 12.0, *) - @available(tvOS, unavailable) - @available(watchOS, unavailable) - public init(_ key: KeyEquivalent, modifiers: EventModifiers = .command, localization: KeyboardShortcut.Localization) { fatalError() } -} - -@available(iOS 15.0, macOS 12.0, *) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -extension KeyboardShortcut : Hashable { + public static let defaultAction = KeyboardShortcut(.return, modifiers: []) + public static let cancelAction = KeyboardShortcut(.escape, modifiers: []) + public let key: KeyEquivalent + public let modifiers: EventModifiers + public let localization: KeyboardShortcut.Localization + public init(_ key: KeyEquivalent, modifiers: EventModifiers = .command, localization: KeyboardShortcut.Localization = .automatic) { + self.key = key + self.modifiers = modifiers + self.localization = localization + } } - -#endif diff --git a/Sources/SkipUI/SkipUI/System/SensoryFeedback.swift b/Sources/SkipUI/SkipUI/System/SensoryFeedback.swift index 2129c6c7..f6712731 100644 --- a/Sources/SkipUI/SkipUI/System/SensoryFeedback.swift +++ b/Sources/SkipUI/SkipUI/System/SensoryFeedback.swift @@ -2,145 +2,44 @@ // under the terms of the GNU Lesser General Public License 3.0 // as published by the Free Software Foundation https://fsf.org -// TODO: Process for use in SkipUI +public struct SensoryFeedback : RawRepresentable, Equatable, Sendable { + public let rawValue: Int -#if !SKIP - -/// Represents a type of haptic and/or audio feedback that can be played. -/// -/// This feedback can be passed to `View.sensoryFeedback` to play it. -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) -@available(xrOS, unavailable) -public struct SensoryFeedback : Equatable, Sendable { - - /// Indicates that a task or action has completed. - /// - /// Only plays feedback on iOS and watchOS. - public static let success: SensoryFeedback = { fatalError() }() - - /// Indicates that a task or action has produced a warning of some kind. - /// - /// Only plays feedback on iOS and watchOS. - public static let warning: SensoryFeedback = { fatalError() }() - - /// Indicates that an error has occurred. - /// - /// Only plays feedback on iOS and watchOS. - public static let error: SensoryFeedback = { fatalError() }() - - /// Indicates that a UI element’s values are changing. - /// - /// Only plays feedback on iOS and watchOS. - public static let selection: SensoryFeedback = { fatalError() }() - - /// Indicates that an important value increased above a significant - /// threshold. - /// - /// Only plays feedback on watchOS. - public static let increase: SensoryFeedback = { fatalError() }() - - /// Indicates that an important value decreased below a significant - /// threshold. - /// - /// Only plays feedback on watchOS. - public static let decrease: SensoryFeedback = { fatalError() }() - - /// Indicates that an activity started. - /// - /// Use this haptic when starting a timer or any other activity that can be - /// explicitly started and stopped. - /// - /// Only plays feedback on watchOS. - public static let start: SensoryFeedback = { fatalError() }() - - /// Indicates that an activity stopped. - /// - /// Use this haptic when stopping a timer or other activity that was - /// previously started. - /// - /// Only plays feedback on watchOS. - public static let stop: SensoryFeedback = { fatalError() }() - - /// Indicates the alignment of a dragged item. - /// - /// For example, use this pattern in a drawing app when the user drags a - /// shape into alignment with another shape. - /// - /// Only plays feedback on macOS. - public static let alignment: SensoryFeedback = { fatalError() }() - - /// Indicates movement between discrete levels of pressure. - /// - /// For example, as the user presses a fast-forward button on a video - /// player, playback could increase or decrease and haptic feedback could be - /// provided as different levels of pressure are reached. - /// - /// Only plays feedback on macOS. - public static let levelChange: SensoryFeedback = { fatalError() }() - - /// Provides a physical metaphor you can use to complement a visual - /// experience. - /// - /// Only plays feedback on iOS, watchOS, and macOS. - public static let impact: SensoryFeedback = { fatalError() }() - - /// Provides a physical metaphor you can use to complement a visual - /// experience. - /// - /// Not all platforms will play different feedback for different weights and - /// intensities of impact. - /// - /// Only plays feedback on iOS, watchOS, and macOS. - public static func impact(weight: SensoryFeedback.Weight = .medium, intensity: Double = 1.0) -> SensoryFeedback { fatalError() } - - /// Provides a physical metaphor you can use to complement a visual - /// experience. - /// - /// Not all platforms will play different feedback for different - /// flexibilities and intensities of impact. - /// - /// Only plays feedback on iOS, watchOS, and macOS. - public static func impact(flexibility: SensoryFeedback.Flexibility, intensity: Double = 1.0) -> SensoryFeedback { fatalError() } - - /// The weight to be represented by a type of feedback. - /// - /// `Weight` values can be passed to - /// `SensoryFeedback.impact(weight:intensity:)`. - public struct Weight : Equatable, Sendable { - - /// Indicates a collision between small or lightweight UI objects. - public static let light: SensoryFeedback.Weight = { fatalError() }() - - /// Indicates a collision between medium-sized or medium-weight UI - /// objects. - public static let medium: SensoryFeedback.Weight = { fatalError() }() - - /// Indicates a collision between large or heavyweight UI objects. - public static let heavy: SensoryFeedback.Weight = { fatalError() }() - - + public init(rawValue: Int) { + self.rawValue = rawValue } - /// The flexibility to be represented by a type of feedback. - /// - /// `Flexibility` values can be passed to - /// `SensoryFeedback.impact(flexibility:intensity:)`. - public struct Flexibility : Equatable, Sendable { - - /// Indicates a collision between hard or inflexible UI objects. - public static let rigid: SensoryFeedback.Flexibility = { fatalError() }() - - /// Indicates a collision between solid UI objects of medium - /// flexibility. - public static let solid: SensoryFeedback.Flexibility = { fatalError() }() + public static let success = SensoryFeedback(rawValue: 1) + public static let warning = SensoryFeedback(rawValue: 2) + public static let error = SensoryFeedback(rawValue: 3) + public static let selection = SensoryFeedback(rawValue: 4) + public static let increase = SensoryFeedback(rawValue: 5) + public static let decrease = SensoryFeedback(rawValue: 6) + public static let start = SensoryFeedback(rawValue: 7) + public static let stop = SensoryFeedback(rawValue: 8) + public static let alignment = SensoryFeedback(rawValue: 9) + public static let levelChange = SensoryFeedback(rawValue: 10) + public static let impact = SensoryFeedback(rawValue: 11) + + @available(*, unavailable) + public static func impact(weight: SensoryFeedback.Weight = .medium, intensity: Double = 1.0) -> SensoryFeedback { + fatalError() + } - /// Indicates a collision between soft or flexible UI objects. - public static let soft: SensoryFeedback.Flexibility = { fatalError() }() + @available(*, unavailable) + public static func impact(flexibility: SensoryFeedback.Flexibility, intensity: Double = 1.0) -> SensoryFeedback { + fatalError() + } - + public enum Weight : Equatable, Sendable { + case light + case medium + case heavy } - + public enum Flexibility : Equatable, Sendable { + case rigid + case solid + case soft + } } - -#endif diff --git a/Sources/SkipUI/SkipUI/Text/Label.swift b/Sources/SkipUI/SkipUI/Text/Label.swift index e716516a..21016246 100644 --- a/Sources/SkipUI/SkipUI/Text/Label.swift +++ b/Sources/SkipUI/SkipUI/Text/Label.swift @@ -107,14 +107,20 @@ public struct LabelStyle: RawRepresentable, Equatable { } public static let automatic = LabelStyle(rawValue: 0) + + @available(*, unavailable) public static let titleOnly = LabelStyle(rawValue: 1) + + @available(*, unavailable) public static let iconOnly = LabelStyle(rawValue: 2) + + @available(*, unavailable) public static let titleAndIcon = LabelStyle(rawValue: 3) } extension View { - @available(*, unavailable) public func labelStyle(_ style: LabelStyle) -> some View { + // We only support .automatic return self } } diff --git a/Sources/SkipUI/SkipUI/Text/Text.swift b/Sources/SkipUI/SkipUI/Text/Text.swift index a7af1900..d000532b 100644 --- a/Sources/SkipUI/SkipUI/Text/Text.swift +++ b/Sources/SkipUI/SkipUI/Text/Text.swift @@ -241,6 +241,26 @@ extension View { return self } + @available(*, unavailable) + public func speechAlwaysIncludesPunctuation(_ value: Bool = true) -> some View { + return self + } + + @available(*, unavailable) + public func speechSpellsOutCharacters(_ value: Bool = true) -> some View { + return self + } + + @available(*, unavailable) + public func speechAdjustedPitch(_ value: Double) -> some View { + return self + } + + @available(*, unavailable) + public func speechAnnouncementsQueued(_ value: Bool = true) -> some View { + return self + } + @available(*, unavailable) public func strikethrough(_ isActive: Bool = true, pattern: Text.LineStyle.Pattern = .solid, color: Color? = nil) -> some View { return self diff --git a/Sources/SkipUI/SkipUI/Text/TextEditor.swift b/Sources/SkipUI/SkipUI/Text/TextEditor.swift index 2e9fd2d6..d107b8df 100644 --- a/Sources/SkipUI/SkipUI/Text/TextEditor.swift +++ b/Sources/SkipUI/SkipUI/Text/TextEditor.swift @@ -213,4 +213,138 @@ extension View { } +@available(iOS 16.0, *) +@available(macOS, unavailable) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +extension View { + + /// Programmatically presents the find and replace interface for text + /// editor views. + /// + /// Add this modifier to a ``TextEditor``, or to a view hierarchy that + /// contains at least one text editor, to control the presentation of + /// the find and replace interface. When you set the `isPresented` binding + /// to `true`, the system shows the interface, and when you set it to + /// `false`, the system hides the interface. The following example shows + /// and hides the interface based on the state of a toolbar button: + /// + /// TextEditor(text: $text) + /// .findNavigator(isPresented: $isPresented) + /// .toolbar { + /// Toggle(isOn: $isPresented) { + /// Label("Find", systemImage: "magnifyingglass") + /// } + /// } + /// + /// The find and replace interface allows people to search for instances + /// of a specified string in the text editor, and optionally to replace + /// instances of the search string with another string. They can also + /// show and hide the interface using built-in controls, like menus and + /// keyboard shortcuts. SkipUI updates `isPresented` to reflect the + /// users's actions. + /// + /// If the text editor view isn't currently in focus, the system still + /// presents the find and replace interface when you set `isPresented` + /// to `true`. If the view hierarchy contains multiple editors, the one + /// that shows the find and replace interface is nondeterministic. + /// + /// You can disable the find and replace interface for a text editor by + /// applying the ``View/findDisabled(_:)`` modifier to the editor. If you + /// do that, setting this modifier's `isPresented` binding to `true` has + /// no effect, but only if the disabling modifier appears closer to the + /// text editor, like this: + /// + /// TextEditor(text: $text) + /// .findDisabled(isDisabled) + /// .findNavigator(isPresented: $isPresented) + /// + /// - Parameter isPresented: A binding to a Boolean value that controls the + /// presentation of the find and replace interface. + /// + /// - Returns: A view that presents the find and replace interface when + /// `isPresented` is `true`. + public func findNavigator(isPresented: Binding) -> some View { return stubView() } + + + /// Prevents find and replace operations in a text editor. + /// + /// Add this modifier to ensure that people can't activate the find + /// and replace interface for a ``TextEditor``: + /// + /// TextEditor(text: $text) + /// .findDisabled() + /// + /// When you disable the find operation, you also implicitly disable the + /// replace operation. If you want to only disable replace, use + /// ``View/replaceDisabled(_:)`` instead. + /// + /// Using this modifer also prevents programmatic find and replace + /// interface presentation using the ``View/findNavigator(isPresented:)`` + /// method. Be sure to place the disabling modifier closer to the text + /// editor for this to work: + /// + /// TextEditor(text: $text) + /// .findDisabled(isDisabled) + /// .findNavigator(isPresented: $isPresented) + /// + /// If you apply this modifer at multiple levels of a view hierarchy, + /// the call closest to the text editor takes precedence. For example, + /// people can activate find and replace for the first text editor + /// in the following example, but not the second: + /// + /// VStack { + /// TextEditor(text: $text1) + /// .findDisabled(false) + /// TextEditor(text: $text2) + /// } + /// .findDisabled(true) + /// + /// - Parameter isDisabled: A Boolean value that indicates whether to + /// disable the find and replace interface for a text editor. + /// + /// - Returns: A view that disables the find and replace interface. + public func findDisabled(_ isDisabled: Bool = true) -> some View { return stubView() } + + + /// Prevents replace operations in a text editor. + /// + /// Add this modifier to ensure that people can't activate the replace + /// feature of a find and replace interface for a ``TextEditor``: + /// + /// TextEditor(text: $text) + /// .replaceDisabled() + /// + /// If you want to disable both find and replace, use the + /// ``View/findDisabled(_:)`` modifier instead. + /// + /// Using this modifer also disables the replace feature of a find and + /// replace interface that you present programmatically using the + /// ``View/findNavigator(isPresented:)`` method. Be sure to place the + /// disabling modifier closer to the text editor for this to work: + /// + /// TextEditor(text: $text) + /// .replaceDisabled(isDisabled) + /// .findNavigator(isPresented: $isPresented) + /// + /// If you apply this modifer at multiple levels of a view hierarchy, + /// the call closest to the text editor takes precedence. For example, + /// people can activate find and replace for the first text editor + /// in the following example, but only find for the second: + /// + /// VStack { + /// TextEditor(text: $text1) + /// .replaceDisabled(false) + /// TextEditor(text: $text2) + /// } + /// .replaceDisabled(true) + /// + /// - Parameter isDisabled: A Boolean value that indicates whether text + /// replacement in the find and replace interface is disabled. + /// + /// - Returns: A view that disables the replace feature of a find and + /// replace interface. + public func replaceDisabled(_ isDisabled: Bool = true) -> some View { return stubView() } +} + #endif diff --git a/Sources/SkipUI/SkipUI/View/View.swift b/Sources/SkipUI/SkipUI/View/View.swift index 67109d41..9462a2b6 100644 --- a/Sources/SkipUI/SkipUI/View/View.swift +++ b/Sources/SkipUI/SkipUI/View/View.swift @@ -126,6 +126,11 @@ extension View { return self } + @available(*, unavailable) + public func backgroundStyle(_ style: any ShapeStyle) -> some View { + return self + } + @available(*, unavailable) public func badge(_ count: Int) -> some View { return self @@ -266,6 +271,16 @@ extension View { return self } + @available(*, unavailable) + public func containerBackground(_ style: any ShapeStyle, for container: ContainerBackgroundPlacement) -> some View { + return self + } + + @available(*, unavailable) + public func containerBackground(for container: ContainerBackgroundPlacement, alignment: Alignment = .center, @ViewBuilder content: () -> any View) -> some View { + return self + } + @available(*, unavailable) public func containerRelativeFrame(_ axes: Axis.Set, alignment: Alignment = .center) -> some View { return self @@ -291,6 +306,11 @@ extension View { return self } + @available(*, unavailable) + public func contentShape(_ kind: ContentShapeKinds, _ shape: any Shape, eoFill: Bool = false) -> some View { + return self + } + @available(*, unavailable) public func contextMenu(@ViewBuilder menuItems: () -> any View) -> some View { return self @@ -311,6 +331,16 @@ extension View { return self } + @available(*, unavailable) + public func controlSize(_ controlSize: ControlSize) -> some View { + return self + } + + @available(*, unavailable) + public func coordinateSpace(_ name: NamedCoordinateSpace) -> some View { + return self + } + @available(*, unavailable) public func defaultHoverEffect(_ effect: HoverEffect?) -> some View { return self @@ -471,6 +501,11 @@ extension View { return self } + @available(*, unavailable) + public func handlesExternalEvents(preferring: Set, allowing: Set) -> some View { + return self + } + @available(*, unavailable) public func headerProminence(_ prominence: Prominence) -> some View { return self @@ -514,11 +549,31 @@ extension View { return self } + @available(*, unavailable) + public func id(_ id: any Hashable) -> some View { + return self + } + @available(*, unavailable) public func ignoresSafeArea(_ regions: SafeAreaRegions = .all, edges: Edge.Set = .all) -> some View { return self } + @available(*, unavailable) + public func inspector(isPresented: Binding, @ViewBuilder content: () -> any View) -> some View { + return self + } + + @available(*, unavailable) + public func inspectorColumnWidth(min: CGFloat? = nil, ideal: CGFloat, max: CGFloat? = nil) -> some View { + return self + } + + @available(*, unavailable) + public func inspectorColumnWidth(_ width: CGFloat) -> some View { + return self + } + @available(*, unavailable) public func interactionActivityTrackingTag(_ tag: String) -> some View { return self @@ -529,6 +584,21 @@ extension View { return self } + @available(*, unavailable) + public func keyboardShortcut(_ key: KeyEquivalent, modifiers: EventModifiers = .command) -> some View { + return self + } + + @available(*, unavailable) + public func keyboardShortcut(_ shortcut: KeyboardShortcut?) -> some View { + return self + } + + @available(*, unavailable) + public func keyboardShortcut(_ key: KeyEquivalent, modifiers: EventModifiers = .command, localization: KeyboardShortcut.Localization) -> some View { + return self + } + public func labelsHidden() -> some View { #if SKIP return environment(\._labelsHidden, true) @@ -562,6 +632,11 @@ extension View { return self } + @available(*, unavailable) + public func matchedGeometryEffect(id: any Hashable, in namespace: Any /* Namespace.ID */, properties: MatchedGeometryProperties = .frame, anchor: UnitPoint = .center, isSource: Bool = true) -> some View { + return self + } + @available(*, unavailable) public func modifier(_ modifier: Any) -> some View { return self @@ -597,6 +672,11 @@ extension View { return self } + @available(*, unavailable) + public func onContinuousHover(coordinateSpace: some CoordinateSpaceProtocol = CoordinateSpaceProtocol.local, perform action: @escaping (HoverPhase) -> Void) -> some View { + return self + } + @available(*, unavailable) public func onDisappear(perform action: (() -> Void)? = nil) -> some View { return self @@ -613,7 +693,7 @@ extension View { } @available(*, unavailable) - public func onTapGesture(count: Int = 1, /* coordinateSpace: some CoordinateSpaceProtocol = .local, */ perform action: @escaping (CGPoint) -> Void) -> some View { + public func onTapGesture(count: Int = 1, coordinateSpace: some CoordinateSpaceProtocol = CoordinateSpaceProtocol.local, perform action: @escaping (CGPoint) -> Void) -> some View { return self } @@ -712,6 +792,16 @@ extension View { return self } + @available(*, unavailable) + public func renameAction(_ isFocused: Any /* FocusState.Binding */) -> some View { + return self + } + + @available(*, unavailable) + public func renameAction(_ action: @escaping () -> Void) -> some View { + return self + } + public func rotationEffect(_ angle: Angle) -> some View { #if SKIP return ComposeModifierView(contextView: self) { @@ -736,6 +826,31 @@ extension View { return self } + @available(*, unavailable) + public func safeAreaInset(edge: VerticalEdge, alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> any View) -> some View { + return self + } + + @available(*, unavailable) + public func safeAreaInset(edge: HorizontalEdge, alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> any View) -> some View { + return self + } + + @available(*, unavailable) + public func safeAreaPadding(_ insets: EdgeInsets) -> some View { + return self + } + + @available(*, unavailable) + public func safeAreaPadding(_ edges: Edge.Set = .all, _ length: CGFloat? = nil) -> some View { + return self + } + + @available(*, unavailable) + public func safeAreaPadding(_ length: CGFloat) -> some View { + return self + } + @available(*, unavailable) public func saturation(_ amount: Double) -> some View { return self @@ -789,6 +904,21 @@ extension View { return self } + @available(*, unavailable) + public func sensoryFeedback(_ feedback: SensoryFeedback, trigger: any Equatable) -> some View { + return self + } + + @available(*, unavailable) + public func sensoryFeedback(_ feedback: SensoryFeedback, trigger: T, condition: @escaping (_ oldValue: T, _ newValue: T) -> Bool) -> some View where T: Equatable { + return self + } + + @available(*, unavailable) + public func sensoryFeedback(trigger: T, _ feedback: @escaping (_ oldValue: T, _ newValue: T) -> SensoryFeedback?) -> some View where T : Equatable { + return self + } + @available(*, unavailable) public func shadow(color: Color = Color(/* .sRGBLinear, */ white: 0.0, opacity: 0.33), radius: CGFloat, x: CGFloat = 0.0, y: CGFloat = 0.0) -> some View { return self @@ -814,6 +944,11 @@ extension View { return self } + @available(*, unavailable) + public func symbolEffectsRemoved(_ isEnabled: Bool = true) -> some View { + return self + } + public func tag(_ tag: any Hashable) -> some View { return self } @@ -859,6 +994,21 @@ extension View { return self } + @available(*, unavailable) + public func typeSelectEquivalent(_ text: Text?) -> some View { + return self + } + + @available(*, unavailable) + public func typeSelectEquivalent(_ stringKey: LocalizedStringKey) -> some View { + return self + } + + @available(*, unavailable) + public func typeSelectEquivalent(_ string: String) -> some View { + return self + } + @available(*, unavailable) public func zIndex(_ value: Double) -> some View { return self