diff --git a/Sources/AppKitBackend/NSViewRepresentable.swift b/Sources/AppKitBackend/NSViewRepresentable.swift index 498518f09a..ac09cdc880 100644 --- a/Sources/AppKitBackend/NSViewRepresentable.swift +++ b/Sources/AppKitBackend/NSViewRepresentable.swift @@ -42,18 +42,13 @@ public protocol NSViewRepresentable: View where Content == Never { /// /// The default implementation uses `nsView.intrinsicContentSize` and /// `nsView.sizeThatFits(_:)` to determine the return value. + /// /// - Parameters: /// - proposal: The proposed frame for the view to render in. - /// - nsVIew: The view being queried for its preferred size. - /// - context: The context, including the coordinator and environment values. - /// - Returns: Information about the view's size. The ``SwiftCrossUI/ViewSize/size`` - /// property is what frame the view will actually be rendered with if the - /// current layout pass is not a dry run, while the other properties are - /// used to inform the layout engine how big or small the view can be. The - /// ``SwiftCrossUI/ViewSize/idealSize`` property should not vary with the - /// `proposal`, and should only depend on the view's contents. Pass `nil` - /// for the maximum width/height if the view has no maximum size (and - /// therefore may occupy the entire screen). + /// - nsView: The view being queried for its preferred size. + /// - context: The context, including the coordinator and environment + /// values. + /// - Returns: The view's size. func determineViewSize( for proposal: ProposedViewSize, nsView: NSViewType, @@ -64,8 +59,9 @@ public protocol NSViewRepresentable: View where Content == Never { /// /// This method is called after all AppKit lifecycle methods, such as /// `nsView.didMoveToSuperview()`. The default implementation does nothing. + /// /// - Parameters: - /// - nsVIew: The view being dismantled. + /// - nsView: The view being dismantled. /// - coordinator: The coordinator. static func dismantleNSView(_ nsView: NSViewType, coordinator: Coordinator) } diff --git a/Sources/SwiftCrossUI/Backend/AnyWidget.swift b/Sources/SwiftCrossUI/Backend/AnyWidget.swift index a561531e0b..7f09c7dc0e 100644 --- a/Sources/SwiftCrossUI/Backend/AnyWidget.swift +++ b/Sources/SwiftCrossUI/Backend/AnyWidget.swift @@ -8,17 +8,23 @@ /// a concrete type before use (removing the need to type-erase specific /// methods or anything like that). public class AnyWidget { - /// The wrapped widget + /// The wrapped widget. var widget: Any /// Erases the specific type of a widget (to allow storage without propagating /// the selected backend type through the whole type system). + /// + /// - Parameter widget: The widget to type-erase. public init(_ widget: Any) { self.widget = widget } - /// Converts the widget back to its original concrete type. If the requested - /// type doesn't match its original type then the method will crash. + /// Converts the widget back to its original concrete type. + /// + /// - Precondition: `backend` is the same backend used to create the widget. + /// + /// - Parameter backend: The backend to use to convert the widget. + /// - Returns: The widget as the backend's widget type. public func concreteWidget( for backend: Backend.Type ) -> Backend.Widget { @@ -28,9 +34,13 @@ public class AnyWidget { return widget } - /// Converts the widget back to its original concrete type. If the requested - /// type doesn't match its original type then the method will crash. Often - /// more concise than using ``AnyWidget/concreteWidget(for:)``. + /// Converts the widget back to its original concrete type. + /// + /// Often more concise than using ``AnyWidget/concreteWidget(for:)``. + /// + /// - Precondition: `T` is the same type as the widget. + /// + /// - Returns: The converted widget. public func into() -> T { guard let widget = widget as? T else { fatalError("AnyWidget used with incompatible widget type \(T.self)") diff --git a/Sources/SwiftCrossUI/Backend/AppBackend.swift b/Sources/SwiftCrossUI/Backend/AppBackend.swift index 6641ff9d71..3672ecf51b 100644 --- a/Sources/SwiftCrossUI/Backend/AppBackend.swift +++ b/Sources/SwiftCrossUI/Backend/AppBackend.swift @@ -8,10 +8,10 @@ import Foundation /// and are simply intended to allow incremental implementation of backends, /// not a production-ready fallback for views that cannot be represented by a /// given backend. The methods you need to implemented up-front (which don't -/// have default implementations) are: ``AppBackend/createRootWindow(withDefaultSize:_:)``, +/// have default implementations) are: ``AppBackend/createWindow(withDefaultSize:)``, /// ``AppBackend/setTitle(ofWindow:to:)``, ``AppBackend/setResizability(ofWindow:to:)``, /// ``AppBackend/setChild(ofWindow:to:)``, ``AppBackend/show(window:)``, -/// ``AppBackend/runMainLoop()``, ``AppBackend/runInMainThread(action:)``, +/// ``AppBackend/runMainLoop(_:)``, ``AppBackend/runInMainThread(action:)``, /// ``AppBackend/isWindowProgrammaticallyResizable(_:)``, /// ``AppBackend/show(widget:)``. /// Many of these can simply be given dummy implementations until you're ready @@ -42,11 +42,17 @@ import Foundation /// and actually displaying the widget anyway). @MainActor public protocol AppBackend: Sendable { + /// The underlying window type. Can be a wrapper or subclass. associatedtype Window + /// The underlying widget type. associatedtype Widget + /// The underlying menu type. Can be a wrapper or subclass. associatedtype Menu + /// The underlying alert type. Can be a wrapper or subclass. associatedtype Alert + /// The underlying path type. Can be a wrapper or subclass. associatedtype Path + /// The underlying sheet type. Can be a wrapper or subclass. associatedtype Sheet /// Creates an instance of the backend. @@ -56,18 +62,21 @@ public protocol AppBackend: Sendable { /// recommendation by the backend that SwiftCrossUI won't necessarily /// follow in all cases. var defaultTableRowContentHeight: Int { get } - /// The default vertical padding to apply to table cells. This is a - /// recommendation by the backend that SwiftCrossUI won't necessarily - /// follow in all cases. This is the amount of padding added above and - /// below each cell, not the total amount added along the vertical axis. + /// The default vertical padding to apply to table cells. + /// + /// This is the amount of padding added above and below each cell, not the + /// total amount added along the vertical axis. It's a recommendation by the + /// backend that SwiftCrossUI won't necessarily follow in all cases. var defaultTableCellVerticalPadding: Int { get } - /// The default amount of padding used when a user uses the ``View/padding(_:_:)`` + /// The default amount of padding used when a user uses the``View/padding(_:_:)`` /// modifier. var defaultPaddingAmount: Int { get } - /// Gets the layout width of a backend's scroll bars. Assumes that the width - /// is the same for both vertical and horizontal scroll bars (where the width - /// of a horizontal scroll bar is what pedants may call its height). If the - /// backend uses overlay scroll bars then this width should be 0. + /// Gets the layout width of a backend's scroll bars. + /// + /// Assumes that the width is the same for both vertical and horizontal + /// scroll bars (where the width of a horizontal scroll bar is what pedants + /// may call its height). If the backend uses overlay scroll bars then this + /// width should be 0. /// /// This value may make sense to have as a computed property for some backends /// such as `AppKitBackend` where plugging in a mouse can cause the default @@ -75,7 +84,8 @@ public protocol AppBackend: Sendable { /// ensure that the configured root environment change handler gets called so /// that SwiftCrossUI can update the app's layout accordingly. var scrollBarWidth: Int { get } - /// If `true`, a toggle in the ``ToggleStyle/switch`` style grows to fill its parent container. + /// If `true`, a toggle in the ``ToggleStyle/switch`` style grows to fill + /// its parent container. var requiresToggleSwitchSpacer: Bool { get } /// If `true`, all images in a window will get updated when the window's /// scale factor changes (``EnvironmentValues/windowScaleFactor``). @@ -86,129 +96,219 @@ public protocol AppBackend: Sendable { /// manually rescale the image meaning that it must get rescaled when the /// scale factor changes. var requiresImageUpdateOnScaleFactorChange: Bool { get } - /// How the backend handles rendering of menu buttons. Affects which menu-related methods - /// are called. + /// How the backend handles rendering of menu buttons. + /// + /// This affects which menu-related methods are called. var menuImplementationStyle: MenuImplementationStyle { get } - /// The class of device that the backend is currently running on. Used to - /// determine text sizing and other adaptive properties. + /// The class of device that the backend is currently running on. + /// + /// This is used to determine text sizing and other adaptive properties. var deviceClass: DeviceClass { get } /// Whether the backend can reveal files in the system file manager or not. /// Mobile backends generally can't. var canRevealFiles: Bool { get } + /// Runs the backend's main run loop. + /// + /// The app will exit when this method returns. This wall always be the + /// first method called by SwiftCrossUI. + /// /// Often in UI frameworks (such as Gtk), code is run in a callback /// after starting the app, and hence this generic root window creation /// API must reflect that. This is always the first method to be called /// and is where boilerplate app setup should happen. /// - /// Runs the backend's main run loop. The app will exit when this method - /// returns. This wall always be the first method called by SwiftCrossUI. - /// /// The callback is where SwiftCrossUI will create windows, render /// initial views, start state handlers, etc. The setup action must be /// run exactly once. The backend must be fully functional before the /// callback is ready. /// /// It is up to the backend to decide whether the callback runs before or - /// after the main loop starts. For example, some backends such as the `AppKit` - /// backend can create windows and widgets before the run loop starts, so it - /// makes the most sense to run the setup before the main run loop starts (it's - /// also not possible to run the setup function once the main run loop starts - /// anyway). On the other side is the `Gtk` backend which must be on the - /// main loop to create windows and widgets (because otherwise the root - /// window has not yet been created, which is essential in Gtk), so the - /// setup function is passed to `Gtk` as a callback to run once the main - /// run loop starts. + /// after the main loop starts. For example, some backends (such as + /// `AppKitBackend`) can create windows and widgets before the run loop + /// starts, so it makes the most sense to run the setup before the main run + /// loop starts (it's also not possible to run the setup function once the + /// main run loop starts anyway). On the other side is `GtkBackend` which + /// must be on the main loop to create windows and widgets (because + /// otherwise the root window has not yet been created, which is essential + /// in Gtk), so the setup function is passed to `Gtk` as a callback to run + /// once the main run loop starts. + /// + /// - Parameter callback: The callback to run. func runMainLoop( _ callback: @escaping @MainActor () -> Void ) - /// Creates a new window. For some backends it may make sense for this - /// method to return the application's root window the first time its - /// called, and only create new windows on subsequent invocations. + /// Creates a new window. /// - /// The default size is a suggestion; for example some backends may choose - /// to restore the user's preferred window size from a previous session. + /// For some backends it may make sense for this method to return the + /// application's root window the first time its called, and only create new + /// windows on subsequent invocations. /// /// A window's content size has precendence over the default size. The /// window should always be at least the size of its content. + /// + /// - Parameter defaultSize: The default size of the window. This is only a + /// suggestion; for example some backends may choose to restore the user's + /// preferred window size from a previous session. + /// - Returns: The created window. func createWindow(withDefaultSize defaultSize: SIMD2?) -> Window /// Sets the title of a window. + /// + /// - Parameters: + /// - window: The window to set the title of. + /// - title: The new title. func setTitle(ofWindow window: Window, to title: String) - /// Sets the resizability of a window. Even if resizable, the window - /// shouldn't be allowed to become smaller than its content. + /// Sets the resizability of a window. + /// + /// Even if resizable, the window shouldn't be allowed to become smaller + /// than its content. + /// + /// - Parameters: + /// - window: The window to set the resizability of. + /// - resizable: Whether the window should be resizable. func setResizability(ofWindow window: Window, to resizable: Bool) - /// Sets the root child of a window (replaces the previous child if any). + /// Sets the root child of a window. + /// + /// This replaces the previous child if one exists. + /// + /// - Parameters: + /// - window: The window to set the root child of. + /// - child: The new root child. func setChild(ofWindow window: Window, to child: Widget) /// Gets the size of the given window in pixels. + /// + /// - Parameter window: The window to get the size of. + /// - Returns: The window's size in pixels. func size(ofWindow window: Window) -> SIMD2 - /// Check whether a window is programmatically resizable. This value does not necessarily - /// reflect whether the window is resizable by the user. + /// Check whether a window is programmatically resizable. + /// + /// This value does not necessarily reflect whether the window is resizable + /// by the user. + /// + /// - Parameter window: The window to check. + /// - Returns: Whether the window is programmatically resizable. func isWindowProgrammaticallyResizable(_ window: Window) -> Bool - /// Sets the size of the given window in pixels. + /// Sets the size (in pixels) of the given window. + /// + /// - Parameters: + /// - window: The window to set the size of. + /// - newSize: The new size. func setSize(ofWindow window: Window, to newSize: SIMD2) - /// Sets the minimum width and height of the window. Prevents the user from making the - /// window any smaller than the given size. + /// Sets the minimum width and height of the window. + /// + /// Prevents the user from making the window any smaller than the given + /// size. + /// + /// - Parameters: + /// - window: The window to set the minimum size of. + /// - minimumSize: The new minimum size. func setMinimumSize(ofWindow window: Window, to minimumSize: SIMD2) - /// Sets the handler for the window's resizing events. `action` takes the proposed size - /// of the window and returns the final size for the window (which allows SwiftCrossUI - /// to implement features such as dynamic minimum window sizes based off the content's - /// minimum size). Setting the resize handler overrides any previous handler. + /// Sets the handler for the window's resizing events. + /// + /// Setting the resize handler overrides any previous handler. + /// + /// - Parameters: + /// - window: The window to set the resize handler of. + /// - action: The new resize handler. Takes the window's proposed size and + /// returns its final size (which allows SwiftCrossUI to implement + /// features such as dynamic minimum window sizes based off the + /// content's minimum size). func setResizeHandler( ofWindow window: Window, to action: @escaping (_ newSize: SIMD2) -> Void ) /// Shows a window after it has been created or updated (may be unnecessary - /// for some backends). Predominantly used by window-based ``Scene`` - /// implementations after propagating updates. + /// for some backends). + /// + /// Predominantly used by window-based ``Scene`` implementations after + /// propagating updates. + /// + /// - Parameter window: The window to show. func show(window: Window) - /// Brings a window to the front if possible. Called when the window - /// receives an external URL or file to handle from the desktop environment. - /// May be used in other circumstances eventually. + /// Brings a window to the front if possible. + /// + /// Called when the window receives an external URL or file to handle from + /// the desktop environment. May be used in other circumstances eventually. + /// + /// - Parameter window: The window to activate. func activate(window: Window) - /// Sets the application's global menu. Some backends may make use of the host - /// platform's global menu bar (such as macOS's menu bar), and others may render their - /// own menu bar within the application. + /// Sets the application's global menu. + /// + /// Some backends may make use of the host platform's global menu bar + /// (such as macOS's menu bar), and others may render their own menu bar + /// within the application. + /// + /// - Parameter submenus: The submenus of the global menu. func setApplicationMenu(_ submenus: [ResolvedMenu.Submenu]) /// Runs an action in the app's main thread if required to perform UI updates - /// by the backend. Predominantly used by ``Publisher`` to publish changes to a thread - /// compatible with dispatching UI updates. Can be synchronous or asynchronous (for now). + /// by the backend. + /// + /// Predominantly used by ``Publisher`` to publish changes to a thread + /// compatible with dispatching UI updates. Can be synchronous or + /// asynchronous (for now). + /// + /// - Parameter action: The action to run in the main thread. nonisolated func runInMainThread(action: @escaping @MainActor () -> Void) - /// Computes the root environment for an app (e.g. by checking the system's current - /// theme). May fall back on the provided defaults where reasonable. + /// Computes the root environment for an app (e.g. by checking the system's + /// current theme). + /// + /// May fall back on the provided defaults where reasonable. + /// + /// - Parameter defaultEnvironment: The default environment. + /// - Returns: The computed root environment. func computeRootEnvironment(defaultEnvironment: EnvironmentValues) -> EnvironmentValues - /// Sets the handler to be notified when the root environment may have to get - /// recomputed. This is intended to only be called once. Calling it more than once - /// may or may not override the previous handler. + /// Sets the handler to be notified when the root environment may need + /// recomputation. + /// + /// This is intended to only be called once. Calling it more than once may + /// or may not override the previous handler. + /// + /// - Parameter action: The root environment change handler. func setRootEnvironmentChangeHandler(to action: @escaping () -> Void) /// Resolves the given text style to concrete font properties. /// /// This method doesn't take ``EnvironmentValues`` because its result - /// should be consistent when given the same text style twice. Font modifiers - /// take effect later in the font resolution process. + /// should be consistent when given the same text style twice. Font + /// modifiers take effect later in the font resolution process. /// /// A default implementation is provided. It uses the backend's reported /// device class and looks up the text style in a lookup table derived - /// from Apple's typography guidelines. See ``TextStyle/resolve(for:)``. + /// from Apple's typography guidelines. + /// + /// - SeeAlso: ``Font/TextStyle/resolve(for:)`` + /// + /// - Parameter textStyle: The unresolved text style. + /// - Returns: The resolved text style. func resolveTextStyle(_ textStyle: Font.TextStyle) -> Font.TextStyle.Resolved - /// Computes a window's environment based off the root environment. This may involve - /// updating ``EnvironmentValues/windowScaleFactor`` etc. + /// Computes a window's environment based off the root environment. + /// + /// This may involve updating ``EnvironmentValues/windowScaleFactor``, etc. + /// + /// - Parameters: + /// - window: The window to compute the environment for. + /// - rootEnvironment: The root environment. + /// - Returns: The computed window environment. func computeWindowEnvironment( window: Window, rootEnvironment: EnvironmentValues ) -> EnvironmentValues /// Sets the handler to be notified when the window's contribution to the - /// environment may have to be recomputed. Use this for things such as - /// updating a window's scale factor in the environment when the window - /// changes displays. + /// environment may have to be recomputed. + /// + /// Use this for things such as updating a window's scale factor in the + /// environment when the window changes displays. In the future this may be + /// useful for color space handling. /// - /// In future this may be useful for color space handling. + /// - Parameters: + /// - window: The window to set the environment change handler of. + /// - action: The window environment change handler. func setWindowEnvironmentChangeHandler( of window: Window, to action: @escaping () -> Void @@ -216,55 +316,116 @@ public protocol AppBackend: Sendable { /// Sets the handler for URLs directed to the application (e.g. URLs /// associated with a custom URL scheme). + /// + /// - Parameter action: The incoming URL handler. func setIncomingURLHandler(to action: @escaping (URL) -> Void) /// Opens an external URL in the system browser or app registered for the /// URL's protocol. + /// + /// - Parameter url: The URL to open. func openExternalURL(_ url: URL) throws - /// Reveals a file in the system's file manager. This opens - /// the file's enclosing directory and highlighting the file. + /// Reveals a file in the system's file manager. + /// + /// This typically opens the file's enclosing directory and highlights the + /// file. + /// + /// - Parameter url: The URL of the file to reveal. func revealFile(_ url: URL) throws - /// Shows a widget after it has been created or updated (may be unnecessary - /// for some backends). Predominantly used by ``ViewGraphNode`` after - /// propagating updates. + /// Shows a widget after it has been created or updated. + /// + /// May be unnecessary for some backends. Predominantly used by + /// ``ViewGraphNode`` after propagating updates. + /// + /// - Parameter widget: The widget to show. func show(widget: Widget) - /// Adds a short tag to a widget to assist during debugging if the backend supports - /// such a feature. Some backends may only apply tags under particular conditions - /// such as when being built in debug mode. + /// Adds a short tag to a widget to assist during debugging, if the backend supports + /// such a feature. + /// + /// Some backends may only apply tags under particular conditions such as + /// when being built in debug mode. + /// + /// - Parameters: + /// - widget: The widget to tag. + /// - tag: The tag. func tag(widget: Widget, as tag: String) // MARK: Containers - /// Creates a container in which children can be layed out by SwiftCrossUI using exact - /// pixel positions. + /// Creates a container in which children can be laid out by SwiftCrossUI + /// using exact pixel positions. + /// + /// - Returns: A container widget. func createContainer() -> Widget /// Removes all children of the given container. + /// + /// - Parameter container: The container to remove the children of. func removeAllChildren(of container: Widget) - /// Adds a child to a given container at an exact position. + /// Adds a child to a given container. + /// + /// - Parameters: + /// - child: The widget to add. + /// - container: The container to add the child to. func addChild(_ child: Widget, to container: Widget) /// Sets the position of the specified child in a container. - func setPosition(ofChildAt index: Int, in container: Widget, to position: SIMD2) - /// Removes a child widget from a container (if the child is a direct child of the container). + /// + /// - Parameters: + /// - index: The index of the child to position. + /// - container: The container holding the child. + /// - position: The position to set. + func setPosition( + ofChildAt index: Int, + in container: Widget, + to position: SIMD2 + ) + /// Removes a child widget from a container (if the child is a direct child + /// of the container). + /// + /// - Parameters: + /// - child: The child to remove. + /// - container: The container holding the child. func removeChild(_ child: Widget, from container: Widget) /// Creates a rectangular widget with configurable color. + /// + /// - Returns: A colorable rectangle. func createColorableRectangle() -> Widget /// Sets the color of a colorable rectangle. + /// + /// - Parameters: + /// - widget: The rectangle to set the color of. + /// - color: The new color. func setColor(ofColorableRectangle widget: Widget, to color: Color) /// Sets the corner radius of a widget (any widget). Should affect the view's border radius /// as well. + /// + /// - Parameters: + /// - widget: The widget to set the corner radius of. + /// - radius: The corner radius. func setCornerRadius(of widget: Widget, to radius: Int) - /// Gets the natural size of a given widget. E.g. the natural size of a button may be the size - /// of the label (without line wrapping) plus a bit of padding and a border. + /// Gets the natural size of a given widget. + /// + /// E.g. the natural size of a button may be the size of the label (without + /// line wrapping) plus a bit of padding and a border. + /// + /// - Parameter widget: The widget to get the natural size of. + /// - Returns: The natural size of `widget`. func naturalSize(of widget: Widget) -> SIMD2 /// Sets the size of a widget. + /// + /// - Parameters: + /// - widget: The widget to set the size of. + /// - size: The new size. func setSize(of widget: Widget, to size: SIMD2) /// Creates a scrollable single-child container wrapping the given widget. + /// + /// - Parameter child: The widget to wrap in a scroll container. + /// - Returns: A scroll container wrapping `child`. func createScrollContainer(for child: Widget) -> Widget /// Updates a scroll container with environment-specific values. /// @@ -272,13 +433,21 @@ public protocol AppBackend: Sendable { /// that affect the scroll view’s behavior, such as keyboard dismissal mode. /// /// - Parameters: - /// - scrollView: The scroll container widget previously created by `createScrollContainer(for:)`. - /// - environment: The current `EnvironmentValues` to apply. + /// - scrollView: The scroll container widget previously created by + /// ``createScrollContainer(for:)-96edi. + /// - environment: The current ``EnvironmentValues`` to apply. func updateScrollContainer( _ scrollView: Widget, environment: EnvironmentValues ) /// Sets the presence of scroll bars along each axis of a scroll container. + /// + /// - Parameters: + /// - scrollView: The scroll view. + /// - hasVerticalScrollBar: Whether the scroll view has a vertical scroll + /// bar. + /// - hasHorizontalScrollBar: Whether the scroll view has a horizontal + /// scroll bar. func setScrollBarPresence( ofScrollContainer scrollView: Widget, hasVerticalScrollBar: Bool, @@ -286,45 +455,92 @@ public protocol AppBackend: Sendable { ) /// Creates a list with selectable rows. + /// + /// - Returns: A list with selectable rows. func createSelectableListView() -> Widget /// Gets the amount of padding introduced by the backend around the content of - /// each row. Ideally backends should get rid of base padding so that SwiftCrossUI - /// can give developers more freedom, but this isn't always possible. + /// each row. + /// + /// Ideally backends should get rid of base padding so that SwiftCrossUI can + /// give developers more freedom, but this isn't always possible. + /// + /// - Parameter listView: The list view. + /// - Returns: An `EdgeInsets` instance describing the amount of base + /// padding around `listView`'s items. func baseItemPadding(ofSelectableListView listView: Widget) -> EdgeInsets - /// Gets the minimum size for rows in the list view. This doesn't necessarily have to - /// be just for hard requirements enforced by the backend, it can also just be an - /// idiomatic minimum size for the platform. + /// Gets the minimum size for rows in the list view. + /// + /// This doesn't necessarily have to be just for hard requirements enforced + /// by the backend, it can also just be an idiomatic minimum size for the + /// platform. + /// + /// - Parameter listView: The list view. + /// - Returns: The minimum size for rows in the list view. func minimumRowSize(ofSelectableListView listView: Widget) -> SIMD2 - /// Sets the items of a selectable list along with their heights. Row heights should - /// include base item padding (i.e. they should be the external height of the row rather - /// than the internal height). + /// Sets the items of a selectable list along with their heights. + /// + /// Row heights should include base item padding (i.e. they should be the + /// external height of the row rather than the internal height). + /// + /// - Parameters: + /// - listView: The list view. + /// - items: An array of widgets to add to `listView`. + /// - rowHeights: The row heights of `items`. func setItems( ofSelectableListView listView: Widget, to items: [Widget], withRowHeights rowHeights: [Int] ) /// Sets the action to perform when a user selects an item in the list. + /// + /// - Parameters: + /// - listView: The list view. + /// - action: The selection handler. Receives the selected item's index. func setSelectionHandler( forSelectableListView listView: Widget, to action: @escaping (_ selectedIndex: Int) -> Void ) /// Sets the list's selected item by index. - func setSelectedItem(ofSelectableListView listView: Widget, toItemAt index: Int?) + /// + /// - Parameters: + /// - listView: The list view. + /// - index: The index of the item to select. + func setSelectedItem( + ofSelectableListView listView: Widget, + toItemAt index: Int? + ) /// Creates a split view containing two children visible side by side. /// - /// If you need to modify the leading and trailing children after creation nest them - /// inside another container such as a VStack (avoiding update methods makes maintaining - /// a multitude of backends a bit easier). + /// If you need to modify the leading and trailing children after creation, + /// nest them inside another container such as a ``VStack`` (avoiding update + /// methods makes maintaining a multitude of backends a bit easier). + /// + /// - Parameters: + /// - leadingChild: The widget to show in the sidebar. + /// - trailingChild: The widget to show in the detail section. func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget /// Sets the function to be called when the split view's panes get resized. + /// + /// - Parameters: + /// - splitView: The split view. + /// - action: The action to perform when the split view's panes are + /// resized. func setResizeHandler( ofSplitView splitView: Widget, to action: @escaping () -> Void ) /// Gets the width of a split view's sidebar. + /// + /// - Parameter splitView: The split view. + /// - Returns: The split view's sidebar width. func sidebarWidth(ofSplitView splitView: Widget) -> Int /// Sets the minimum and maximum width of a split view's sidebar. + /// + /// - Parameters: + /// - splitView: The split view. + /// - minimumWidth: The minimum width of the split view's sidebar. + /// - maximumWidth: The maximum width of the split view's sidebar. func setSidebarWidthBounds( ofSplitView splitView: Widget, minimum minimumWidth: Int, @@ -333,22 +549,32 @@ public protocol AppBackend: Sendable { // MARK: Passive views - /// Gets the size that the given text would have if it were layed out attempting to stay - /// within the proposed frame. The given text should be truncated/ellipsized to fit within - /// the proposal if possible. + /// Gets the size that the given text would have if it were laid out while + /// attempting to stay within the proposed frame. /// - /// The size returned by this function will be upheld by the layout - /// system; child views always get the final say on their own size, parents just choose how - /// the children get layed out. The text should get truncated to fit within the proposal if - /// possible. + /// The size returned by this function will be upheld by the layout system; + /// child views always get the final say on their own size, parents just + /// choose how the children get laid out. The given text should be + /// truncated/ellipsized to fit within the proposal if possible. /// - /// SwiftCrossUI will never supply zero as the proposed width or height, because some UI - /// frameworks handle that in special ways. + /// SwiftCrossUI will never supply zero as the proposed width or height, + /// because some UI frameworks handle that in special ways. /// - /// The target widget is supplied because some backends (such as Gtk) require a - /// reference to the target widget to get a text layout context. + /// Most backends only use the proposed width and ignore the proposed height. /// - /// Used by both ``SwiftCrossUI/Text`` and ``SwiftCrossUI/TextEditor``. + /// Used by both ``Text`` and ``TextEditor``. + /// + /// - Parameters: + /// - text: The text to get the size of. + /// - widget: The target widget. Some backends (such as GTK) require a + /// reference to the target widget to get a text layout context. + /// - proposedWidth: The proposed width of the text. If `nil`, the text + /// should be laid out on a single line taking up as much width as it + /// needs. + /// - proposedHeight: The proposed height of the text. + /// - environment: The current environment. + /// - Returns: The size of `text` if it were laid out while attempting to + /// stay within `proposedFrame`. func size( of text: String, whenDisplayedIn widget: Widget, @@ -357,37 +583,56 @@ public protocol AppBackend: Sendable { environment: EnvironmentValues ) -> SIMD2 - /// Creates a non-editable text view with optional text wrapping. Predominantly used - /// by ``Text``. + /// Creates a non-editable text view with optional text wrapping. + /// + /// Predominantly used by ``Text``. + /// + /// The returned widget should truncate and ellipsize its content when + /// given a size which isn't big enough to fit the full content, as per + /// ``size(of:whenDisplayedIn:proposedWidth:proposedHeight:environment:)-757xh``. /// - /// The returned widget should truncate and ellipsize its content when given a size - /// which isn't big enough to fit the full content, as per - /// ``size(of:whenDisplayedIn:proposedWidth:proposedHeight:environment)``. + /// - Returns: A text view. func createTextView() -> Widget /// Sets the content and wrapping mode of a non-editable text view. - func updateTextView(_ textView: Widget, content: String, environment: EnvironmentValues) + /// + /// - Parameters: + /// - textView: The text view. + /// - content: The text view's content. + /// - environment: The current environment. + func updateTextView( + _ textView: Widget, + content: String, + environment: EnvironmentValues + ) - /// Creates an image view from an image file (specified by path). Predominantly used - /// by ``Image``. + /// Creates an image view from an image file (specified by path). + /// + /// Predominantly used by ``Image``. + /// + /// - Returns: An image view. func createImageView() -> Widget /// Sets the image data to be displayed. + /// /// - Parameters: /// - imageView: The image view to update. - /// - rgbaData: The pixel data (as rows of pixels concatenated into a flat array). - /// - width: The width of the image in pixels (should only be used to interpret `rgbaData`, not - /// to influence the size of the image on-screen). - /// - height: The height of the image in pixels (should only be used to interpret `rgbaData`, not - /// to influence the size of the image on-screen). - /// - targetWidth: The width that the image must have on-screen. Guaranteed to match - /// the width the widget will be given, so backends that don't have to manually - /// scale the underlying pixel data can safely ignore this parameter. - /// - targetHeight: the height that the image must have on-screen. Guaranteed to match - /// the height the widget will be given, so backends that don't have to manually - /// scale the underlying pixel data can safely ignore this parameter. - /// - dataHasChanged: If `false`, then `rgbaData` hasn't changed since the last call, - /// so backends that don't have to manually resize the image data don't have to do - /// anything. - /// - environment: The environment of the image view. + /// - rgbaData: The pixel data, as rows of pixels concatenated into a + /// flat array. + /// - width: The width of the image in pixels. Should only be used to + /// interpret `rgbaData`, _not_ to set the size of the image on-screen. + /// - height: The height of the image in pixels. Should only be used to + /// interpret `rgbaData`, _not_ to set the size of the image on-screen. + /// - targetWidth: The width that the image must have on-screen. + /// Guaranteed to match the width the widget will be given, so backends + /// that don't have to manually scale the underlying pixel data can + /// safely ignore this parameter. + /// - targetHeight: The height that the image must have on-screen. + /// Guaranteed to match the height the widget will be given, so backends + /// that don't have to manually scale the underlying pixel data can + /// safely ignore this parameter. + /// - dataHasChanged: If `false`, then `rgbaData` hasn't changed since the + /// last call, so backends that don't have to manually resize the image + /// data don't have to do anything. + /// - environment: The current environment. func updateImageView( _ imageView: Widget, rgbaData: [UInt8], @@ -400,33 +645,77 @@ public protocol AppBackend: Sendable { ) /// Creates an empty table. + /// + /// - Returns: A table. func createTable() -> Widget - /// Sets the number of rows of a table. Existing rows outside of the new bounds should - /// be deleted. + /// Sets the number of rows of a table. + /// + /// Existing rows outside of the new bounds should be deleted. + /// + /// - Parameters: + /// - table: The table to set the row count of. + /// - rows: The number of rows. func setRowCount(ofTable table: Widget, to rows: Int) - /// Sets the labels of a table's columns. Also sets the number of columns of the table to the - /// number of labels provided. - func setColumnLabels(ofTable table: Widget, to labels: [String], environment: EnvironmentValues) - /// Sets the contents of the table as a flat array of cells in order of and grouped by row. Also - /// sets the height of each row's content. + /// Sets the labels of a table's columns. Also sets the number of columns of + /// the table to the number of labels provided. /// - /// A nested array would have significantly more overhead, especially for large arrays. - func setCells(ofTable table: Widget, to cells: [Widget], withRowHeights rowHeights: [Int]) + /// - Parameters: + /// - table: The table. + /// - labels: The column labels to set. + /// - environment: The current environment. + func setColumnLabels( + ofTable table: Widget, + to labels: [String], + environment: EnvironmentValues + ) + /// Sets the contents of the table as a flat array of cells in order of and + /// grouped by row. Also sets the height of each row's content. + /// + /// A nested array would have significantly more overhead, especially for + /// large arrays. + /// + /// - Parameters: + /// - table: The table. + /// - cells: The widgets to fill the table with. + /// - rowHeights: The heights of the table's rows. + func setCells( + ofTable table: Widget, + to cells: [Widget], + withRowHeights rowHeights: [Int] + ) // MARK: Controls - /// Creates a labelled button with an action triggered on click. Predominantly used - /// by ``Button``. + /// Creates a labelled button with an action triggered on click/tap. + /// + /// Predominantly used by ``Button``. + /// + /// - Returns: A button. func createButton() -> Widget - /// Sets a button's label and action. The action replaces any existing actions. + /// Sets a button's label and action. + /// + /// - Parameters: + /// - button: The button to update. + /// - label: The button's label. + /// - environment: The current environment. + /// - action: The action to perform when the button is clicked/tapped. + /// This replaces any existing actions. func updateButton( _ button: Widget, label: String, environment: EnvironmentValues, action: @escaping () -> Void ) - /// Sets a button's label and menu. Only used when ``menuImplementationStyle`` is + /// Sets a button's label and menu. + /// + /// Only used when ``menuImplementationStyle`` is /// ``MenuImplementationStyle/menuButton``. + /// + /// - Parameters: + /// - button: The button to update. + /// - label: The button's label. + /// - menu: The menu to show when the button is clicked/tapped. + /// - environment: The current environment. func updateButton( _ button: Widget, label: String, @@ -435,48 +724,90 @@ public protocol AppBackend: Sendable { ) /// Creates a labelled toggle that is either on or off. + /// + /// - Returns: A toggle. func createToggle() -> Widget - /// Sets the label and change handler of a toggle (replaces any existing change handlers). - /// The change handler is called whenever the button is toggled on or off. + /// Sets the label and change handler of a toggle. + /// + /// - Parameters: + /// - toggle: The toggle to update. + /// - label: The toggle's label. + /// - environment: The current environment. + /// - onChange: The action to perform when the button is toggled on or + /// off. This replaces any existing change handlers. func updateToggle( _ toggle: Widget, label: String, environment: EnvironmentValues, onChange: @escaping (Bool) -> Void ) - /// Sets the state of the button to active or not. + /// Sets the state of a toggle. + /// + /// - Parameters: + /// - toggle: The toggle to set the state of. + /// - state: The new state. func setState(ofToggle toggle: Widget, to state: Bool) /// Creates a switch that is either on or off. + /// + /// - Returns: A switch. func createSwitch() -> Widget - /// Sets the change handler of a switch (replaces any existing change handlers). - /// The change handler is called whenever the switch is toggled on or off. + /// Sets the change handler of a switch. + /// + /// - Parameters: + /// - switchWidget: The switch to update. + /// - environment: The current environment. + /// - onChange: The action to perform when the switch is toggled on or + /// off. This replaces any existing change handlers. func updateSwitch( _ switchWidget: Widget, environment: EnvironmentValues, onChange: @escaping (Bool) -> Void ) - /// Sets the state of the switch to active or not. + /// Sets the state of a switch. + /// + /// - Parameters: + /// - switchWidget: The switch to set the state of. + /// - state: The new state. func setState(ofSwitch switchWidget: Widget, to state: Bool) /// Creates a checkbox that is either on or off. + /// + /// - Returns: A checkbox. func createCheckbox() -> Widget - /// Sets the change handler of a checkbox (replaces any existing change handlers). - /// The change handler is called whenever the checkbox is toggled on or off. + /// Sets the change handler of a checkbox. + /// + /// - Parameters: + /// - checkboxWidget: The checkbox to update. + /// - environment: The current environment. + /// - onChange: The action to perform when the checkbox is toggled on or + /// off. This replaces any existing change handlers. func updateCheckbox( _ checkboxWidget: Widget, environment: EnvironmentValues, onChange: @escaping (Bool) -> Void ) - /// Sets the state of the checkbox to active or not. + /// Sets the state of a checkbox. + /// + /// - Parameters: + /// - checkboxWidget: The checkbox to set the state of. + /// - state: The new state. func setState(ofCheckbox checkboxWidget: Widget, to state: Bool) /// Creates a slider for choosing a numerical value from a range. Predominantly used /// by ``Slider``. func createSlider() -> Widget - /// Sets the minimum and maximum selectable value of a slider (inclusive), the number of decimal - /// places displayed by the slider, and the slider's change handler (replaces any existing - /// change handlers). + /// Sets the minimum and maximum selectable value of a slider, the number of + /// decimal places displayed by the slider, and the slider's change handler. + /// + /// - Parameters: + /// - slider: The slider to update. + /// - minimum: The minimum selectable value of the slider (inclusive). + /// - maximum: The maximum selectable value of the slider (inclusive). + /// - decimalPlaces: The number of decimal places displayed by the slider. + /// - environment: The current environment. + /// - onChange: The action to perform when the slider's value changes. + /// This replaces any existing change handlers. func updateSlider( _ slider: Widget, minimum: Double, @@ -486,19 +817,30 @@ public protocol AppBackend: Sendable { onChange: @escaping (Double) -> Void ) /// Sets the selected value of a slider. + /// + /// - Parameters: + /// - slider: The slider to set the value of. + /// - value: The new value. func setValue(ofSlider slider: Widget, to value: Double) - /// Creates an editable text field with a placeholder label and change handler. The - /// change handler is called whenever the displayed value changes. Predominantly - /// used by ``TextField``. + /// Creates an editable text field with a placeholder label and change + /// handler. + /// + /// Predominantly used by ``TextField``. + /// + /// - Returns: A text field. func createTextField() -> Widget - /// Sets the placeholder label and change handler of an editable text field. The new - /// change handler replaces any existing change handlers, and is called whenever the - /// displayed value changes. `onSubmit` gets called when the user hits Enter/Return, - /// or whatever the backend decides counts as submission of the field. + /// Sets the placeholder label and change handler of an editable text field. /// - /// The backend shouldn't wait until the user finishes typing to call the change handler; - /// it should allow live access to the value. + /// - Parameters: + /// - textField: The text field to update. + /// - placeholder: The text field's placeholder label. + /// - environment: The current environment. + /// - onChange: The action to perform when the text field's content + /// changes. This replaces any existing change handlers, and is called + /// whenever the displayed value changes. + /// - onSubmit: The action to perform when the user hits Enter/Return, + /// or whatever the backend decides counts as submission of the field. func updateTextField( _ textField: Widget, placeholder: String, @@ -507,37 +849,73 @@ public protocol AppBackend: Sendable { onSubmit: @escaping () -> Void ) /// Sets the value of an editable text field. + /// + /// - Parameters: + /// - textField: The text field to set the content of. + /// - content: The new content. func setContent(ofTextField textField: Widget, to content: String) /// Gets the value of an editable text field. + /// + /// - Parameter textField: The text field to get the content of. + /// - Returns: `textField`'s content. func getContent(ofTextField textField: Widget) -> String /// Creates an editable multi-line text editor with a placeholder label and change - /// handler. The change handler is called whenever the displayed value changes. + /// handler. + /// /// Predominantly used by ``TextEditor``. + /// + /// - Returns: A text editor. func createTextEditor() -> Widget - /// Sets the placeholder label and change handler of an editable multi-line text editor. - /// The new change handler replaces any existing change handlers, and is called - /// whenever the displayed value changes. + /// Sets the placeholder label and change handler of an editable multi-line + /// text editor. + /// + /// The backend shouldn't wait until the user finishes typing to call the + /// change handler; it should allow live access to the value. /// - /// The backend shouldn't wait until the user finishes typing to call the change - /// handler; it should allow live access to the value. + /// - Parameters: + /// - textEditor: The text editor to update. + /// - environment: The current environment. + /// - onChange: The action to perform when the text editor's content + /// changes. This replaces any existing change handlers, and is called + /// whenever the displayed value changes. func updateTextEditor( _ textEditor: Widget, environment: EnvironmentValues, onChange: @escaping (String) -> Void ) /// Sets the value of an editable multi-line text editor. + /// + /// - Parameters: + /// - textEditor: The text editor to set the content of. + /// - content: The new content. func setContent(ofTextEditor textEditor: Widget, to content: String) /// Gets the value of an editable multi-line text editor. + /// + /// - Parameter textEditor: The text editor to get the content of. + /// - Returns: `textEditor`'s content. func getContent(ofTextEditor textEditor: Widget) -> String /// Creates a picker for selecting from a finite set of options (e.g. a radio button group, - /// a drop-down, a picker wheel). Predominantly used by ``Picker``. The change handler is - /// called whenever a selection is made (even if the same option is picked again). + /// a drop-down, a picker wheel). + /// + /// Predominantly used by ``Picker``. + /// + /// - Returns: A picker. func createPicker() -> Widget /// Sets the options for a picker to display, along with a change handler for when its - /// selected option changes. The change handler replaces any existing change handlers and - /// is called whenever a selection is made (even if the same option is picked again). + /// selected option changes. + /// + /// The change handler + /// + /// - Parameters: + /// - picker: The picker to update. + /// - options: The picker's options. + /// - environment: The current environment. + /// - onChange: The action to perform when the selected option changes. + /// This handler replaces any existing change handlers and is called + /// whenever a selection is made, even if the same option is picked + /// again. func updatePicker( _ picker: Widget, options: [String], @@ -545,17 +923,30 @@ public protocol AppBackend: Sendable { onChange: @escaping (Int?) -> Void ) /// Sets the index of the selected option of a picker. + /// + /// - Parameters: + /// - picker: The picker. + /// - selectedOption: The index of the option to select. If `nil`, all + /// options should be deselected. func setSelectedOption(ofPicker picker: Widget, to selectedOption: Int?) /// Creates an indeterminate progress spinner. + /// + /// - Returns: A progress spinner. func createProgressSpinner() -> Widget /// Creates a progress bar. + /// + /// - Returns: A progress bar. func createProgressBar() -> Widget - /// Updates a progress bar to reflect the given progress (between 0 and 1), and the - /// current view environment. - /// - Parameter progressFraction: If `nil`, then the bar should show an indeterminate - /// animation if possible. + /// Updates a progress bar to reflect the given progress (between 0 and 1), + /// and the current view environment. + /// + /// - Parameters: + /// - widget: The progress bar to update. + /// - progressFraction: The current progress. If `nil`, then the bar + /// should show an indeterminate animation if possible. + /// - environment: The current environment. func updateProgressBar( _ widget: Widget, progressFraction: Double?, @@ -563,16 +954,33 @@ public protocol AppBackend: Sendable { ) /// Creates a popover menu (the sort you often see when right clicking on - /// apps). The menu won't be visible when first created. + /// apps). + /// + /// The menu won't be visible when first created. + /// + /// - Returns: A popover menu. func createPopoverMenu() -> Menu /// Updates a popover menu's content and appearance. + /// + /// - Parameters: + /// - menu: The menu to update. + /// - content: The menu content. + /// - environment: The current environment. func updatePopoverMenu( _ menu: Menu, content: ResolvedMenu, environment: EnvironmentValues ) - /// Shows the popover menu at a position relative to the given widget. Only used when - /// ``menuImplementationStyle`` is ``MenuImplementationStyle/dynamicPopover``. + /// Shows the popover menu at a position relative to the given widget. + /// + /// Only used when ``menuImplementationStyle`` is + /// ``MenuImplementationStyle/dynamicPopover``. + /// + /// - Parameters: + /// - menu: The menu to show. + /// - position: The position to show the menu at, relative to `widget`. + /// - widget: The widget to attach `menu` to. + /// - handleClose: The action performed when the menu is closed. func showPopoverMenu( _ menu: Menu, at position: SIMD2, @@ -580,11 +988,23 @@ public protocol AppBackend: Sendable { closeHandler handleClose: @escaping () -> Void ) - /// Creates an alert object (without showing it yet). Alerts contain a - /// title, an optional body, and a set of action buttons. They also - /// prevent users from interacting with the parent window until dimissed. + /// Creates an alert object (without showing it). + /// + /// Alerts contain a title, an optional body, and a set of action buttons. + /// They prevent users from interacting with the parent window until + /// dimissed. + /// + /// - Returns: An alert. func createAlert() -> Alert - /// Updates the content and appearance of an alert. Can only be called once. + /// Updates the content and appearance of an alert. + /// + /// Can only be called once. + /// + /// - Parameters: + /// - alert: The alert to update. + /// - title: The title of the alert. + /// - actionLabels: The labels of the alert's action buttons. + /// - environment: The current environment. func updateAlert( _ alert: Alert, title: String, @@ -592,60 +1012,80 @@ public protocol AppBackend: Sendable { environment: EnvironmentValues ) /// Shows an alert as a modal on top of or within the given window. + /// /// Users should be unable to interact with the parent window until the - /// alert gets dismissed. `handleResponse` must get called once an action - /// button is selected, with its sole argument representing the index of - /// the action selected (as per the `actionLabels` array). The alert will - /// have been hidden by the time the response handler gets called. + /// alert is dismissed. /// - /// Must only get called once for any given alert. + /// Must only be called once for any given alert. /// - /// If `window` is `nil`, the backend can either make the alert a whole - /// app modal, a standalone window, or a modal for a window of its choosing. + /// - Parameters: + /// - alert: The alert to show. + /// - window: The window to attach the alert to. If `nil`, the backend can + /// either make the alert a whole app modal, a standalone window, or a + /// modal for a window of its choosing. + /// - handleResponse: The code to run when an action is selected. Receives + /// the index of the chosen action (as per the `actionLabels` array). + /// The alert will have already been hidden by the time this gets + /// called. func showAlert( _ alert: Alert, window: Window?, responseHandler handleResponse: @escaping (Int) -> Void ) /// Dismisses an alert programmatically without invoking the response - /// handler. Must only be called after - /// ``showAlert(_:window:responseHandler:)``. + /// handler. + /// + /// Must only be called after ``showAlert(_:window:responseHandler:)-9iu82``. + /// + /// - Parameters: + /// - alert: The alert to dismiss. + /// - window: The window the alert is attached to, if any. func dismissAlert(_ alert: Alert, window: Window?) - /// Creates a sheet object (without showing it yet). Sheets contain view content. - /// They optionally execute provided code on dismiss and - /// prevent users from interacting with the parent window until dimissed. + /// Creates a sheet object (without showing it). + /// + /// Sheets contain view content. They prevent users from interacting with + /// the parent window until dimissed and can optionally execute a callback + /// on dismiss. + /// + /// - Parameter content: The content of the sheet. + /// - Returns: A sheet containing `content`. func createSheet(content: Widget) -> Sheet /// Updates the content, appearance and behaviour of a sheet. + /// /// - Parameters: /// - sheet: The sheet to update. /// - window: The root window that the sheet will be presented in. Used on - /// platforms such as tvOS to compute layout constraints. The sheet - /// shouldn't get attached to the window by updateSheet. That is handled - /// by presentSheet which is guaranteed to be called exactly once (unlike - /// updateSheet which gets called whenever preferences or sizing change). - /// - environment: The environment that the sheet will be presented in. This - /// differs from the environment passed to the sheet's content. + /// platforms such as tvOS to compute layout constraints. + /// + /// The sheet shouldn't be attached to the window by `updateSheet`. That + /// is handled by ``presentSheet(_:window:parentSheet:)-5w3ko`` which is + /// guaranteed to be called exactly once (unlike `updateSheet` which + /// gets called whenever preferences or sizing change). + /// - environment: The environment that the sheet will be presented in. + /// This differs from the environment passed to the sheet's content. + /// - size: The size of the sheet. /// - onDismiss: An action to perform when the sheet gets dismissed by - /// the user. Not triggered by programmatic dismissals. But is triggered - /// by the implicit dismissals of nested sheets when their parent sheet - /// is programmatically dismissed. + /// the user. Not triggered by programmatic dismissals, but _is_ + /// triggered by the implicit dismissals of nested sheets when their + /// parent sheet is programmatically dismissed. /// - cornerRadius: The radius of the sheet. If `nil`, the platform - /// default should be used. Not all backends can support this (e.g. macOS - /// doesn't support custom window corner radii). + /// default should be used. Not all backends can support this (e.g. + /// macOS doesn't support custom window corner radii). /// - detents: An array of sizes that the sheet should snap to. This is /// generally only a thing on mobile where sheets can be dragged up /// and down. /// - dragIndicatorVisibility: Whether the drag indicator should be shown. /// Sheet drag indicators are generally only a thing on mobile, and /// usually appear as a small horizontal bar at the top of the sheet. - /// - backgroundColor: The background color to use for the sheet. If `nil`, - /// the platform's default sheet background style should be used. - /// - interactiveDismissDisabled: Whether to dismiss user-driven sheet - /// dismissal. On mobile this disables swiping to dismiss a sheet, and on - /// desktop this usually disabled dismissal shortcuts such as the escape - /// key and/or removes system-provided close/cancel buttons from the sheet. + /// - backgroundColor: The background color to use for the sheet. If + /// `nil`, the platform's default sheet background style should be used. + /// - interactiveDismissDisabled: Whether to disable user-driven sheet + /// dismissal. On mobile this disables swiping to dismiss a sheet, and + /// on desktop this usually disables dismissal shortcuts such as the + /// escape key and/or removes system-provided close/cancel buttons from + /// the sheet. func updateSheet( _ sheet: Sheet, window: Window, @@ -659,9 +1099,10 @@ public protocol AppBackend: Sendable { interactiveDismissDisabled: Bool ) - /// Presents a sheet as a modal on top of or within the given window. Sheets - /// should disable interaction with all content below them until they get - /// dismissed. + /// Presents a sheet as a modal on top of or within the given window. + /// + /// Sheets should disable interaction with all content below them until they + /// get dismissed. /// /// `onDismiss` only gets called once the sheet has been closed. /// @@ -670,15 +1111,18 @@ public protocol AppBackend: Sendable { /// - Parameters: /// - sheet: The sheet to present. /// - window: The window to present the sheet on top of. - /// - parentSheet: The sheet that the current sheet was presented from, if any. + /// - parentSheet: The sheet that the current sheet was presented from, + /// if any. func presentSheet( _ sheet: Sheet, window: Window, parentSheet: Sheet? ) - /// Dismisses a sheet programmatically. Used by the ``View/sheet`` modifier - /// to close sheets. + /// Dismisses a sheet programmatically. + /// + /// Used by the ``View/sheet(isPresented:onDismiss:content:)`` modifier to + /// close sheets. /// /// - Parameters: /// - sheet: The sheet to dismiss. @@ -686,14 +1130,24 @@ public protocol AppBackend: Sendable { /// - parentSheet: The sheet that presented the current sheet, if any. func dismissSheet(_ sheet: Sheet, window: Window, parentSheet: Sheet?) - /// Get the dimensions of a sheet + /// Get the size of a sheet. + /// + /// - Parameter sheet: The sheet to get the size of. + /// - Returns: The sheet's size. func size(ofSheet sheet: Sheet) -> SIMD2 /// Presents an 'Open file' dialog to the user for selecting files or /// folders. /// - /// If `window` is `nil`, the backend can either make the dialog a whole - /// app modal, a standalone window, or a modal for a window of its choosing. + /// - Parameters: + /// - fileDialogOptions: The general options for the file dialog. + /// - openDialogOptions: The options specific to the open dialog. + /// - window: The window to attach the dialog to. If `nil`, the backend + /// can either make the dialog a whole app modal, a standalone window, + /// or a modal for a window of its choosing. + /// - handleResult: The action to perform when the user chooses an item + /// (or multiple items) or cancels the dialog. Receives a + /// `DialogResult<[URL]>`. func showOpenDialog( fileDialogOptions: FileDialogOptions, openDialogOptions: OpenDialogOptions, @@ -704,8 +1158,14 @@ public protocol AppBackend: Sendable { /// Presents a 'Save file' dialog to the user for selecting a file save /// destination. /// - /// If `window` is `nil`, the backend can either make the dialog a whole - /// app modal, a standalone window, or a modal for a window of its choosing. + /// - Parameters: + /// - fileDialogOptions: The general options for the file dialog. + /// - saveDialogOptions: The options specific to the save dialog. + /// - window: The window to attach the dialog to. If `nil`, the backend + /// can either make the dialog a whole app modal, a standalone window, + /// or a modal for a window of its choosing. + /// - handleResult: The action to perform when the user chooses a + /// destination or cancels the dialog. Receives a `DialogResult`. func showSaveDialog( fileDialogOptions: FileDialogOptions, saveDialogOptions: SaveDialogOptions, @@ -713,12 +1173,25 @@ public protocol AppBackend: Sendable { resultHandler handleResult: @escaping (DialogResult) -> Void ) - /// Wraps a view in a container that can receive tap gestures. Some - /// backends may not have to wrap the child, in which case they may + /// Wraps a view in a container that can receive tap gesture events. + /// + /// Some backends may not have to wrap the child, in which case they may /// just return the child as is. + /// + /// - Parameters: + /// - child: The child to wrap. + /// - gesture: The gesture to listen for. + /// - Returns: A widget that can receive tap gesture events. func createTapGestureTarget(wrapping child: Widget, gesture: TapGesture) -> Widget - /// Update the tap gesture target with a new action. Replaces the old - /// action. + /// Update the tap gesture target with a new action. + /// + /// The new action replaces the old action. + /// + /// - Parameters: + /// - tapGestureTarget: The tap gesture target to update. + /// - gesture: The gesture to listen for. + /// - environment: The current environment. + /// - action: The action to perform when a tap gesture occurs. func updateTapGestureTarget( _ tapGestureTarget: Widget, gesture: TapGesture, @@ -726,12 +1199,23 @@ public protocol AppBackend: Sendable { action: @escaping () -> Void ) - /// Wraps a view in a container that can receive mouse hover events. Some - /// backends may not have to wrap the child, in which case they may - /// just return the child as is. + /// Wraps a view in a container that can receive mouse hover events. + /// + /// Some backends may not have to wrap the child, in which case they may + /// just return the child as-is. + /// + /// - Parameter child: The child to wrap. + /// - Returns: A widget that can receive mouse hover events. func createHoverTarget(wrapping child: Widget) -> Widget - /// Update the hover target with a new action. Replaces the old - /// action. + /// Update the hover target with a new action. + /// + /// The new action replaces the old action. + /// + /// - Parameters: + /// - hoverTarget: The hover target to update. + /// - environment: The current environment. + /// - action: The action to perform when the hover state changes. Receives + /// a `Bool` indicating whether the hover has started or stopped. func updateHoverTarget( _ hoverTarget: Widget, environment: EnvironmentValues, @@ -741,11 +1225,23 @@ public protocol AppBackend: Sendable { // MARK: Paths /// Create a widget that can contain a path. + /// + /// - Returns: A path widget. func createPathWidget() -> Widget - /// Create a path. It will not be shown until ``renderPath(_:container:)`` is called. + /// Create a path. + /// + /// The path will not be shown until + /// ``renderPath(_:container:strokeColor:fillColor:overrideStrokeStyle:)-2qfb0`` + /// is called. + /// + /// - Returns: A path. func createPath() -> Path - /// Update a path. The updates do not need to be visible before ``renderPath(_:container:)`` + /// Update a path. + /// + /// The updates do not need to be visible before + /// ``renderPath(_:container:strokeColor:fillColor:overrideStrokeStyle:)-2qfb0`` /// is called. + /// /// - Parameters: /// - path: The path to be updated. /// - source: The source to copy the path from. @@ -763,13 +1259,14 @@ public protocol AppBackend: Sendable { environment: EnvironmentValues ) /// Draw a path to the screen. + /// /// - Parameters: /// - path: The path to be rendered. - /// - container: The container widget that the path will render in. Created with - /// ``createPathWidget()``. + /// - container: The container widget that the path will render in. + /// Created with ``createPathWidget()``. /// - strokeColor: The color to draw the path's stroke. /// - fillColor: The color to shade the path's fill. - /// - overrideStrokeStyle: If present, a value to override the path's stroke style. + /// - overrideStrokeStyle: A value to override the path's stroke style. func renderPath( _ path: Path, container: Widget, @@ -781,15 +1278,26 @@ public protocol AppBackend: Sendable { // MARK: Web view /// Create a web view. + /// + /// - Returns: A web view. func createWebView() -> Widget /// Update a web view to reflect the given environment and use the given /// navigation handler. + /// + /// - Parameters: + /// - webView: The web view. + /// - environment: The current environment. + /// - onNavigate: The action to perform when a navigation occurs. func updateWebView( _ webView: Widget, environment: EnvironmentValues, onNavigate: @escaping (URL) -> Void ) /// Navigates a web view to a given URL. + /// + /// - Parameters: + /// - webView: The web view. + /// - url: The URL to navigate `webView` to. func navigateWebView(_ webView: Widget, to url: URL) } diff --git a/Sources/SwiftCrossUI/Backend/CellPosition.swift b/Sources/SwiftCrossUI/Backend/CellPosition.swift index 3b5c856fae..eb8ebd1cc0 100644 --- a/Sources/SwiftCrossUI/Backend/CellPosition.swift +++ b/Sources/SwiftCrossUI/Backend/CellPosition.swift @@ -1,11 +1,15 @@ /// The position of a cell in a table (with row and column numbers starting from 0). public struct CellPosition { - /// The row number starting from 0. + /// The row number (starting from 0). public var row: Int - /// The column number starting from 0. + /// The column number (starting from 0). public var column: Int - /// Creates a cell position from a row and column number (starting from 0). + /// Creates a cell position from a row and column number. + /// + /// - Parameters: + /// - row: The row number (starting from 0). + /// - column: The column number (starting from 0). public init(_ row: Int, _ column: Int) { self.row = row self.column = column diff --git a/Sources/SwiftCrossUI/Backend/DialogResult.swift b/Sources/SwiftCrossUI/Backend/DialogResult.swift index 34fb86aced..a8dc25bab1 100644 --- a/Sources/SwiftCrossUI/Backend/DialogResult.swift +++ b/Sources/SwiftCrossUI/Backend/DialogResult.swift @@ -1,4 +1,7 @@ +/// The result of a dialog. public enum DialogResult { + /// The dialog succeeded with a result value. case success(T) + /// The dialog was cancelled. case cancelled } diff --git a/Sources/SwiftCrossUI/Backend/FileChooserResult.swift b/Sources/SwiftCrossUI/Backend/FileChooserResult.swift index f8aa299d5d..bdbd7819d3 100644 --- a/Sources/SwiftCrossUI/Backend/FileChooserResult.swift +++ b/Sources/SwiftCrossUI/Backend/FileChooserResult.swift @@ -3,6 +3,8 @@ import Foundation /// The result returned when a user either selects a file or dismisses the /// file chooser dialog. public enum FileChooserResult { + /// The user selected a file at the given URL. case success(URL) + /// The user closed the file chooser dialog. case cancelled } diff --git a/Sources/SwiftCrossUI/Backend/FileDialogOptions.swift b/Sources/SwiftCrossUI/Backend/FileDialogOptions.swift index 00c90e08ce..92d615ad95 100644 --- a/Sources/SwiftCrossUI/Backend/FileDialogOptions.swift +++ b/Sources/SwiftCrossUI/Backend/FileDialogOptions.swift @@ -1,10 +1,22 @@ import Foundation +/// Options for file dialogs. public struct FileDialogOptions { + /// The dialog title. public var title: String + /// The label of the primary button. public var defaultButtonLabel: String + /// The content types allowed by the dialog. public var allowedContentTypes: [ContentType] + /// Whether to show hidden files. + /// + /// The definition of "hidden" is platform-specific, but typically involves + /// a special metadata flag and/or a leading period in the file name. public var showHiddenFiles: Bool + /// Whether to allow selecting items that are not one of the types specified + /// in ``allowedContentTypes``. public var allowOtherContentTypes: Bool + /// The directory the file dialog starts in. If `nil`, the starting + /// directory is backend-specific. public var initialDirectory: URL? } diff --git a/Sources/SwiftCrossUI/Backend/MenuImplementationStyle.swift b/Sources/SwiftCrossUI/Backend/MenuImplementationStyle.swift index 7e4443d285..22f7634d15 100644 --- a/Sources/SwiftCrossUI/Backend/MenuImplementationStyle.swift +++ b/Sources/SwiftCrossUI/Backend/MenuImplementationStyle.swift @@ -1,18 +1,21 @@ /// How a backend implements popover menus. /// /// Regardless of implementation style, backends are expected to implement -/// ``AppBackend/createPopoverMenu()``, ``AppBackend/updatePopoverMenu(_:content:environment:)``, -/// and ``AppBackend/updateButton(_:label:action:environment:)``. +/// ``AppBackend/createPopoverMenu()-9qdz1``, +/// ``AppBackend/updatePopoverMenu(_:content:environment:)-6cws8``, and +/// ``AppBackend/updateButton(_:label:environment:action:)-2n3zk``. public enum MenuImplementationStyle { /// The backend can show popover menus arbitrarily. /// /// Backends that use this style must implement - /// ``AppBackend/showPopoverMenu(_:at:relativeTo:closeHandler:)``. For these backends, - /// ``AppBackend/createPopoverMenu()`` is not called until after the button is tapped. + /// ``AppBackend/showPopoverMenu(_:at:relativeTo:closeHandler:)-3p1ct``. For + /// these backends, ``AppBackend/createPopoverMenu()-9qdz1`` is not called + /// until after the button is tapped. case dynamicPopover - /// The backend requires menus to be constructed and attached to buttons ahead-of-time. + /// The backend requires menus to be constructed and attached to buttons + /// ahead of time. /// /// Backends that use this style must implement - /// ``AppBackend/updateButton(_:label:menu:environment:)``. + /// ``AppBackend/updateButton(_:label:menu:environment:)-17ypq``. case menuButton } diff --git a/Sources/SwiftCrossUI/Backend/OpenDialogOptions.swift b/Sources/SwiftCrossUI/Backend/OpenDialogOptions.swift index 6595f76ca1..8bcc364100 100644 --- a/Sources/SwiftCrossUI/Backend/OpenDialogOptions.swift +++ b/Sources/SwiftCrossUI/Backend/OpenDialogOptions.swift @@ -1,5 +1,10 @@ +/// Options for 'open file' dialogs. public struct OpenDialogOptions { + /// Whether to allow selecting files. public var allowSelectingFiles: Bool + /// Whether to allow selecting directories (folders). public var allowSelectingDirectories: Bool + /// Whether to allow multiple selections. If `false`, the user can only + /// select one item. public var allowMultipleSelections: Bool } diff --git a/Sources/SwiftCrossUI/Backend/ResolvedMenu.swift b/Sources/SwiftCrossUI/Backend/ResolvedMenu.swift index 36cb95474d..9f5966bf63 100644 --- a/Sources/SwiftCrossUI/Backend/ResolvedMenu.swift +++ b/Sources/SwiftCrossUI/Backend/ResolvedMenu.swift @@ -8,7 +8,9 @@ public struct ResolvedMenu { /// The menu's items. public var items: [Item] - /// Memberwise initializer. + /// Creates a ``ResolvedMenu`` instance. + /// + /// - Parameter items: The menu's items. public init(items: [ResolvedMenu.Item]) { self.items = items } @@ -28,7 +30,11 @@ public struct ResolvedMenu { /// The menu displayed when the submenu gets activated. public var content: ResolvedMenu - /// Memberwise initializer. + /// Creates a ``Submenu`` instance. + /// + /// - Parameters: + /// - label: The label of the submenu's entry in its parent menu. + /// - content: The menu displayed when the submenu gets activated. public init(label: String, content: ResolvedMenu) { self.label = label self.content = content diff --git a/Sources/SwiftCrossUI/Backend/SaveDialogOptions.swift b/Sources/SwiftCrossUI/Backend/SaveDialogOptions.swift index 910597e09f..16949ba99c 100644 --- a/Sources/SwiftCrossUI/Backend/SaveDialogOptions.swift +++ b/Sources/SwiftCrossUI/Backend/SaveDialogOptions.swift @@ -1,4 +1,9 @@ +/// Options for 'save file' dialogs. public struct SaveDialogOptions { + /// The placeholder label for the file name field. If `nil`, the label is + /// set to a backend-specific value. public var nameFieldLabel: String? + /// The default file name. If `nil`, the file name will typically be empty, + /// but can be any backend-specific value. public var defaultFileName: String? } diff --git a/Sources/SwiftCrossUI/Builders/AlertActionsBuilder.swift b/Sources/SwiftCrossUI/Builders/AlertActionsBuilder.swift index a78436c86c..4c70c32d9b 100644 --- a/Sources/SwiftCrossUI/Builders/AlertActionsBuilder.swift +++ b/Sources/SwiftCrossUI/Builders/AlertActionsBuilder.swift @@ -1,4 +1,4 @@ -/// A builder for `[AlertAction]`. +/// A result builder for `[AlertAction]`. @resultBuilder public struct AlertActionsBuilder { public static func buildPartialBlock(first: Button) -> [AlertAction] { diff --git a/Sources/SwiftCrossUI/Builders/CommandsBuilder.swift b/Sources/SwiftCrossUI/Builders/CommandsBuilder.swift index 355d97beab..60ad45e84e 100644 --- a/Sources/SwiftCrossUI/Builders/CommandsBuilder.swift +++ b/Sources/SwiftCrossUI/Builders/CommandsBuilder.swift @@ -1,4 +1,4 @@ -/// A builder for ``Commands``. +/// A result builder for ``Commands``. @resultBuilder public struct CommandsBuilder { public static func buildBlock(_ menus: CommandMenu...) -> Commands { diff --git a/Sources/SwiftCrossUI/Builders/MenuItemsBuilder.swift b/Sources/SwiftCrossUI/Builders/MenuItemsBuilder.swift index fc23e4f164..56e9583917 100644 --- a/Sources/SwiftCrossUI/Builders/MenuItemsBuilder.swift +++ b/Sources/SwiftCrossUI/Builders/MenuItemsBuilder.swift @@ -1,4 +1,4 @@ -/// A builder for ``[MenuItem]``. +/// A result builder for `[MenuItem]`. @resultBuilder public struct MenuItemsBuilder { public static func buildBlock() -> [MenuItem] { @@ -74,7 +74,7 @@ public struct MenuItemsBuilder { Block(items: component) } - /// An implementation detail of ``MenuItemBuilder``'s support for + /// An implementation detail of ``MenuItemsBuilder``'s support for /// `if`/`else if`/`else` blocks. public struct Block { var items: [MenuItem] diff --git a/Sources/SwiftCrossUI/Environment/Actions/AlertAction.swift b/Sources/SwiftCrossUI/Environment/Actions/AlertAction.swift index 3ad758ddb0..d86f62617f 100644 --- a/Sources/SwiftCrossUI/Environment/Actions/AlertAction.swift +++ b/Sources/SwiftCrossUI/Environment/Actions/AlertAction.swift @@ -1,12 +1,17 @@ -/// An alert action button. Only backends should interface with this type -/// directly. See ``View/alert(_:isPresented:actions:message:)``. +/// An alert action button. /// -/// This exists to avoid having to expose internal details of ``Button``, since -/// breaking ``Button``'s API would have much more wide-reaching impacts than -/// breaking this single-purpose API. +/// Only backends should interface with this type directly. This exists to avoid +/// having to expose internal details of ``Button``, since breaking `Button`'s +/// API would have much more wide-reaching impacts than breaking this +/// single-purpose API. +/// +/// # See Also +/// - ``View/alert(_:isPresented:actions:)`` public struct AlertAction: Sendable { public static let ok = AlertAction(label: "Ok", action: {}) + /// The button's label. public var label: String + /// The button's action. public var action: @MainActor @Sendable () -> Void } diff --git a/Sources/SwiftCrossUI/Environment/Actions/OpenURLAction.swift b/Sources/SwiftCrossUI/Environment/Actions/OpenURLAction.swift index 3efeab506b..54bbf9cdf0 100644 --- a/Sources/SwiftCrossUI/Environment/Actions/OpenURLAction.swift +++ b/Sources/SwiftCrossUI/Environment/Actions/OpenURLAction.swift @@ -1,7 +1,9 @@ import Foundation -/// Opens a URL with the default application. May present an application picker -/// if multiple applications are registered for the given URL protocol. +/// Opens a URL with the default application. +/// +/// May present an application picker if multiple applications are registered +/// for the given URL protocol. @MainActor public struct OpenURLAction { let action: (URL) -> Void @@ -19,6 +21,9 @@ public struct OpenURLAction { } } + /// Opens a URL with the default application. + /// + /// - Parameter url: The URL to open. public func callAsFunction(_ url: URL) { action(url) } diff --git a/Sources/SwiftCrossUI/Environment/Actions/PresentAlertAction.swift b/Sources/SwiftCrossUI/Environment/Actions/PresentAlertAction.swift index a045493766..7811ae9635 100644 --- a/Sources/SwiftCrossUI/Environment/Actions/PresentAlertAction.swift +++ b/Sources/SwiftCrossUI/Environment/Actions/PresentAlertAction.swift @@ -1,12 +1,18 @@ -/// Presents an alert to the user. Returns once an action has been selected and -/// the corresponding action handler has been run. Returns the index of the -/// selected action. By default, the alert will have a single button labelled -/// `OK`. All buttons will dismiss the alert even if you provide your own -/// actions. +/// Presents an alert to the user. +/// +/// Returns once an action has been selected and the corresponding action +/// handler has been run. Returns the index of the selected action. By default, +/// the alert will have a single button labelled "OK". All buttons will dismiss +/// the alert even if you provide your own actions. @MainActor public struct PresentAlertAction { let environment: EnvironmentValues + /// Presents an alert to the user. + /// + /// - Parameters: + /// - title: The title of the alert. + /// - actions: A list of actions the user can perform. @discardableResult public func callAsFunction( _ title: String, diff --git a/Sources/SwiftCrossUI/Environment/Actions/RevealFileAction.swift b/Sources/SwiftCrossUI/Environment/Actions/RevealFileAction.swift index 5f0efbbf8e..f68b40577d 100644 --- a/Sources/SwiftCrossUI/Environment/Actions/RevealFileAction.swift +++ b/Sources/SwiftCrossUI/Environment/Actions/RevealFileAction.swift @@ -1,7 +1,7 @@ import Foundation /// Reveals a file in the system's file manager. This opens -/// the file's enclosing directory and highlighting the file. +/// the file's enclosing directory and highlights the file. @MainActor public struct RevealFileAction { let action: (URL) -> Void diff --git a/Sources/SwiftCrossUI/Environment/Environment.swift b/Sources/SwiftCrossUI/Environment/Environment.swift index a7a42669d0..ca842b073a 100644 --- a/Sources/SwiftCrossUI/Environment/Environment.swift +++ b/Sources/SwiftCrossUI/Environment/Environment.swift @@ -1,8 +1,10 @@ -/// A property wrapper used to access environment values within a `View` or -/// `App`. Must not be used before the view graph accesses the view or app's -/// `body` (i.e. don't access it from an initializer). +/// A property wrapper used to access environment values within a ``View`` or +/// ``App``. /// -/// ``` +/// Must not be used before the view graph accesses the view or app's `body` +/// (so, don't access it from an initializer). +/// +/// ```swift /// struct ContentView: View { /// @Environment(\.colorScheme) var colorScheme /// @@ -16,7 +18,7 @@ /// The environment also contains UI-related actions, such as the /// ``EnvironmentValues/chooseFile`` action used to present 'Open file' dialogs. /// -/// ``` +/// ```swift /// struct ContentView: View { /// @Environment(\.chooseFile) var chooseFile /// @@ -36,7 +38,11 @@ /// ``` @propertyWrapper public struct Environment: DynamicProperty { + /// A key path to the enviornment value to access. var keyPath: KeyPath + /// The underlying value. + /// + /// `nil` if ``update(with:previousValue:)`` has not yet been called. var value: Box public func update( @@ -46,6 +52,7 @@ public struct Environment: DynamicProperty { value.value = environment[keyPath: keyPath] } + /// The environment value that this property refers to. public var wrappedValue: Value { guard let value = value.value else { fatalError( @@ -59,6 +66,9 @@ public struct Environment: DynamicProperty { return value } + /// Initializes an ``Environment`` property wrapper. + /// + /// - Parameter keyPath: A key path to the enviornment value to access. public init(_ keyPath: KeyPath) { self.keyPath = keyPath value = Box(value: nil) diff --git a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift index 8c055bb1b5..02557ce9d7 100644 --- a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift +++ b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift @@ -54,7 +54,7 @@ public struct EnvironmentValues { public var foregroundColor: Color? /// The suggested foreground color for backends to use. Backends don't - /// neccessarily have to obey this when ``Environment/foregroundColor`` + /// neccessarily have to obey this when ``EnvironmentValues/foregroundColor`` /// is `nil`. public var suggestedForegroundColor: Color { foregroundColor ?? colorScheme.defaultForegroundColor @@ -205,6 +205,8 @@ public struct EnvironmentValues { } /// Creates the default environment. + /// + /// - Parameter backend: The app's backend. package init(backend: Backend) { self.backend = backend @@ -231,6 +233,12 @@ public struct EnvironmentValues { /// Returns a copy of the environment with the specified property set to the /// provided new value. + /// + /// - Parameters: + /// - keyPath: A key path to the property to set. + /// - newValue: The new value of the property. + /// - Returns: A copy of the environment with the specified property set to + /// `newValue`. public func with(_ keyPath: WritableKeyPath, _ newValue: T) -> Self { var environment = self environment[keyPath: keyPath] = newValue diff --git a/Sources/SwiftCrossUI/Environment/ListStyle.swift b/Sources/SwiftCrossUI/Environment/ListStyle.swift index 27042aa177..6ce9be9cf0 100644 --- a/Sources/SwiftCrossUI/Environment/ListStyle.swift +++ b/Sources/SwiftCrossUI/Environment/ListStyle.swift @@ -1,4 +1,7 @@ +/// A style for ``List`` views. package enum ListStyle { + /// The default list style. case `default` + /// The sidebar list style. case sidebar } diff --git a/Sources/SwiftCrossUI/Layout/ViewSize.swift b/Sources/SwiftCrossUI/Layout/ViewSize.swift index 528f6460b4..3b474c63e2 100644 --- a/Sources/SwiftCrossUI/Layout/ViewSize.swift +++ b/Sources/SwiftCrossUI/Layout/ViewSize.swift @@ -9,12 +9,20 @@ public struct ViewSize: Hashable, Sendable { public var height: Double /// Creates a view size. + /// + /// - Parameters: + /// - width: The view's width. + /// - height: The view's height. public init(_ width: Double, _ height: Double) { self.width = width self.height = height } /// Creates a view size from an integer vector. + /// + /// - Parameter vector: The vector to create the view size from. The + /// X component becomes the width, and the Y component becomes the + /// height. init(_ vector: SIMD2) { width = Double(vector.x) height = Double(vector.y) @@ -29,6 +37,9 @@ public struct ViewSize: Hashable, Sendable { } /// The size component associated with the given orientation. + /// + /// - Parameter orientation: The orientation. + /// - Returns: The component corresponding to `orientation`. public subscript(component orientation: Orientation) -> Double { get { switch orientation { @@ -49,6 +60,9 @@ public struct ViewSize: Hashable, Sendable { } /// The size component associated with the given axis. + /// + /// - Parameter axis: The axis. + /// - Returns: The component corresponding to `axis`. public subscript(component axis: Axis) -> Double { get { self[component: axis.orientation] diff --git a/Sources/SwiftCrossUI/Scenes/SceneGraphNode.swift b/Sources/SwiftCrossUI/Scenes/SceneGraphNode.swift index a7a39e48e9..146297b123 100644 --- a/Sources/SwiftCrossUI/Scenes/SceneGraphNode.swift +++ b/Sources/SwiftCrossUI/Scenes/SceneGraphNode.swift @@ -1,27 +1,40 @@ -/// A persistent representation of a scene that maintains its state even when the scene -/// itself gets recomputed. Required to store view graphs and widget handles etc. +/// A persistent representation of a scene that maintains its state even when +/// the scene itself gets recomputed. /// -/// Treat scenes as basic data structures that simply encode the structure of the app; -/// the actual rendering and logic is handled by the node. +/// This is required to store view graphs, widget handles, etc. +/// +/// Treat scenes as basic data structures that simply encode the structure of +/// the app; the actual rendering and logic is handled by the node. @MainActor public protocol SceneGraphNode: AnyObject { /// The type of scene managed by this node. associatedtype NodeScene: Scene where NodeScene.Node == Self - /// Creates a node from a corresponding scene. Should perform initial setup of - /// any widgets required to display the scene (although ``SceneGraphNode/update(_:)`` - /// is guaranteed to be called immediately after initialization). + /// Creates a node from a corresponding scene. + /// + /// Should perform initial setup of any widgets required to display the + /// scene (although ``SceneGraphNode/update(_:backend:environment:)`` is + /// guaranteed to be called immediately after initialization). + /// + /// - Parameters: + /// - scene: The scene to create the node from. + /// - backend: The app's backend. + /// - environment: The current root-level environment. init( from scene: NodeScene, backend: Backend, environment: EnvironmentValues ) - /// Unlike views (which have state), scenes are only ever updated when they're - /// recomputed or immediately after they're created. + /// Updates the scene. + /// + /// Unlike views (which have state), scenes are only ever updated when + /// they're recomputed or immediately after they're created. + /// /// - Parameters: - /// - newScene: The new recomputed scene if the update is due to being recomputed. - /// - backend: The backend to use. + /// - newScene: The recomputed scene if the update is due to it being + /// recomputed. + /// - backend: The app's backend. /// - environment: The current root-level environment. func update( _ newScene: NodeScene?, diff --git a/Sources/SwiftCrossUI/State/Binding.swift b/Sources/SwiftCrossUI/State/Binding.swift index c1c72bf8f5..8a0c8bf27c 100644 --- a/Sources/SwiftCrossUI/State/Binding.swift +++ b/Sources/SwiftCrossUI/State/Binding.swift @@ -1,8 +1,24 @@ -/// A value that can read and write a value owned by a source of truth. Can be thought of -/// as a writable reference to the value. +/// A value that can read and write a different value owned by a source of +/// truth. +/// +/// You can create a binding in several different ways: +/// - by accessing the ``projectedValue`` (via leading `$` syntax) on a piece of +/// ``State`` or another `Binding` +/// - by _projecting_ a property of an existing binding with dynamic member +/// lookup (``subscript(dynamicMember:)``) +/// - by calling ``init(get:set:)`` with a custom getter and setter +/// +/// That last one reveals something important about bindings: while they can be +/// thought of as writable references to their sources of truth, in reality +/// they're nothing more than getter-setter pairs. A binding can have any +/// arbitrary getter and setter, and the two functions don't even have to be +/// related. However, SwiftCrossUI's reactivity relies on a binding's getter and +/// setter consistently managing the same source of truth; see +/// ``init(get:set:)`` for more info. @dynamicMemberLookup @propertyWrapper public struct Binding { + /// The binding's wrapped value. public var wrappedValue: Value { get { getValue() @@ -12,9 +28,11 @@ public struct Binding { } } + /// The binding itself. + /// + /// This is a handy helper so that you can use ``Binding`` properties like + /// you would with ``State`` properties. public var projectedValue: Binding { - // Just a handy helper so that you can use `@Binding` properties like - // you would `@State` properties. self } @@ -23,15 +41,31 @@ public struct Binding { /// The stored setter. private let setValue: (Value) -> Void - /// Creates a binding with a custom getter and setter. To create a binding from - /// an `@State` property use its projected value instead: e.g. `$myStateProperty` - /// will give you a binding for reading and writing `myStateProperty` (assuming that - /// `myStateProperty` is marked with `@State` at its declaration site). + /// Creates a binding with a custom getter and setter. + /// + /// To create a binding from a ``State`` property, use its projected value + /// instead: e.g. `$myStateProperty` will give you a binding for reading and + /// writing `myStateProperty`. + /// + /// - Important: SwiftCrossUI's reactivity relies on a binding's getter and + /// setter consistently reading and updating the same source of truth --- + /// calling `get` immediately after calling `set` should always return the + /// same value (barring data races). Views will not update as you expect + /// if you break this assumption. + /// + /// - Parameters: + /// - get: The binding's getter. + /// - set: The binding's setter. public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) { self.getValue = get self.setValue = set } + /// Converts a `Binding` into a `Binding?`, returning `nil` + /// if the `wrappedValue` of `other` is `nil`. + /// + /// - Parameter other: A binding with an optional value. + /// - Returns: An optional binding with a non-optional value. public init?(_ other: Binding) { if let initialValue = other.wrappedValue { self.init( @@ -48,6 +82,9 @@ public struct Binding { } /// Projects a property of a binding. + /// + /// - Parameter keyPath: A key path from this binding's value type. + /// - Returns: A binding to the property referenced by `keyPath`. public subscript(dynamicMember keyPath: WritableKeyPath) -> Binding { get { Binding( @@ -63,6 +100,10 @@ public struct Binding { /// Returns a new binding that will perform an action whenever it is used to set /// the source of truth's value. + /// + /// - Parameter action: The action to perform. + /// - Returns: A binding that calls `action` with the new value after + /// setting it. public func onChange(_ action: @escaping (Value) -> Void) -> Binding { return Binding( get: getValue, diff --git a/Sources/SwiftCrossUI/State/Cancellable.swift b/Sources/SwiftCrossUI/State/Cancellable.swift index d2c71519d8..661d41a067 100644 --- a/Sources/SwiftCrossUI/State/Cancellable.swift +++ b/Sources/SwiftCrossUI/State/Cancellable.swift @@ -1,21 +1,24 @@ /// Will run a 'cancel' action when the cancellable falls out of scope (gets -/// deinit'd by ARC). Protects against calling the action twice. +/// deinited by ARC). Protects against calling the action twice. public class Cancellable { /// The cancel action to call on deinit. private var closure: (() -> Void)? - /// Human-readable tag for debugging purposes. + /// A human-readable tag for debugging purposes. var tag: String? /// If defused, the cancellable won't cancel the ongoing action on deinit. var defused = false /// Creates a new cancellable. + /// + /// - Parameter closure: The closure to call when this cancellable falls out + /// of scope (i.e. is deinited). public init(closure: @escaping () -> Void) { self.closure = closure } - /// Extends a cancellable's lifetime to match its corresponding ongoing - /// action. This doesn't actually extend the + /// Prevents the cancellable from calling its cancel action when it goes out + /// of scope. func defuse() { defused = true } @@ -27,12 +30,18 @@ public class Cancellable { } } - /// Runs the cancel action and ensures that it can't be called a second time. + /// Runs the cancel action and ensures that it can't be called a second + /// time. public func cancel() { closure?() closure = nil } + /// Adds a human-readable tag to the cancellable. + /// + /// This method is a no-op in release mode. + /// + /// - Parameter tag: The tag to add. @discardableResult func tag(with tag: @autoclosure () -> String?) -> Self { #if DEBUG diff --git a/Sources/SwiftCrossUI/State/DynamicKeyPath.swift b/Sources/SwiftCrossUI/State/DynamicKeyPath.swift index ea3a247c52..5c163e17dd 100644 --- a/Sources/SwiftCrossUI/State/DynamicKeyPath.swift +++ b/Sources/SwiftCrossUI/State/DynamicKeyPath.swift @@ -9,20 +9,28 @@ #endif /// A type similar to KeyPath, but that can be constructed at run time given -/// an instance of a struct, and the value of the desired property. Construction -/// fails if the property's in-memory representation is not unique within the -/// struct. SwiftCrossUI only uses ``DynamicKeyPath`` in situations where it is +/// an instance of a struct, and the value of the desired property. +/// +/// Construction fails if the property's in-memory representation is not unique within the +/// struct. SwiftCrossUI only uses `DynamicKeyPath` in situations where it is /// highly likely for properties to have unique in-memory representations, such /// as when properties have internal storage pointers. struct DynamicKeyPath { - /// The property's offset within instances of ``T``. + /// The property's offset within instances of `Base`. var offset: Int /// Constructs a key path given an instance of the base type, and the - /// value of the desired property. The initializer will search through - /// the base instance's in-memory representation to find the unique offset - /// that matches the representation of the given property value. If such an - /// offset can't be found or isn't unique, then the initialiser returns `nil`. + /// value of the desired property. + /// + /// The initializer will search through the base instance's in-memory + /// representation to find the unique offset that matches the representation + /// of the given property value. If such an offset can't be found or isn't + /// unique, then the initialiser returns `nil`. + /// + /// - Parameters: + /// - value: The value to construct a key path to. + /// - base: The value to construct a key path from. + /// - label: The property's label, if any. Only used for debugging. init?( forProperty value: Value, of base: Base, @@ -71,6 +79,9 @@ struct DynamicKeyPath { } /// Gets the property's value on the given instance. + /// + /// - Parameter base: The instance to get the property on. + /// - Returns: This property's value on `base`. func get(_ base: Base) -> Value { withUnsafeBytes(of: base) { buffer in buffer.baseAddress!.advanced(by: offset) @@ -80,6 +91,10 @@ struct DynamicKeyPath { } /// Sets the property's value to a new value on the given instance. + /// + /// - Parameters: + /// - base: The instance to set the property on. + /// - newValue: The new value of the property. func set(_ base: inout Base, _ newValue: Value) { withUnsafeMutableBytes(of: &base) { buffer in buffer.baseAddress!.advanced(by: offset) diff --git a/Sources/SwiftCrossUI/State/DynamicProperty.swift b/Sources/SwiftCrossUI/State/DynamicProperty.swift index c573415936..b44554944b 100644 --- a/Sources/SwiftCrossUI/State/DynamicProperty.swift +++ b/Sources/SwiftCrossUI/State/DynamicProperty.swift @@ -1,11 +1,19 @@ /// A property wrapper updated by the view graph before each access to -/// ``View/body``. Conforming types should use internal mutability (see ``Box``) -/// to implement this protocol's non-mutable methods if required. This -/// protocol avoids mutation to allow state properties and such to be -/// captured even though views are structs. +/// ``View/body``. +/// +/// Conforming types should use internal mutability to implement this protocol's +/// non-`mutating` methods if required. This protocol avoids mutation to allow +/// state properties and such to be captured even though views are structs. public protocol DynamicProperty { - /// Updates the property. Called by SwiftCrossUI before every access it - /// makes to an ``App/body`` or ``View/body``. + /// Updates the property. + /// + /// This is called by SwiftCrossUI before every access it makes to an + /// `App.body` or `View.body`. + /// + /// - Parameters: + /// - environment: The current environment. + /// - previousValue: The previous value of the property. `nil` if this is + /// the first time an update has been made. func update( with environment: EnvironmentValues, previousValue: Self? diff --git a/Sources/SwiftCrossUI/State/DynamicPropertyUpdater.swift b/Sources/SwiftCrossUI/State/DynamicPropertyUpdater.swift index bab949376d..ad42ee1084 100644 --- a/Sources/SwiftCrossUI/State/DynamicPropertyUpdater.swift +++ b/Sources/SwiftCrossUI/State/DynamicPropertyUpdater.swift @@ -1,7 +1,8 @@ -/// A cache for dynamic property updaters. The keys are the ObjectIdentifiers of -/// various Base types that we have already computed dynamic property updaters -/// for, and the elements are corresponding cached instances of -/// DynamicPropertyUpdater. +/// A cache for dynamic property updaters. +/// +/// The keys are the `ObjectIdentifier`s of various `Base` types that we have +/// already computed dynamic property updaters for, and the elements are +/// corresponding cached instances of `DynamicPropertyUpdater`. /// /// From some basic testing, this caching seems to reduce layout times by 5-10% /// (at the time of implementation). @@ -9,16 +10,18 @@ var updaterCache: [ObjectIdentifier: Any] = [:] /// A helper for updating the dynamic properties of a stateful struct (e.g. -/// a View or App conforming struct). Dynamic properties are those that conform -/// to ``DynamicProperty``, e.g. properties annotated with `@State`. +/// a ``View``- or ``App``-conforming struct). +/// +/// Dynamic properties are those that conform to ``DynamicProperty``, e.g. those +/// annotated with the ``State`` property wrapper. /// -/// At initialisation the updater will attempt to determine the byte offset of +/// At initialisation, the updater will attempt to determine the byte offset of /// each stateful property in the struct. This is guaranteed to succeed if every /// dynamic property in the provided struct instance contains internal mutable /// storage, because the storage pointers will provide unique byte sequences. /// Otherwise, offset discovery will fail when two dynamic properties share the /// same pattern in memory. When offset discovery fails the updater will fall -/// back to using Mirrors each time `update` gets called, which can be 1500x +/// back to using `Mirror`s each time `update` gets called, which can be 1500x /// times slower when the view has 0 state properties, and 9x slower when the /// view has 4 properties, with the factor slowly dropping as the number of /// properties increases. @@ -29,14 +32,19 @@ struct DynamicPropertyUpdater { _ environment: EnvironmentValues ) -> Void - /// The updaters for each of Base's dynamic properties. If `nil`, then we - /// failed to compute + /// The updaters for each of `Base`'s dynamic properties. + /// + /// If `nil`, then we failed to compute the updaters. let propertyUpdaters: [PropertyUpdater]? /// Creates a new dynamic property updater which can efficiently update - /// all dynamic properties on any value of type Base without creating - /// any mirrors after the initial creation of the updater. Pass in a - /// `mirror` of base if you already have one to save us creating another one. + /// all dynamic properties on any value of type `Base` without creating + /// any mirrors after the initial creation of the updater. + /// + /// - Parameters: + /// - base: The base value to update the dynamic properties of. + /// - mirror: An existing mirror of `base`. If not provided, a mirror will + /// be created. @MainActor init(for base: Base, mirror: Mirror? = nil) { // Unlikely shortcut, but worthwhile when we can. diff --git a/Sources/SwiftCrossUI/State/Publisher.swift b/Sources/SwiftCrossUI/State/Publisher.swift index 571f2b058c..271fa5ac9c 100644 --- a/Sources/SwiftCrossUI/State/Publisher.swift +++ b/Sources/SwiftCrossUI/State/Publisher.swift @@ -10,7 +10,7 @@ public class Publisher { /// Human-readable tag for debugging purposes. private var tag: String? - /// We guard this against data races, with serialUpdateHandlingQueue, and + /// We guard this against data races, with `serialUpdateHandlingQueue`, and /// with our lives. private class UpdateStatistics: @unchecked Sendable { /// The time at which the last update merging event occurred in @@ -68,7 +68,7 @@ public class Publisher { return self } - /// A specialized version of `observe(with:)` designed to mitigate main thread + /// A specialized version of ``observe(with:)`` designed to mitigate main thread /// starvation issues observed on weaker systems when using the Gtk3Backend. /// /// If observations are produced faster than the update handler (`closure`) can @@ -85,7 +85,7 @@ public class Publisher { /// update handler is still running constantly (since there's always going to /// be a new update waiting before the the running update completes). In this /// situation we introduce a sleep after handling each update to give the backend - /// time to catch up. Hueristically I've found that a delay of 1.5x the length of + /// time to catch up. Heuristically I've found that a delay of 1.5x the length of /// the update is required on my old Linux laptop using ``Gtk3Backend``, so I'm /// going with that for now. Importantly, this delay is only used whenever updates /// start running back-to-back with no gap so it shouldn't affect fast systems diff --git a/Sources/SwiftCrossUI/State/State.swift b/Sources/SwiftCrossUI/State/State.swift index e49fc1e696..c70e9d2514 100644 --- a/Sources/SwiftCrossUI/State/State.swift +++ b/Sources/SwiftCrossUI/State/State.swift @@ -4,15 +4,19 @@ import Foundation // - It supports value types // - It supports ObservableObject // - It supports Optional + +/// A property wrapper that acts as a source of truth for view state. @propertyWrapper public struct State: ObservableProperty { class Storage { - // This inner box is what stays constant between view updates. The - // outer box (Storage) is used so that we can assign this box to - // future state instances from the non-mutating - // `update(with:previousValue:)` method. It's vital that the inner - // box remains the same so that bindings can be stored across view - // updates. + /// The inner storage of ``State``. + /// + /// This inner box is what stays constant between view updates. The + /// outer box (`Storage`) is used so that we can assign this box to + /// future ``State`` instances from the non-`mutating` + /// ``State/update(with:previousValue:)``. It's vital that the inner + /// box remains the same so that bindings can be stored across view + /// updates. var box: InnerBox class InnerBox { @@ -25,12 +29,12 @@ public struct State: ObservableProperty { } /// Call this to publish an observation to all observers after - /// setting a new value. This isn't in a didSet property accessor + /// setting a new value. This isn't in a `didSet` property accessor /// because we want more granular control over when it does and /// doesn't trigger. /// /// Additionally updates the downstream observation if the - /// wrapped value is an Optional and the + /// wrapped value is an `Optional` and the /// current case has toggled. func postSet() { // If the wrapped value is an Optional @@ -59,6 +63,7 @@ public struct State: ObservableProperty { storage.box.didChange } + /// Accesses the underlying value of this `State`. public var wrappedValue: Value { get { storage.box.value @@ -69,6 +74,7 @@ public struct State: ObservableProperty { } } + /// Returns a ``Binding`` to this state. public var projectedValue: Binding { // Specifically link the binding to the inner box instead of the outer // storage which changes with each view update. @@ -84,6 +90,9 @@ public struct State: ObservableProperty { ) } + /// Creates a `State` given an initial value. + /// + /// - Parameter initialValue: The state's initial value. public init(wrappedValue initialValue: Value) { storage = Storage(initialValue) diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/DefaultBackend.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/DefaultBackend.md index f0ef93357a..3b40773bf6 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/DefaultBackend.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/DefaultBackend.md @@ -4,7 +4,7 @@ An adaptive backend which uses different backends under the hood depending on th ## Overview -The beauty of SwiftCrossUI is that you can write your app once and have it look native everywhere. For this reason I recommend using DefaultBackend unless you've got particular constraints. It uses on macOS, on Window, on Linux, and on on iOS/tvOS. +The beauty of SwiftCrossUI is that you can write your app once and have it look native everywhere. For this reason I recommend using DefaultBackend unless you've got particular constraints. It uses on macOS, on Window, on Linux, and on iOS/tvOS. > Tip: If you're using DefaultBackend, you can override the underlying backend during compilation by setting the `SCUI_DEFAULT_BACKEND` environment variable to the name of the desired backend. This is useful when you e.g. want to test the Gtk version of your app while using a Mac. Note that this only works for built-in backends and still requires the chosen backend to be compatible with your machine. diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Examples.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Examples.md index ab54ea10e2..9deeae259f 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Examples.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Examples.md @@ -10,7 +10,7 @@ A few examples are included with SwiftCrossUI to demonstrate some of its basic f - `RandomNumberGeneratorExample`, a simple app to generate random numbers between a minimum and maximum. - `WindowingExample`, a simple app showcasing how ``WindowGroup`` is used to make multi-window apps and control the properties of each window. It also demonstrates the use of modals - such as alerts and file pickers. + such as alerts, sheets, and file pickers. - `GreetingGeneratorExample`, a simple app demonstrating dynamic state and the ``ForEach`` view. - `NavigationExample`, an app showcasing ``NavigationStack`` and related concepts. - `SplitExample`, an app showcasing ``NavigationSplitView``-based hierarchical navigation. diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Gestures.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Gestures.md index 920fdd407a..cf8d3b73c0 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Gestures.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Gestures.md @@ -4,3 +4,4 @@ - ``View/onTapGesture(gesture:perform:)`` - ``TapGesture`` +- ``View/onHover(perform:)`` diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Hot reloading.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Hot reloading.md index db8ac5b6f2..54bbb1294e 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Hot reloading.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Hot reloading.md @@ -6,8 +6,8 @@ SwiftCrossUI supports hot reloading when used together with Swift Bundler. Currently this is only supported on macOS, but the macros work everywhere so that you can safely leave them in even when unused. -Use ``HotReloadable`` to enable hot reloading within your app. Use -``hotReloadable`` to define hot reloading boundaries; anything within a hot +Use ``HotReloadable()`` to enable hot reloading within your app. Use +``hotReloadable(_:)`` to define hot reloading boundaries; anything within a hot reloading boundary gets reloaded during hot reloading (with state persisted), and everything outside remains unchanged within any given hot reloading session. diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Implementation details.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Implementation details.md index cde5d8e66f..37b029dadd 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Implementation details.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Implementation details.md @@ -4,12 +4,204 @@ You don't need to interact with any of this directly unless you're doing something very advanced. Many of these are only exposed as `public` so that -advanced users don't have to reimplement helpers that we've already -implemented, and others are exposed to enable unique use-cases such as embedding -SwiftCrossUI view graphs inside existing non-SwiftCrossUI apps. +advanced users don't have to reimplement helpers that we've already implemented, +and others are exposed to enable unique use-cases such as embedding SwiftCrossUI +view graphs inside existing non-SwiftCrossUI apps. + +### Contents +- + - + - + - + - + - + - + - +- + - + - +## High-level overview + +It's a good idea to refer to [the source code] while reading this, as not all of +the APIs mentioned are made public. + +[the source code]: https://github.com/stackotter/swift-cross-ui/tree/main/Sources/SwiftCrossUI + +### Entry point + +The entry point of SwiftCrossUI apps is the implementation of ``App/main()`` +provided by the ``App`` protocol. That function defers to +`_App.run()`. + +`_App.run()` starts the backend's main loop, and does the rest of the setup once +the backend is ready. The rest of the setup phase involves: +- Computing the root environment +- Instantiating any ``Environment`` properties on the app struct via + `updateDynamicProperties(of:previousValue:environment:)` +- Observing any ``State`` properties on the app struct using `Mirror` and + `observeAsUIUpdater(backend:action:)` (which handles basic update debouncing) +- Creating the root scene graph node +- Listening for environment changes (e.g. system theme changes) +- Updating the root scene graph node + +### Scenes + +Scene types (structs conforming to ``Scene``) each have their own node type +(conforming to ``SceneGraphNode``). These node types are classes and store any +persistent state associated with the scene (e.g. the window pointer or the +``ViewGraph`` associated with the scene). + +_Scenes_ here are just anything that exists at the level above views (e.g. +windows and app menus). + +### The view graph + +The root of the view graph is the ``ViewGraph`` class. It stores a root node and +some information required to glue scenes and view graphs together (such as the +last known window size). ``ViewGraph`` is a pretty thin wrapper around +``ViewGraphNode`` and essentially just hides some of the more internal details +that scenes don't need to worry about. + +Each view generally has an associated ``ViewGraphNode``. This is where the +view's state is persisted, and is where any fancy layout caching happens. The +view graph node's lifecycle is also what ``View/onAppear(perform:)`` and +``View/onDisappear(perform:)`` are based on; for example, +``View/onDisappear(perform:)`` runs when a node deinits. + +``ViewGraphNode``s store their children using types conforming to the +``ViewGraphNodeChildren`` protocol.Tthe exact type is determined by the +associated view. For example, ``TupleView3`` stores its three child nodes --- +`child0`, `child1`, and `child2` --- using ``TupleViewChildren3``, while +``ForEach`` stores its children in the `ForEachViewChildren` class which is +designed for storing a homogenous array of child nodes. It's the responsibility +of ``View/computeLayout(_:children:proposedSize:environment:backend:)-2gzmc`` +implementations to propagate updates on to any relevant child nodes. + +### View updates + +View updates happen in two phases; the dry-run phase and the commit phase. + +The dry-run phase allows views to efficiently be queried for their layout at +multiple different sizes without too much overhead. During a dry run, +``ViewGraphNode`` can avoid querying its associated view for a potentially +expensive layout computation if it believes that it can already satisfy the +query using basic assumptions about view layout behaviour and the results of +previous layout updates (see `ViewGraphNode.resultCache` and +`ViewGraphNode.currentResult`). + +There are two types of view updates; top-down and bottom-up. Top-down updates +occur when a view's parent view has updated for some reason (be that a state +update, or the parent's parent updating, or some other reason). Bottom-up +updates happen when a view's child updated due to a state change _and_ the child +changed size due to that update. Bottom-up updates continue propagating upwards +until a view doesn't change size (meaning that we can avoid notifying its +parent). + +### The View protocol + +The most famous requirement of ``View`` is ``View/body``; this is the property +you know and love from SwiftUI. It's sufficient to just implement this property +when you're implementing regular views. + +The following requirements are used to implement internal views such as +``Button``: + +- term ``View/children(backend:snapshots:environment:)-3yoia``: This method + produces the ``ViewGraphNodeChildren`` instance that the view would like to + use to store its children. The return type is an existential because otherwise + we would need to expose an `associatedtype Children: ViewGraphNodeChildren` + requirement to users just trying to write regular views using the + ``View/body`` requirement. (There is an internal `TypeSafeView` protocol that + adds this requirement.) + +- term ``View/layoutableChildren(backend:children:)-182zg``: This method is only + implemented by the `TupleViewN` types. It essentially just gets the view's + children as an array of ``LayoutSystem/LayoutableChild``s, the type that + ``LayoutSystem`` works with when computing stack layouts. + +- term ``View/asWidget(_:backend:)-88tbd``: This method is generally pretty + simple; just use the backend instance to produce the widget type the view + wants to use. Views only ever have a single associated widget instance; views + that want to change their underlying widget can use an intermediate container + widget to satisfy this requirement. + + This method shouldn't configure the widget at all, that's handled by + ``View/computeLayout(_:children:proposedSize:environment:backend:)-2gzmc`` and + ``View/commit(_:children:layout:environment:backend:)-6kzjk`` + (which are guaranteed to be called between ``View/asWidget(_:backend:)-88tbd`` + and the first time the view appears on screen). + +- term ``View/computeLayout(_:children:proposedSize:environment:backend:)-2gzmc``: + This method is the meat of most view implementations; its role is to compute + view layouts. It may be called multiple times before the layout system settles + on a result. + +- term ``View/commit(_:children:layout:environment:backend:)-6kzjk``: This + method updates the widgets to be displayed on-screen. It recieves the most + recent result of ``View/computeLayout(_:children:proposedSize:environment:backend:)-2gzmc`` + as the `layout` parameter. + +Internally, we have the `ElementaryView` and `TypeSafeView` protocols (which +extend ``View``) and are just nicer versions of the ``View`` protocol useful for +certain purposes. `ElementaryView` is for views with no children, and +`TypeSafeView` is identical to ``View`` but with a `Children` associated type. + +### Environment + +If a view or modifier wants to change the environment for all child views, it +does this in +``View/computeLayout(_:children:proposedSize:environment:backend:)-2gzmc`` by +passing a modified copy of the environment to child nodes when calling their +update methods. + +### Preferences + +Preferences are similar to environment values, except they propagate _up_ the +view graph instead of _down_. For instance, the ``PreferenceValues/onOpenURL`` +preference is used to propagate external URL handlers to the top level to be +registered with the backend. + +Preferences are propagated as part of the ``ViewLayoutResult`` type. Container +views can pass multiple ``ViewLayoutResult`` instances to +``ViewLayoutResult/init(size:childResults:participateInStackLayoutsWhenEmpty:preferencesOverlay:)`` +to have their preferences merged automatically into a single ``PreferenceValues`` +instance. + +## Dynamic properties + +Dynamic properties are properties of ``View``- or ``App``-conforming structs +that conform to the ``DynamicProperty`` protocol --- for example, ``State`` or +``Environment``. + +There are two methods used to update these properties when view or app bodies +are recomputed: the primary one calculates the offsets to the properties at +runtime, while the fallback method uses `Mirror` to set the properties. Choosing +which method to use, as well as actually performing the update, is handled by +`DynamicPropertyUpdater`. + +### Primary method + +This method uses the `DynamicKeyPath` type, which can construct a "key path" at +runtime given an instance of the type and the current value of the property in +question. `DynamicKeyPath` performs some `withUnsafeBytes(of:_:)` magic to find +the property's offset within the type. + +### Fallback method + +The fallback method (which was the _only_ method before the + PR was merged) simply uses `Mirror` to query the type's +properties, check them for conformance to ``DynamicProperty``, and update the +values. It's only used when computing offsets for the primary method fails +(usually because multiple properties of the type have the same bit-level +representation of their current values). + +It can be up to 1500 times slower than the primary method (with the difference +decreasing as more stateful properties are added); this is why the primary +method is preferred wherever possible. + ## Topics - ``LayoutSystem`` @@ -20,7 +212,7 @@ SwiftCrossUI view graphs inside existing non-SwiftCrossUI apps. - ``HotReloadableView`` - ``ViewSize`` -- ``ViewUpdateResult`` +- ``ViewLayoutResult`` - ``AnyWidget`` diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Layout performance.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Layout performance.md new file mode 100644 index 0000000000..8222084d1a --- /dev/null +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Layout performance.md @@ -0,0 +1,156 @@ +# Layout performance + +Recently (December 2025), SwiftCrossUI's layout algorithm received a massive +overhaul, drastically boosting layout performance. + +Detailed information can be found in [the PR]; it's reproduced and edited here +for convenience (written from the perspective of \@stackotter, the project's +lead maintainer). + +[the PR]: https://github.com/stackotter/swift-cross-ui/pull/278 + +## Optimisations + +### Splitting `View.update` into `View.computeLayout` and `View.commit` + +Before this PR, `View` had a single update-related method, `View.update`, which +had two modes: dry run, and non-dry run. Dry runs were used to probe the sizes +of views without actually committing the layout to the underlying widgets. This +let us cache layout results (because we didn't have to worry about keeping the +widgets in sync during dry run updates). Then when the final layout was decided, +a non-dry run update would happen, during which the layout would be committed to +the underlying widgets. This architecture was inefficient because the final +non-dry run update would effectively have to compute the entire layout again, +leading to a lot of doubling up of work. + +My solution was to split `View.update` into two separate requirements: +``View/computeLayout(_:children:proposedSize:environment:backend:)-2gzmc`` and +``View/commit(_:children:layout:environment:backend:)-6kzjk``. +`View.computeLayout` computes layouts as usual without committing anything to +the underlying widgets, and then `View.commit` can be called with the result of +`View.computeLayout` to efficiently commit the last computed layout of the view +and all of its children. + +This lead to a 7.7x improvement for the grid benchmark, and a 2.8x improvement +for the message list benchmark. + +The main reason that this was so effective is that during the commit phase, each +view gets handed its last computed result which it can blindly trust, as opposed +to each view having to recompute its desired size. + +### Update layout algorithm to match SwiftUI + +The layout system's biggest layout problem prior to this PR was that its stack +layout algorithm required computing the layout of each child view multiple +times. This led to exponentially worse layout performance as the amount of +nesting increased. My caching system managed to curb the exponential growth in +some situations, but it was easy to render the caching useless. + +After much thinking, I discovered a few unavoidable approximations that we'd +have to adopt if we were to avoid computing the layout of each child view +multiple times per stack layout pass. I created [some edge cases] that would +behave differently depending on whether or not SwiftUI used these +approximations I had landed on. SwiftUI uses all of them. + +In my opinion, these approximations lead to less desirable behaviour than +SwiftCrossUI's previous layout system, but I don't think that we can handle all +of the aforementioned edge cases well without keeping our serious performance +issues. There are likely other sets of approximations that would lead to similar +performance, but given that SwiftUI already uses these approximations, and that +I arrived at these approximations independently, I decided that they're our best +option. + +Adopting said approximations went hand in hand with updating ``ViewSize`` to be +a simple size type (rather than tracking minimum size, maximum size, ideal size, +etc), and our proposed view sizes to be 2D vectors of `Double?`. + +Together, adopting the approximations and updating simplifying our ``ViewSize`` +type allowed us to reduce our effective child layout passes per stack layout +pass to 1. I say effective layout passes, because we still query the child +multiple times for its minimum, maximum, and final sizes, but together those +roughly equate to the same amount of work as a single layout pass would have if +we still had our old ``ViewSize`` type. The key to making it work out like so is +that querying the minimum, maximum or ideal size of a stack layout now only +requires computing the minimum, maximum or ideal sizes of its children +respectively. This means that minimum, maximum and ideal size requests are +linear in the number of views in the stack's view hierarchy. Additionally, our +probing child layout requests enable `environment.allowLayoutCaching`, so any +given view only ends up computing its minimum, maximum or ideal size once during +given update cycle. This means that even though our stack layout algorithm +technically queries each child multiple times, the minimum and maximum size +requests are free if any parent view has already computed them, meaning that we +effectively only query each child once. + +This lead to a 4x improvement for the grid benchmark, but somehow made our +message list benchmark twice as bad. I haven't properly investigated why that +happened, but that'd be a good place to start for future performance work, as it +would probably help us figure out exactly what became slower when introducing +this new layout system. + +[some edge cases]: https://github.com/stackotter/swiftui-layout-edge-cases + +### Layout system changes + +Any ``VStack`` (or height-specific) behaviours described here apply to +``HStack``s as well. I'm just being lazy. + +- The minimum height of a stack layout is just the minimum heights of its + children added together. Due to the greedy nature of the stack layout + algorithm this can lead to the stack overflowing its frame when proposed its + minimum size. This is most noticeable when the stack is in the layout context + of a window (rather than inside of a decoupling container such as a + ``ScrollView``). +- When a ``VStack`` is given a concrete height and an unspecified width, it may + end up overflowing its own reported bounds, because we delay the final layout + pass until the commit step. This isn't ideal, but it's what SwiftUI does, and + it lets us keep the effective branching factor of our stack layout algorithm + at 1. +- When a `minHeight` frame is proposed an unspecified height, it may end up with + a different width to its child. The child gets proposed an unspecified height, + then the frame clamps the resulting height and assumes that the child will + keep its reported width. During commit, the frame will lay the child out again + with the clamped height, and the child may end up growing or shrinking + horizontally. This means that the unconstrained axis of a frame may end up not + hugging its content even though it has no reason not to. This less than ideal + behaviour is to keep our branching factor at 1. + +### Using a custom `Mirror` replacement + +I noticed that we were spending about half of each layout update in +`Mirror`-related code. I've had a `Mirror` optimisation idea in the back of my +head for a while so I gave it a go, and it basically eliminated `Mirror` +overhead for stateless views (1500x faster), and made updating dynamic +properties (e.g. ``State`` properties) 5-10x faster for views with less than 16 +dynamic properties. The new system still uses `Mirror` during `ViewGraphNode` +creation, but it uses a custom technique to infer the offset of each dynamic +property discovered by the mirror. We can then reuse the offsets for the rest of +the `ViewGraphNode`'s lifecycle, and we cache the offsets for each type in a +global look up table to reduce `Mirror` usage even further. + +I've documented this system quite thoroughly in +`Sources/SwiftCrossUI/State/DynamicPropertyUpdater.swift`, so if you wanna know +more about how it works, give that a read. + +This made both benchmarks (grid and message list) twice as fast. + +### Benchmarks + +All benchmarking has been done on my M2 MacBook Air with 8GB of RAM. + +- The grid benchmark is now 61 times faster. +- The message list benchmark is now 4 times faster (it didn't benefit as much + from our branching factor reductions because it has less nesting). +- @bbrk24's private DiscordBotGUI app now has 29 times faster window resize + handling, and a synthetic benchmark based of the core of its performance + issues is 11.34 times faster. + +### Future directions + +- Investigate why `ed24b0fa` (splitting `View.update` into `computeLayout` and + `commit`) → `e4daa213` (adopting SwiftUI's layout approximations) made + the message list benchmark twice as slow. It may help us figure out + inefficiencies in the new layout system. +- Optimise `ViewGraphNode` and other generic parts of the layout system for some + linear performance gains. E.g. `TupleViewChildren` does a bunch of work + computing information related to the state snapshotting system, even when no + snapshots are present. diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Layout.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Layout.md index d14287c57c..a533cb20f8 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Layout.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Layout.md @@ -2,9 +2,13 @@ ## Topics +### Performance + +- + ### Padding -- ``View/padding(_:)`` +- ``View/padding(_:)-(Int?)`` - ``View/padding(_:_:)`` - ``Edge`` - ``Edge/Set`` @@ -12,7 +16,7 @@ ### Aspect ratio -- ``View/aspectRatio(_:contentMode:)`` +- ``View/aspectRatio(_:contentMode:)-(Double?,_)`` - ``ContentMode`` ### Frame diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Scene graph.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Scene graph.md index a632441ce5..4f8d4da913 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Scene graph.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Scene graph.md @@ -3,7 +3,6 @@ ## Topics - ``SceneGraphNode`` -- ``CommandsModifierNode`` - ``WindowGroupNode`` - ``TupleScene2`` - ``TupleScene3`` diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Setting up a virtual development environment.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Setting up a virtual development environment.md index 47ab4b4435..fa77f02a16 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Setting up a virtual development environment.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/Setting up a virtual development environment.md @@ -6,7 +6,7 @@ Detailed set up instructions for virtual Linux and Windows development environme Most developers only have access to one or two of the major operating systems for development. Setting up virtual machines is an important part of developing and testing cross-platform apps, especially for smaller less-specialized teams. -This article covers VM creation, leaving set up to the more general article which applies to both physical and virtual machines. +This article covers VM creation, leaving set up to the more general article which applies to both physical and virtual machines. So far this article only covers macOS hosts but will be expanded to cover other host OSes in future. @@ -52,7 +52,7 @@ The next few steps are optional, and walk through setting up a shared directory You should now find the shared directory mounted as a drive inside your Windows VM. -You're now ready to begin setting up a SwiftCrossUI development environment inside your Windows VM. Head over to to continue. +You're now ready to begin setting up a SwiftCrossUI development environment inside your Windows VM. Head over to to continue. ### Linux guests @@ -116,7 +116,7 @@ sudo reboot -h now 3. You'll find your directory mounted at `~/utm`. If you run into file ownership related issues, check your macOS user id (with the `id -u` command). If it isn't `501`, update the `bindfs` fstab rule accordingly and reboot again. -You're now ready to begin setting up a SwiftCrossUI development environment inside your Linux VM. Head over to to continue. +You're now ready to begin setting up a SwiftCrossUI development environment inside your Linux VM. Head over to to continue. diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/SwiftCrossUI.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/SwiftCrossUI.md index 8a0610fb11..72ed7561bb 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/SwiftCrossUI.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/SwiftCrossUI.md @@ -42,7 +42,7 @@ SwiftCrossUI takes inspiration from SwiftUI, allowing you to use the basic conce ### State - -- +- - ### User input diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Environment.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/The environment.md similarity index 97% rename from Sources/SwiftCrossUI/SwiftCrossUI.docc/Environment.md rename to Sources/SwiftCrossUI/SwiftCrossUI.docc/The environment.md index dc82185e8f..87a755d1ce 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/Environment.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/The environment.md @@ -4,6 +4,7 @@ - ``Environment`` - ``EnvironmentValues`` +- ``EnvironmentKey`` ### Layout diff --git a/Sources/SwiftCrossUI/SwiftCrossUI.docc/View fundamentals.md b/Sources/SwiftCrossUI/SwiftCrossUI.docc/View fundamentals.md index e354cc24e2..91a601c65b 100644 --- a/Sources/SwiftCrossUI/SwiftCrossUI.docc/View fundamentals.md +++ b/Sources/SwiftCrossUI/SwiftCrossUI.docc/View fundamentals.md @@ -24,6 +24,5 @@ The main two view composition primitives are ``VStack``, for vertical layouts, a - ``Group`` - ``ScrollView`` - ``Axis`` -- ``Axis.Set`` - ``ProgressView`` - ``AnyView`` diff --git a/Sources/SwiftCrossUI/Values/Alignment.swift b/Sources/SwiftCrossUI/Values/Alignment.swift index fac80c0bc5..fd64c55ef5 100644 --- a/Sources/SwiftCrossUI/Values/Alignment.swift +++ b/Sources/SwiftCrossUI/Values/Alignment.swift @@ -1,4 +1,4 @@ -/// The 2d alignment of a view. +/// The 2D alignment of a view. public struct Alignment: Hashable, Sendable { /// Centered in both dimensions. public static let center = Self(horizontal: .center, vertical: .center) @@ -29,12 +29,23 @@ public struct Alignment: Hashable, Sendable { /// Creates a custom alignment with the given horizontal and vertical /// components. + /// + /// - Parameters: + /// - horizontal: The horizontal alignment component. + /// - vertical: The vertical alignment component. public init(horizontal: HorizontalAlignment, vertical: VerticalAlignment) { self.horizontal = horizontal self.vertical = vertical } - /// Computes the position of a child in a parent view using the provided sizes. + /// Computes the position of a child in a parent view using the provided + /// sizes. + /// + /// - Parameters: + /// - child: The size of the child, as a width/height vector. + /// - parent: The size of the parent, as a width/height vector. + /// - Returns: The position of the child within the parent, as an x/y + /// vector. public func position( ofChild child: SIMD2, in parent: SIMD2 diff --git a/Sources/SwiftCrossUI/Values/AppMetadata.swift b/Sources/SwiftCrossUI/Values/AppMetadata.swift index c28cb786df..60567c315c 100644 --- a/Sources/SwiftCrossUI/Values/AppMetadata.swift +++ b/Sources/SwiftCrossUI/Values/AppMetadata.swift @@ -7,6 +7,12 @@ public struct AppMetadata { /// Additional developer-defined metadata. public var additionalMetadata: [String: Any] + /// Creates an ``AppMetadata`` instance. + /// + /// - Parameters: + /// - identifier: The app's reverse domain name identifier. + /// - version: The app's version (generally a semantic version string). + /// - additionalMetadata: Additional developer-defined metadata. public init( identifier: String, version: String, diff --git a/Sources/SwiftCrossUI/Values/Color.swift b/Sources/SwiftCrossUI/Values/Color.swift index ef67f26d41..7f9e7696be 100644 --- a/Sources/SwiftCrossUI/Values/Color.swift +++ b/Sources/SwiftCrossUI/Values/Color.swift @@ -13,6 +13,12 @@ public struct Color: Sendable, Equatable, Hashable { public var alpha: Float /// Creates a color from its components with values between 0 and 1. + /// + /// - Parameters: + /// - red: The red component. + /// - green: The green component. + /// - blue: The blue component. + /// - alpha: The alpha component. public init( _ red: Float, _ green: Float, @@ -26,9 +32,11 @@ public struct Color: Sendable, Equatable, Hashable { } /// Multiplies the opacity of the color by the given amount. - public consuming func opacity( - _ opacity: Float - ) -> Color { + /// + /// - Parameter opacity: The new opacity of the color, relative to its + /// current opacity. + /// - Returns: A color with the opacity changed. + public consuming func opacity(_ opacity: Float) -> Color { self.alpha *= opacity return self } diff --git a/Sources/SwiftCrossUI/Values/ColorScheme.swift b/Sources/SwiftCrossUI/Values/ColorScheme.swift index d2fd897484..a59c9016c5 100644 --- a/Sources/SwiftCrossUI/Values/ColorScheme.swift +++ b/Sources/SwiftCrossUI/Values/ColorScheme.swift @@ -1,7 +1,11 @@ +/// A color scheme to apply to views. public enum ColorScheme: Sendable { + /// Light mode (usually black on white). case light + /// Dark mode (usually white on black). case dark + /// The opposite of this color scheme. package var opposite: ColorScheme { switch self { case .light: .dark @@ -9,6 +13,7 @@ public enum ColorScheme: Sendable { } } + /// The default foreground color for this color scheme. public var defaultForegroundColor: Color { switch self { case .light: .black diff --git a/Sources/SwiftCrossUI/Values/ContentType.swift b/Sources/SwiftCrossUI/Values/ContentType.swift index 325ddb62b7..25b3b33f02 100644 --- a/Sources/SwiftCrossUI/Values/ContentType.swift +++ b/Sources/SwiftCrossUI/Values/ContentType.swift @@ -1,15 +1,26 @@ /// A content type corresponding to a specific file/data format. public struct ContentType: Sendable { + /// The HTML content type. public static let html = ContentType( name: "HTML", mimeTypes: ["text/html"], fileExtensions: ["html", "htm"] ) + /// The name of this content type. public var name: String + /// An array of MIME types associated with this content type. public var mimeTypes: [String] + /// An array of file extensions associated with this content type. public var fileExtensions: [String] + /// Creates an instance of `ContentType`. + /// + /// - Parameters: + /// - name: The name of this content type. + /// - mimeTypes: An array of MIME types associated with this content type. + /// - fileExtensions: An array of file extensions associated with this + /// content type. public init(name: String, mimeTypes: [String], fileExtensions: [String]) { self.name = name self.mimeTypes = mimeTypes diff --git a/Sources/SwiftCrossUI/Values/Font.swift b/Sources/SwiftCrossUI/Values/Font.swift index b9187f3d98..b5187f479b 100644 --- a/Sources/SwiftCrossUI/Values/Font.swift +++ b/Sources/SwiftCrossUI/Values/Font.swift @@ -50,6 +50,9 @@ public struct Font: Hashable, Sendable { public static let footnote = Font(dynamic: .footnote) /// Selects whether or not to use the font's emphasized variant. + /// + /// - Paramater emphasized: Whether to emphasize the font. + /// - Returns: The updated font. public func emphasized(_ emphasized: Bool = true) -> Font { var font = self font.overlay.emphasize = emphasized @@ -57,14 +60,20 @@ public struct Font: Hashable, Sendable { } /// Selects whether or not to italicize the font. + /// + /// - Paramater emphasized: Whether to italicize the font. + /// - Returns: The updated font. public func italic(_ italic: Bool = true) -> Font { var font = self font.overlay.italicize = italic return font } - /// Overrides the font's weight. Takes an optional for convenience. Does - /// nothing if given `nil`. + /// Overrides the font's weight. + /// + /// - Paramater weight: The font's new weight. If `nil`, this method does + /// nothing. + /// - Returns: The updated font. public func weight(_ weight: Weight?) -> Font { var font = self if let weight { @@ -73,8 +82,11 @@ public struct Font: Hashable, Sendable { return font } - /// Overrides the font's design. Takes an optional for convenience. Does - /// nothing if given `nil`. + /// Overrides the font's design. + /// + /// - Paramater design: The font's new design. If `nil`, this method does + /// nothing. + /// - Returns: The updated font. public func design(_ design: Design?) -> Font { var font = self if let design { @@ -84,6 +96,9 @@ public struct Font: Hashable, Sendable { } /// Overrides the font's point size. + /// + /// - Paramater design: The font's new point size. + /// - Returns: The updated font. public func pointSize(_ pointSize: Double) -> Font { var font = self font.overlay.pointSize = pointSize @@ -92,6 +107,10 @@ public struct Font: Hashable, Sendable { } /// Scales the font's point size and line height by a given factor. + /// + /// - Parameter factor: The factor to scale the point size and line height + /// by. + /// - Returns: The updated font. public func scaled(by factor: Double) -> Font { var font = self font.overlay.pointSizeScaleFactor *= factor @@ -100,6 +119,9 @@ public struct Font: Hashable, Sendable { } /// Selects whether or not to use the font's monospaced variant. + /// + /// - Parameter monospaced: Whether to use the font's monospaced variant. + /// - Returns: The updated font. public func monospaced(_ monospaced: Bool = true) -> Font { var font = self if monospaced { @@ -136,20 +158,31 @@ public struct Font: Hashable, Sendable { /// /// The cases are in order of increasing weight. public enum Weight: Hashable, Sendable, CaseIterable, Codable { + /// The ultra-light weight. case ultraLight + /// The thin weight. case thin + /// The light weight. case light + /// The regular weight. case regular + /// The medium weight. case medium + /// The semibold weight. case semibold + /// The bold weight. case bold + /// The heavy weight. case heavy + /// The black weight. case black } /// A font's design. public enum Design: Hashable, Sendable, CaseIterable, Codable { + /// The default design. case `default` + /// The monospaced design. case monospaced } @@ -167,7 +200,7 @@ public struct Font: Hashable, Sendable { var lineHeightScaleFactor: Double = 1 /// Overrides the font's weight. Applied before (i.e. overridden by) - /// ``Self/isEmphasized``. + /// ``emphasize``. var weight: Weight? /// If `true`, overrides the font's weight with the font's emphasized /// weight. If `false`, does nothing. Applied after the ``weight`` @@ -181,8 +214,13 @@ public struct Font: Hashable, Sendable { /// Overrides the font's design. var design: Design? - /// Applies an overlay to a resolved font. Requires an emphasized weight - /// for the resolved font. + /// Applies an overlay to a resolved font. + /// + /// - Parameters: + /// - resolvedFont: The font to apply the overlay to. Passed as + /// `inout`. + /// - emphasizedWeight: The weight to use for the font's emphasized + /// variant. func apply( to resolvedFont: inout Font.Resolved, emphasizedWeight: Weight @@ -210,10 +248,13 @@ public struct Font: Hashable, Sendable { } } + /// A resolved font. public struct Resolved: Hashable, Sendable { + /// A font identifier. public struct Identifier: Hashable, Sendable { package var kind: Kind + /// The system font. public static let system = Self(kind: .system) package enum Kind: Hashable { @@ -221,11 +262,17 @@ public struct Font: Hashable, Sendable { } } + /// The font's identifier. public var identifier: Identifier + /// The font's point size. public var pointSize: Double + /// The font's line height, in points. public var lineHeight: Double + /// The font's weight. public var weight: Weight + /// The font's design. public var design: Design + /// Whether the font is italicized. public var isItalic: Bool } diff --git a/Sources/SwiftCrossUI/Values/HorizontalAlignment.swift b/Sources/SwiftCrossUI/Values/HorizontalAlignment.swift index db00d9002a..8ec2230998 100644 --- a/Sources/SwiftCrossUI/Values/HorizontalAlignment.swift +++ b/Sources/SwiftCrossUI/Values/HorizontalAlignment.swift @@ -1,9 +1,13 @@ /// Alignment of items layed out along the horizontal axis. public enum HorizontalAlignment: Sendable { + /// Leading alignment (left in left-to-right locales). case leading + /// Center alignment. case center + /// Trailing alignment (right in left-to-right locales). case trailing + /// Converts this value to a ``StackAlignment``. var asStackAlignment: StackAlignment { switch self { case .leading: diff --git a/Sources/SwiftCrossUI/Values/Orientation.swift b/Sources/SwiftCrossUI/Values/Orientation.swift index 01ac11dac3..47831021eb 100644 --- a/Sources/SwiftCrossUI/Values/Orientation.swift +++ b/Sources/SwiftCrossUI/Values/Orientation.swift @@ -1,6 +1,8 @@ /// The orientation of a view (usually in reference to a stack view). public enum Orientation: Sendable { + /// The horizontal orientation. case horizontal + /// The vertical orientation. case vertical /// The orientation perpendicular to this one. diff --git a/Sources/SwiftCrossUI/Values/Path.swift b/Sources/SwiftCrossUI/Values/Path.swift index 8981376349..8ad18f031a 100644 --- a/Sources/SwiftCrossUI/Values/Path.swift +++ b/Sources/SwiftCrossUI/Values/Path.swift @@ -171,27 +171,49 @@ public struct AffineTransform: Equatable, Sendable, CustomDebugStringConvertible } public struct Path: Sendable { - /// A rectangle in 2-D space. + /// A rectangle in 2D space. /// /// This type is inspired by `CGRect`. public struct Rect: Equatable, Sendable { + /// The rectangle's origin (its top-leading corner), as an x/y vector. public var origin: SIMD2 + /// The rectangle's size, as a width/height vector. public var size: SIMD2 + /// Creates a ``Path/Rect`` instance. + /// + /// - Parameters: + /// - origin: The rectangle's origin (its top-leading corner), as an + /// x/y vector. + /// - size: The rectangle's size, as a width/height vector. public init(origin: SIMD2, size: SIMD2) { self.origin = origin self.size = size } + /// The X position of the rectangle's leading edge. public var x: Double { origin.x } + /// The Y position of the rectangle's top edge. public var y: Double { origin.y } + /// The rectangle's width. public var width: Double { size.x } + /// The rectangle's height. public var height: Double { size.y } + /// The position of the rectangle's center. public var center: SIMD2 { size * 0.5 + origin } + /// The X position of the rectangle's trailing edge. public var maxX: Double { size.x + origin.x } + /// The Y position of the rectangle's bottom edge. public var maxY: Double { size.y + origin.y } + /// Creates a ``Path/Rect`` instance. + /// + /// - Parameters: + /// - x: The X position of the rectangle's leading edge. + /// - y: The Y position of the rectangle's top edge. + /// - width: The rectangle's width. + /// - height: The rectangle's height. public init(x: Double, y: Double, width: Double, height: Double) { origin = SIMD2(x: x, y: y) size = SIMD2(x: width, y: height) @@ -199,16 +221,39 @@ public struct Path: Sendable { } /// The types of actions that can be performed on a path. + /// + /// The first action's starting point is (0, 0). public enum Action: Equatable, Sendable { + /// Moves to the specified point without drawing anything. case moveTo(SIMD2) - /// If this is the first action in a path then (0, 0) is inferred to be the start point. + /// Draws a line from the path's current point to the specified point. case lineTo(SIMD2) - /// If this is the first action in a path then (0, 0) is inferred to be the start point. + /// Draws an order-2 curve from the path's current point, bending + /// towards `control`, and ending at `endPoint`. + /// + /// After this, the path's current point will be `endPoint`. case quadCurve(control: SIMD2, end: SIMD2) - /// If this is the first action in a path then (0, 0) is inferred to be the start point. - case cubicCurve(control1: SIMD2, control2: SIMD2, end: SIMD2) + /// Draws an order-3 curve starting at the path's current point, bending + /// towards `control1` and `control2`, and ending at `endPoint`. + /// + /// After this, the path's current point will be `endPoint`. + case cubicCurve( + control1: SIMD2, + control2: SIMD2, + end: SIMD2 + ) + /// Draws a rectangle. case rectangle(Rect) + /// Draws a circle with the specified `center` and `radius`. case circle(center: SIMD2, radius: Double) + /// Draws an arc segment with the specified `center` and `radius`, + /// starting at `startAngle` and ending at `endAngle`. + /// + /// `startAngle` and `endAngle` are measured in radians clockwise from + /// the trailing direction, and must be between 0 and 2π, inclusive. + /// + /// If `clockwise` is `true`, the arc is drawn in a clockwise direction; + /// otherwise, it is drawn counterclockwise. case arc( center: SIMD2, radius: Double, @@ -216,20 +261,28 @@ public struct Path: Sendable { endAngle: Double, clockwise: Bool ) + /// Applies a transform to the currently drawn path. case transform(AffineTransform) + /// Performs each action in order. + /// + /// ``transform(_:)`` actions inside of a `subpath` do not affect + /// the outer path. case subpath([Action]) } /// A list of every action that has been performed on this path. /// - /// This property is meant for backends implementing paths. If the backend has a similar - /// path type built-in (such as `UIBezierPath` or `GskPathBuilder`), constructing the - /// path should consist of looping over this array and calling the method that corresponds - /// to each action. + /// This property is meant for backends implementing paths. If the backend + /// has a similar path type built-in (such as `UIBezierPath` or + /// `GskPathBuilder`), constructing the path should consist of looping over + /// this array and calling the method that corresponds to each action. public private(set) var actions: [Action] = [] + /// The fill rule for this path. public private(set) var fillRule: FillRule = .evenOdd + /// The stroke style for this path. public private(set) var strokeStyle = StrokeStyle(width: 1.0) + /// Creates a ``Path`` instance. public init() {} /// Move the path's current point to the given point. @@ -238,8 +291,12 @@ public struct Path: Sendable { /// /// If ``addLine(to:)``, ``addQuadCurve(control:to:)``, /// ``addCubicCurve(control1:control2:to:)``, or - /// ``addArc(center:radius:startAngle:endAngle:clockwise:)`` is called on an empty path - /// without calling this method first, the start point is implicitly (0, 0). + /// ``addArc(center:radius:startAngle:endAngle:clockwise:)`` is called on an + /// empty path without calling this method first, the start point is + /// implicitly (0, 0). + /// + /// - Parameter point: The point to move to. + /// - Returns: The updated path. public consuming func move(to point: SIMD2) -> Path { actions.append(.moveTo(point)) return self @@ -247,7 +304,11 @@ public struct Path: Sendable { /// Add a line segment from the current point to the given point. /// - /// After this, the path's current point will be the endpoint of this line segment. + /// After this, the path's current point will be the endpoint of this line + /// segment. + /// + /// - Parameter point: The point to draw the line to. + /// - Returns: The updated path. public consuming func addLine(to point: SIMD2) -> Path { actions.append(.lineTo(point)) return self @@ -255,9 +316,14 @@ public struct Path: Sendable { /// Add a quadratic Bézier curve to the path. /// - /// This creates an order-2 curve starting at the path's current point, bending towards - /// `control`, and ending at `endPoint`. After this, the path's current point will be - /// `endPoint`. + /// This creates an order-2 curve starting at the path's current point, + /// bending towards `control`, and ending at `endPoint`. After this, the + /// path's current point will be `endPoint`. + /// + /// - Parameters: + /// - control: The control point. + /// - endPoint: The end point. + /// - Returns: The updated path. public consuming func addQuadCurve( control: SIMD2, to endPoint: SIMD2 @@ -268,9 +334,15 @@ public struct Path: Sendable { /// Add a cubic Bézier curve to the path. /// - /// This creates an order-3 curve starting at the path's current point, bending towards - /// `control1` and `control2`, and ending at `endPoint`. After this, the path's current - /// point will be `endPoint`. + /// This creates an order-3 curve starting at the path's current point, + /// bending towards `control1` and `control2`, and ending at `endPoint`. + /// After this, the path's current point will be `endPoint`. + /// + /// - Parameters: + /// - control1: The first control point. + /// - control2: The second control point. + /// - endPoint: The end point. + /// - Returns: The updated path. public consuming func addCubicCurve( control1: SIMD2, control2: SIMD2, @@ -280,11 +352,21 @@ public struct Path: Sendable { return self } + /// Adds a rectangle to the path. + /// + /// - Parameter rect: The rectangle to add. + /// - Returns: The updated path. public consuming func addRectangle(_ rect: Rect) -> Path { actions.append(.rectangle(rect)) return self } + /// Adds a circle to the path. + /// + /// - Parameters: + /// - center: The circle's center. + /// - radius: The circle's radius. + /// - Returns: The updated path. public consuming func addCircle(center: SIMD2, radius: Double) -> Path { actions.append(.circle(center: center, radius: radius)) return self @@ -292,18 +374,23 @@ public struct Path: Sendable { /// Add an arc segment to the path. /// - /// After this, the path's current point will be the endpoint implied by `center`, `radius`, - /// and `endAngle`. + /// After this, the path's current point will be the endpoint implied by + /// `center`, `radius`, and `endAngle`. + /// /// - Parameters: /// - center: The location of the center of the circle. /// - radius: The radius of the circle. - /// - startAngle: The angle of the start of the arc, measured in radians clockwise from - // right. Must be between 0 and 2pi (inclusive). - /// - endAngle: The angle of the end of the arc, measured in radians clockwise from right. - /// Must be between 0 and 2pi (inclusive). - /// - clockwise: `true` if the arc is to be drawn clockwise, `false` if the arc is to - /// be drawn counter-clockwise. Used to determine which of the two possible arcs to - /// draw between the given start and end angles. + /// - startAngle: The angle of the start of the arc, measured in radians + /// clockwise from the trailing direction. Must be between 0 and 2π, + /// inclusive. + /// - endAngle: The angle of the end of the arc, measured in radians + /// clockwise from the trailing direction. Must be between 0 and 2π, + /// inclusive. + /// - clockwise: `true` if the arc is to be drawn clockwise, `false` if + /// the arc is to be drawn counter-clockwise. Used to determine which of + /// the two possible arcs to draw between the given start and end + /// angles. + /// - Returns: The updated path. public consuming func addArc( center: SIMD2, radius: Double, @@ -326,8 +413,11 @@ public struct Path: Sendable { /// Apply the given transform to the segments in the path so far. /// - /// While this may adjust the path's current point, it does not otherwise affect segments - /// that are added to the path after this method call. + /// While this may adjust the path's current point, it does not otherwise + /// affect segments that are added to the path after this method call. + /// + /// - Parameter transform: The transform to apply. + /// - Returns: The updated path. public consuming func applyTransform(_ transform: AffineTransform) -> Path { actions.append(.transform(transform)) return self @@ -335,10 +425,13 @@ public struct Path: Sendable { /// Add the entirety of another path as part of this path. /// - /// This can be necessary to section off transforms, as transforms applied to `subpath` - /// will not affect this path. + /// This can be necessary to section off transforms, as transforms applied + /// to `subpath` will not affect this path. /// /// The fill rule and preferred stroke style of the subpath are ignored. + /// + /// - Parameter subpath: The subpath to add. + /// - Returns: The updated path. public consuming func addSubpath(_ subpath: Path) -> Path { actions.append(.subpath(subpath.actions)) return self @@ -346,14 +439,21 @@ public struct Path: Sendable { /// Set the default stroke style for the path. /// - /// This is not necessarily respected; it can be overridden by ``Shape/stroke(_:style:)``, - /// and is lost when the path is passed to ``addSubpath(_:)``. + /// This is not necessarily respected; it can be overridden by + /// ``Shape/stroke(_:style:)``, and is lost when the path is passed to + /// ``addSubpath(_:)``. + /// + /// - Parameter style: The stroke style to set. + /// - Returns: The updated path. public consuming func stroke(style: StrokeStyle) -> Path { strokeStyle = style return self } /// Set the fill rule for the path. + /// + /// - Parameter rule: The fill rule to set. + /// - Returns: The updated path. public consuming func fillRule(_ rule: FillRule) -> Path { fillRule = rule return self @@ -361,6 +461,15 @@ public struct Path: Sendable { } extension Path { + /// Conditionally modifies a path. + /// + /// - Parameters: + /// - condition: The condition to check. + /// - ifTrue: The action to perform if `condition` is `true`. Receives + /// a copy of the path. + /// - ifFalse: The action to perform if `condition` is `false`. Receives + /// a copy of the path. Defaults to leaving the path unchanged. + /// - Returns: The updated path. @inlinable public consuming func `if`( _ condition: Bool, diff --git a/Sources/SwiftCrossUI/Values/PresentationDetent.swift b/Sources/SwiftCrossUI/Values/PresentationDetent.swift index 7aedeb66b1..223adf3715 100644 --- a/Sources/SwiftCrossUI/Values/PresentationDetent.swift +++ b/Sources/SwiftCrossUI/Values/PresentationDetent.swift @@ -6,13 +6,14 @@ public enum PresentationDetent: Sendable, Hashable { /// A detent that represents a large (full-height) sheet. case large - /// A detent at a custom fractional height of the available space. - /// Falls back to medium on iOS 15. - /// - Parameter fraction: A value between 0 and 1 representing the fraction of available height. + /// A detent at a custom fractional height (between 0 and 1) of the + /// available space. + /// + /// Falls back to ``medium`` on iOS 15. case fraction(Double) /// A detent at a specific fixed height in points. - /// Falls back to medium on iOS 15. - /// - Parameter height: The height + /// + /// Falls back to ``medium`` on iOS 15. case height(Double) } diff --git a/Sources/SwiftCrossUI/Values/StackAlignment.swift b/Sources/SwiftCrossUI/Values/StackAlignment.swift index 56bc81d13b..d9507e5db7 100644 --- a/Sources/SwiftCrossUI/Values/StackAlignment.swift +++ b/Sources/SwiftCrossUI/Values/StackAlignment.swift @@ -1,6 +1,9 @@ /// Alignment of items layed out along either a horizontal or vertical axis. public enum StackAlignment: Sendable { + /// Leading alignment (left/top for left-to-right locales). case leading + /// Center alignment. case center + /// Trailing alignment (right/bottom for left-to-right locales). case trailing } diff --git a/Sources/SwiftCrossUI/Values/TextStyle.swift b/Sources/SwiftCrossUI/Values/TextStyle.swift index a129d5be86..9e3f6d8a8d 100644 --- a/Sources/SwiftCrossUI/Values/TextStyle.swift +++ b/Sources/SwiftCrossUI/Values/TextStyle.swift @@ -29,15 +29,20 @@ extension Font { extension Font.TextStyle { /// A text style's resolved properties. public struct Resolved: Sendable { + /// The point size. public var pointSize: Double + /// The weight. public var weight: Font.Weight = .regular + /// The emphasized weight. public var emphasizedWeight: Font.Weight + /// The line height, in points. public var lineHeight: Double - /// Fallback to macOS's body text style. This isn't expected to ever - /// get used because it's only reachable if we forgot to supply text - /// styles for a new device class or miss a text style in a device - /// class' text style lookup table. + /// Fallback to macOS's body text style. + /// + /// This isn't expected to ever get used because it's only reachable if + /// we forgot to supply text styles for a new device class or missed a + /// text style in a device class' text style lookup table. static let fallback = Self( pointSize: 13, weight: .regular, @@ -46,10 +51,14 @@ extension Font.TextStyle { ) } - /// Resolves the text style's concrete text properties for the given - /// device class. Generally follows [Apple's typography guidelines](https://developer.apple.com/design/human-interface-guidelines/typography). - /// Our styles only differ from Apple's where Apple decided not to - /// specify a text style for a specific platform. + /// Resolves the text style's concrete text properties for the given device + /// class. + /// + /// Generally follows [Apple's typography guidelines][typography]. Our + /// styles only differ from Apple's where Apple decided not to specify a + /// text style for a specific platform. + /// + /// [typography]: https://developer.apple.com/design/human-interface-guidelines/typography public func resolve(for deviceClass: DeviceClass) -> Resolved { guard let textStyles = Self.resolvedTextStyles[deviceClass] else { logger.warning("missing text styles for device class \(deviceClass)") diff --git a/Sources/SwiftCrossUI/Values/VerticalAlignment.swift b/Sources/SwiftCrossUI/Values/VerticalAlignment.swift index b7d1c86e08..9adcae6456 100644 --- a/Sources/SwiftCrossUI/Values/VerticalAlignment.swift +++ b/Sources/SwiftCrossUI/Values/VerticalAlignment.swift @@ -1,9 +1,13 @@ /// Alignment of items layed out along the vertical axis. public enum VerticalAlignment: Sendable { + /// Top alignment. case top + /// Center alignment. case center + /// Bottom alignment. case bottom + /// Converts this value to a ``StackAlignment``. var asStackAlignment: StackAlignment { switch self { case .top: diff --git a/Sources/SwiftCrossUI/Values/Visibility.swift b/Sources/SwiftCrossUI/Values/Visibility.swift index 88a5aa2c35..dc28e36e5e 100644 --- a/Sources/SwiftCrossUI/Values/Visibility.swift +++ b/Sources/SwiftCrossUI/Values/Visibility.swift @@ -1,3 +1,9 @@ +/// The visibility of a component. public enum Visibility: Sendable { - case automatic, hidden, visible + /// The component is automatically hidden or shown based on context. + case automatic + /// The component is hidden. + case hidden + /// The component is visible. + case visible } diff --git a/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift index 5ade76afb9..47e4e02161 100644 --- a/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift @@ -78,6 +78,12 @@ public class AnyViewGraphNode { /// Computes a view's layout. Propagates to the view's children unless /// the given size proposal already has a cached result. + /// + /// - Parameters: + /// - newView: The recomputed view. + /// - proposedSize: The view's proposed size. + /// - environment: The current environment. + /// - Returns: The result of computing the view's layout. public func computeLayout( with newView: NodeView?, proposedSize: ProposedViewSize, @@ -93,14 +99,22 @@ public class AnyViewGraphNode { } /// Gets the node's wrapped view. + /// + /// - Returns: The node's wrapped view. public func getView() -> NodeView { _getNodeView() } + /// Gets the node's children. + /// + /// - Returns: The node's children. public func getChildren() -> any ViewGraphNodeChildren { _getNodeChildren() } + /// Gets the node's backend. + /// + /// - Returns: The node's backend. public func getBackend() -> any AppBackend { _getBackend() } diff --git a/Sources/SwiftCrossUI/ViewGraph/Box.swift b/Sources/SwiftCrossUI/ViewGraph/Box.swift index 5314aaa4a7..7d13f1d8a7 100644 --- a/Sources/SwiftCrossUI/ViewGraph/Box.swift +++ b/Sources/SwiftCrossUI/ViewGraph/Box.swift @@ -1,8 +1,13 @@ -/// A simple wrapper used to implement internal mutability. Mostly used by -/// dynamic property wrappers (see ``DynamicProperty``). +/// A simple wrapper used to implement internal mutability. +/// +/// Mostly used by dynamic property wrappers (see ``DynamicProperty``). class Box { + /// The underlying value. var value: V + /// Creates a box with its underlying value. + /// + /// - Parameter value: The box's underlying value. init(value: V) { self.value = value } diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift index 0cdd4a8067..52d89e53ec 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift @@ -33,6 +33,11 @@ public class ViewGraph { private var setIncomingURLHandler: (@escaping (URL) -> Void) -> Void /// Creates a view graph for a root view with a specific backend. + /// + /// - Parameters: + /// - view: The view to create a graph for. + /// - backend: The app's backend. + /// - environment: The current environment. public init( for view: Root, backend: Backend, @@ -49,6 +54,7 @@ public class ViewGraph { } /// Recomputes the entire UI (e.g. due to the root view's state updating). + /// /// If the update is due to the parent scene getting updated then the view /// is recomputed and passed as `newView`. public func computeLayout( diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift index dcadae0036..90d62c5f2a 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift @@ -161,10 +161,19 @@ public class ViewGraphNode: Sendable { } } - /// Recomputes the view's body and computes the layout of it and all its children - /// if necessary. If `newView` is provided (in the case that the parent's body got - /// updated) then it simply replaces the old view while inheriting the old view's - /// state. + /// Recomputes the view's body and computes its layout and the layout of + /// its children. + /// + /// The view may or may not propagate the update to its children depending + /// on the nature of the update. If `newView` is provided (in the case that + /// the parent's body got updated) then it simply replaces the old view + /// while inheriting the old view's state. + /// + /// - Parameters: + /// - newView: The recomputed view. + /// - proposedSize: The view's proposed size. + /// - environment: The current environment. + /// - Returns: The result of laying out the view. public func computeLayout( with newView: NodeView? = nil, proposedSize: ProposedViewSize, @@ -232,8 +241,10 @@ public class ViewGraphNode: Sendable { /// Commits the view's most recently computed layout and any view state changes /// that have occurred since the last update (e.g. text content changes or font - /// size changes). Returns the most recently computed layout for convenience, - /// although it's guaranteed to match the result of the last call to computeLayout. + /// size changes). + /// + /// - Returns: The most recently computed layout. Guaranteed to match the + /// result of the last call to ``computeLayout(with:proposedSize:environment:)``. public func commit() -> ViewLayoutResult { backend.show(widget: widget) diff --git a/Sources/SwiftCrossUI/Views/AnyView.swift b/Sources/SwiftCrossUI/Views/AnyView.swift index c83d533404..07020682a9 100644 --- a/Sources/SwiftCrossUI/Views/AnyView.swift +++ b/Sources/SwiftCrossUI/Views/AnyView.swift @@ -1,10 +1,10 @@ import Foundation -/// A view which erases the type of its child. Useful in dynamic -/// use-cases such as hot reloading, but not recommended if there -/// are alternate strongly-typed solutions to your problem since -/// ``AnyView`` has significantly more overhead than strongly -/// typed views. +/// A view which erases the type of its child. +/// +/// Useful in dynamic use-cases such as hot reloading, but not recommended if +/// there are alternate strongly-typed solutions to your problem since +/// ``AnyView`` has significantly more overhead than strongly typed views. public struct AnyView: TypeSafeView { typealias Children = AnyViewChildren diff --git a/Sources/SwiftCrossUI/Views/Button.swift b/Sources/SwiftCrossUI/Views/Button.swift index dc86fdc694..e85e0d87a9 100644 --- a/Sources/SwiftCrossUI/Views/Button.swift +++ b/Sources/SwiftCrossUI/Views/Button.swift @@ -8,6 +8,10 @@ public struct Button: Sendable { var width: Int? /// Creates a button that displays a custom label. + /// + /// - Parameters: + /// - label: The label to show on the button. + /// - action: The action to be performed when the button is clicked. public init(_ label: String, action: @escaping @MainActor @Sendable () -> Void = {}) { self.label = label self.action = action diff --git a/Sources/SwiftCrossUI/Views/Checkbox.swift b/Sources/SwiftCrossUI/Views/Checkbox.swift index 1d9d810f9b..e6256b09a3 100644 --- a/Sources/SwiftCrossUI/Views/Checkbox.swift +++ b/Sources/SwiftCrossUI/Views/Checkbox.swift @@ -1,9 +1,13 @@ /// A checkbox control that is either on or off. +/// +/// This corresponds to the ``ToggleStyle/checkbox`` toggle style. struct Checkbox: ElementaryView, View { /// Whether the checkbox is active or not. private var active: Binding /// Creates a checkbox. + /// + /// - Parameter active: Whether the checkbox is active or not. public init(active: Binding) { self.active = active } diff --git a/Sources/SwiftCrossUI/Views/Divider.swift b/Sources/SwiftCrossUI/Views/Divider.swift index f57182703b..0a18fb32a4 100644 --- a/Sources/SwiftCrossUI/Views/Divider.swift +++ b/Sources/SwiftCrossUI/Views/Divider.swift @@ -1,6 +1,9 @@ -/// A divider that expands along the minor axis of the containing stack layout -/// (or horizontally otherwise). In dark mode it's white with 10% opacity, and -/// in light mode it's black with 10% opacity. +/// A divider that expands along the minor axis of the containing stack layout. +/// +/// If not contained within a stack, this view expands horizontally. +/// +/// In dark mode it's white with 10% opacity, and in light mode it's black with +/// 10% opacity. public struct Divider: View { @Environment(\.colorScheme) var colorScheme @Environment(\.layoutOrientation) var layoutOrientation @@ -14,6 +17,7 @@ public struct Divider: View { } } + /// Creates a divider. public init() {} public var body: some View { diff --git a/Sources/SwiftCrossUI/Views/ElementaryView.swift b/Sources/SwiftCrossUI/Views/ElementaryView.swift index 3e93b86f22..78149b9603 100644 --- a/Sources/SwiftCrossUI/Views/ElementaryView.swift +++ b/Sources/SwiftCrossUI/Views/ElementaryView.swift @@ -1,6 +1,7 @@ /// A complimentary protocol for ``View`` to simplify implementation of -/// elementary (i.e. atomic) views which have no children. Think of them -/// as the leaves at the end of the view tree. +/// elementary (i.e. atomic) views which have no children. +/// +/// Think of elementary views as the leaves at the end of the view tree. @MainActor protocol ElementaryView: View where Content == EmptyView { func asWidget( diff --git a/Sources/SwiftCrossUI/Views/EmptyView.swift b/Sources/SwiftCrossUI/Views/EmptyView.swift index 90cc881507..ca65ffee8b 100644 --- a/Sources/SwiftCrossUI/Views/EmptyView.swift +++ b/Sources/SwiftCrossUI/Views/EmptyView.swift @@ -1,12 +1,20 @@ -/// A placeholder view used by elementary ``View`` implementations which don't have bodies. Fatally -/// crashes if rendered. +/// A placeholder view used by elementary ``View`` implementations which don't +/// have bodies. +/// +/// Triggers a fatal error if rendered. public struct EmptyView: View, Sendable { + /// The nonexistent body of an ``EmptyView``. + /// + /// - Warning: Do not access this property directly; it will trigger a fatal + /// error at runtime. public var body: Never { return fatalError("Rendered EmptyView") } - /// Creates a placeholder view (will crash if used in a ``View`` that doesn't override the default - /// widget creation code, not intended for regular use). + /// Creates an instance of ``EmptyView``. + /// + /// This will crash if used in a ``View`` that doesn't override the default + /// widget creation code; it's not intended for regular use. public nonisolated init() {} public func children( diff --git a/Sources/SwiftCrossUI/Views/ForEach.swift b/Sources/SwiftCrossUI/Views/ForEach.swift index b050be0658..a1128247f7 100644 --- a/Sources/SwiftCrossUI/Views/ForEach.swift +++ b/Sources/SwiftCrossUI/Views/ForEach.swift @@ -9,7 +9,13 @@ public struct ForEach where Items.Index == Int { } extension ForEach where Child == [MenuItem] { - /// Creates a view that creates child views on demand based on a collection of data. + /// Creates a view that creates child views on demand based on a collection + /// of data. + /// + /// - Parameters: + /// - elements: The collection to build an array of views from. + /// - child: A menu items builder that takes an element of `elements` and + /// returns an appropriate list of menu items. @_disfavoredOverload public init( _ elements: Items, @@ -21,7 +27,13 @@ extension ForEach where Child == [MenuItem] { } extension ForEach where Items == [Int] { - /// Creates a view that creates child views on demand based on a given ClosedRange + /// Creates a view that creates child views on demand based on a given + /// `ClosedRange`. + /// + /// - Parameters: + /// - range: The range to build an array of views from. + /// - child: A view builder that takes an element of `range` and returns + /// an appropriate view. @_disfavoredOverload public init( _ range: ClosedRange, @@ -31,7 +43,13 @@ extension ForEach where Items == [Int] { self.child = child } - /// Creates a view that creates child views on demand based on a given Range + /// Creates a view that creates child views on demand based on a given + /// `Range`. + /// + /// - Parameters: + /// - range: The range to build an array of views from. + /// - child: A view builder that takes an element of `range` and returns + /// an appropriate view. @_disfavoredOverload public init( _ range: Range, @@ -49,7 +67,16 @@ extension ForEach: TypeSafeView, View where Child: View { return EmptyView() } - /// Creates a view that creates child views on demand based on a collection of data. + /// Creates a view that creates child views on demand based on a collection + /// of data. + /// + /// One instance of `child` will be rendered for every element in + /// `elements`. + /// + /// - Parameters: + /// - elements: The collection to build an array of views from. + /// - child: A view builder that takes an element of `elements` and + /// returns an appropriate view. public init( _ elements: Items, @ViewBuilder _ child: @escaping (Items.Element) -> Child diff --git a/Sources/SwiftCrossUI/Views/Group.swift b/Sources/SwiftCrossUI/Views/Group.swift index c2143070be..613bc1f1b0 100644 --- a/Sources/SwiftCrossUI/Views/Group.swift +++ b/Sources/SwiftCrossUI/Views/Group.swift @@ -3,7 +3,9 @@ public struct Group: View { public var body: Content - /// Creates a horizontal stack with the given spacing. + /// Creates a group. + /// + /// - Parameter content: The content of this group. public init(@ViewBuilder content: () -> Content) { self.init(content: content()) } diff --git a/Sources/SwiftCrossUI/Views/HStack.swift b/Sources/SwiftCrossUI/Views/HStack.swift index 58784ba470..0a64d04113 100644 --- a/Sources/SwiftCrossUI/Views/HStack.swift +++ b/Sources/SwiftCrossUI/Views/HStack.swift @@ -7,7 +7,13 @@ public struct HStack: View { /// The alignment of the stack's children in the vertical direction. private var alignment: VerticalAlignment - /// Creates a horizontal stack with the given spacing. + /// Creates a horizontal stack with the given spacing and alignment. + /// + /// - Parameters: + /// - alignment: The alignment of the stack's children in the vertical + /// direction. + /// - spacing: The amount of spacing to apply between children. + /// - content: The content of this stack. public init( alignment: VerticalAlignment = .center, spacing: Int? = nil, diff --git a/Sources/SwiftCrossUI/Views/Image.swift b/Sources/SwiftCrossUI/Views/Image.swift index b363c2919d..974a08b382 100644 --- a/Sources/SwiftCrossUI/Views/Image.swift +++ b/Sources/SwiftCrossUI/Views/Image.swift @@ -3,7 +3,9 @@ import ImageFormats /// A view that displays an image. public struct Image: Sendable { + /// Whether the image is resizable. private var isResizable = false + /// The source of the image. private var source: Source enum Source: Equatable { @@ -11,16 +13,21 @@ public struct Image: Sendable { case image(ImageFormats.Image) } - /// Displays an image file. `png`, `jpg`, and `webp` are supported. + /// Creates an image view. + /// + /// `png`, `jpg`, and `webp` are supported. + /// /// - Parameters: - /// - url: The url of the file to display. - /// - useFileExtension: If `true`, the file extension is used to determine the file type, - /// otherwise the first few ('magic') bytes of the file are used. + /// - url: The URL of the file to display. + /// - useFileExtension: If `true`, the file extension is used to determine + /// the file type, otherwise the first few ('magic') bytes of the file + /// are used. public init(_ url: URL, useFileExtension: Bool = true) { source = .url(url, useFileExtension: useFileExtension) } /// Displays an image from raw pixel data. + /// /// - Parameter image: The image data to display. public init(_ image: ImageFormats.Image) { source = .image(image) diff --git a/Sources/SwiftCrossUI/Views/List.swift b/Sources/SwiftCrossUI/Views/List.swift index 8253c78c39..26dff53900 100644 --- a/Sources/SwiftCrossUI/Views/List.swift +++ b/Sources/SwiftCrossUI/Views/List.swift @@ -1,14 +1,25 @@ +/// A view that displays a selectable list of views. public struct List: TypeSafeView, View { typealias Children = ListViewChildren> public let body = EmptyView() + /// The current selection, if any. var selection: Binding var rowContent: (Int) -> RowView var associatedSelectionValue: (Int) -> SelectionValue var find: (SelectionValue) -> Int? var rowCount: Int + /// Creates a list view. + /// + /// - Parameters: + /// - data: A collection of `Identifiable` values to construct the list + /// from. + /// - selection: A binding to the ID of the value that is currently + /// selected. + /// - rowContent: A view builder that renders a single row of the list. + /// Receives an element of `data`. public init( _ data: Data, selection: Binding, @@ -17,6 +28,14 @@ public struct List: TypeSafeView, View self.init(data, id: \.id, selection: selection, rowContent: rowContent) } + /// Creates a list view that renders `Text` views based on the elements of + /// `data`. + /// + /// - Parameters: + /// - data: A collection of `Identifiable` values to construct the list + /// from. + /// - selection: A binding to the ID of the value that is currently + /// selected. public init( _ data: Data, selection: Binding @@ -32,6 +51,15 @@ public struct List: TypeSafeView, View } } + /// Creates a list view that renders `Text` views based on the elements of + /// `data`. + /// + /// - Parameters: + /// - data: A collection of values to construct the list from. + /// - id: A closure that returns the ID to use for a given element of + /// `data`. + /// - selection: A binding to the ID of the value that is currently + /// selected. public init( _ data: Data, id: @escaping (Data.Element) -> SelectionValue, @@ -42,6 +70,14 @@ public struct List: TypeSafeView, View } } + /// Creates a list view that renders `Text` views based on the elements of + /// `data`. + /// + /// - Parameters: + /// - data: A collection of values to construct the list from. + /// - id: A key path to the ID to use for an element of `data`. + /// - selection: A binding to the ID of the value that is currently + /// selected. public init( _ data: Data, id: KeyPath, @@ -52,6 +88,15 @@ public struct List: TypeSafeView, View } } + /// Creates a list view. + /// + /// - Parameters: + /// - data: A collection of values to construct the list from. + /// - id: A key path to the ID to use for an element of `data`. + /// - selection: A binding to the ID of the value that is currently + /// selected. + /// - rowContent: A view builder that renders a single row of the list. + /// Receives an element of `data`. public init( _ data: Data, id: KeyPath, @@ -61,6 +106,16 @@ public struct List: TypeSafeView, View self.init(data, id: { $0[keyPath: id] }, selection: selection, rowContent: rowContent) } + /// Creates a list view. + /// + /// - Parameters: + /// - data: A collection of values to construct the list from. + /// - id: A closure that returns the ID to use for a given element of + /// `data`. + /// - selection: A binding to the ID of the value that is currently + /// selected. + /// - rowContent: A view builder that renders a single row of the list. + /// Receives an element of `data`. public init( _ data: Data, id: @escaping (Data.Element) -> SelectionValue, diff --git a/Sources/SwiftCrossUI/Views/Menu.swift b/Sources/SwiftCrossUI/Views/Menu.swift index cf9a78b80c..da15c48bfc 100644 --- a/Sources/SwiftCrossUI/Views/Menu.swift +++ b/Sources/SwiftCrossUI/Views/Menu.swift @@ -1,13 +1,20 @@ /// A button that shows a popover menu when clicked. /// -/// Due to technical limitations, the minimum supported OS's for menu buttons in UIKitBackend -/// are iOS 14 and tvOS 17. +/// Due to technical limitations, the minimum supported OS's for menu buttons in +/// UIKitBackend are iOS 14 and tvOS 17. public struct Menu: Sendable { + /// The menu's label. public var label: String + /// The menu's items. public var items: [MenuItem] var buttonWidth: Int? + /// Creates a menu. + /// + /// - Parameters: + /// - label: The menu's label. + /// - items: The menu's items. public init(_ label: String, @MenuItemsBuilder items: () -> [MenuItem]) { self.label = label self.items = items() diff --git a/Sources/SwiftCrossUI/Views/MenuItem.swift b/Sources/SwiftCrossUI/Views/MenuItem.swift index 11d47e25b7..46bd8546fb 100644 --- a/Sources/SwiftCrossUI/Views/MenuItem.swift +++ b/Sources/SwiftCrossUI/Views/MenuItem.swift @@ -1,6 +1,9 @@ /// An item of a ``Menu`` or ``CommandMenu``. public enum MenuItem: Sendable { + /// A button. case button(Button) + /// Text. case text(Text) + /// A submenu. case submenu(Menu) } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift index 8d95d2a6e2..d2c4abdc3b 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift @@ -1,6 +1,8 @@ extension View { /// Disables user interaction in any subviews that support disabling /// interaction. + /// + /// - Parameter disabled: Whether to disable user interaction. public func disabled(_ disabled: Bool = true) -> some View { EnvironmentModifier(self) { environment in environment.with(\.isEnabled, !disabled) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift index fd5cf30988..98531481cc 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift @@ -1,4 +1,10 @@ extension View { + /// A view modifier that runs an action whenever a piece of state changes. + /// + /// - Parameters: + /// - value: The value to observe for changes. Must be `Equatable`. + /// - initial: Whether to call `action` when the view first appears. + /// - action: The action to perform. public func onChange( of value: Value, initial: Bool = false, diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift index 4cd346a44e..142193eb5b 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift @@ -1,8 +1,13 @@ extension View { - /// Adds an action to perform when the user's pointer enters/leaves this view. - public func onHover(perform action: @escaping (_ hovering: Bool) -> Void) - -> some View - { + /// Adds an action to perform when the user's pointer enters/leaves this + /// view. + /// + /// - Parameter action: The action to perform when the hover state changes. + /// Receives a `Bool` parameter indicated whether the pointer entered or + /// left the view. + public func onHover( + perform action: @escaping (_ hovering: Bool) -> Void + ) -> some View { OnHoverModifier(body: TupleView1(self), action: action) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift index 9b52440bd1..d99975c03a 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift @@ -1,7 +1,13 @@ import Foundation extension View { - public func onOpenURL(perform action: @escaping (URL) -> Void) -> some View { + /// Performs an action whenever a URL is opened. + /// + /// - Parameter action: The action to perform when a URL is opened. Receives + /// the URL in question. + public func onOpenURL( + perform action: @escaping (URL) -> Void + ) -> some View { PreferenceModifier(self) { preferences, environment in var newPreferences = preferences newPreferences.onOpenURL = { url in diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift index 9f79a02fdb..707ef3b961 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift @@ -4,6 +4,8 @@ extension View { /// handlers get called before inner `onSubmit` handlers. To prevent /// submissions from propagating upwards, use ``View/submitScope()`` after /// adding the handler. + /// + /// - Parameter action: The action to perform. public func onSubmit(perform action: @escaping () -> Void) -> some View { EnvironmentModifier(self) { environment in environment.with(\.onSubmit) { diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift index 1feb583a42..380d234d63 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift @@ -1,14 +1,17 @@ +/// A type of tap gesture. public struct TapGesture: Sendable, Hashable { package var kind: TapGestureKind - /// The idiomatic "primary" interaction for the device, such as a left-click with the mouse - /// or normal tap on a touch screen. + /// The idiomatic "primary" interaction for the device, such as a left-click + /// with the mouse or normal tap on a touch screen. public static let primary = TapGesture(kind: .primary) - /// The idiomatic "secondary" interaction for the device, such as a right-click with the - /// mouse or long press on a touch screen. + /// The idiomatic "secondary" interaction for the device, such as a + /// right-click with the mouse or long press on a touch screen. public static let secondary = TapGesture(kind: .secondary) - /// A long press of the same interaction type as ``primary``. May be equivalent to - /// ``secondary`` on some backends, particularly on mobile devices. + /// A long press of the same interaction type as ``primary``. + /// + /// May be equivalent to ``secondary`` on some backends, particularly on + /// mobile devices. public static let longPress = TapGesture(kind: .longPress) package enum TapGestureKind { @@ -19,11 +22,16 @@ public struct TapGesture: Sendable, Hashable { extension View { /// Adds an action to perform when the user taps or clicks this view. /// - /// Any tappable elements within the view will no longer be tappable with the same gesture - /// type. - public func onTapGesture(gesture: TapGesture = .primary, perform action: @escaping () -> Void) - -> some View - { + /// Any tappable elements within the view will no longer be tappable with + /// the same gesture type. + /// + /// - Parameters: + /// - gesture: The type of gesture to listen for. + /// - action: The action to perform. + public func onTapGesture( + gesture: TapGesture = .primary, + perform action: @escaping () -> Void + ) -> some View { OnTapGestureModifier(body: TupleView1(self), gesture: gesture, action: action) } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift index a2da3d6882..4b77616cfc 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift @@ -1,13 +1,16 @@ extension View { - /// Modifies size proposals to match the specified aspect ratio, or the view's - /// ideal aspect ratio if unspecified. + /// Modifies size proposals to match the specified aspect ratio, or the + /// view's ideal aspect ratio if unspecified. /// /// This modifier doesn't guarantee that the view's size will maintain a /// constant aspect ratio, as it only changes the size proposed to the /// wrapped content. - /// - Parameter aspectRatio: The aspect ratio to maintain. Use `nil` to - /// maintain the view's ideal aspect ratio. - /// - Parameter contentMode: How the view should fill available space. + /// + /// - Parameters: + /// - aspectRatio: The aspect ratio to maintain. Use `nil` to + /// maintain the view's ideal aspect ratio. + /// - contentMode: How the view should fill available space. + /// - Returns: A view with its aspect ratio configured. public func aspectRatio(_ aspectRatio: Double? = nil, contentMode: ContentMode) -> some View { AspectRatioView(self, aspectRatio: aspectRatio, contentMode: contentMode) } @@ -17,9 +20,12 @@ extension View { /// This modifier doesn't guarantee that the view's size will maintain a /// constant aspect ratio, as it only changes the size proposed to the /// wrapped content. - /// - Parameter aspectRatio: The aspect ratio to maintain, specified as a - /// size with the desired aspect ratio. - /// - Parameter contentMode: How the view should fill available space. + /// + /// - Parameters: + /// - aspectRatio: The aspect ratio to maintain, specified as a + /// size with the desired aspect ratio. + /// - contentMode: How the view should fill available space. + /// - Returns: A view with its aspect ratio configured. public func aspectRatio(_ aspectRatio: SIMD2, contentMode: ContentMode) -> some View { AspectRatioView( self, diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift index c408871021..7bf239d27c 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift @@ -1,4 +1,7 @@ extension View { + /// Sets the background of this view to another view. + /// + /// - Parameter background: The view to set as this view's background. public func background(_ background: Background) -> some View { BackgroundModifier(background: background, foreground: self) } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift index 9000ff2a18..4d7dba571d 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift @@ -1,8 +1,14 @@ extension View { + /// Locks this view's size on both the horizontal and vertical axes. public func fixedSize() -> some View { FixedSizeModifier(self, horizontal: true, vertical: true) } + /// Locks this view's size. + /// + /// - Parameters: + /// - horizontal: Whether to lock this view's size on the horizontal axis. + /// - vertical: Whether to lock this view's size on the vertical axis. public func fixedSize(horizontal: Bool, vertical: Bool) -> some View { FixedSizeModifier(self, horizontal: horizontal, vertical: vertical) } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift index 1ebf5e9824..46d77bc154 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift @@ -1,5 +1,13 @@ extension View { - /// Positions this view within an invisible frame having the specified minimum size constraints. + /// Positions this view within an invisible frame having the specified + /// minimum size constraints. + /// + /// - Parameters: + /// - width: The view's exact width. `nil` lets the view choose its own + /// width instead. + /// - height: The view's exact height. `nil` lets the view choose its own + /// height instead. + /// - alignment: How to align this view within its container. public func frame( width: Int? = nil, height: Int? = nil, @@ -13,7 +21,23 @@ extension View { ) } - /// Positions this view within an invisible frame having the specified minimum size constraints. + /// Positions this view within an invisible frame having the specified + /// minimum size constraints. + /// + /// - Parameters: + /// - minWidth: The view's minimum width. `nil` means there is no minimum + /// width. + /// - idealWidth: The view's ideal width. `nil` lets the view choose its + /// own width instead. + /// - maxWidth: The view's maximum width. `nil` means there is no maximum + /// width. + /// - minHeight: The view's minimum height. `nil` means there is no + /// minimum height. + /// - idealHeight: The view's ideal height. `nil` lets the view choose its + /// own height instead. + /// - maxHeight: The view's maximum height. `nil` means there is no + /// maximum height. + /// - alignment: How to align this view within its container. public func frame( minWidth: Int? = nil, idealWidth: Int? = nil, diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift index 7907863a1a..bc5832183a 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift @@ -1,6 +1,8 @@ extension View { /// Sets the alignment of lines of text relative to each other in multiline /// text views. + /// + /// - Parameter alignment: The direction to align the text in. public func multilineTextAlignment(_ alignment: HorizontalAlignment) -> some View { return EnvironmentModifier(self) { environment in return environment.with(\.multilineTextAlignment, alignment) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift index 51cc165e64..c33c391cb3 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift @@ -1,4 +1,7 @@ extension View { + /// Overlays another view on top of this view. + /// + /// - Parameter content: The view to overlay this view with. public func overlay(@ViewBuilder content: () -> some View) -> some View { OverlayModifier(content: self, overlay: content()) } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift index d2429b6942..e608c6534c 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift @@ -1,19 +1,30 @@ extension View { - /// Adds padding to a view. If `amount` is `nil`, then a backend-specific default value - /// is used. Separate from ``View/padding(_:_:)`` because overload resolution didn't + /// Adds padding to a view. + /// + /// Separate from ``View/padding(_:_:)`` because overload resolution didn't /// like the double default parameters. + /// + /// - Parameter amount: The amount of padding to use. If `nil`, a + /// backend-specific default value is used. public func padding(_ amount: Int? = nil) -> some View { return padding(.all, amount) } - /// Adds padding to a view. If `amount` is `nil`, then a backend-specific - /// default value is used. + /// Adds padding to a view. + /// + /// - Parameters: + /// - edges: The edges to apply the padding to. Defaults to + /// ``Edge/Set/all``. + /// - amount: The amount of padding to use. If `nil`, a backend-specific + /// default value is used. public func padding(_ edges: Edge.Set = .all, _ amount: Int? = nil) -> some View { let insets = EdgeInsets.Internal(edges: edges, amount: amount) return PaddingModifierView(body: TupleView1(self), insets: insets) } /// Adds padding to a view with a different amount for each edge. + /// + /// - Parameter insets: The edge insets to use. public func padding(_ insets: EdgeInsets) -> some View { return PaddingModifierView(body: TupleView1(self), insets: EdgeInsets.Internal(insets)) } @@ -21,9 +32,13 @@ extension View { /// Insets for the sides of a rectangle. Generally used to represent view padding. public struct EdgeInsets: Equatable { + /// The top inset. public var top: Int + /// The bottom inset. public var bottom: Int + /// The leading inset. public var leading: Int + /// The trailing inset. public var trailing: Int /// The total inset along each axis. @@ -35,6 +50,12 @@ public struct EdgeInsets: Equatable { } /// Constructs edge insets from individual insets. + /// + /// - Parameters: + /// - top: The top inset. + /// - bottom: The bottom inset. + /// - leading: The leading inset. + /// - trailing: The trailing inset. public init(top: Int = 0, bottom: Int = 0, leading: Int = 0, trailing: Int = 0) { self.top = top self.bottom = bottom diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift index 79c6479aca..a6f5c04d72 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift @@ -6,6 +6,8 @@ extension View { /// view's ``View/body`` and before the view appears on screen. Currently, /// if these docs have been kept up to date, the action gets called just /// before creating the view's widget. + /// + /// - Parameter action: The action to perform when this view appears. public func onAppear(perform action: @escaping @MainActor () -> Void) -> some View { OnAppearModifier(body: TupleView1(self), action: action) } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift index 0d214cbef1..4cff79695e 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift @@ -4,6 +4,8 @@ extension View { /// `onDisappear` actions on outermost views are called first and propagate /// down to the leaf views due to essentially relying on the `deinit` of the /// modifier view's ``ViewGraphNode``. + /// + /// - Parameter action: The action to perform when this view disappears. public func onDisappear(perform action: @escaping @Sendable @MainActor () -> Void) -> some View { OnDisappearModifier(body: TupleView1(self), action: action) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift index d0cd8e797c..6451bc4ca3 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift @@ -6,6 +6,11 @@ extension View { /// This variant of `task` can be useful when the lifetime of the task /// must be linked to a value with a potentially shorter lifetime than the /// view. + /// + /// - Parameters: + /// - id: The ID of the task. + /// - priority: The priority of the task. + /// - action: The action to perform within the task. public nonisolated func task( id: Id, priority: TaskPriority = .userInitiated, @@ -21,6 +26,10 @@ extension View { /// Starts a task before a view appears (but after ``View/body`` has been /// accessed), and cancels the task when the view disappears. + /// + /// - Parameters: + /// - priority: The priority of the task. + /// - action: The action to perform within the task. public nonisolated func task( priority: TaskPriority = .userInitiated, _ action: @escaping () async -> Void diff --git a/Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift b/Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift index 0b6d460f85..64bd20ed8f 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift @@ -1,41 +1,53 @@ extension View { - /// Sets the detents (heights) for the enclosing sheet presentation to snap to - /// when the user resizes it interactively. + /// Sets the detents (heights) for the enclosing sheet presentation to snap + /// to when the user resizes it interactively. /// /// Detents are only respected on platforms that support sheet resizing, /// and sheet resizing is generally only supported on mobile. /// /// If no detents are specified, then a single default detent is used. The - /// default is platform-specific. On iOS the default is `.large`. + /// default is platform-specific. On iOS the default is + /// ``PresentationDetent/large``. /// - /// - Supported platforms: iOS & Mac Catalyst 15+ (ignored on unsupported platforms) - /// - `.fraction` and `.height` fall back to `.medium` on iOS 15 and earlier + /// - Supported platforms: iOS & Mac Catalyst 15+ (ignored on unsupported + /// platforms) + /// - ``PresentationDetent/fraction(_:)`` and + /// ``PresentationDetent/height(_:)`` fall back to + /// ``PresentationDetent/medium`` on iOS 15 and earlier /// /// - Parameter detents: A set of detents that the sheet can be resized to. - /// - Returns: A view with the presentationDetents preference set. + /// - Returns: A view with the ``PreferenceValues/presentationDetents`` + /// preference set. public func presentationDetents(_ detents: Set) -> some View { preference(key: \.presentationDetents, value: Array(detents)) } /// Sets the corner radius for the enclosing sheet presentation. /// - /// - Supported platforms: iOS & Mac Catalyst 15+, Gtk4 (ignored on unsupported platforms) + /// - Supported platforms: iOS & Mac Catalyst 15+, Gtk4 (ignored on + /// unsupported platforms) /// /// - Parameter radius: The corner radius in points. - /// - Returns: A view with the presentationCornerRadius preference set. + /// - Returns: A view with the ``PreferenceValues/presentationCornerRadius`` + /// preference set. public func presentationCornerRadius(_ radius: Double) -> some View { preference(key: \.presentationCornerRadius, value: radius) } - /// Sets the visibility of the enclosing sheet presentation's drag indicator. + /// Sets the visibility of the enclosing sheet presentation's drag + /// indicator. + /// /// Drag indicators are only supported on platforms that support sheet - /// resizing, and sheet resizing is generally only support on mobile. + /// resizing, and sheet resizing is generally only supported on mobile. /// - /// - Supported platforms: iOS & Mac Catalyst 15+ (ignored on unsupported platforms) + /// - Supported platforms: iOS & Mac Catalyst 15+ (ignored on unsupported + /// platforms) /// /// - Parameter visibility: The visibility to use for the drag indicator of /// the enclosing sheet. - /// - Returns: A view with the presentationDragIndicatorVisibility preference set. + /// - Returns: A view with the + /// ``PreferenceValues/presentationDragIndicatorVisibility`` preference + /// set. public func presentationDragIndicatorVisibility( _ visibility: Visibility ) -> some View { @@ -44,8 +56,10 @@ extension View { /// Sets the background of the enclosing sheet presentation. /// - /// - Parameter color: The background color to use for the enclosing sheet presentation. - /// - Returns: A view with the presentationBackground preference set. + /// - Parameter color: The background color to use for the enclosing sheet + /// presentation. + /// - Returns: A view with the ``PreferenceValues/presentationBackground`` + /// preference set. public func presentationBackground(_ color: Color) -> some View { preference(key: \.presentationBackground, value: color) } @@ -57,7 +71,8 @@ extension View { /// dismissals. /// /// - Parameter isDisabled: Whether interactive dismissal is disabled. - /// - Returns: A view with the interactiveDismissDisabled preference set. + /// - Returns: A view with the + /// ``PreferenceValues/interactiveDismissDisabled`` preference set. public func interactiveDismissDisabled(_ isDisabled: Bool = true) -> some View { preference(key: \.interactiveDismissDisabled, value: isDisabled) } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/ScrollDismissesKeyboardModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/ScrollDismissesKeyboardModifier.swift index 0655a8e907..6633c0ffc0 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/ScrollDismissesKeyboardModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/ScrollDismissesKeyboardModifier.swift @@ -1,12 +1,12 @@ extension View { - /// Configures the behavior in which scrollable content interacts with - /// the software keyboard. + /// Configures the behavior in which scrollable content interacts with the + /// software keyboard. /// - /// You use this modifier to customize how scrollable content interacts - /// with the software keyboard. For example, you can specify a value of - /// ``ScrollDismissesKeyboardMode/immediately`` to indicate that you - /// would like scrollable content to immediately dismiss the keyboard if - /// present when a scroll drag gesture begins. + /// You use this modifier to customize how scrollable content interacts with + /// the software keyboard. For example, you can specify a value of + /// ``ScrollDismissesKeyboardMode/immediately`` to indicate that you would + /// like scrollable content to immediately dismiss the keyboard if present + /// when a scroll drag gesture begins. /// /// @State private var text = "" /// @@ -23,11 +23,12 @@ extension View { /// behavior for other kinds of scrollable views, like a ``List`` or a /// ``TextEditor``. /// - /// By default, scrollable content dismisses the keyboard interactively as the user scrolls. - /// Pass a different value of ``ScrollDismissesKeyboardMode`` to change this behavior. - /// For example, use ``ScrollDismissesKeyboardMode/never`` to prevent the keyboard from - /// dismissing automatically. Note that ``TextEditor`` may still use a different - /// default to preserve expected editing behavior. + /// By default, scrollable content dismisses the keyboard interactively as + /// the user scrolls. Pass a different value of + /// ``ScrollDismissesKeyboardMode`` to change this behavior. For example, + /// use ``ScrollDismissesKeyboardMode/never`` to prevent the keyboard from + /// dismissing automatically. Note that ``TextEditor`` may still use a + /// different default to preserve expected editing behavior. /// /// - Parameter mode: The keyboard dismissal mode that scrollable content /// uses. diff --git a/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift index edfe1c577f..f3778a0e58 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift @@ -1,5 +1,6 @@ extension View { - /// Presents a conditional modal overlay. `onDismiss` gets invoked when the sheet is dismissed. + /// Presents a conditional modal overlay. `onDismiss` gets invoked when the + /// sheet is dismissed. /// /// On most platforms sheets appear as form-style modals. On tvOS, sheets /// appear as full screen overlays (non-opaque). @@ -14,6 +15,7 @@ extension View { /// - isPresented: A binding controlling whether the sheet is presented. /// - onDismiss: An action to perform when the sheet is dismissed /// by the user. + /// - content: The content of the sheet public func sheet( isPresented: Binding, onDismiss: (() -> Void)? = nil, diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Style/ColorSchemeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Style/ColorSchemeModifier.swift index ac719dc051..9829e3c3ee 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Style/ColorSchemeModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Style/ColorSchemeModifier.swift @@ -1,4 +1,7 @@ extension View { + /// Sets the color scheme for this view. + /// + /// - Parameter colorScheme: The color scheme to set. public func colorScheme(_ colorScheme: ColorScheme) -> some View { EnvironmentModifier(self) { environment in environment.with(\.colorScheme, colorScheme) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift index b6eb15e100..a80e29e659 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Style/FontModifier.swift @@ -3,34 +3,39 @@ extension View { /// modifiers within the contained view, unlike other font-related /// modifiers such as ``View/fontWeight(_:)`` and ``View/emphasized()`` /// which override the font properties of all contained text. + /// + /// - Parameter font: The font to set. public func font(_ font: Font) -> some View { EnvironmentModifier(self) { environment in environment.with(\.font, font) } } - /// Overrides the font weight of any contained text. Optional for - /// convenience. If given `nil`, does nothing. + /// Overrides the font weight of any contained text. + /// + /// - Parameter weight: The font weight to set. If `nil`, this modifier + /// does nothing. public func fontWeight(_ weight: Font.Weight?) -> some View { EnvironmentModifier(self) { environment in environment.with(\.fontOverlay.weight, weight) } } - /// Overrides the font design of any contained text. Optional for - /// convenience. If given `nil`, does nothing. + /// Overrides the font design of any contained text. + /// + /// - Parameter design: The font design to set. If `nil`, this modifier + /// does nothing. public func fontDesign(_ design: Font.Design?) -> some View { EnvironmentModifier(self) { environment in environment.with(\.fontOverlay.design, design) } } - /// Forces any contained text to be bold, or if the a contained font is - /// a ``Font/TextStyle``, forces the style's emphasized weight to be - /// used. + /// Forces any contained text to be bold, or if the a contained font is a + /// ``Font/TextStyle``, forces the style's emphasized weight to be used. /// - /// Deprecated and renamed for clarity. Use ``View.fontWeight(_:)`` - /// to make text bold. + /// Deprecated and renamed for clarity. Use ``fontWeight(_:)`` to make text + /// bold. @available( *, deprecated, message: "Use View.emphasized() instead", diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Style/ForegroundColorModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Style/ForegroundColorModifier.swift index 120fcb2e60..ad7d8b7b73 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Style/ForegroundColorModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Style/ForegroundColorModifier.swift @@ -1,5 +1,7 @@ extension View { /// Sets the color of the foreground elements displayed by this view. + /// + /// - Parameter color: The new foreground color. public func foregroundColor(_ color: Color) -> some View { return EnvironmentModifier(self) { environment in return environment.with(\.foregroundColor, color) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift index e6258ea45d..6b04df6131 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift @@ -1,5 +1,7 @@ extension View { - /// Sets the style of the toggle. + /// Sets the style of toggles contained within this view. + /// + /// - Parameter toggleStyle: The new toggle style. public func toggleStyle(_ toggleStyle: ToggleStyle) -> some View { return EnvironmentModifier(self) { environment in return environment.with(\.toggleStyle, toggleStyle) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/TextContentTypeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/TextContentTypeModifier.swift index b6d267d4ef..3efccaa8b2 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/TextContentTypeModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/TextContentTypeModifier.swift @@ -1,8 +1,10 @@ extension View { /// Set the content type of text fields. /// - /// This controls autocomplete suggestions, and on mobile devices, which on-screen keyboard - /// is shown. + /// This controls autocomplete suggestions, and on mobile devices, which + /// on-screen keyboard is shown. + /// + /// - Parameter type: The content type for text fields. public func textContentType(_ type: TextContentType) -> some View { EnvironmentModifier(self) { environment in environment.with(\.textContentType, type) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/TextSelectionModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/TextSelectionModifier.swift index 6d044b163a..8e95f15302 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/TextSelectionModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/TextSelectionModifier.swift @@ -1,5 +1,7 @@ extension View { /// Sets selectability of contained text. Ignored on tvOS. + /// + /// - Parameter isEnabled: Whether text selection is enabled. public func textSelectionEnabled(_ isEnabled: Bool = true) -> some View { EnvironmentModifier( self, diff --git a/Sources/SwiftCrossUI/Views/NavigationLink.swift b/Sources/SwiftCrossUI/Views/NavigationLink.swift index 388e704c4e..c9bae73c8b 100644 --- a/Sources/SwiftCrossUI/Views/NavigationLink.swift +++ b/Sources/SwiftCrossUI/Views/NavigationLink.swift @@ -21,7 +21,12 @@ public struct NavigationLink: View { /// Creates a navigation link that presents the view corresponding to a value. /// The link is handled by whatever ``NavigationStack`` is sharing the same /// navigation path. - public init(_ label: String, value: C, path: Binding) { + /// + /// - Parameters: + /// - label: The label to display on the button. + /// - value: The value to append to the navigation path when clicked. + /// - path: The navigation path to append to when clicked. + public init(_ label: String, value: some Codable, path: Binding) { self.label = label self.value = value self.path = path diff --git a/Sources/SwiftCrossUI/Views/NavigationPath.swift b/Sources/SwiftCrossUI/Views/NavigationPath.swift index fbe50d0f69..42b3da6edb 100644 --- a/Sources/SwiftCrossUI/Views/NavigationPath.swift +++ b/Sources/SwiftCrossUI/Views/NavigationPath.swift @@ -2,34 +2,40 @@ import Foundation /// A type-erased list of data representing the content of a navigation stack. /// -/// If you are persisting a path using the ``Codable`` implementation, you must +/// If you are persisting a path using the `Codable` implementation, you must /// not change type definitions in a non-backwards compatible way. Otherwise, /// the path may fail to decode. public struct NavigationPath { - /// A storage class used so that we have control over exactly which changes are published (to - /// avoid infinite loops). + /// A storage class used so that we have control over exactly which changes + /// are published (to avoid infinite loops). private class Storage { - /// An entry that will be decoded next time it is used by a ``NavigationStack`` (we need to - /// wait until we know what concrete entry types are available). + /// An entry that will be decoded next time it is used by a + /// ``NavigationStack`` (we need to wait until we know what concrete + /// entry types are available). struct EncodedEntry: Codable { var type: String var value: Data } - /// The current path. If both this and `encodedEntries` are non-empty, the elements in path - /// were added before the navigation path was even used to render a view. By design they - /// come after the encodedEntries (because they can only be the result of appending and - /// maybe popping). + /// The current path. + /// + /// If both this and ``encodedEntries`` are non-empty, the elements in + /// `path` were added before the navigation path was even used to render + /// a view. By design they come after the `encodedEntries` (because they + /// can only be the result of appending and maybe popping). var path: [any Codable] = [] - /// Entries that will be encoded when this navigation path is first used by a - /// ``NavigationStack``. It is not possible to decode the entries without first knowing - /// what types the path can possibly contain (which only the ``NavigationStack`` will know). + /// Entries that will be encoded when this navigation path is first used + /// by a ``NavigationStack``. + /// + /// It is not possible to decode the entries without first knowing what + /// types the path can possibly contain (which only the + /// ``NavigationStack`` will know). var encodedEntries: [EncodedEntry] = [] } - /// The path and any elements waiting to be decoded are stored in a class so that changes are - /// triggered from within NavigationStack when decoding the elements (which causes an infinite - /// loop of updates). + /// The path and any elements waiting to be decoded are stored in a class so + /// that changes are triggered from within ``NavigationStack`` when decoding + /// the elements (which causes an infinite loop of updates). private var storage = Storage() /// Indicates whether this path is empty. @@ -46,13 +52,17 @@ public struct NavigationPath { public init() {} /// Appends a new value to the end of the path. - public mutating func append(_ component: C) { + /// + /// - Parameter component: The component to append. + public mutating func append(_ component: some Codable) { storage.path.append(component) } /// Removes values from the end of this path. /// - /// - Parameter k: The number of elements to remove from the path. ``k`` must be greater than or equal to zero. + /// - Precondition: `k >= 0`. + /// + /// - Parameter k: The number of elements to remove from the path. public mutating func removeLast(_ k: Int = 1) { precondition(k >= 0, "`k` must be greater than or equal to zero") if k < storage.path.count { @@ -71,11 +81,17 @@ public struct NavigationPath { storage.encodedEntries.removeAll() } - /// Gets the path's current entries. If the path was decoded from a stored representation and - /// has not been used by a ``NavigationStack`` yet, the ``destinationTypes`` will be used to - /// decode all elements in the path. Without knowing the ``destinationTypes``, the entries - /// cannot be decoded (after macOS 11 they can be decoded by using `_typeByName`, but we can't - /// use that because of backwards compatibility). + /// Gets the path's current entries. + /// + /// If the path was decoded from a stored representation and has not been + /// used by a ``NavigationStack`` yet, the `destinationTypes` will be used + /// to decode all elements in the path. Without knowing the + /// `destinationTypes`, the entries cannot be decoded (after macOS 11 they + /// can be decoded by using `_typeByName`, but we can't use that because of + /// backwards compatibility). + /// + /// - Parameter destinationTypes: The types to use to decode the entries. + /// - Returns: The decoded entries. func path(destinationTypes: [any Codable.Type]) -> [any Codable] { guard !storage.encodedEntries.isEmpty else { return storage.path diff --git a/Sources/SwiftCrossUI/Views/NavigationSplitView.swift b/Sources/SwiftCrossUI/Views/NavigationSplitView.swift index a573dcdbed..9692efb974 100644 --- a/Sources/SwiftCrossUI/Views/NavigationSplitView.swift +++ b/Sources/SwiftCrossUI/Views/NavigationSplitView.swift @@ -22,11 +22,19 @@ public struct NavigationSplitView: ) } + /// The sidebar content. public var sidebar: Sidebar + /// The middle content. public var content: MiddleBar + /// The detail content. public var detail: Detail /// Creates a three column split view. + /// + /// - Parameters: + /// - sidebar: The sidebar content. + /// - content: The middle content. + /// - detail: The detail content. public init( @ViewBuilder sidebar: () -> Sidebar, @ViewBuilder content: () -> MiddleBar, @@ -40,6 +48,10 @@ public struct NavigationSplitView: extension NavigationSplitView where MiddleBar == EmptyView { /// Creates a two column split view. + /// + /// - Parameters: + /// - sidebar: The sidebar content. + /// - detail: The detail content. public init( @ViewBuilder sidebar: () -> Sidebar, @ViewBuilder detail: () -> Detail diff --git a/Sources/SwiftCrossUI/Views/NavigationStack.swift b/Sources/SwiftCrossUI/Views/NavigationStack.swift index 2dd5e0ae17..b8994ceb64 100644 --- a/Sources/SwiftCrossUI/Views/NavigationStack.swift +++ b/Sources/SwiftCrossUI/Views/NavigationStack.swift @@ -2,9 +2,11 @@ /// of a detail view. struct NavigationStackRootPath: Codable {} -/// A view that displays a root view and enables you to present additional views over the root view. +/// A view that displays a root view and enables you to present additional views +/// over the root view. /// -/// Use .navigationDestination(for:destination:) on this view instead of its children unlike Apples SwiftUI API. +/// Use ``navigationDestination(for:destination:)`` on this view instead of its +/// children unlike, Apple's SwiftUI API. public struct NavigationStack: View { public var body: some View { if let element = elements.last { @@ -45,9 +47,11 @@ public struct NavigationStack: View { return [NavigationStackRootPath()] + resolvedPath } - /// Creates a navigation stack with heterogeneous navigation state that you can control. + /// Creates a navigation stack with heterogeneous navigation state that you + /// can control. + /// /// - Parameters: - /// - path: A `Binding` to the navigation state for this stack. + /// - path: A ``Binding`` to the navigation state for this stack. /// - root: The view to display when the stack is empty. public init( path: Binding, @@ -64,16 +68,19 @@ public struct NavigationStack: View { } } - /// Associates a destination view with a presented data type for use within a navigation stack. + /// Associates a destination view with a presented data type for use within + /// a navigation stack. + /// + /// Add this view modifer to describe the view that the stack displays when + /// presenting a particular kind of data. Use a ``NavigationLink`` to + /// present the data. You can add more than one navigation destination + /// modifier to the stack if it needs to present more than one kind of data. /// - /// Add this view modifer to describe the view that the stack displays when presenting a particular - /// kind of data. Use a `NavigationLink` to present the data. You can add more than one navigation - /// destination modifier to the stack if it needs to present more than one kind of data. /// - Parameters: /// - data: The type of data that this destination matches. - /// - destination: A view builder that defines a view to display when the stack’s navigation - /// state contains a value of type data. The closure takes one argument, which is the value - /// of the data to present. + /// - destination: A view builder that defines a view to display when the + /// stack's navigation state contains a value of type data. The closure + /// takes one argument, which is the value of the data to present. public func navigationDestination( for data: D.Type, @ViewBuilder destination: @escaping (D) -> C @@ -110,7 +117,7 @@ public struct NavigationStack: View { /// Attempts to compute the detail view for the given element (the type of /// the element decides which detail is shown). Crashes if no suitable detail /// view is found. - func childOrCrash(for element: any Codable) -> Detail { + func childOrCrash(for element: some Codable) -> Detail { guard let child = child(element) else { fatalError( "Failed to find detail view for \"\(element)\", make sure you have called .navigationDestination for this type." diff --git a/Sources/SwiftCrossUI/Views/Picker.swift b/Sources/SwiftCrossUI/Views/Picker.swift index d7157fccb3..dff09d3981 100644 --- a/Sources/SwiftCrossUI/Views/Picker.swift +++ b/Sources/SwiftCrossUI/Views/Picker.swift @@ -2,7 +2,7 @@ public struct Picker: ElementaryView, View { /// The options to be offered by the picker. private var options: [Value] - /// The picker's selected option. + /// A binding to the picker's selected option. private var value: Binding /// The index of the selected option (if any). @@ -12,7 +12,12 @@ public struct Picker: ElementaryView, View { } } - /// Creates a new picker with the given options and a binding for the selected value. + /// Creates a new picker with the given options and a binding for the + /// selected value. + /// + /// - Parameters: + /// - options: The options to be offered by the picker. + /// - value: A binding to the picker's selected option. public init(of options: [Value], selection value: Binding) { self.options = options self.value = value diff --git a/Sources/SwiftCrossUI/Views/ProgressView.swift b/Sources/SwiftCrossUI/Views/ProgressView.swift index abd34ca147..bab6ccc658 100644 --- a/Sources/SwiftCrossUI/Views/ProgressView.swift +++ b/Sources/SwiftCrossUI/Views/ProgressView.swift @@ -1,7 +1,10 @@ import Foundation +/// A progress indicator, either a bar or a spinner. public struct ProgressView: View { + /// The label for this progress view. private var label: Label + /// The current progress, if this is a progress bar. private var progress: Double? private var kind: Kind @@ -29,11 +32,19 @@ public struct ProgressView: View { } } + /// Creates an indeterminate progress view (a spinner). + /// + /// - Parameter label: The label for this progress view. public init(_ label: Label) { self.label = label self.kind = .spinner } + /// Creates a progress bar. + /// + /// - Parameters: + /// - label: The label for this progress view. + /// - progress: The current progress. public init(_ label: Label, _ progress: Progress) { self.label = label self.kind = .bar @@ -43,8 +54,12 @@ public struct ProgressView: View { } } - /// Creates a progress bar view. If `value` is `nil`, an indeterminate progress - /// bar will be shown. + /// Creates a progress bar. + /// + /// - Parameters: + /// - label: The label for this progress view. + /// - value: The current progress. If `nil`, an indeterminate progress bar + /// will be shown. public init(_ label: Label, value: Value?) { self.label = label self.kind = .bar @@ -53,11 +68,16 @@ public struct ProgressView: View { } extension ProgressView where Label == EmptyView { + /// Creates an indeterminate progress view (a spinner). public init() { self.label = EmptyView() self.kind = .spinner } + /// Creates a progress bar. + /// + /// - Parameters: + /// - progress: The current progress. public init(_ progress: Progress) { self.label = EmptyView() self.kind = .bar @@ -67,8 +87,11 @@ extension ProgressView where Label == EmptyView { } } - /// Creates a progress bar view. If `value` is `nil`, an indeterminate progress - /// bar will be shown. + /// Creates a progress bar. + /// + /// - Parameters: + /// - value: The current progress. If `nil`, an indeterminate progress bar + /// will be shown. public init(value: Value?) { self.label = EmptyView() self.kind = .bar @@ -77,11 +100,19 @@ extension ProgressView where Label == EmptyView { } extension ProgressView where Label == Text { + /// Creates an indeterminate progress view (a spinner). + /// + /// - Parameter label: The label for this progress view. public init(_ label: String) { self.label = Text(label) self.kind = .spinner } + /// Creates a progress bar. + /// + /// - Parameters: + /// - label: The label for this progress view. + /// - progress: The current progress. public init(_ label: String, _ progress: Progress) { self.label = Text(label) self.kind = .bar @@ -91,8 +122,12 @@ extension ProgressView where Label == Text { } } - /// Creates a progress bar view. If `value` is `nil`, an indeterminate progress - /// bar will be shown. + /// Creates a progress bar. + /// + /// - Parameters: + /// - label: The label for this progress view. + /// - value: The current progress. If `nil`, an indeterminate progress bar + /// will be shown. public init(_ label: String, value: Value?) { self.label = Text(label) self.kind = .bar diff --git a/Sources/SwiftCrossUI/Views/ScrollView.swift b/Sources/SwiftCrossUI/Views/ScrollView.swift index 828f12a2b4..522232c72a 100644 --- a/Sources/SwiftCrossUI/Views/ScrollView.swift +++ b/Sources/SwiftCrossUI/Views/ScrollView.swift @@ -1,12 +1,19 @@ import Foundation -/// A view that is scrollable when it would otherwise overflow available space. Use the -/// ``View/frame`` modifier to constrain height if necessary. +/// A view that is scrollable when it would otherwise overflow available space. +/// +/// Use the ``View/frame(width:height:alignment:)`` modifier to constrain height +/// if necessary. public struct ScrollView: TypeSafeView, View { public var body: VStack public var axes: Axis.Set /// Wraps a view in a scrollable container. + /// + /// - Parameters: + /// - axes: The axes to enable scrolling on. Defaults to + /// ``Axis/Set/vertical``. + /// - content: The content of this scroll view. public init(_ axes: Axis.Set = .vertical, @ViewBuilder _ content: () -> Content) { self.axes = axes body = VStack(content: content()) diff --git a/Sources/SwiftCrossUI/Views/Shapes/Capsule.swift b/Sources/SwiftCrossUI/Views/Shapes/Capsule.swift index cdc02e05a1..c670722737 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/Capsule.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/Capsule.swift @@ -1,5 +1,7 @@ -/// A rounded rectangle whose corner radius is equal to half the length of its shortest side. +/// A rounded rectangle whose corner radius is equal to half the length of its +/// shortest side. public struct Capsule: Shape { + /// Creates a ``Capsule`` instance. public nonisolated init() {} public nonisolated func path(in bounds: Path.Rect) -> Path { diff --git a/Sources/SwiftCrossUI/Views/Shapes/Circle.swift b/Sources/SwiftCrossUI/Views/Shapes/Circle.swift index cfa8035afd..8ddaa73ed7 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/Circle.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/Circle.swift @@ -1,7 +1,12 @@ +/// A circle. +/// +/// Circles have equal widths and heights; the `Circle` shape will take on the +/// minimum of its proposed width and height. public struct Circle: Shape { - /// The ideal diameter of a Circle. + /// The ideal diameter of a `Circle`. static let idealDiameter = 10.0 + /// Creates a ``Circle`` instance. public nonisolated init() {} public nonisolated func path(in bounds: Path.Rect) -> Path { diff --git a/Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift b/Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift index eb6c6794ea..0e19176bfe 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift @@ -1,4 +1,6 @@ +/// An ellipse. public struct Ellipse: Shape { + /// Creates an ``Ellipse`` instance. public nonisolated init() {} public nonisolated func path(in bounds: Path.Rect) -> Path { diff --git a/Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift b/Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift index 21ce292fca..fd0f96d17e 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift @@ -1,4 +1,6 @@ +/// A rectangle. public struct Rectangle: Shape { + /// Creates a ``Rectangle`` instance. public nonisolated init() {} public nonisolated func path(in bounds: Path.Rect) -> Path { diff --git a/Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift b/Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift index 285249bcbc..be33e1e6b0 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift @@ -1,10 +1,16 @@ /// A rounded rectangle. /// -/// This is not necessarily four line segments and four circular arcs. If possible, this shape -/// uses smoother curves to make the transition between the edges and corners less abrupt. +/// This is not necessarily four line segments and four circular arcs. If +/// possible, this shape uses smoother curves to make the transition between the +/// edges and corners less abrupt. public struct RoundedRectangle { public var cornerRadius: Double + /// Creates a ``RoundedRectangle`` instance. + /// + /// - Precondition: `cornerRadius` must be finite and positive. + /// + /// - Parameter cornerRadius: The corner radius for this rounded rectangle. public init(cornerRadius: Double) { assert( cornerRadius >= 0.0 && cornerRadius.isFinite, @@ -12,14 +18,14 @@ public struct RoundedRectangle { self.cornerRadius = cornerRadius } - // This shape tries to mimic an order 5 superellipse, extending the sides with line segments. - // Since paths don't support quintic curves, I'm using an approximation consisting of - // two cubic curves and a line segment. This constant is the list of control points for - // the cubic curves. See https://www.desmos.com/calculator/chwx3ddx6u . - // - // Preconditions: - // - points.0 is the same as if a line segment and a circular arc were used - // - points.6.y == 0.0 + /// This shape tries to mimic an order 5 superellipse, extending the sides with line segments. + /// Since paths don't support quintic curves, I'm using an approximation consisting of + /// two cubic curves and a line segment. This constant is the list of control points for + /// the cubic curves. See https://www.desmos.com/calculator/chwx3ddx6u . + /// + /// Preconditions: + /// - points.0 is the same as if a line segment and a circular arc were used + /// - points.6.y == 0.0 fileprivate static let points = ( SIMD2(0.292893218813, 0.292893218813), SIMD2(0.517, 0.0687864376269), @@ -30,9 +36,9 @@ public struct RoundedRectangle { SIMD2(1.7, 0.0) ) - // This corresponds to r_{min} in the above Desmos link. This is the minimum ratio of - // cornerRadius to half the side length at which the superellipse is not applicable. Above this, - // line segments and circular arcs are used. + /// This corresponds to r_{min} in the above Desmos link. This is the minimum ratio of + /// cornerRadius to half the side length at which the superellipse is not applicable. Above this, + /// line segments and circular arcs are used. fileprivate static let rMin = 0.441968022436 } diff --git a/Sources/SwiftCrossUI/Views/Shapes/Shape.swift b/Sources/SwiftCrossUI/Views/Shapes/Shape.swift index c19a56030f..9e7a753432 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/Shape.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/Shape.swift @@ -1,13 +1,14 @@ -/// A 2-D shape that can be drawn as a view. +/// A 2D shape that can be drawn as a view. /// -/// If no stroke color or fill color is specified, the default is no stroke and a fill of the -/// current foreground color. +/// If no stroke color or fill color is specified, the default is no stroke and +/// a fill of the current foreground color. public protocol Shape: View, Sendable where Content == EmptyView { /// Draw the path for this shape. /// - /// The bounds passed to a shape that is immediately drawn as a view will always have an - /// origin of (0, 0). However, you may pass a different bounding box to subpaths. For example, - /// this code draws a rectangle in the left half of the bounds and an ellipse in the right half: + /// The bounds passed to a shape that is immediately drawn as a view will + /// always have an origin of (0, 0). However, you may pass a different + /// bounding box to subpaths. For example, this code draws a rectangle in + /// the left half of the bounds and an ellipse in the right half: /// ```swift /// func path(in bounds: Path.Rect) -> Path { /// Path() @@ -33,11 +34,16 @@ public protocol Shape: View, Sendable where Content == EmptyView { /// ) /// } /// ``` + /// + /// - Parameter bounds: The bounds of this shape. func path(in bounds: Path.Rect) -> Path + /// Determine the ideal size of this shape given the proposed bounds. /// /// The default implementation accepts the proposal, replacing unspecified - /// dimensions with `10`. + /// dimensions with 10. + /// + /// - Parameter proposal: The proposed bounds of this shape. /// - Returns: The shape's size for the given proposal. func size(fitting proposal: ProposedViewSize) -> ViewSize } diff --git a/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift b/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift index 7b04c9692e..a8671ce699 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift @@ -1,7 +1,11 @@ -/// A shape that has style information attached to it, including color and stroke style. +/// A shape that has style information attached to it, including color and +/// stroke style. public protocol StyledShape: Shape { + /// The shape's stroke color. var strokeColor: Color? { get } + /// The shape's fill color. var fillColor: Color? { get } + /// The shape's stroke style. var strokeStyle: StrokeStyle? { get } } diff --git a/Sources/SwiftCrossUI/Views/Slider.swift b/Sources/SwiftCrossUI/Views/Slider.swift index a7bc029c87..a351b75ea1 100644 --- a/Sources/SwiftCrossUI/Views/Slider.swift +++ b/Sources/SwiftCrossUI/Views/Slider.swift @@ -1,13 +1,13 @@ -/// A value convertible to and from a ``Double``.` +/// A value convertible to and from a `Double`. public protocol DoubleConvertible { - /// Creates a value from a ``Double``.` + /// Creates a value from a `Double`. init(_ value: Double) - /// Converts the value to a ``Double``.` + /// Converts the value to a `Double`. var doubleRepresentation: Double { get } } -/// A value represented by a ``BinaryFloatingPoint``. +/// A value represented by a `BinaryFloatingPoint`. struct FloatingPointValue: DoubleConvertible { var value: Value @@ -24,7 +24,7 @@ struct FloatingPointValue: DoubleConvertible { } } -/// A value represented by a ``BinaryInteger``. +/// A value represented by a `BinaryInteger`. struct IntegerValue: DoubleConvertible { var value: Value @@ -56,6 +56,11 @@ public struct Slider: ElementaryView, View { private var decimalPlaces: Int /// Creates a slider to select a value between a minimum and maximum value. + /// + /// - Parameters: + /// - value: A binding to the current value. + /// - minimum: The slider's minumum value. + /// - maximum: The slider's maximum value. public init(_ value: Binding? = nil, minimum: T, maximum: T) { if let value = value { self.value = Binding( @@ -73,6 +78,11 @@ public struct Slider: ElementaryView, View { } /// Creates a slider to select a value between a minimum and maximum value. + /// + /// - Parameters: + /// - value: A binding to the current value. + /// - minimum: The slider's minumum value. + /// - maximum: The slider's maximum value. public init(_ value: Binding? = nil, minimum: T, maximum: T) { if let value = value { self.value = Binding( diff --git a/Sources/SwiftCrossUI/Views/Spacer.swift b/Sources/SwiftCrossUI/Views/Spacer.swift index f0a22ce01a..6b387492d0 100644 --- a/Sources/SwiftCrossUI/Views/Spacer.swift +++ b/Sources/SwiftCrossUI/Views/Spacer.swift @@ -10,6 +10,8 @@ public struct Spacer: ElementaryView, View { /// Creates a spacer with a given minimum length along its axis or axes /// of expansion. + /// + /// - Parameter minLength: The spacer's minimum length. public init(minLength: Int? = nil) { self.minLength = minLength } diff --git a/Sources/SwiftCrossUI/Views/SplitView.swift b/Sources/SwiftCrossUI/Views/SplitView.swift index 86b8231ba8..a7ef387df2 100644 --- a/Sources/SwiftCrossUI/Views/SplitView.swift +++ b/Sources/SwiftCrossUI/Views/SplitView.swift @@ -1,11 +1,16 @@ import Foundation +/// A two-column split view. struct SplitView: TypeSafeView, View { typealias Children = SplitViewChildren, Detail> var body: TupleView2, Detail> - /// Creates a two column split view. + /// Creates a two-column split view. + /// + /// - Parameters: + /// - sidebar: The sidebar content. + /// - detail: The detail content. init(@ViewBuilder sidebar: () -> Sidebar, @ViewBuilder detail: () -> Detail) { body = TupleView2( EnvironmentModifier(sidebar()) { $0.with(\.listStyle, .sidebar) }, diff --git a/Sources/SwiftCrossUI/Views/Table.swift b/Sources/SwiftCrossUI/Views/Table.swift index 82d2f8efec..8f38357400 100644 --- a/Sources/SwiftCrossUI/Views/Table.swift +++ b/Sources/SwiftCrossUI/Views/Table.swift @@ -10,8 +10,17 @@ public struct Table>: TypeSafeVi /// ``Table/Row`` instances). private var columns: RowContent - /// Creates a table that computes its cell values based on a collection of rows. - public init(_ rows: [RowValue], @TableRowBuilder _ columns: () -> RowContent) { + /// Creates a table that computes its cell values based on a collection of + /// rows. + /// + /// - Parameters: + /// - rows: The row data to display. + /// - columns: The columns to display (which each compute their cell + /// values when given `Row` instances). + public init( + _ rows: [RowValue], + @TableRowBuilder _ columns: () -> RowContent + ) { self.rows = rows self.columns = columns() } diff --git a/Sources/SwiftCrossUI/Views/TableColumn.swift b/Sources/SwiftCrossUI/Views/TableColumn.swift index 4f8ea085b8..88b8502b87 100644 --- a/Sources/SwiftCrossUI/Views/TableColumn.swift +++ b/Sources/SwiftCrossUI/Views/TableColumn.swift @@ -8,6 +8,12 @@ public struct TableColumn { extension TableColumn { /// Creates a column. + /// + /// - Parameters: + /// - label: The label displayed at the top of the column (also known as + /// the column title). + /// - content: The content displayed for this column of each row of the + /// table. public init(_ label: String, @ViewBuilder content: @escaping (RowValue) -> Content) { self.label = label self.content = content @@ -15,7 +21,13 @@ extension TableColumn { } extension TableColumn where Content == Text { - /// Creates a column with that displays a string property and has a text label. + /// Creates a column with that displays a string property and has a text + /// label. + /// + /// - Parameters: + /// - label: The label displayed at the top of the column (also known as + /// the column title). + /// - keyPath: A key path to the string value to display. public init(_ label: String, value keyPath: KeyPath) { self.label = label self.content = { row in diff --git a/Sources/SwiftCrossUI/Views/Text.swift b/Sources/SwiftCrossUI/Views/Text.swift index 3c0f54b790..586d3467b3 100644 --- a/Sources/SwiftCrossUI/Views/Text.swift +++ b/Sources/SwiftCrossUI/Views/Text.swift @@ -6,48 +6,52 @@ /// ``View/fixedSize(horizontal:vertical:)`` with `horizontal` set to false and /// `vertical` set to true, but be aware that this may lead to unintuitive /// minimum sizing behaviour when used within a window. Often when developers -/// use ``fixedSize`` on text, what they really need is a ``ScrollView``. +/// use ``View/fixedSize()`` on text, what they really need is a ``ScrollView``. /// /// To avoid wrapping and truncation entirely, use ``View/fixedSize()``. /// /// ## Technical notes /// /// The reason that ``Text`` truncates its content to fit its proposed size is -/// that SwiftCrossUI's layout system behaves rather unintuitively with views that -/// trade off width for height. The layout system used to support this behaviour -/// well, but when overhauling the layout system with performance in mind, we -/// discovered that it's not possible to handle minimum view sizing in the -/// intuitive way that we were, without a large performance cost or layout system -/// complexity cost. +/// that SwiftCrossUI's layout system behaves rather unintuitively with views +/// that trade off width for height. The layout system used to support this +/// behaviour well, but when overhauling the layout system with performance in +/// mind, we discovered that it's not possible to handle minimum view sizing in +/// the intuitive way that we were, without a large performance cost or layout +/// system complexity cost. /// /// With the current system, windows determine the minimum size of their content /// by proposing a size of 0x0. A text view that doesn't truncate its content /// would take on a width of 0 and then lay out each character on a new line (as -/// that's what most UI frameworks do when text is given a small width). This leads -/// to the window thinking that its minimum height is `characterCount * lineHeight`, -/// even though when given a width larger than zero, the text view would be shorter -/// than this 'minimum height'. The underlying cause is the assumption that -/// 'minimum size' is a sensible notion for every view. A text view without -/// truncation doesn't have a 'minimum size'; are we minimizing width? minimizing -/// height? minimizing width + height? minimizing area? +/// that's what most UI frameworks do when text is given a small width). This +/// leads to the window thinking that its minimum height is +/// `characterCount * lineHeight`, even though when given a width larger than +/// zero, the text view would be shorter than this 'minimum height'. The +/// underlying cause is the assumption that 'minimum size' is a sensible notion +/// for every view. A text view without truncation doesn't have a +/// 'minimum size'; are we minimizing width? height? width + height? area? /// /// SwiftCrossUI's old layout system separated the concept of minimum size into /// 'minimum width for current height', and 'minimum height for current width'. /// This led to much more intuitive window sizing behaviour. If you had /// non-truncating text inside a window, and resized the width of the window -/// such that the height of the text became taller than the window, then the window -/// would become taller, and if you resized the height of the window then you'd reach -/// the window's minimum height before the text could overflow the window horizontally. -/// Unfortunately this required a lot of book-keeping, and was deemed to be unfeasible -/// to do without significantly hurting performance due to all the layout assumptions -/// that we'd have to drop from our stack layout algorithm. +/// such that the height of the text became taller than the window, then the +/// window would become taller, and if you resized the height of the window then +/// you'd reach the window's minimum height before the text could overflow the +/// window horizontally. Unfortunately this required a lot of book-keeping, and +/// was deemed to be unfeasible to do without significantly hurting performance +/// due to all the layout assumptions that we'd have to drop from our stack +/// layout algorithm. /// -/// The new layout system behaviour is in-line with SwiftUI's layout behaviour. +/// The new layout system behaviour is in line with SwiftUI's layout behaviour. public struct Text: Sendable { /// The string to be shown in the text view. var string: String - /// Creates a new text view that displays a string with configurable wrapping. + /// Creates a new text view that displays a string with configurable + /// wrapping. + /// + /// - Parameter string: The string to display. public init(_ string: String) { self.string = string } diff --git a/Sources/SwiftCrossUI/Views/TextEditor.swift b/Sources/SwiftCrossUI/Views/TextEditor.swift index 3e64f7b2f6..9da0e64fdd 100644 --- a/Sources/SwiftCrossUI/Views/TextEditor.swift +++ b/Sources/SwiftCrossUI/Views/TextEditor.swift @@ -1,7 +1,11 @@ /// A control for editing multiline text. public struct TextEditor: ElementaryView { + /// The editor's content. @Binding var text: String + /// Creates a text editor. + /// + /// - Parameter text: The editor's content. public init(text: Binding) { _text = text } diff --git a/Sources/SwiftCrossUI/Views/TextField.swift b/Sources/SwiftCrossUI/Views/TextField.swift index 01117d417a..bcc76605f6 100644 --- a/Sources/SwiftCrossUI/Views/TextField.swift +++ b/Sources/SwiftCrossUI/Views/TextField.swift @@ -9,6 +9,10 @@ public struct TextField: ElementaryView, View { private var value: Binding? /// Creates an editable text field with a given placeholder. + /// + /// - Parameters: + /// - placeholder: The label to show when the field is empty. + /// - text: The field's content. public init(_ placeholder: String = "", text: Binding) { self.placeholder = placeholder self.value = text diff --git a/Sources/SwiftCrossUI/Views/Toggle.swift b/Sources/SwiftCrossUI/Views/Toggle.swift index dc787811ea..46d41b5440 100644 --- a/Sources/SwiftCrossUI/Views/Toggle.swift +++ b/Sources/SwiftCrossUI/Views/Toggle.swift @@ -1,4 +1,7 @@ /// A control for toggling between two values (usually representing on and off). +/// +/// Depending on the value of ``EnvironmentValues/toggleStyle``, this control +/// can appear as a switch, a button, or a checkbox. public struct Toggle: View { @Environment(\.backend) var backend @Environment(\.toggleStyle) var toggleStyle @@ -9,6 +12,10 @@ public struct Toggle: View { var active: Binding /// Creates a toggle that displays a custom label. + /// + /// - Parameters: + /// - label: The label to be shown on or beside the toggle. + /// - active: Whether the toggle is active or not. public init(_ label: String, active: Binding) { self.label = label self.active = active diff --git a/Sources/SwiftCrossUI/Views/ToggleButton.swift b/Sources/SwiftCrossUI/Views/ToggleButton.swift index c6a1e12959..d583df5172 100644 --- a/Sources/SwiftCrossUI/Views/ToggleButton.swift +++ b/Sources/SwiftCrossUI/Views/ToggleButton.swift @@ -1,4 +1,6 @@ /// A button style control that is either on or off. +/// +/// This corresponds to the ``ToggleStyle/button`` toggle style. struct ToggleButton: ElementaryView, View { /// The label to show on the toggle button. private var label: String @@ -6,6 +8,10 @@ struct ToggleButton: ElementaryView, View { private var active: Binding /// Creates a toggle button that displays a custom label. + /// + /// - Parameters: + /// - label: The label to show on the toggle button. + /// - active: Whether the button is active or not. public init(_ label: String, active: Binding) { self.label = label self.active = active diff --git a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift b/Sources/SwiftCrossUI/Views/ToggleSwitch.swift index ccee23d89b..b3fa42e6dc 100644 --- a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift +++ b/Sources/SwiftCrossUI/Views/ToggleSwitch.swift @@ -1,9 +1,13 @@ /// A light switch style control that is either on or off. +/// +/// This corresponds to the ``ToggleStyle/switch`` toggle style. struct ToggleSwitch: ElementaryView, View { /// Whether the switch is active or not. private var active: Binding /// Creates a switch. + /// + /// - Parameter active: Whether the switch is active or not. public init(active: Binding) { self.active = active } diff --git a/Sources/SwiftCrossUI/Views/VStack.swift b/Sources/SwiftCrossUI/Views/VStack.swift index e757d932a8..419426a58e 100644 --- a/Sources/SwiftCrossUI/Views/VStack.swift +++ b/Sources/SwiftCrossUI/Views/VStack.swift @@ -9,7 +9,13 @@ public struct VStack: View { /// The alignment of the stack's children in the horizontal direction. private var alignment: HorizontalAlignment - /// Creates a horizontal stack with the given spacing. + /// Creates a vertical stack with the given spacing and alignment. + /// + /// - Parameters: + /// - alignment: The alignment of the stack's children in the horizontal + /// direction. + /// - spacing: The amount of spacing to apply between children. + /// - content: The content of this stack. public init( alignment: HorizontalAlignment = .center, spacing: Int? = nil, @@ -18,6 +24,13 @@ public struct VStack: View { self.init(alignment: alignment, spacing: spacing, content: content()) } + /// Creates a vertical stack with the given spacing and alignment. + /// + /// - Parameters: + /// - alignment: The alignment of the stack's children in the horizontal + /// direction. + /// - spacing: The amount of spacing to apply between children. + /// - content: The content of this stack. init( alignment: HorizontalAlignment = .center, spacing: Int? = nil, diff --git a/Sources/SwiftCrossUI/Views/View.swift b/Sources/SwiftCrossUI/Views/View.swift index 562f025cfb..dd1a28d7b7 100644 --- a/Sources/SwiftCrossUI/Views/View.swift +++ b/Sources/SwiftCrossUI/Views/View.swift @@ -7,8 +7,18 @@ public protocol View { /// The view's contents. @ViewBuilder var body: Content { get } - /// Gets the view's children as a type-erased collection of view graph nodes. Type-erased - /// to avoid leaking complex requirements to users implementing their own regular views. + /// Gets the view's children as a type-erased collection of view graph + /// nodes. + /// + /// Type-erased to avoid leaking complex requirements to users implementing + /// their own regular views. + /// + /// - Parameters: + /// - backend: The app's backend. + /// - snapshots: <#FIXME: document#> + /// - environment: The current environment. + /// - Returns: The view's children as a type-erased collection of view graph + /// nodes. func children( backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, @@ -17,10 +27,18 @@ public protocol View { // TODO: Perhaps this can be split off into a separate protocol for the `TupleViewN`s // if we can set up the generics right for VStack. - /// Gets the view's children in a format that can be consumed by the ``LayoutSystem``. - /// This really only needs to be its own method for views such as VStack which treat - /// their child's children as their own and skip over their direct child. Only needs to - /// be implemented by the `TupleViewN`s. + /// Gets the view's children in a format that can be consumed by the + /// ``LayoutSystem``. + /// + /// This really only needs to be its own method for views such as ``VStack`` + /// which treat their child's children as their own and skip over their + /// direct child. Only needs to be implemented by the `TupleViewN`s. + /// + /// - Parameters: + /// - backend: The app's backend. + /// - children: The view's children. + /// - Returns: The view's children in a format that can be consumed by the + /// ``LayoutSystem``. func layoutableChildren( backend: Backend, children: any ViewGraphNodeChildren @@ -33,19 +51,39 @@ public protocol View { /// while deciding the structure of the widget. For example, a view /// displaying one of two children should use ``AppBackend/createContainer()`` /// to create a container for the displayed child instead of just directly - /// returning the widget of the currently displayed child (which would result - /// in you not being able to ever switch to displaying the other child). This - /// constraint significantly simplifies view implementations without - /// requiring widgets to be re-created after every single update. + /// returning the widget of the currently displayed child (which would + /// result in you not being able to ever switch to displaying the other + /// child). This constraint significantly simplifies view implementations + /// without requiring widgets to be re-created after every single update. + /// + /// - Parameters: + /// - children: The view's children. + /// - backend: The app's backend. + /// - Returns: The view's underlying widget for `backend`. func asWidget( _ children: any ViewGraphNodeChildren, backend: Backend ) -> Backend.Widget /// Computes this view's layout after a state change or a change in - /// available space. `proposedSize` is the size suggested by the parent - /// container, but child views always get the final call on their own size. - /// - Returns: The view's layout size. + /// available space. + /// + /// This method should _not_ apply the layout to `widget`; that should be + /// done in + /// instead. + /// + /// `proposedSize` is the size suggested by the parent container, but child + /// views always get the final call on their own size. + /// + /// - Parameters: + /// - widget: The view's underlying widget. + /// - children: The view's children. + /// - proposedSize: The size suggested to the view by its parent + /// container. + /// - environment: The current environment. + /// - backend: The app's backend. + /// - Returns: The view's computed size, along with any propagated + /// preferences. func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, @@ -55,7 +93,15 @@ public protocol View { ) -> ViewLayoutResult /// Commits the last computed layout to the underlying widget hierarchy. - /// `layout` is guaranteed to be the last value returned by ``computeLayout``. + /// + /// - Parameters: + /// - widget: The view's underlying widget. + /// - children: The view's children. + /// - layout: The layout to use for the view. Guaranteed to be the + /// last value returned by + /// . + /// - environment: The current environment. + /// - backend: The app's backend. func commit( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, diff --git a/Sources/SwiftCrossUI/Views/WebView.swift b/Sources/SwiftCrossUI/Views/WebView.swift index fab062c431..c0a97770b7 100644 --- a/Sources/SwiftCrossUI/Views/WebView.swift +++ b/Sources/SwiftCrossUI/Views/WebView.swift @@ -1,5 +1,6 @@ import Foundation +/// A web view. @available(tvOS, unavailable) public struct WebView: ElementaryView { /// The ideal size of a WebView. @@ -8,6 +9,9 @@ public struct WebView: ElementaryView { @State var currentURL: URL? @Binding var url: URL + /// Creates a web view. + /// + /// - Parameter url: A binding to the web view's URL. public init(_ url: Binding) { _url = url } diff --git a/Sources/SwiftCrossUI/Views/ZStack.swift b/Sources/SwiftCrossUI/Views/ZStack.swift index 87884604bc..8cf43edbb0 100644 --- a/Sources/SwiftCrossUI/Views/ZStack.swift +++ b/Sources/SwiftCrossUI/Views/ZStack.swift @@ -1,7 +1,15 @@ +/// A container that lays its views on top of each other. public struct ZStack: View { + /// The stack's alignment. public var alignment: Alignment + /// The stack's content. public var body: Content + /// Creates a ``ZStack``. + /// + /// - Parameters: + /// - alignment: The stack's alignment. + /// - content: The stack's content. public init( alignment: Alignment = .center, @ViewBuilder content: () -> Content diff --git a/Sources/SwiftCrossUI/_App.swift b/Sources/SwiftCrossUI/_App.swift index 7cccf22b1c..04c9bff9f1 100644 --- a/Sources/SwiftCrossUI/_App.swift +++ b/Sources/SwiftCrossUI/_App.swift @@ -1,7 +1,7 @@ // TODO: This could possibly be renamed to ``SceneGraph`` now that that's basically the role // it has taken on since introducing scenes. /// A top-level wrapper providing an entry point for the app. Exists to be able to persist -/// the view graph alongside the app (we can't do that on a user's `App` implementation because +/// the view graph alongside the app (we can't do that on a user's ``App`` implementation because /// we can only add computed properties). @MainActor class _App {