Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions Sources/AppKitBackend/NSViewRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
}
Expand Down
22 changes: 16 additions & 6 deletions Sources/SwiftCrossUI/Backend/AnyWidget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Backend: AppBackend>(
for backend: Backend.Type
) -> Backend.Widget {
Expand All @@ -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>() -> T {
guard let widget = widget as? T else {
fatalError("AnyWidget used with incompatible widget type \(T.self)")
Expand Down
992 changes: 750 additions & 242 deletions Sources/SwiftCrossUI/Backend/AppBackend.swift

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions Sources/SwiftCrossUI/Backend/CellPosition.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftCrossUI/Backend/DialogResult.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/// The result of a dialog.
public enum DialogResult<T> {
/// The dialog succeeded with a result value.
case success(T)
/// The dialog was cancelled.
case cancelled
}
2 changes: 2 additions & 0 deletions Sources/SwiftCrossUI/Backend/FileChooserResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
12 changes: 12 additions & 0 deletions Sources/SwiftCrossUI/Backend/FileDialogOptions.swift
Original file line number Diff line number Diff line change
@@ -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?
}
15 changes: 9 additions & 6 deletions Sources/SwiftCrossUI/Backend/MenuImplementationStyle.swift
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions Sources/SwiftCrossUI/Backend/OpenDialogOptions.swift
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 8 additions & 2 deletions Sources/SwiftCrossUI/Backend/ResolvedMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions Sources/SwiftCrossUI/Backend/SaveDialogOptions.swift
Original file line number Diff line number Diff line change
@@ -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?
}
2 changes: 1 addition & 1 deletion Sources/SwiftCrossUI/Builders/AlertActionsBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// A builder for `[AlertAction]`.
/// A result builder for `[AlertAction]`.
@resultBuilder
public struct AlertActionsBuilder {
public static func buildPartialBlock(first: Button) -> [AlertAction] {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftCrossUI/Builders/CommandsBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// A builder for ``Commands``.
/// A result builder for ``Commands``.
@resultBuilder
public struct CommandsBuilder {
public static func buildBlock(_ menus: CommandMenu...) -> Commands {
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftCrossUI/Builders/MenuItemsBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// A builder for ``[MenuItem]``.
/// A result builder for `[MenuItem]`.
@resultBuilder
public struct MenuItemsBuilder {
public static func buildBlock() -> [MenuItem] {
Expand Down Expand Up @@ -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]
Expand Down
15 changes: 10 additions & 5 deletions Sources/SwiftCrossUI/Environment/Actions/AlertAction.swift
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 7 additions & 2 deletions Sources/SwiftCrossUI/Environment/Actions/OpenURLAction.swift
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
Expand Down
16 changes: 11 additions & 5 deletions Sources/SwiftCrossUI/Environment/Actions/PresentAlertAction.swift
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
20 changes: 15 additions & 5 deletions Sources/SwiftCrossUI/Environment/Environment.swift
Original file line number Diff line number Diff line change
@@ -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
///
Expand All @@ -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
///
Expand All @@ -36,7 +38,11 @@
/// ```
@propertyWrapper
public struct Environment<Value>: DynamicProperty {
/// A key path to the enviornment value to access.
var keyPath: KeyPath<EnvironmentValues, Value>
/// The underlying value.
///
/// `nil` if ``update(with:previousValue:)`` has not yet been called.
var value: Box<Value?>

public func update(
Expand All @@ -46,6 +52,7 @@ public struct Environment<Value>: 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(
Expand All @@ -59,6 +66,9 @@ public struct Environment<Value>: DynamicProperty {
return value
}

/// Initializes an ``Environment`` property wrapper.
///
/// - Parameter keyPath: A key path to the enviornment value to access.
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
self.keyPath = keyPath
value = Box(value: nil)
Expand Down
Loading
Loading