From b8534f34c3d912171ee135ccdbe30efe6fe22665 Mon Sep 17 00:00:00 2001 From: stackotter Date: Fri, 30 May 2025 00:41:29 +1000 Subject: [PATCH 1/2] Split View.update into View.computeLayout and View.commit --- .../LayoutPerformanceBenchmark.swift | 4 +- .../WindowingExample/WindowingApp.swift | 2 + Sources/AppKitBackend/AppKitBackend.swift | 4 +- .../AppKitBackend/NSViewRepresentable.swift | 23 +- Sources/Gtk/Generated/Entry.swift | 166 ++++++------- Sources/Gtk/Generated/FilterChange.swift | 2 - Sources/Gtk/Generated/GLArea.swift | 7 - Sources/Gtk/Generated/Image.swift | 25 +- Sources/Gtk/Generated/Picture.swift | 4 +- Sources/Gtk/Generated/Popover.swift | 13 +- Sources/Gtk3/Generated/Entry.swift | 223 +++++++++--------- .../SwiftCrossUI/Layout/LayoutSystem.swift | 181 +++++++------- .../Layout/ViewUpdateResult.swift | 6 +- .../SwiftCrossUI/Scenes/WindowGroupNode.swift | 4 +- Sources/SwiftCrossUI/Values/Color.swift | 23 +- .../ViewGraph/AnyViewGraphNode.swift | 34 +-- .../ViewGraph/ErasedViewGraphNode.swift | 24 +- .../SwiftCrossUI/ViewGraph/ViewGraph.swift | 26 +- .../ViewGraph/ViewGraphNode.swift | 128 +++++----- Sources/SwiftCrossUI/Views/AnyView.swift | 37 +-- Sources/SwiftCrossUI/Views/Button.swift | 24 +- Sources/SwiftCrossUI/Views/Checkbox.swift | 28 ++- Sources/SwiftCrossUI/Views/EitherView.swift | 51 ++-- .../SwiftCrossUI/Views/ElementaryView.swift | 41 +++- Sources/SwiftCrossUI/Views/EmptyView.swift | 17 +- Sources/SwiftCrossUI/Views/ForEach.swift | 95 +++++--- .../SwiftCrossUI/Views/GeometryReader.swift | 31 ++- Sources/SwiftCrossUI/Views/Group.swift | 26 +- Sources/SwiftCrossUI/Views/HStack.swift | 32 ++- .../Views/HotReloadableView.swift | 43 ++-- Sources/SwiftCrossUI/Views/Image.swift | 47 ++-- Sources/SwiftCrossUI/Views/List.swift | 76 +++--- Sources/SwiftCrossUI/Views/Menu.swift | 37 +-- .../Views/Modifiers/AlertModifier.swift | 29 ++- .../Views/Modifiers/EnvironmentModifier.swift | 28 ++- .../Modifiers/Handlers/OnChangeModifier.swift | 13 +- .../Modifiers/Handlers/OnHoverModifier.swift | 35 +-- .../Handlers/OnTapGestureModifier.swift | 37 +-- .../Layout/AspectRatioModifier.swift | 51 ++-- .../Modifiers/Layout/BackgroundModifier.swift | 49 ++-- .../Modifiers/Layout/FixedSizeModifier.swift | 44 ++-- .../Modifiers/Layout/FrameModifier.swift | 81 ++++--- .../Modifiers/Layout/OverlayModifier.swift | 49 ++-- .../Modifiers/Layout/PaddingModifier.swift | 43 ++-- .../Lifecycle/OnDisappearModifier.swift | 28 ++- .../Modifiers/Lifecycle/TaskModifier.swift | 17 +- .../Views/Modifiers/PreferenceModifier.swift | 12 +- .../Views/Modifiers/SheetModifier.swift | 39 ++- .../Style/CornerRadiusModifier.swift | 35 ++- Sources/SwiftCrossUI/Views/OptionalView.swift | 51 ++-- Sources/SwiftCrossUI/Views/Picker.swift | 29 ++- Sources/SwiftCrossUI/Views/ProgressView.swift | 40 ++-- Sources/SwiftCrossUI/Views/ScrollView.swift | 129 +++++----- Sources/SwiftCrossUI/Views/Shapes/Shape.swift | 62 ++--- .../Views/Shapes/StyledShape.swift | 62 ++--- Sources/SwiftCrossUI/Views/Slider.swift | 62 ++--- Sources/SwiftCrossUI/Views/Spacer.swift | 25 +- Sources/SwiftCrossUI/Views/SplitView.swift | 97 ++++---- Sources/SwiftCrossUI/Views/Table.swift | 152 +++++++----- Sources/SwiftCrossUI/Views/Text.swift | 23 +- Sources/SwiftCrossUI/Views/TextEditor.swift | 41 ++-- Sources/SwiftCrossUI/Views/TextField.swift | 51 ++-- Sources/SwiftCrossUI/Views/ToggleButton.swift | 21 +- Sources/SwiftCrossUI/Views/ToggleSwitch.swift | 29 ++- Sources/SwiftCrossUI/Views/TupleView.swift | 40 +++- .../SwiftCrossUI/Views/TupleView.swift.gyb | 40 +++- Sources/SwiftCrossUI/Views/TypeSafeView.swift | 43 +++- Sources/SwiftCrossUI/Views/VStack.swift | 32 ++- Sources/SwiftCrossUI/Views/View.swift | 88 ++++--- Sources/SwiftCrossUI/Views/WebView.swift | 38 +-- Sources/SwiftCrossUI/Views/ZStack.swift | 54 +++-- .../UIKitBackend/UIViewRepresentable.swift | 21 +- 72 files changed, 1863 insertions(+), 1441 deletions(-) diff --git a/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift b/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift index 539f533013..d19d5ae0ba 100644 --- a/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift +++ b/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift @@ -35,8 +35,8 @@ struct Benchmarks { @MainActor func updateNode(_ node: ViewGraphNode, _ size: SIMD2) { - _ = node.update(proposedSize: size, environment: environment, dryRun: true) - _ = node.update(proposedSize: size, environment: environment, dryRun: false) + _ = node.computeLayout(proposedSize: size, environment: environment) + _ = node.commit() } #if BENCHMARK_VIZ diff --git a/Examples/Sources/WindowingExample/WindowingApp.swift b/Examples/Sources/WindowingExample/WindowingApp.swift index 4049151923..29056b0605 100644 --- a/Examples/Sources/WindowingExample/WindowingApp.swift +++ b/Examples/Sources/WindowingExample/WindowingApp.swift @@ -177,6 +177,8 @@ struct WindowingApp: App { } Image(Bundle.module.bundleURL.appendingPathComponent("Banner.png")) + .resizable() + .aspectRatio(contentMode: .fit) Divider() diff --git a/Sources/AppKitBackend/AppKitBackend.swift b/Sources/AppKitBackend/AppKitBackend.swift index 32adf784eb..aa5becc71a 100644 --- a/Sources/AppKitBackend/AppKitBackend.swift +++ b/Sources/AppKitBackend/AppKitBackend.swift @@ -31,12 +31,14 @@ public final class AppKitBackend: AppBackend { // We assume that all scrollers have their controlSize set to `.regular` by default. // The internet seems to indicate that this is true regardless of any system wide // preferences etc. - Int( + let result = Int( NSScroller.scrollerWidth( for: .regular, scrollerStyle: NSScroller.preferredScrollerStyle ).rounded(.awayFromZero) ) + print(result) + return result } private let appDelegate = NSCustomApplicationDelegate() diff --git a/Sources/AppKitBackend/NSViewRepresentable.swift b/Sources/AppKitBackend/NSViewRepresentable.swift index 115b323a8a..31238dce7b 100644 --- a/Sources/AppKitBackend/NSViewRepresentable.swift +++ b/Sources/AppKitBackend/NSViewRepresentable.swift @@ -139,15 +139,14 @@ extension View where Self: NSViewRepresentable { } } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - guard let backend = backend as? AppKitBackend else { + backend: Backend + ) -> ViewLayoutResult { + guard backend is AppKitBackend else { fatalError("NSViewRepresentable updated by \(Backend.self)") } @@ -160,11 +159,17 @@ extension View where Self: NSViewRepresentable { context: representingWidget.context! ) - if !dryRun { - backend.setSize(of: representingWidget, to: size.size) - } + return ViewLayoutResult.leafView(size: size) + } - return ViewUpdateResult.leafView(size: size) + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.setSize(of: widget, to: layout.size.size) } } diff --git a/Sources/Gtk/Generated/Entry.swift b/Sources/Gtk/Generated/Entry.swift index 26b74f887b..97cc334be7 100644 --- a/Sources/Gtk/Generated/Entry.swift +++ b/Sources/Gtk/Generated/Entry.swift @@ -336,439 +336,415 @@ open class Entry: Widget, CellEditable, Editable { SignalBox1.run(data, value1) } - addSignal(name: "notify::menu-entry-icon-primary-text", handler: gCallback(handler21)) { - [weak self] (param0: OpaquePointer) in - guard let self = self else { return } - self.notifyMenuEntryIconPrimaryText?(self, param0) - } - - let handler22: - @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = - { _, value1, data in - SignalBox1.run(data, value1) - } - - addSignal(name: "notify::menu-entry-icon-secondary-text", handler: gCallback(handler22)) { - [weak self] (param0: OpaquePointer) in - guard let self = self else { return } - self.notifyMenuEntryIconSecondaryText?(self, param0) - } - - let handler23: - @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = - { _, value1, data in - SignalBox1.run(data, value1) - } - - addSignal(name: "notify::overwrite-mode", handler: gCallback(handler23)) { + addSignal(name: "notify::overwrite-mode", handler: gCallback(handler21)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyOverwriteMode?(self, param0) } - let handler24: + let handler22: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::placeholder-text", handler: gCallback(handler24)) { + addSignal(name: "notify::placeholder-text", handler: gCallback(handler22)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPlaceholderText?(self, param0) } - let handler25: + let handler23: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-activatable", handler: gCallback(handler25)) { + addSignal(name: "notify::primary-icon-activatable", handler: gCallback(handler23)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconActivatable?(self, param0) } - let handler26: + let handler24: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-gicon", handler: gCallback(handler26)) { + addSignal(name: "notify::primary-icon-gicon", handler: gCallback(handler24)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconGicon?(self, param0) } - let handler27: + let handler25: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-name", handler: gCallback(handler27)) { + addSignal(name: "notify::primary-icon-name", handler: gCallback(handler25)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconName?(self, param0) } - let handler28: + let handler26: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-paintable", handler: gCallback(handler28)) { + addSignal(name: "notify::primary-icon-paintable", handler: gCallback(handler26)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconPaintable?(self, param0) } - let handler29: + let handler27: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-sensitive", handler: gCallback(handler29)) { + addSignal(name: "notify::primary-icon-sensitive", handler: gCallback(handler27)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconSensitive?(self, param0) } - let handler30: + let handler28: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-storage-type", handler: gCallback(handler30)) { + addSignal(name: "notify::primary-icon-storage-type", handler: gCallback(handler28)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconStorageType?(self, param0) } - let handler31: + let handler29: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-tooltip-markup", handler: gCallback(handler31)) { + addSignal(name: "notify::primary-icon-tooltip-markup", handler: gCallback(handler29)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconTooltipMarkup?(self, param0) } - let handler32: + let handler30: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-tooltip-text", handler: gCallback(handler32)) { + addSignal(name: "notify::primary-icon-tooltip-text", handler: gCallback(handler30)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconTooltipText?(self, param0) } - let handler33: + let handler31: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::progress-fraction", handler: gCallback(handler33)) { + addSignal(name: "notify::progress-fraction", handler: gCallback(handler31)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyProgressFraction?(self, param0) } - let handler34: + let handler32: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::progress-pulse-step", handler: gCallback(handler34)) { + addSignal(name: "notify::progress-pulse-step", handler: gCallback(handler32)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyProgressPulseStep?(self, param0) } - let handler35: + let handler33: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::scroll-offset", handler: gCallback(handler35)) { + addSignal(name: "notify::scroll-offset", handler: gCallback(handler33)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyScrollOffset?(self, param0) } - let handler36: + let handler34: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-activatable", handler: gCallback(handler36)) { + addSignal(name: "notify::secondary-icon-activatable", handler: gCallback(handler34)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconActivatable?(self, param0) } - let handler37: + let handler35: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-gicon", handler: gCallback(handler37)) { + addSignal(name: "notify::secondary-icon-gicon", handler: gCallback(handler35)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconGicon?(self, param0) } - let handler38: + let handler36: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-name", handler: gCallback(handler38)) { + addSignal(name: "notify::secondary-icon-name", handler: gCallback(handler36)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconName?(self, param0) } - let handler39: + let handler37: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-paintable", handler: gCallback(handler39)) { + addSignal(name: "notify::secondary-icon-paintable", handler: gCallback(handler37)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconPaintable?(self, param0) } - let handler40: + let handler38: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-sensitive", handler: gCallback(handler40)) { + addSignal(name: "notify::secondary-icon-sensitive", handler: gCallback(handler38)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconSensitive?(self, param0) } - let handler41: + let handler39: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-storage-type", handler: gCallback(handler41)) { + addSignal(name: "notify::secondary-icon-storage-type", handler: gCallback(handler39)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconStorageType?(self, param0) } - let handler42: + let handler40: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-tooltip-markup", handler: gCallback(handler42)) { + addSignal(name: "notify::secondary-icon-tooltip-markup", handler: gCallback(handler40)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconTooltipMarkup?(self, param0) } - let handler43: + let handler41: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-tooltip-text", handler: gCallback(handler43)) { + addSignal(name: "notify::secondary-icon-tooltip-text", handler: gCallback(handler41)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconTooltipText?(self, param0) } - let handler44: + let handler42: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::show-emoji-icon", handler: gCallback(handler44)) { + addSignal(name: "notify::show-emoji-icon", handler: gCallback(handler42)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyShowEmojiIcon?(self, param0) } - let handler45: + let handler43: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::tabs", handler: gCallback(handler45)) { + addSignal(name: "notify::tabs", handler: gCallback(handler43)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyTabs?(self, param0) } - let handler46: + let handler44: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::text-length", handler: gCallback(handler46)) { + addSignal(name: "notify::text-length", handler: gCallback(handler44)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyTextLength?(self, param0) } - let handler47: + let handler45: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::truncate-multiline", handler: gCallback(handler47)) { + addSignal(name: "notify::truncate-multiline", handler: gCallback(handler45)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyTruncateMultiline?(self, param0) } - let handler48: + let handler46: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::visibility", handler: gCallback(handler48)) { + addSignal(name: "notify::visibility", handler: gCallback(handler46)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyVisibility?(self, param0) } - let handler49: + let handler47: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::editing-canceled", handler: gCallback(handler49)) { + addSignal(name: "notify::editing-canceled", handler: gCallback(handler47)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyEditingCanceled?(self, param0) } - let handler50: + let handler48: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::cursor-position", handler: gCallback(handler50)) { + addSignal(name: "notify::cursor-position", handler: gCallback(handler48)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyCursorPosition?(self, param0) } - let handler51: + let handler49: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::editable", handler: gCallback(handler51)) { + addSignal(name: "notify::editable", handler: gCallback(handler49)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyEditable?(self, param0) } - let handler52: + let handler50: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::enable-undo", handler: gCallback(handler52)) { + addSignal(name: "notify::enable-undo", handler: gCallback(handler50)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyEnableUndo?(self, param0) } - let handler53: + let handler51: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::max-width-chars", handler: gCallback(handler53)) { + addSignal(name: "notify::max-width-chars", handler: gCallback(handler51)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyMaxWidthChars?(self, param0) } - let handler54: + let handler52: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::selection-bound", handler: gCallback(handler54)) { + addSignal(name: "notify::selection-bound", handler: gCallback(handler52)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySelectionBound?(self, param0) } - let handler55: + let handler53: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::text", handler: gCallback(handler55)) { + addSignal(name: "notify::text", handler: gCallback(handler53)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyText?(self, param0) } - let handler56: + let handler54: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::width-chars", handler: gCallback(handler56)) { + addSignal(name: "notify::width-chars", handler: gCallback(handler54)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyWidthChars?(self, param0) } - let handler57: + let handler55: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::xalign", handler: gCallback(handler57)) { + addSignal(name: "notify::xalign", handler: gCallback(handler55)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyXalign?(self, param0) @@ -937,10 +913,6 @@ open class Entry: Widget, CellEditable, Editable { public var notifyMaxLength: ((Entry, OpaquePointer) -> Void)? - public var notifyMenuEntryIconPrimaryText: ((Entry, OpaquePointer) -> Void)? - - public var notifyMenuEntryIconSecondaryText: ((Entry, OpaquePointer) -> Void)? - public var notifyOverwriteMode: ((Entry, OpaquePointer) -> Void)? public var notifyPlaceholderText: ((Entry, OpaquePointer) -> Void)? diff --git a/Sources/Gtk/Generated/FilterChange.swift b/Sources/Gtk/Generated/FilterChange.swift index ef229a3148..39c9f528a0 100644 --- a/Sources/Gtk/Generated/FilterChange.swift +++ b/Sources/Gtk/Generated/FilterChange.swift @@ -6,8 +6,6 @@ import CGtk /// If you are writing an implementation and are not sure which /// value to pass, `GTK_FILTER_CHANGE_DIFFERENT` is always a correct /// choice. -/// -/// New values may be added in the future. public enum FilterChange: GValueRepresentableEnum { public typealias GtkEnum = GtkFilterChange diff --git a/Sources/Gtk/Generated/GLArea.swift b/Sources/Gtk/Generated/GLArea.swift index 5247e75482..3d286c2204 100644 --- a/Sources/Gtk/Generated/GLArea.swift +++ b/Sources/Gtk/Generated/GLArea.swift @@ -41,13 +41,6 @@ import CGtk /// glClearColor (0, 0, 0, 0); /// glClear (GL_COLOR_BUFFER_BIT); /// -/// // record the active framebuffer ID, so we can return to it -/// // with `glBindFramebuffer (GL_FRAMEBUFFER, screen_fb)` should -/// // we, for instance, intend on utilizing the results of an -/// // intermediate render texture pass -/// GLuint screen_fb = 0; -/// glGetIntegerv (GL_FRAMEBUFFER_BINDING, &screen_fb); -/// /// // draw your object /// // draw_an_object (); /// diff --git a/Sources/Gtk/Generated/Image.swift b/Sources/Gtk/Generated/Image.swift index be138278de..455f4f9772 100644 --- a/Sources/Gtk/Generated/Image.swift +++ b/Sources/Gtk/Generated/Image.swift @@ -15,9 +15,9 @@ import CGtk /// If the file isn’t loaded successfully, the image will contain a /// “broken image” icon similar to that used in many web browsers. /// -/// If you want to handle errors in loading the file yourself, for example -/// by displaying an error message, then load the image with an image -/// loading framework such as libglycin, then create the `GtkImage` with +/// If you want to handle errors in loading the file yourself, +/// for example by displaying an error message, then load the image with +/// [ctor@Gdk.Texture.new_from_file], then create the `GtkImage` with /// [ctor@Gtk.Image.new_from_paintable]. /// /// Sometimes an application will want to avoid depending on external data @@ -53,9 +53,9 @@ open class Image: Widget { /// will display a “broken image” icon. This function never returns %NULL, /// it always returns a valid `GtkImage` widget. /// - /// If you need to detect failures to load the file, use an - /// image loading framework such as libglycin to load the file - /// yourself, then create the `GtkImage` from the texture. + /// If you need to detect failures to load the file, use + /// [ctor@Gdk.Texture.new_from_file] to load the file yourself, + /// then create the `GtkImage` from the texture. /// /// The storage type (see [method@Gtk.Image.get_storage_type]) /// of the returned image is not defined, it will be whatever @@ -96,13 +96,6 @@ open class Image: Widget { /// /// The `GtkImage` will track changes to the @paintable and update /// its size and contents in response to it. - /// - /// Note that paintables are still subject to the icon size that is - /// set on the image. If you want to display a paintable at its intrinsic - /// size, use [class@Gtk.Picture] instead. - /// - /// If @paintable is a [iface@Gtk.SymbolicPaintable], then it will be - /// recolored with the symbolic palette from the theme. public convenience init(paintable: OpaquePointer) { self.init( gtk_image_new_from_paintable(paintable) @@ -115,9 +108,9 @@ open class Image: Widget { /// display a “broken image” icon. This function never returns %NULL, /// it always returns a valid `GtkImage` widget. /// - /// If you need to detect failures to load the file, use an - /// image loading framework such as libglycin to load the file - /// yourself, then create the `GtkImage` from the texture. + /// If you need to detect failures to load the file, use + /// [ctor@GdkPixbuf.Pixbuf.new_from_file] to load the file yourself, + /// then create the `GtkImage` from the pixbuf. /// /// The storage type (see [method@Gtk.Image.get_storage_type]) of /// the returned image is not defined, it will be whatever is diff --git a/Sources/Gtk/Generated/Picture.swift b/Sources/Gtk/Generated/Picture.swift index 641cd3ecf3..df026896e3 100644 --- a/Sources/Gtk/Generated/Picture.swift +++ b/Sources/Gtk/Generated/Picture.swift @@ -16,8 +16,8 @@ import CGtk /// “broken image” icon similar to that used in many web browsers. /// If you want to handle errors in loading the file yourself, /// for example by displaying an error message, then load the image with -/// and image loading framework such as libglycin, then create the `GtkPicture` -/// with [ctor@Gtk.Picture.new_for_paintable]. +/// [ctor@Gdk.Texture.new_from_file], then create the `GtkPicture` with +/// [ctor@Gtk.Picture.new_for_paintable]. /// /// Sometimes an application will want to avoid depending on external data /// files, such as image files. See the documentation of `GResource` for details. diff --git a/Sources/Gtk/Generated/Popover.swift b/Sources/Gtk/Generated/Popover.swift index 0a68d9b281..03aee0c25b 100644 --- a/Sources/Gtk/Generated/Popover.swift +++ b/Sources/Gtk/Generated/Popover.swift @@ -5,17 +5,12 @@ import CGtk /// An example GtkPopover /// /// It is primarily meant to provide context-dependent information -/// or options. Popovers are attached to a parent widget. The parent widget -/// must support popover children, as [class@Gtk.MenuButton] and -/// [class@Gtk.PopoverMenuBar] do. If you want to make a custom widget that -/// has an attached popover, you need to call [method@Gtk.Popover.present] -/// in your [vfunc@Gtk.Widget.size_allocate] vfunc, in order to update the -/// positioning of the popover. +/// or options. Popovers are attached to a parent widget. By default, +/// they point to the whole widget area, although this behavior can be +/// changed with [method@Gtk.Popover.set_pointing_to]. /// /// The position of a popover relative to the widget it is attached to -/// can also be changed with [method@Gtk.Popover.set_position]. By default, -/// it points to the whole widget area, but it can be made to point to -/// a specific area using [method@Gtk.Popover.set_pointing_to]. +/// can also be changed with [method@Gtk.Popover.set_position] /// /// By default, `GtkPopover` performs a grab, in order to ensure input /// events get redirected to it while it is shown, and also so the popover diff --git a/Sources/Gtk3/Generated/Entry.swift b/Sources/Gtk3/Generated/Entry.swift index a7982980d9..c76a025c13 100644 --- a/Sources/Gtk3/Generated/Entry.swift +++ b/Sources/Gtk3/Generated/Entry.swift @@ -201,11 +201,6 @@ open class Entry: Widget, CellEditable, Editable { self.preeditChanged?(self, param0) } - addSignal(name: "toggle-direction") { [weak self] () in - guard let self = self else { return } - self.toggleDirection?(self) - } - addSignal(name: "toggle-overwrite") { [weak self] () in guard let self = self else { return } self.toggleOverwrite?(self) @@ -226,19 +221,19 @@ open class Entry: Widget, CellEditable, Editable { self.changed?(self) } - let handler17: + let handler16: @convention(c) (UnsafeMutableRawPointer, Int, Int, UnsafeMutableRawPointer) -> Void = { _, value1, value2, data in SignalBox2.run(data, value1, value2) } - addSignal(name: "delete-text", handler: gCallback(handler17)) { + addSignal(name: "delete-text", handler: gCallback(handler16)) { [weak self] (param0: Int, param1: Int) in guard let self = self else { return } self.deleteText?(self, param0, param1) } - let handler18: + let handler17: @convention(c) ( UnsafeMutableRawPointer, UnsafePointer, Int, gpointer, UnsafeMutableRawPointer @@ -248,631 +243,631 @@ open class Entry: Widget, CellEditable, Editable { data, value1, value2, value3) } - addSignal(name: "insert-text", handler: gCallback(handler18)) { + addSignal(name: "insert-text", handler: gCallback(handler17)) { [weak self] (param0: UnsafePointer, param1: Int, param2: gpointer) in guard let self = self else { return } self.insertText?(self, param0, param1, param2) } - let handler19: + let handler18: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::activates-default", handler: gCallback(handler19)) { + addSignal(name: "notify::activates-default", handler: gCallback(handler18)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyActivatesDefault?(self, param0) } - let handler20: + let handler19: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::attributes", handler: gCallback(handler20)) { + addSignal(name: "notify::attributes", handler: gCallback(handler19)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyAttributes?(self, param0) } - let handler21: + let handler20: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::buffer", handler: gCallback(handler21)) { + addSignal(name: "notify::buffer", handler: gCallback(handler20)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyBuffer?(self, param0) } - let handler22: + let handler21: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::caps-lock-warning", handler: gCallback(handler22)) { + addSignal(name: "notify::caps-lock-warning", handler: gCallback(handler21)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyCapsLockWarning?(self, param0) } - let handler23: + let handler22: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::completion", handler: gCallback(handler23)) { + addSignal(name: "notify::completion", handler: gCallback(handler22)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyCompletion?(self, param0) } - let handler24: + let handler23: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::cursor-position", handler: gCallback(handler24)) { + addSignal(name: "notify::cursor-position", handler: gCallback(handler23)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyCursorPosition?(self, param0) } - let handler25: + let handler24: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::editable", handler: gCallback(handler25)) { + addSignal(name: "notify::editable", handler: gCallback(handler24)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyEditable?(self, param0) } - let handler26: + let handler25: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::enable-emoji-completion", handler: gCallback(handler26)) { + addSignal(name: "notify::enable-emoji-completion", handler: gCallback(handler25)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyEnableEmojiCompletion?(self, param0) } - let handler27: + let handler26: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::has-frame", handler: gCallback(handler27)) { + addSignal(name: "notify::has-frame", handler: gCallback(handler26)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyHasFrame?(self, param0) } - let handler28: + let handler27: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::im-module", handler: gCallback(handler28)) { + addSignal(name: "notify::im-module", handler: gCallback(handler27)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyImModule?(self, param0) } - let handler29: + let handler28: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::inner-border", handler: gCallback(handler29)) { + addSignal(name: "notify::inner-border", handler: gCallback(handler28)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyInnerBorder?(self, param0) } - let handler30: + let handler29: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::input-hints", handler: gCallback(handler30)) { + addSignal(name: "notify::input-hints", handler: gCallback(handler29)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyInputHints?(self, param0) } - let handler31: + let handler30: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::input-purpose", handler: gCallback(handler31)) { + addSignal(name: "notify::input-purpose", handler: gCallback(handler30)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyInputPurpose?(self, param0) } - let handler32: + let handler31: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::invisible-char", handler: gCallback(handler32)) { + addSignal(name: "notify::invisible-char", handler: gCallback(handler31)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyInvisibleCharacter?(self, param0) } - let handler33: + let handler32: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::invisible-char-set", handler: gCallback(handler33)) { + addSignal(name: "notify::invisible-char-set", handler: gCallback(handler32)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyInvisibleCharacterSet?(self, param0) } - let handler34: + let handler33: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::max-length", handler: gCallback(handler34)) { + addSignal(name: "notify::max-length", handler: gCallback(handler33)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyMaxLength?(self, param0) } - let handler35: + let handler34: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::max-width-chars", handler: gCallback(handler35)) { + addSignal(name: "notify::max-width-chars", handler: gCallback(handler34)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyMaxWidthChars?(self, param0) } - let handler36: + let handler35: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::overwrite-mode", handler: gCallback(handler36)) { + addSignal(name: "notify::overwrite-mode", handler: gCallback(handler35)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyOverwriteMode?(self, param0) } - let handler37: + let handler36: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::placeholder-text", handler: gCallback(handler37)) { + addSignal(name: "notify::placeholder-text", handler: gCallback(handler36)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPlaceholderText?(self, param0) } - let handler38: + let handler37: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::populate-all", handler: gCallback(handler38)) { + addSignal(name: "notify::populate-all", handler: gCallback(handler37)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPopulateAll?(self, param0) } - let handler39: + let handler38: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-activatable", handler: gCallback(handler39)) { + addSignal(name: "notify::primary-icon-activatable", handler: gCallback(handler38)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconActivatable?(self, param0) } - let handler40: + let handler39: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-gicon", handler: gCallback(handler40)) { + addSignal(name: "notify::primary-icon-gicon", handler: gCallback(handler39)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconGicon?(self, param0) } - let handler41: + let handler40: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-name", handler: gCallback(handler41)) { + addSignal(name: "notify::primary-icon-name", handler: gCallback(handler40)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconName?(self, param0) } - let handler42: + let handler41: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-pixbuf", handler: gCallback(handler42)) { + addSignal(name: "notify::primary-icon-pixbuf", handler: gCallback(handler41)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconPixbuf?(self, param0) } - let handler43: + let handler42: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-sensitive", handler: gCallback(handler43)) { + addSignal(name: "notify::primary-icon-sensitive", handler: gCallback(handler42)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconSensitive?(self, param0) } - let handler44: + let handler43: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-stock", handler: gCallback(handler44)) { + addSignal(name: "notify::primary-icon-stock", handler: gCallback(handler43)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconStock?(self, param0) } - let handler45: + let handler44: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-storage-type", handler: gCallback(handler45)) { + addSignal(name: "notify::primary-icon-storage-type", handler: gCallback(handler44)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconStorageType?(self, param0) } - let handler46: + let handler45: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-tooltip-markup", handler: gCallback(handler46)) { + addSignal(name: "notify::primary-icon-tooltip-markup", handler: gCallback(handler45)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconTooltipMarkup?(self, param0) } - let handler47: + let handler46: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::primary-icon-tooltip-text", handler: gCallback(handler47)) { + addSignal(name: "notify::primary-icon-tooltip-text", handler: gCallback(handler46)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyPrimaryIconTooltipText?(self, param0) } - let handler48: + let handler47: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::progress-fraction", handler: gCallback(handler48)) { + addSignal(name: "notify::progress-fraction", handler: gCallback(handler47)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyProgressFraction?(self, param0) } - let handler49: + let handler48: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::progress-pulse-step", handler: gCallback(handler49)) { + addSignal(name: "notify::progress-pulse-step", handler: gCallback(handler48)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyProgressPulseStep?(self, param0) } - let handler50: + let handler49: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::scroll-offset", handler: gCallback(handler50)) { + addSignal(name: "notify::scroll-offset", handler: gCallback(handler49)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyScrollOffset?(self, param0) } - let handler51: + let handler50: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-activatable", handler: gCallback(handler51)) { + addSignal(name: "notify::secondary-icon-activatable", handler: gCallback(handler50)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconActivatable?(self, param0) } - let handler52: + let handler51: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-gicon", handler: gCallback(handler52)) { + addSignal(name: "notify::secondary-icon-gicon", handler: gCallback(handler51)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconGicon?(self, param0) } - let handler53: + let handler52: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-name", handler: gCallback(handler53)) { + addSignal(name: "notify::secondary-icon-name", handler: gCallback(handler52)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconName?(self, param0) } - let handler54: + let handler53: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-pixbuf", handler: gCallback(handler54)) { + addSignal(name: "notify::secondary-icon-pixbuf", handler: gCallback(handler53)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconPixbuf?(self, param0) } - let handler55: + let handler54: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-sensitive", handler: gCallback(handler55)) { + addSignal(name: "notify::secondary-icon-sensitive", handler: gCallback(handler54)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconSensitive?(self, param0) } - let handler56: + let handler55: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-stock", handler: gCallback(handler56)) { + addSignal(name: "notify::secondary-icon-stock", handler: gCallback(handler55)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconStock?(self, param0) } - let handler57: + let handler56: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-storage-type", handler: gCallback(handler57)) { + addSignal(name: "notify::secondary-icon-storage-type", handler: gCallback(handler56)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconStorageType?(self, param0) } - let handler58: + let handler57: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-tooltip-markup", handler: gCallback(handler58)) { + addSignal(name: "notify::secondary-icon-tooltip-markup", handler: gCallback(handler57)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconTooltipMarkup?(self, param0) } - let handler59: + let handler58: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::secondary-icon-tooltip-text", handler: gCallback(handler59)) { + addSignal(name: "notify::secondary-icon-tooltip-text", handler: gCallback(handler58)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySecondaryIconTooltipText?(self, param0) } - let handler60: + let handler59: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::selection-bound", handler: gCallback(handler60)) { + addSignal(name: "notify::selection-bound", handler: gCallback(handler59)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifySelectionBound?(self, param0) } - let handler61: + let handler60: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::shadow-type", handler: gCallback(handler61)) { + addSignal(name: "notify::shadow-type", handler: gCallback(handler60)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyShadowType?(self, param0) } - let handler62: + let handler61: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::show-emoji-icon", handler: gCallback(handler62)) { + addSignal(name: "notify::show-emoji-icon", handler: gCallback(handler61)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyShowEmojiIcon?(self, param0) } - let handler63: + let handler62: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::tabs", handler: gCallback(handler63)) { + addSignal(name: "notify::tabs", handler: gCallback(handler62)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyTabs?(self, param0) } - let handler64: + let handler63: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::text", handler: gCallback(handler64)) { + addSignal(name: "notify::text", handler: gCallback(handler63)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyText?(self, param0) } - let handler65: + let handler64: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::text-length", handler: gCallback(handler65)) { + addSignal(name: "notify::text-length", handler: gCallback(handler64)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyTextLength?(self, param0) } - let handler66: + let handler65: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::truncate-multiline", handler: gCallback(handler66)) { + addSignal(name: "notify::truncate-multiline", handler: gCallback(handler65)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyTruncateMultiline?(self, param0) } - let handler67: + let handler66: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::visibility", handler: gCallback(handler67)) { + addSignal(name: "notify::visibility", handler: gCallback(handler66)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyVisibility?(self, param0) } - let handler68: + let handler67: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::width-chars", handler: gCallback(handler68)) { + addSignal(name: "notify::width-chars", handler: gCallback(handler67)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyWidthChars?(self, param0) } - let handler69: + let handler68: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::xalign", handler: gCallback(handler69)) { + addSignal(name: "notify::xalign", handler: gCallback(handler68)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyXalign?(self, param0) } - let handler70: + let handler69: @convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void = { _, value1, data in SignalBox1.run(data, value1) } - addSignal(name: "notify::editing-canceled", handler: gCallback(handler70)) { + addSignal(name: "notify::editing-canceled", handler: gCallback(handler69)) { [weak self] (param0: OpaquePointer) in guard let self = self else { return } self.notifyEditingCanceled?(self, param0) @@ -999,8 +994,6 @@ open class Entry: Widget, CellEditable, Editable { /// connect to this signal. public var preeditChanged: ((Entry, UnsafePointer) -> Void)? - public var toggleDirection: ((Entry) -> Void)? - /// The ::toggle-overwrite signal is a /// [keybinding signal][GtkBindingSignal] /// which gets emitted to toggle the overwrite mode of the entry. diff --git a/Sources/SwiftCrossUI/Layout/LayoutSystem.swift b/Sources/SwiftCrossUI/Layout/LayoutSystem.swift index 4618efff1e..bd0de8df00 100644 --- a/Sources/SwiftCrossUI/Layout/LayoutSystem.swift +++ b/Sources/SwiftCrossUI/Layout/LayoutSystem.swift @@ -45,29 +45,36 @@ public enum LayoutSystem { } public struct LayoutableChild { - private var update: + private var computeLayout: @MainActor ( _ proposedSize: SIMD2, - _ environment: EnvironmentValues, - _ dryRun: Bool - ) -> ViewUpdateResult + _ environment: EnvironmentValues + ) -> ViewLayoutResult + private var _commit: @MainActor () -> ViewLayoutResult var tag: String? public init( - update: @escaping @MainActor (SIMD2, EnvironmentValues, Bool) -> ViewUpdateResult, + computeLayout: @escaping @MainActor (SIMD2, EnvironmentValues) -> ViewLayoutResult, + commit: @escaping @MainActor () -> ViewLayoutResult, tag: String? = nil ) { - self.update = update + self.computeLayout = computeLayout + self._commit = commit self.tag = tag } @MainActor - public func update( + public func computeLayout( proposedSize: SIMD2, environment: EnvironmentValues, dryRun: Bool = false - ) -> ViewUpdateResult { - update(proposedSize, environment, dryRun) + ) -> ViewLayoutResult { + computeLayout(proposedSize, environment) + } + + @MainActor + public func commit() -> ViewLayoutResult { + _commit() } } @@ -77,61 +84,43 @@ public enum LayoutSystem { /// ``Group`` to avoid changing stack layout participation (since ``Group`` /// is meant to appear completely invisible to the layout system). @MainActor - public static func updateStackLayout( + public static func computeStackLayout( container: Backend.Widget, children: [LayoutableChild], proposedSize: SIMD2, environment: EnvironmentValues, backend: Backend, - dryRun: Bool, inheritStackLayoutParticipation: Bool = false - ) -> ViewUpdateResult { + ) -> ViewLayoutResult { let spacing = environment.layoutSpacing - let alignment = environment.layoutAlignment let orientation = environment.layoutOrientation - var renderedChildren: [ViewUpdateResult] = Array( - repeating: ViewUpdateResult.leafView(size: .empty), + var renderedChildren: [ViewLayoutResult] = Array( + repeating: ViewLayoutResult.leafView(size: .empty), count: children.count ) - // Figure out which views to treat as hidden. This could be the cause - // of issues if a view has some threshold at which it suddenly becomes - // invisible. + // My thanks go to this great article for investigating and explaining + // how SwiftUI determines child view 'flexibility': + // https://www.objc.io/blog/2020/11/10/hstacks-child-ordering/ var isHidden = [Bool](repeating: false, count: children.count) - for (i, child) in children.enumerated() { - let result = child.update( + let flexibilities = children.enumerated().map { i, child in + let result = child.computeLayout( proposedSize: proposedSize, - environment: environment, - dryRun: true + environment: environment ) isHidden[i] = !result.participatesInStackLayouts - } - - // My thanks go to this great article for investigating and explaining - // how SwiftUI determines child view 'flexibility': - // https://www.objc.io/blog/2020/11/10/hstacks-child-ordering/ - let visibleChildrenCount = isHidden.filter { hidden in - !hidden - }.count - let totalSpacing = max(visibleChildrenCount - 1, 0) * spacing - let proposedSizeWithoutSpacing = SIMD2( - proposedSize.x - (orientation == .horizontal ? totalSpacing : 0), - proposedSize.y - (orientation == .vertical ? totalSpacing : 0) - ) - let flexibilities = children.map { child in - let size = child.update( - proposedSize: proposedSizeWithoutSpacing, - environment: environment, - dryRun: true - ).size return switch orientation { case .horizontal: - size.maximumWidth - Double(size.minimumWidth) + result.size.maximumWidth - Double(result.size.minimumWidth) case .vertical: - size.maximumHeight - Double(size.minimumHeight) + result.size.maximumHeight - Double(result.size.minimumHeight) } } + let visibleChildrenCount = isHidden.filter { hidden in + !hidden + }.count + let totalSpacing = max(visibleChildrenCount - 1, 0) * spacing let sortedChildren = zip(children.enumerated(), flexibilities) .sorted { first, second in first.1 <= second.1 @@ -146,10 +135,9 @@ public enum LayoutSystem { // Update child in case it has just changed from visible to hidden, // and to make sure that the view is still hidden (if it's not then // it's a bug with either the view or the layout system). - let result = child.update( + let result = child.computeLayout( proposedSize: .zero, - environment: environment, - dryRun: dryRun + environment: environment ) if result.participatesInStackLayouts { print( @@ -179,13 +167,12 @@ public enum LayoutSystem { proposedWidth = Double(proposedSize.x) } - let childResult = child.update( + let childResult = child.computeLayout( proposedSize: SIMD2( Int(proposedWidth.rounded(.towardZero)), Int(proposedHeight.rounded(.towardZero)) ), - environment: environment, - dryRun: dryRun + environment: environment ) renderedChildren[index] = childResult @@ -249,53 +236,13 @@ public enum LayoutSystem { + totalSpacing } - if !dryRun { - backend.setSize(of: container, to: size) - - var x = 0 - var y = 0 - for (index, childSize) in renderedChildren.enumerated() { - // Avoid the whole iteration if the child is hidden. If there - // are weird positioning issues for views that do strange things - // then this could be the cause. - if isHidden[index] { - continue - } - - // Compute alignment - switch (orientation, alignment) { - case (.vertical, .leading): - x = 0 - case (.horizontal, .leading): - y = 0 - case (.vertical, .center): - x = (size.x - childSize.size.size.x) / 2 - case (.horizontal, .center): - y = (size.y - childSize.size.size.y) / 2 - case (.vertical, .trailing): - x = (size.x - childSize.size.size.x) - case (.horizontal, .trailing): - y = (size.y - childSize.size.size.y) - } - - backend.setPosition(ofChildAt: index, in: container, to: SIMD2(x, y)) - - switch orientation { - case .horizontal: - x += childSize.size.size.x + spacing - case .vertical: - y += childSize.size.size.y + spacing - } - } - } - // If the stack has been told to inherit its stack layout participation // and all of its children are hidden, then the stack itself also // shouldn't participate in stack layouts. let shouldGetIgnoredInStackLayouts = inheritStackLayoutParticipation && isHidden.allSatisfy { $0 } - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: size, idealSize: idealSize, @@ -310,4 +257,58 @@ public enum LayoutSystem { childResults: renderedChildren ) } + + @MainActor + public static func commitStackLayout( + container: Backend.Widget, + children: [LayoutableChild], + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let size = layout.size.size + backend.setSize(of: container, to: size) + + let renderedChildren = children.map { $0.commit() } + + let alignment = environment.layoutAlignment + let spacing = environment.layoutSpacing + let orientation = environment.layoutOrientation + + var x = 0 + var y = 0 + for (index, child) in renderedChildren.enumerated() { + // Avoid the whole iteration if the child is hidden. If there + // are weird positioning issues for views that do strange things + // then this could be the cause. + if !child.participatesInStackLayouts { + continue + } + + // Compute alignment + switch (orientation, alignment) { + case (.vertical, .leading): + x = 0 + case (.horizontal, .leading): + y = 0 + case (.vertical, .center): + x = (size.x - child.size.size.x) / 2 + case (.horizontal, .center): + y = (size.y - child.size.size.y) / 2 + case (.vertical, .trailing): + x = (size.x - child.size.size.x) + case (.horizontal, .trailing): + y = (size.y - child.size.size.y) + } + + backend.setPosition(ofChildAt: index, in: container, to: SIMD2(x, y)) + + switch orientation { + case .horizontal: + x += child.size.size.x + spacing + case .vertical: + y += child.size.size.y + spacing + } + } + } } diff --git a/Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift b/Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift index bd907bc728..246bbf4fce 100644 --- a/Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift +++ b/Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift @@ -1,4 +1,4 @@ -public struct ViewUpdateResult { +public struct ViewLayoutResult { public var size: ViewSize public var preferences: PreferenceValues @@ -12,7 +12,7 @@ public struct ViewUpdateResult { public init( size: ViewSize, - childResults: [ViewUpdateResult], + childResults: [ViewLayoutResult], preferencesOverlay: PreferenceValues? = nil ) { self.size = size @@ -24,7 +24,7 @@ public struct ViewUpdateResult { } public static func leafView(size: ViewSize) -> Self { - ViewUpdateResult(size: size, preferences: .default) + ViewLayoutResult(size: size, preferences: .default) } public var participatesInStackLayouts: Bool { diff --git a/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift b/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift index 717906b484..02dbae4141 100644 --- a/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift +++ b/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift @@ -102,7 +102,7 @@ public final class WindowGroupNode: SceneGraphNode { backend: Backend, environment: EnvironmentValues, windowSizeIsFinal: Bool = false - ) -> ViewUpdateResult { + ) -> ViewLayoutResult { guard let window = window as? Backend.Window else { fatalError("Scene updated with a backend incompatible with the window it was given") } @@ -136,7 +136,7 @@ public final class WindowGroupNode: SceneGraphNode { } .with(\.window, window) - let dryRunResult: ViewUpdateResult? + let dryRunResult: ViewLayoutResult? if !windowSizeIsFinal { // Perform a dry-run update of the root view to check if the window // needs to change size. diff --git a/Sources/SwiftCrossUI/Values/Color.swift b/Sources/SwiftCrossUI/Values/Color.swift index aa9c18f6d0..dcd95dc80f 100644 --- a/Sources/SwiftCrossUI/Values/Color.swift +++ b/Sources/SwiftCrossUI/Values/Color.swift @@ -69,18 +69,13 @@ extension Color: ElementaryView { backend.createColorableRectangle() } - func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - if !dryRun { - backend.setSize(of: widget, to: proposedSize) - backend.setColor(ofColorableRectangle: widget, to: self) - } - return ViewUpdateResult.leafView( + backend: Backend + ) -> ViewLayoutResult { + ViewLayoutResult.leafView( size: ViewSize( size: proposedSize, idealSize: SIMD2(10, 10), @@ -91,4 +86,14 @@ extension Color: ElementaryView { ) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.setSize(of: widget, to: layout.size.size) + backend.setColor(ofColorableRectangle: widget, to: self) + } } diff --git a/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift index 2edc517100..9fa4b09bcc 100644 --- a/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift @@ -12,14 +12,15 @@ public class AnyViewGraphNode { _getWidget() } - /// The node's type-erased update method for update the view. - private var _updateWithNewView: + /// The node's type-erased layout computing method. + private var _computeLayoutWithNewView: ( _ newView: NodeView?, _ proposedSize: SIMD2, - _ environment: EnvironmentValues, - _ dryRun: Bool - ) -> ViewUpdateResult + _ environment: EnvironmentValues + ) -> ViewLayoutResult + /// The node's type-erased commit method. + private var _commit: () -> ViewLayoutResult /// The type-erased getter for the node's widget. private var _getWidget: () -> AnyWidget /// The type-erased getter for the node's view. @@ -32,7 +33,8 @@ public class AnyViewGraphNode { /// Type-erases a view graph node. public init(_ node: ViewGraphNode) { self.node = node - _updateWithNewView = node.update(with:proposedSize:environment:dryRun:) + _computeLayoutWithNewView = node.computeLayout(with:proposedSize:environment:) + _commit = node.commit _getWidget = { AnyWidget(node.widget) } @@ -64,16 +66,20 @@ public class AnyViewGraphNode { ) } - /// Updates the view after it got recomputed (e.g. due to the parent's state changing) - /// or after its own state changed (depending on the presence of `newView`). - /// - Parameter dryRun: If `true`, only compute sizing and don't update the underlying widget. - public func update( + /// Computes a view's layout. Propagates to the view's children unless + /// the given size proposal already has a cached result. + public func computeLayout( with newView: NodeView?, proposedSize: SIMD2, - environment: EnvironmentValues, - dryRun: Bool - ) -> ViewUpdateResult { - _updateWithNewView(newView, proposedSize, environment, dryRun) + environment: EnvironmentValues + ) -> ViewLayoutResult { + _computeLayoutWithNewView(newView, proposedSize, environment) + } + + /// Commits the view's most recently computed layout. Propagates to the + /// view's children. Also commits any view state changes. + public func commit() -> ViewLayoutResult { + _commit() } /// Gets the node's wrapped view. diff --git a/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift index 23bba12d40..b84853a9d1 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift @@ -8,13 +8,14 @@ public struct ErasedViewGraphNode { /// value will have `viewTypeMatched` set to `false`, allowing views such as `AnyView` /// to choose how to react to a mismatch. In `AnyView`'s case this means throwing away /// the current view graph node and creating a new one for the new view type. - public var updateWithNewView: + public var computeLayoutWithNewView: ( _ newView: Any?, _ proposedSize: SIMD2, - _ environment: EnvironmentValues, - _ dryRun: Bool - ) -> (viewTypeMatched: Bool, size: ViewUpdateResult) + _ environment: EnvironmentValues + ) -> (viewTypeMatched: Bool, size: ViewLayoutResult) + /// The underlying view graph node's commit method. + public var commit: () -> ViewLayoutResult public var getWidget: () -> AnyWidget public var viewType: any View.Type @@ -42,28 +43,27 @@ public struct ErasedViewGraphNode { self.node = node backendType = Backend.self viewType = V.self - updateWithNewView = { view, proposedSize, environment, dryRun in + computeLayoutWithNewView = { view, proposedSize, environment in if let view { guard let view = view as? V else { - return (false, ViewUpdateResult.leafView(size: .empty)) + return (false, ViewLayoutResult.leafView(size: .empty)) } - let size = node.update( + let size = node.computeLayout( with: view, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) return (true, size) } else { - let size = node.update( + let size = node.computeLayout( with: nil, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) return (true, size) } } + commit = node.commit getWidget = { return AnyWidget(node.widget) } diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift index b868d9ab7e..a14bf62ef1 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift @@ -22,7 +22,7 @@ public class ViewGraph { /// change as opposed to a window resizing event). private var windowSize: SIMD2 /// The current size of the root view. - private var currentRootViewResult: ViewUpdateResult + private var currentRootViewResult: ViewLayoutResult /// The environment most recently provided by this node's parent scene. private var parentEnvironment: EnvironmentValues @@ -41,7 +41,7 @@ public class ViewGraph { self.view = view windowSize = .zero parentEnvironment = environment - currentRootViewResult = ViewUpdateResult.leafView(size: .empty) + currentRootViewResult = ViewLayoutResult.leafView(size: .empty) setIncomingURLHandler = backend.setIncomingURLHandler(to:) } @@ -53,15 +53,23 @@ public class ViewGraph { proposedSize: SIMD2, environment: EnvironmentValues, dryRun: Bool - ) -> ViewUpdateResult { + ) -> ViewLayoutResult { parentEnvironment = environment windowSize = proposedSize - let result = rootNode.update( - with: newView ?? view, - proposedSize: proposedSize, - environment: parentEnvironment, - dryRun: dryRun - ) + + // TODO: Refactor view graph node to be computeLayout+commit based + // instead of update based. + let result: ViewLayoutResult + if dryRun { + result = rootNode.computeLayout( + with: newView ?? view, + proposedSize: proposedSize, + environment: parentEnvironment + ) + } else { + result = rootNode.commit() + } + self.currentRootViewResult = result if isFirstUpdate, !dryRun { setIncomingURLHandler { url in diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift index b80b848699..1f698b32f0 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift @@ -37,10 +37,10 @@ public class ViewGraphNode: Sendable { public var backend: Backend /// The most recent update result for the wrapped view. - var currentResult: ViewUpdateResult? + public var currentLayout: ViewLayoutResult? /// A cache of update results keyed by the proposed size they were for. Gets cleared before the /// results' sizes become invalid. - var resultCache: [SIMD2: ViewUpdateResult] + var resultCache: [SIMD2: ViewLayoutResult] /// The most recent size proposed by the parent view. Used when updating the wrapped /// view as a result of a state change rather than the parent view updating. private var lastProposedSize: SIMD2 @@ -70,7 +70,7 @@ public class ViewGraphNode: Sendable { snapshot?.isValid(for: NodeView.self) == true ? snapshot?.children : snapshot.map { [$0] } - currentResult = nil + currentLayout = nil resultCache = [:] lastProposedSize = .zero parentEnvironment = environment @@ -136,34 +136,18 @@ public class ViewGraphNode: Sendable { private func bottomUpUpdate() { // First we compute what size the view will be after the update. If it will change size, // propagate the update to this node's parent instead of updating straight away. - let currentSize = currentResult?.size - let newResult = self.update( + let currentSize = currentLayout?.size + let newLayout = self.computeLayout( proposedSize: lastProposedSize, - environment: parentEnvironment, - dryRun: true + environment: parentEnvironment ) - if newResult.size != currentSize { - self.currentResult = newResult - resultCache[lastProposedSize] = newResult - parentEnvironment.onResize(newResult.size) + self.currentLayout = newLayout + if newLayout.size != currentSize { + resultCache[lastProposedSize] = newLayout + parentEnvironment.onResize(newLayout.size) } else { - let finalResult = self.update( - proposedSize: lastProposedSize, - environment: parentEnvironment, - dryRun: false - ) - if finalResult.size != newResult.size { - print( - """ - warning: State-triggered view update had mismatch \ - between dry-run size and final size. - -> dry-run size: \(newResult.size) - -> final size: \(finalResult.size) - """ - ) - } - self.currentResult = finalResult + _ = self.commit() } } @@ -174,17 +158,15 @@ public class ViewGraphNode: Sendable { } } - /// Recomputes the view's body, and updates its widget accordingly. 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. - /// - Parameter dryRun: If `true`, only compute sizing and don't update the underlying widget. - public func update( + /// 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. + public func computeLayout( with newView: NodeView? = nil, proposedSize: SIMD2, - environment: EnvironmentValues, - dryRun: Bool - ) -> ViewUpdateResult { + environment: EnvironmentValues + ) -> ViewLayoutResult { // Defensively ensure that all future scene implementations obey this // precondition. By putting the check here instead of only in views // that require `environment.window` (such as the alert modifier view), @@ -194,7 +176,8 @@ public class ViewGraphNode: Sendable { "View graph updated without parent window present in environment" ) - if dryRun, let cachedResult = resultCache[proposedSize] { + if let cachedResult = resultCache[proposedSize] { + currentLayout = cachedResult return cachedResult } @@ -204,33 +187,33 @@ public class ViewGraphNode: Sendable { // since the last update cycle (checked via`!sizeCache.isEmpty`) to // ensure that the view has been updated at least once with the // current view state. - if dryRun, let currentResult, !resultCache.isEmpty { + if let currentLayout, !resultCache.isEmpty { // If both the previous and current proposed sizes are larger than // the view's previously computed maximum size, reuse the previous // result (currentResult). - if ((Double(lastProposedSize.x) >= currentResult.size.maximumWidth - && Double(proposedSize.x) >= currentResult.size.maximumWidth) + if ((Double(lastProposedSize.x) >= currentLayout.size.maximumWidth + && Double(proposedSize.x) >= currentLayout.size.maximumWidth) || proposedSize.x == lastProposedSize.x) - && ((Double(lastProposedSize.y) >= currentResult.size.maximumHeight - && Double(proposedSize.y) >= currentResult.size.maximumHeight) + && ((Double(lastProposedSize.y) >= currentLayout.size.maximumHeight + && Double(proposedSize.y) >= currentLayout.size.maximumHeight) || proposedSize.y == lastProposedSize.y) { - return currentResult + return currentLayout } // If the view has already been updated this update cycle and claims // to be fixed size (maximumSize == minimumSize) then reuse the current // result. let maximumSize = SIMD2( - currentResult.size.maximumWidth, - currentResult.size.maximumHeight + currentLayout.size.maximumWidth, + currentLayout.size.maximumHeight ) let minimumSize = SIMD2( - Double(currentResult.size.minimumWidth), - Double(currentResult.size.minimumHeight) + Double(currentLayout.size.minimumWidth), + Double(currentLayout.size.minimumHeight) ) if maximumSize == minimumSize { - return currentResult + return currentLayout } } @@ -253,30 +236,45 @@ public class ViewGraphNode: Sendable { environment: viewEnvironment ) - if !dryRun { - backend.show(widget: widget) - } - let result = view.update( + let result = view.computeLayout( widget, children: children, proposedSize: proposedSize, environment: viewEnvironment, - backend: backend, - dryRun: dryRun + backend: backend ) - // We assume that the view's sizing behaviour won't change between consecutive dry run updates - // and the following real update because groups of updates following that pattern are assumed to - // be occurring within a single overarching view update. It may seem weird that we set it - // to false after real updates, but that's because it may get invalidated between a real - // update and the next dry-run update. - if !dryRun { - resultCache = [:] - } else { - resultCache[proposedSize] = result - } + // We assume that the view's sizing behaviour won't change between consecutive + // layout computations and the following commit, because groups of updates + // following that pattern are assumed to be occurring within a single overarching + // view update. Under that assumption, we can cache view layout results. + resultCache[proposedSize] = result - currentResult = result + currentLayout = result return result } + + /// 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. + public func commit() -> ViewLayoutResult { + backend.show(widget: widget) + + guard let currentLayout else { + print("warning: layout committed before being computed, ignoring") + return .leafView(size: .empty) + } + + view.commit( + widget, + children: children, + layout: currentLayout, + environment: parentEnvironment, + backend: backend + ) + resultCache = [:] + + return currentLayout + } } diff --git a/Sources/SwiftCrossUI/Views/AnyView.swift b/Sources/SwiftCrossUI/Views/AnyView.swift index 7004374fad..c80ada76b9 100644 --- a/Sources/SwiftCrossUI/Views/AnyView.swift +++ b/Sources/SwiftCrossUI/Views/AnyView.swift @@ -52,19 +52,17 @@ public struct AnyView: TypeSafeView { /// Attempts to update the child. If the initial update fails then it means that the child's /// concrete type has changed and we must recreate the child node and swap out our current /// child widget with the new view's widget. - func update( + func computeLayout( _ widget: Backend.Widget, children: AnyViewChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - var (viewTypesMatched, result) = children.node.updateWithNewView( + backend: Backend + ) -> ViewLayoutResult { + var (viewTypesMatched, result) = children.node.computeLayoutWithNewView( child, proposedSize, - environment, - dryRun + environment ) // If the new view's type doesn't match the old view's type then we need to create a new @@ -79,29 +77,34 @@ public struct AnyView: TypeSafeView { // We can just assume that the update succeeded because we just created the node // a few lines earlier (so it's guaranteed that the view types match). - let (_, newResult) = children.node.updateWithNewView( + let (_, newResult) = children.node.computeLayoutWithNewView( child, proposedSize, - environment, - dryRun + environment ) result = newResult } - // If the child view has changed types and this isn't a dry-run then switch to displaying - // the new child widget. - if !dryRun, let widgetToReplace = children.widgetToReplace { + return result + } + + func commit( + _ widget: Backend.Widget, + children: AnyViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + if let widgetToReplace = children.widgetToReplace { backend.removeChild(widgetToReplace.into(), from: widget) backend.addChild(children.node.getWidget().into(), to: widget) backend.setPosition(ofChildAt: 0, in: widget, to: .zero) children.widgetToReplace = nil } - if !dryRun { - backend.setSize(of: widget, to: result.size.size) - } + _ = children.node.commit() - return result + backend.setSize(of: widget, to: layout.size.size) } } diff --git a/Sources/SwiftCrossUI/Views/Button.swift b/Sources/SwiftCrossUI/Views/Button.swift index 739d1ba407..2145d94cd3 100644 --- a/Sources/SwiftCrossUI/Views/Button.swift +++ b/Sources/SwiftCrossUI/Views/Button.swift @@ -29,15 +29,14 @@ extension Button: ElementaryView { return backend.createButton() } - public func update( + public func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - // TODO: Implement button sizing within SwiftCrossUI so that we can properly implement - // `dryRun`. Relying on the backend for button sizing also makes the Gtk 3 backend + backend: Backend + ) -> ViewLayoutResult { + // TODO: Implement button sizing within SwiftCrossUI so that we can move this to + // commit. Relying on the backend for button sizing also makes the Gtk 3 backend // basically impossible to implement correctly, hence the // `finalContentSize != contentSize` check in WindowGroupNode to catch any weird // behaviour. Without that extra safety net logic, buttons all end up label-less @@ -58,10 +57,15 @@ extension Button: ElementaryView { naturalSize.y ) - if !dryRun { - backend.setSize(of: widget, to: size) - } + return ViewLayoutResult.leafView(size: ViewSize(fixedSize: size)) + } - return ViewUpdateResult.leafView(size: ViewSize(fixedSize: size)) + public func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.setSize(of: widget, to: layout.size.size) } } diff --git a/Sources/SwiftCrossUI/Views/Checkbox.swift b/Sources/SwiftCrossUI/Views/Checkbox.swift index 7c80d354f7..7ddd8a1766 100644 --- a/Sources/SwiftCrossUI/Views/Checkbox.swift +++ b/Sources/SwiftCrossUI/Views/Checkbox.swift @@ -12,22 +12,26 @@ struct Checkbox: ElementaryView, View { return backend.createCheckbox() } - public func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - if !dryRun { - backend.updateCheckbox(widget, environment: environment) { newActiveState in - active.wrappedValue = newActiveState - } - backend.setState(ofCheckbox: widget, to: active.wrappedValue) - } - - return ViewUpdateResult.leafView( + backend: Backend + ) -> ViewLayoutResult { + return ViewLayoutResult.leafView( size: ViewSize(fixedSize: backend.naturalSize(of: widget)) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.updateCheckbox(widget, environment: environment) { newActiveState in + active.wrappedValue = newActiveState + } + backend.setState(ofCheckbox: widget, to: active.wrappedValue) + } } diff --git a/Sources/SwiftCrossUI/Views/EitherView.swift b/Sources/SwiftCrossUI/Views/EitherView.swift index fac32056ff..916b4d8872 100644 --- a/Sources/SwiftCrossUI/Views/EitherView.swift +++ b/Sources/SwiftCrossUI/Views/EitherView.swift @@ -47,25 +47,23 @@ extension EitherView: TypeSafeView { return backend.createContainer() } - func update( + func computeLayout( _ widget: Backend.Widget, children: EitherViewChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let result: ViewUpdateResult + backend: Backend + ) -> ViewLayoutResult { + let result: ViewLayoutResult let hasSwitchedCase: Bool switch storage { case .a(let a): switch children.node { - case .a(let nodeA): - result = nodeA.update( + case let .a(nodeA): + result = nodeA.computeLayout( with: a, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) hasSwitchedCase = false case .b: @@ -75,22 +73,20 @@ extension EitherView: TypeSafeView { environment: environment ) children.node = .a(nodeA) - result = nodeA.update( + result = nodeA.computeLayout( with: a, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) hasSwitchedCase = true } case .b(let b): switch children.node { - case .b(let nodeB): - result = nodeB.update( + case let .b(nodeB): + result = nodeB.computeLayout( with: b, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) hasSwitchedCase = false case .a: @@ -100,29 +96,36 @@ extension EitherView: TypeSafeView { environment: environment ) children.node = .b(nodeB) - result = nodeB.update( + result = nodeB.computeLayout( with: b, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) hasSwitchedCase = true } } children.hasSwitchedCase = children.hasSwitchedCase || hasSwitchedCase - if !dryRun && children.hasSwitchedCase { + return result + } + + func commit( + _ widget: Backend.Widget, + children: EitherViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + if children.hasSwitchedCase { backend.removeAllChildren(of: widget) backend.addChild(children.node.widget.into(), to: widget) backend.setPosition(ofChildAt: 0, in: widget, to: .zero) children.hasSwitchedCase = false } - if !dryRun { - backend.setSize(of: widget, to: result.size.size) - } + _ = children.node.erasedNode.commit() - return result + backend.setSize(of: widget, to: layout.size.size) } } diff --git a/Sources/SwiftCrossUI/Views/ElementaryView.swift b/Sources/SwiftCrossUI/Views/ElementaryView.swift index df835d0e01..cf55fd78fa 100644 --- a/Sources/SwiftCrossUI/Views/ElementaryView.swift +++ b/Sources/SwiftCrossUI/Views/ElementaryView.swift @@ -7,13 +7,19 @@ protocol ElementaryView: View where Content == EmptyView { backend: Backend ) -> Backend.Widget - func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult + backend: Backend + ) -> ViewLayoutResult + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) } extension ElementaryView { @@ -30,20 +36,33 @@ extension ElementaryView { } /// Do not implement yourself, implement ``ElementaryView/update(_:proposedSize:environment:backend:)`` instead. - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - update( + backend: Backend + ) -> ViewLayoutResult { + computeLayout( widget, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + commit( + widget, + layout: layout, + environment: environment, + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/EmptyView.swift b/Sources/SwiftCrossUI/Views/EmptyView.swift index b541a72366..44ff860c8d 100644 --- a/Sources/SwiftCrossUI/Views/EmptyView.swift +++ b/Sources/SwiftCrossUI/Views/EmptyView.swift @@ -36,16 +36,23 @@ public struct EmptyView: View, Sendable { backend.createContainer() } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - ViewUpdateResult.leafView(size: .empty) + backend: Backend + ) -> ViewLayoutResult { + ViewLayoutResult.leafView(size: .empty) } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) {} } /// The children of a node with no children. diff --git a/Sources/SwiftCrossUI/Views/ForEach.swift b/Sources/SwiftCrossUI/Views/ForEach.swift index 81ad975c1a..29572a95ea 100644 --- a/Sources/SwiftCrossUI/Views/ForEach.swift +++ b/Sources/SwiftCrossUI/Views/ForEach.swift @@ -1,3 +1,5 @@ +import Foundation + /// A view that displays a variable amount of children. public struct ForEach where Items.Index == Int { /// A variable-length collection of elements to display. @@ -76,40 +78,19 @@ extension ForEach: TypeSafeView, View where Child: View { return backend.createContainer() } - func update( + func computeLayout( _ widget: Backend.Widget, children: ForEachViewChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { func addChild(_ child: Backend.Widget) { - if dryRun { - children.queuedChanges.append(.addChild(AnyWidget(child))) - } else { - backend.addChild(child, to: widget) - } + children.queuedChanges.append(.addChild(AnyWidget(child))) } func removeChild(_ child: Backend.Widget) { - if dryRun { - children.queuedChanges.append(.removeChild(AnyWidget(child))) - } else { - backend.removeChild(child, from: widget) - } - } - - if !dryRun { - for change in children.queuedChanges { - switch change { - case .addChild(let child): - backend.addChild(child.into(), to: widget) - case .removeChild(let child): - backend.removeChild(child.into(), from: widget) - } - } - children.queuedChanges = [] + children.queuedChanges.append(.removeChild(AnyWidget(child))) } // TODO: The way we're reusing nodes for technically different elements means that if @@ -128,14 +109,14 @@ extension ForEach: TypeSafeView, View where Child: View { } layoutableChildren.append( LayoutSystem.LayoutableChild( - update: { proposedSize, environment, dryRun in - node.update( + computeLayout: { proposedSize, environment in + node.computeLayout( with: childContent, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) - } + }, + commit: node.commit ) ) } @@ -156,14 +137,14 @@ extension ForEach: TypeSafeView, View where Child: View { addChild(node.widget.into()) layoutableChildren.append( LayoutSystem.LayoutableChild( - update: { proposedSize, environment, dryRun in - node.update( + computeLayout: { proposedSize, environment in + node.computeLayout( with: childContent, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) - } + }, + commit: node.commit ) ) } @@ -175,13 +156,49 @@ extension ForEach: TypeSafeView, View where Child: View { children.nodes.removeLast(unused) } - return LayoutSystem.updateStackLayout( + return LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + func commit( + _ widget: Backend.Widget, + children: ForEachViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + for change in children.queuedChanges { + switch change { + case .addChild(let child): + backend.addChild(child.into(), to: widget) + case .removeChild(let child): + backend.removeChild(child.into(), from: widget) + } + } + children.queuedChanges = [] + + LayoutSystem.commitStackLayout( + container: widget, + children: children.nodes.map { node in + LayoutSystem.LayoutableChild( + computeLayout: { proposedSize, environment in + node.computeLayout( + with: nil, + proposedSize: proposedSize, + environment: environment + ) + }, + commit: node.commit + ) + }, + layout: layout, + environment: environment, + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/GeometryReader.swift b/Sources/SwiftCrossUI/Views/GeometryReader.swift index 79ff46d3d1..7f49e52587 100644 --- a/Sources/SwiftCrossUI/Views/GeometryReader.swift +++ b/Sources/SwiftCrossUI/Views/GeometryReader.swift @@ -52,14 +52,13 @@ public struct GeometryReader: TypeSafeView, View { return backend.createContainer() } - func update( + func computeLayout( _ widget: Backend.Widget, children: GeometryReaderChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let view = content(GeometryProxy(size: proposedSize)) let environment = environment.with(\.layoutAlignment, .leading) @@ -85,19 +84,13 @@ public struct GeometryReader: TypeSafeView, View { // to do so we'd have to give up on preferences being allowed to affect // layout (which is probably something we don't want to support anyway // because it sounds like feedback loop central). - let contentResult = contentNode.update( + let contentResult = contentNode.computeLayout( with: view, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) - if !dryRun { - backend.setPosition(ofChildAt: 0, in: widget, to: .zero) - backend.setSize(of: widget, to: proposedSize) - } - - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: proposedSize, idealSize: SIMD2(10, 10), @@ -109,6 +102,18 @@ public struct GeometryReader: TypeSafeView, View { childResults: [contentResult] ) } + + func commit( + _ widget: Backend.Widget, + children: GeometryReaderChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + _ = children.node?.commit() + backend.setPosition(ofChildAt: 0, in: widget, to: .zero) + backend.setSize(of: widget, to: layout.size.size) + } } class GeometryReaderChildren: ViewGraphNodeChildren { diff --git a/Sources/SwiftCrossUI/Views/Group.swift b/Sources/SwiftCrossUI/Views/Group.swift index 7d967f91e1..41a2786b50 100644 --- a/Sources/SwiftCrossUI/Views/Group.swift +++ b/Sources/SwiftCrossUI/Views/Group.swift @@ -23,22 +23,36 @@ public struct Group: View { return container } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - LayoutSystem.updateStackLayout( + backend: Backend + ) -> ViewLayoutResult { + LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), proposedSize: proposedSize, environment: environment, backend: backend, - dryRun: dryRun, inheritStackLayoutParticipation: true ) } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + LayoutSystem.commitStackLayout( + container: widget, + children: layoutableChildren(backend: backend, children: children), + layout: layout, + environment: environment, + backend: backend + ) + } } diff --git a/Sources/SwiftCrossUI/Views/HStack.swift b/Sources/SwiftCrossUI/Views/HStack.swift index 097df08c0c..85756c9919 100644 --- a/Sources/SwiftCrossUI/Views/HStack.swift +++ b/Sources/SwiftCrossUI/Views/HStack.swift @@ -29,15 +29,14 @@ public struct HStack: View { return vStack } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - return LayoutSystem.updateStackLayout( + backend: Backend + ) -> ViewLayoutResult { + return LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), proposedSize: proposedSize, @@ -46,8 +45,27 @@ public struct HStack: View { .with(\.layoutOrientation, .horizontal) .with(\.layoutAlignment, alignment.asStackAlignment) .with(\.layoutSpacing, spacing), - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + LayoutSystem.commitStackLayout( + container: widget, + children: layoutableChildren(backend: backend, children: children), + layout: layout, + environment: + environment + .with(\.layoutOrientation, .horizontal) + .with(\.layoutAlignment, alignment.asStackAlignment) + .with(\.layoutSpacing, spacing), + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/HotReloadableView.swift b/Sources/SwiftCrossUI/Views/HotReloadableView.swift index 5c00505748..44d46dc527 100644 --- a/Sources/SwiftCrossUI/Views/HotReloadableView.swift +++ b/Sources/SwiftCrossUI/Views/HotReloadableView.swift @@ -51,19 +51,17 @@ public struct HotReloadableView: TypeSafeView { /// view graph sub tree's state onto the new view graph sub tree. This is not possible to do /// perfectly by definition, so if we can't successfully transfer the state of the sub tree /// we just fall back on the failing view's default state. - func update( + func computeLayout( _ widget: Backend.Widget, children: HotReloadableViewChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - var (viewTypeMatched, result) = children.node.updateWithNewView( + backend: Backend + ) -> ViewLayoutResult { + var (viewTypeMatched, result) = children.node.computeLayoutWithNewView( child, proposedSize, - environment, - dryRun + environment ) if !viewTypeMatched { @@ -78,28 +76,35 @@ public struct HotReloadableView: TypeSafeView { // We can assume that the view types match since we just recreated the view // on the line above. - let (_, newResult) = children.node.updateWithNewView( + let (_, newResult) = children.node.computeLayoutWithNewView( child, proposedSize, - environment, - dryRun + environment ) result = newResult children.hasChangedChild = true } - if !dryRun { - if children.hasChangedChild { - backend.removeAllChildren(of: widget) - backend.addChild(children.node.getWidget().into(), to: widget) - backend.setPosition(ofChildAt: 0, in: widget, to: .zero) - children.hasChangedChild = false - } + return result + } - backend.setSize(of: widget, to: result.size.size) + func commit( + _ widget: Backend.Widget, + children: HotReloadableViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + if children.hasChangedChild { + backend.removeAllChildren(of: widget) + backend.addChild(children.node.getWidget().into(), to: widget) + backend.setPosition(ofChildAt: 0, in: widget, to: .zero) + children.hasChangedChild = false } - return result + _ = children.node.commit() + + backend.setSize(of: widget, to: layout.size.size) } } diff --git a/Sources/SwiftCrossUI/Views/Image.swift b/Sources/SwiftCrossUI/Views/Image.swift index 2f3be3e6f8..de02903564 100644 --- a/Sources/SwiftCrossUI/Views/Image.swift +++ b/Sources/SwiftCrossUI/Views/Image.swift @@ -66,14 +66,13 @@ extension Image: TypeSafeView { children.container.into() } - func update( + func computeLayout( _ widget: Backend.Widget, children: _ImageChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let image: ImageFormats.Image? if source != children.cachedImageSource { switch source { @@ -117,21 +116,32 @@ extension Image: TypeSafeView { size = ViewSize(fixedSize: idealSize) } - let hasResized = children.cachedImageDisplaySize != size.size - if !dryRun - && (children.imageChanged - || hasResized - || (backend.requiresImageUpdateOnScaleFactorChange - && children.lastScaleFactor != environment.windowScaleFactor)) + return ViewLayoutResult.leafView(size: size) + } + + func commit( + _ widget: Backend.Widget, + children: _ImageChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let size = layout.size.size + let hasResized = children.cachedImageDisplaySize != size + children.cachedImageDisplaySize = layout.size.size + if children.imageChanged + || hasResized + || (backend.requiresImageUpdateOnScaleFactorChange + && children.lastScaleFactor != environment.windowScaleFactor) { - if let image { + if let image = children.cachedImage { backend.updateImageView( children.imageWidget.into(), rgbaData: image.bytes, width: image.width, height: image.height, - targetWidth: size.size.x, - targetHeight: size.size.y, + targetWidth: size.x, + targetHeight: size.y, dataHasChanged: children.imageChanged, environment: environment ) @@ -147,15 +157,8 @@ extension Image: TypeSafeView { children.imageChanged = false children.lastScaleFactor = environment.windowScaleFactor } - - children.cachedImageDisplaySize = size.size - - if !dryRun { - backend.setSize(of: children.container.into(), to: size.size) - backend.setSize(of: children.imageWidget.into(), to: size.size) - } - - return ViewUpdateResult.leafView(size: size) + backend.setSize(of: children.container.into(), to: size) + backend.setSize(of: children.imageWidget.into(), to: size) } } diff --git a/Sources/SwiftCrossUI/Views/List.swift b/Sources/SwiftCrossUI/Views/List.swift index 4283e5249e..6a988d4862 100644 --- a/Sources/SwiftCrossUI/Views/List.swift +++ b/Sources/SwiftCrossUI/Views/List.swift @@ -98,14 +98,13 @@ public struct List: TypeSafeView, View backend.createSelectableListView() } - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { // Padding that the backend could not remove (some frameworks have a small // constant amount of required padding within each row). let baseRowPadding = backend.baseItemPadding(ofSelectableListView: widget) @@ -138,18 +137,17 @@ public struct List: TypeSafeView, View children.nodes.removeLast(children.nodes.count - rowCount) } - var childResults: [ViewUpdateResult] = [] + var childResults: [ViewLayoutResult] = [] for (rowView, node) in zip(rowViews, children.nodes) { - let preferredSize = node.update( + let preferredSize = node.computeLayout( with: rowView, proposedSize: SIMD2( max(proposedSize.x, minimumRowSize.x) - baseRowPadding.axisTotals.x, max(proposedSize.y, minimumRowSize.y) - baseRowPadding.axisTotals.y ), - environment: environment, - dryRun: true + environment: environment ).size - let childResult = node.update( + let childResult = node.computeLayout( with: nil, proposedSize: SIMD2( max(proposedSize.x, minimumRowSize.x) - horizontalBasePadding, @@ -158,8 +156,7 @@ public struct List: TypeSafeView, View minimumRowSize.y - baseRowPadding.axisTotals.y ) ), - environment: environment, - dryRun: dryRun + environment: environment ) childResults.append(childResult) } @@ -177,28 +174,7 @@ public struct List: TypeSafeView, View }.reduce(0, +) ) - if !dryRun { - backend.setItems( - ofSelectableListView: widget, - to: children.widgets.map { $0.into() }, - withRowHeights: childResults.map(\.size.size.y).map { height in - height + verticalBasePadding - } - ) - backend.setSize(of: widget, to: size) - backend.setSelectionHandler(forSelectableListView: widget) { selectedIndex in - selection.wrappedValue = associatedSelectionValue(selectedIndex) - } - let selectedIndex: Int? - if let selectedItem = selection.wrappedValue { - selectedIndex = find(selectedItem) - } else { - selectedIndex = nil - } - backend.setSelectedItem(ofSelectableListView: widget, toItemAt: selectedIndex) - } - - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: size, idealSize: SIMD2( @@ -215,6 +191,40 @@ public struct List: TypeSafeView, View childResults: childResults ) } + + func commit( + _ widget: Backend.Widget, + children: Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let baseRowPadding = backend.baseItemPadding(ofSelectableListView: widget) + let verticalBasePadding = baseRowPadding.axisTotals.y + + let childResults = children.nodes.map { $0.commit() } + backend.setItems( + ofSelectableListView: widget, + to: children.widgets.map { $0.into() }, + withRowHeights: childResults.map(\.size.size.y).map { height in + height + verticalBasePadding + } + ) + + backend.setSize(of: widget, to: layout.size.size) + backend.setSelectionHandler(forSelectableListView: widget) { selectedIndex in + selection.wrappedValue = associatedSelectionValue(selectedIndex) + } + + let selectedIndex: Int? + if let selectedItem = selection.wrappedValue { + selectedIndex = find(selectedItem) + } else { + selectedIndex = nil + } + + backend.setSelectedItem(ofSelectableListView: widget, toItemAt: selectedIndex) + } } class ListViewChildren: ViewGraphNodeChildren { diff --git a/Sources/SwiftCrossUI/Views/Menu.swift b/Sources/SwiftCrossUI/Views/Menu.swift index 7773db5215..1b87b9d967 100644 --- a/Sources/SwiftCrossUI/Views/Menu.swift +++ b/Sources/SwiftCrossUI/Views/Menu.swift @@ -64,19 +64,28 @@ extension Menu: TypeSafeView { [] } - func update( + func computeLayout( _ widget: Backend.Widget, children: MenuStorage, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { // TODO: Store popped menu in view graph node children so that we can // continue updating it even once it's open. var size = backend.naturalSize(of: widget) size.x = buttonWidth ?? size.x + return ViewLayoutResult.leafView(size: ViewSize(fixedSize: size)) + } + func commit( + _ widget: Backend.Widget, + children: MenuStorage, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let size = layout.size.size let content = resolve().content switch backend.menuImplementationStyle { case .dynamicPopover: @@ -102,14 +111,12 @@ extension Menu: TypeSafeView { } ) - if !dryRun { - backend.setSize(of: widget, to: size) - children.updateMenuIfShown( - content: content, - environment: environment, - backend: backend - ) - } + backend.setSize(of: widget, to: size) + children.updateMenuIfShown( + content: content, + environment: environment, + backend: backend + ) case .menuButton: let menu = children.menu as? Backend.Menu ?? backend.createPopoverMenu() children.menu = menu @@ -120,12 +127,8 @@ extension Menu: TypeSafeView { ) backend.updateButton(widget, label: label, menu: menu, environment: environment) - if !dryRun { - backend.setSize(of: widget, to: size) - } + backend.setSize(of: widget, to: size) } - - return ViewUpdateResult.leafView(size: ViewSize(fixedSize: size)) } /// A temporary button width solution until arbitrary labels are supported. diff --git a/Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift index 3f3b9e5dfa..79851ef4d2 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift @@ -58,24 +58,35 @@ struct AlertModifierView: TypeSafeView { ) } - func asWidget(_ children: Children, backend: Backend) -> Backend.Widget { + func asWidget( + _ children: Children, + backend: Backend + ) -> Backend.Widget { children.childNode.widget.into() } - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let childResult = children.childNode.update( + backend: Backend + ) -> ViewLayoutResult { + children.childNode.computeLayout( with: child, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) + } + + func commit( + _ widget: Backend.Widget, + children: AlertModifierViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + _ = children.childNode.commit() if isPresented.wrappedValue && children.alert == nil { let alert = backend.createAlert() @@ -101,8 +112,6 @@ struct AlertModifierView: TypeSafeView { ) children.alert = nil } - - return childResult } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift index 801354c213..20f2cc5d8b 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift @@ -19,21 +19,35 @@ package struct EnvironmentModifier: View { ) } - package func update( + package func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - body.update( + backend: Backend + ) -> ViewLayoutResult { + body.computeLayout( widget, children: children, proposedSize: proposedSize, environment: modification(environment), - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + package func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + body.commit( + widget, + children: children, + layout: layout, + environment: modification(environment), + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift index 9c226532d3..aced06c2ee 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift @@ -24,14 +24,14 @@ struct OnChangeModifier: View { var action: () -> Void var initial: Bool - func update( + // TODO: Should this go in computeLayout or commit? + func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { if let previousValue = previousValue, value != previousValue { action() } else if initial && previousValue == nil { @@ -42,13 +42,12 @@ struct OnChangeModifier: View { previousValue = value } - return defaultUpdate( + return defaultComputeLayout( widget, children: children, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift index 0d1d000f84..9109323540 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift @@ -32,28 +32,33 @@ struct OnHoverModifier: TypeSafeView { backend.createHoverTarget(wrapping: children.child0.widget.into()) } - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let childResult = children.child0.update( + backend: Backend + ) -> ViewLayoutResult { + children.child0.computeLayout( with: body.view0, proposedSize: proposedSize, + environment: environment + ) + } + + func commit( + _ widget: Backend.Widget, + children: Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let size = children.child0.commit().size.size + backend.setSize(of: widget, to: size) + backend.updateHoverTarget( + widget, environment: environment, - dryRun: dryRun + action: action ) - if !dryRun { - backend.setSize(of: widget, to: childResult.size.size) - backend.updateHoverTarget( - widget, - environment: environment, - action: action - ) - } - return childResult } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift index b8688eabbd..a43c8bc922 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift @@ -61,29 +61,34 @@ struct OnTapGestureModifier: TypeSafeView { backend.createTapGestureTarget(wrapping: children.child0.widget.into(), gesture: gesture) } - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let childResult = children.child0.update( + backend: Backend + ) -> ViewLayoutResult { + children.child0.computeLayout( with: body.view0, proposedSize: proposedSize, + environment: environment + ) + } + + func commit( + _ widget: Backend.Widget, + children: TupleView1.Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let size = children.child0.commit().size.size + backend.setSize(of: widget, to: size) + backend.updateTapGestureTarget( + widget, + gesture: gesture, environment: environment, - dryRun: dryRun + action: action ) - if !dryRun { - backend.setSize(of: widget, to: childResult.size.size) - backend.updateTapGestureTarget( - widget, - gesture: gesture, - environment: environment, - action: action - ) - } - return childResult } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift index 2fc864a04e..f44a6f9445 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift @@ -63,23 +63,21 @@ struct AspectRatioView: TypeSafeView { return container } - func update( + func computeLayout( _ widget: Backend.Widget, children: TupleViewChildren1, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let evaluatedAspectRatio: Double if let aspectRatio { evaluatedAspectRatio = aspectRatio == 0 ? 1 : aspectRatio } else { - let childResult = children.child0.update( + let childResult = children.child0.computeLayout( with: body.view0, proposedSize: proposedSize, - environment: environment, - dryRun: true + environment: environment ) evaluatedAspectRatio = childResult.size.idealAspectRatio } @@ -90,11 +88,10 @@ struct AspectRatioView: TypeSafeView { contentMode: contentMode ) - let childResult = children.child0.update( + let childResult = children.child0.computeLayout( with: nil, proposedSize: proposedFrameSize, - environment: environment, - dryRun: dryRun + environment: environment ) let frameSize = LayoutSystem.frameSize( @@ -103,19 +100,7 @@ struct AspectRatioView: TypeSafeView { contentMode: contentMode.opposite ) - if !dryRun { - // Center child in frame for cases where it's smaller or bigger than - // aspect ratio locked frame (not all views can achieve every aspect - // ratio). - let childPosition = Alignment.center.position( - ofChild: childResult.size.size, - in: frameSize - ) - backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) - backend.setSize(of: widget, to: frameSize) - } - - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: frameSize, idealSize: LayoutSystem.frameSize( @@ -145,4 +130,24 @@ struct AspectRatioView: TypeSafeView { childResults: [childResult] ) } + + func commit( + _ widget: Backend.Widget, + children: TupleViewChildren1, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + // Center child in frame for cases where it's smaller or bigger than + // aspect ratio locked frame (not all views can achieve every aspect + // ratio). + let childResult = children.child0.commit() + print(childResult.size.size) + let childPosition = Alignment.center.position( + ofChild: childResult.size.size, + in: layout.size.size + ) + backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift index 8b34d55c80..3c135a1ce6 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift @@ -34,26 +34,23 @@ struct BackgroundModifier: TypeSafeView { body.asWidget(children, backend: backend) } - func update( + func computeLayout( _ widget: Backend.Widget, children: TupleView2.Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let foregroundResult = children.child1.update( + backend: Backend + ) -> ViewLayoutResult { + let foregroundResult = children.child1.computeLayout( with: body.view1, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) let foregroundSize = foregroundResult.size - let backgroundResult = children.child0.update( + let backgroundResult = children.child0.computeLayout( with: body.view0, proposedSize: foregroundSize.size, - environment: environment, - dryRun: dryRun + environment: environment ) let backgroundSize = backgroundResult.size @@ -62,17 +59,7 @@ struct BackgroundModifier: TypeSafeView { max(backgroundSize.size.y, foregroundSize.size.y) ) - if !dryRun { - let backgroundPosition = (frameSize &- backgroundSize.size) / 2 - let foregroundPosition = (frameSize &- foregroundSize.size) / 2 - - backend.setPosition(ofChildAt: 0, in: widget, to: backgroundPosition) - backend.setPosition(ofChildAt: 1, in: widget, to: foregroundPosition) - - backend.setSize(of: widget, to: frameSize) - } - - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: frameSize, idealSize: SIMD2( @@ -95,4 +82,24 @@ struct BackgroundModifier: TypeSafeView { childResults: [backgroundResult, foregroundResult] ) } + + public func commit( + _ widget: Backend.Widget, + children: TupleView2.Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let frameSize = layout.size.size + let backgroundSize = children.child0.commit().size + let foregroundSize = children.child1.commit().size + + let backgroundPosition = (frameSize &- backgroundSize.size) / 2 + let foregroundPosition = (frameSize &- foregroundSize.size) / 2 + + backend.setPosition(ofChildAt: 0, in: widget, to: backgroundPosition) + backend.setPosition(ofChildAt: 1, in: widget, to: foregroundPosition) + + backend.setSize(of: widget, to: frameSize) + } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift index fa6300781c..85c7363ef4 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift @@ -37,19 +37,17 @@ struct FixedSizeModifier: TypeSafeView { return container } - func update( + func computeLayout( _ widget: Backend.Widget, children: TupleViewChildren1, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let probingChildResult = children.child0.update( + backend: Backend + ) -> ViewLayoutResult { + let probingChildResult = children.child0.computeLayout( with: body.view0, proposedSize: proposedSize, - environment: environment, - dryRun: true + environment: environment ) var frameSize = probingChildResult.size.size @@ -61,23 +59,13 @@ struct FixedSizeModifier: TypeSafeView { frameSize.y = probingChildResult.size.idealHeightForProposedWidth } - let childResult = children.child0.update( + let childResult = children.child0.computeLayout( with: body.view0, proposedSize: frameSize, - environment: environment, - dryRun: dryRun + environment: environment ) - if !dryRun { - let childPosition = Alignment.center.position( - ofChild: childResult.size.size, - in: frameSize - ) - backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) - backend.setSize(of: widget, to: frameSize) - } - - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: frameSize, idealSize: childResult.size.idealSize, @@ -91,4 +79,20 @@ struct FixedSizeModifier: TypeSafeView { childResults: [childResult] ) } + + func commit( + _ widget: Backend.Widget, + children: TupleViewChildren1, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let childResult = children.child0.commit() + let childPosition = Alignment.center.position( + ofChild: childResult.size.size, + in: layout.size.size + ) + backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift index eebfc350c8..953f85aac8 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift @@ -72,24 +72,22 @@ struct StrictFrameView: TypeSafeView { return container } - func update( + func computeLayout( _ widget: Backend.Widget, children: TupleViewChildren1, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let proposedSize = SIMD2( width ?? proposedSize.x, height ?? proposedSize.y ) - let childResult = children.child0.update( + let childResult = children.child0.computeLayout( with: body.view0, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) let childSize = childResult.size @@ -97,14 +95,6 @@ struct StrictFrameView: TypeSafeView { width ?? childSize.size.x, height ?? childSize.size.y ) - if !dryRun { - let childPosition = alignment.position( - ofChild: childSize.size, - in: frameSize - ) - backend.setSize(of: widget, to: frameSize) - backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) - } let idealWidth: Int let idealHeight: Int @@ -132,7 +122,7 @@ struct StrictFrameView: TypeSafeView { idealHeightForProposedWidth = idealHeight } - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: frameSize, idealSize: SIMD2( @@ -149,6 +139,24 @@ struct StrictFrameView: TypeSafeView { childResults: [childResult] ) } + + func commit( + _ widget: Backend.Widget, + children: TupleViewChildren1, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let frameSize = layout.size.size + let childSize = children.child0.commit().size + + let childPosition = alignment.position( + ofChild: childSize.size, + in: frameSize + ) + backend.setSize(of: widget, to: frameSize) + backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) + } } /// The implementation for the ``View/frame(width:height:)`` view modifier. @@ -202,14 +210,13 @@ struct FlexibleFrameView: TypeSafeView { return container } - func update( + func computeLayout( _ widget: Backend.Widget, children: TupleViewChildren1, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { var proposedFrameSize = proposedSize if let minWidth { proposedFrameSize.x = max(proposedFrameSize.x, minWidth) @@ -228,11 +235,10 @@ struct FlexibleFrameView: TypeSafeView { ) } - let childResult = children.child0.update( + let childResult = children.child0.computeLayout( with: body.view0, proposedSize: proposedFrameSize, - environment: environment, - dryRun: dryRun + environment: environment ) let childSize = childResult.size @@ -297,18 +303,27 @@ struct FlexibleFrameView: TypeSafeView { frameSize.idealSize.y = idealHeight } - if !dryRun { - let childPosition = alignment.position( - ofChild: childSize.size, - in: frameSize.size - ) - backend.setSize(of: widget, to: frameSize.size) - backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) - } - - return ViewUpdateResult( + return ViewLayoutResult( size: frameSize, childResults: [childResult] ) } + + func commit( + _ widget: Backend.Widget, + children: TupleViewChildren1, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let frameSize = layout.size.size + let childSize = children.child0.commit().size + + let childPosition = alignment.position( + ofChild: childSize.size, + in: frameSize + ) + backend.setSize(of: widget, to: frameSize) + backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) + } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift index 34edb7438d..70d62db0ed 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift @@ -38,26 +38,23 @@ struct OverlayModifier: TypeSafeView { body.asWidget(children, backend: backend) } - func update( + func computeLayout( _ widget: Backend.Widget, children: TupleView2.Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let contentResult = children.child0.update( + backend: Backend + ) -> ViewLayoutResult { + let contentResult = children.child0.computeLayout( with: body.view0, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) let contentSize = contentResult.size - let overlayResult = children.child1.update( + let overlayResult = children.child1.computeLayout( with: body.view1, proposedSize: contentSize.size, - environment: environment, - dryRun: dryRun + environment: environment ) let overlaySize = overlayResult.size @@ -66,17 +63,7 @@ struct OverlayModifier: TypeSafeView { max(contentSize.size.y, overlaySize.size.y) ) - if !dryRun { - let contentPosition = (frameSize &- contentSize.size) / 2 - let overlayPosition = (frameSize &- overlaySize.size) / 2 - - backend.setPosition(ofChildAt: 0, in: widget, to: contentPosition) - backend.setPosition(ofChildAt: 1, in: widget, to: overlayPosition) - - backend.setSize(of: widget, to: frameSize) - } - - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: frameSize, idealSize: contentSize.idealSize, @@ -88,4 +75,24 @@ struct OverlayModifier: TypeSafeView { childResults: [contentResult, overlayResult] ) } + + func commit( + _ widget: Backend.Widget, + children: TupleView2.Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let frameSize = layout.size.size + let contentSize = children.child0.commit().size + let overlaySize = children.child1.commit().size + + let contentPosition = (frameSize &- contentSize.size) / 2 + let overlayPosition = (frameSize &- overlaySize.size) / 2 + + backend.setPosition(ofChildAt: 0, in: widget, to: contentPosition) + backend.setPosition(ofChildAt: 1, in: widget, to: overlayPosition) + + backend.setSize(of: widget, to: frameSize) + } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift index c5441093ac..01d8f6ca8d 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift @@ -97,24 +97,24 @@ struct PaddingModifierView: TypeSafeView { return container } - func update( + func computeLayout( _ container: Backend.Widget, children: TupleViewChildren1, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { + // This first block of calculations is somewhat repeated in `commit`, + // make sure to update things in both places. let insets = EdgeInsets(insets, defaultAmount: backend.defaultPaddingAmount) - let childResult = children.child0.update( + let childResult = children.child0.computeLayout( with: body.view0, proposedSize: SIMD2( max(proposedSize.x - insets.leading - insets.trailing, 0), max(proposedSize.y - insets.top - insets.bottom, 0) ), - environment: environment, - dryRun: dryRun + environment: environment ) let childSize = childResult.size @@ -124,17 +124,15 @@ struct PaddingModifierView: TypeSafeView { childSize.size.x, childSize.size.y ) &+ paddingSize - if !dryRun { - backend.setSize(of: container, to: size) - backend.setPosition(ofChildAt: 0, in: container, to: SIMD2(insets.leading, insets.top)) - } - return ViewUpdateResult( + let idealWidth = childSize.idealWidthForProposedHeight + paddingSize.x + let idealHeight = childSize.idealHeightForProposedWidth + paddingSize.y + return ViewLayoutResult( size: ViewSize( size: size, idealSize: childSize.idealSize &+ paddingSize, - idealWidthForProposedHeight: childSize.idealWidthForProposedHeight + paddingSize.x, - idealHeightForProposedWidth: childSize.idealHeightForProposedWidth + paddingSize.y, + idealWidthForProposedHeight: idealWidth, + idealHeightForProposedWidth: idealHeight, minimumWidth: childSize.minimumWidth + paddingSize.x, minimumHeight: childSize.minimumHeight + paddingSize.y, maximumWidth: childSize.maximumWidth + Double(paddingSize.x), @@ -143,4 +141,21 @@ struct PaddingModifierView: TypeSafeView { childResults: [childResult] ) } + + func commit( + _ container: Backend.Widget, + children: TupleViewChildren1, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + _ = children.child0.commit() + + let size = layout.size.size + backend.setSize(of: container, to: size) + + let insets = EdgeInsets(insets, defaultAmount: backend.defaultPaddingAmount) + let childPosition = SIMD2(insets.leading, insets.top) + backend.setPosition(ofChildAt: 0, in: container, to: childPosition) + } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift index 6c9ce558c9..cf70e083e6 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift @@ -46,21 +46,35 @@ struct OnDisappearModifier: TypeSafeView { defaultAsWidget(children.wrappedChildren, backend: backend) } - func update( + func computeLayout( _ widget: Backend.Widget, children: OnDisappearModifierChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - defaultUpdate( + backend: Backend + ) -> ViewLayoutResult { + defaultComputeLayout( widget, children: children.wrappedChildren, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + func commit( + _ widget: Backend.Widget, + children: OnDisappearModifierChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + defaultCommit( + widget, + children: children.wrappedChildren, + layout: layout, + environment: environment, + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift index 62726cf32a..d0cd8e797c 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift @@ -47,16 +47,13 @@ extension TaskModifier: View { var body: some View { // Explicitly return to disable result builder (we don't want an extra // layer of views). - return - content - .onChange(of: id, initial: true) { - task?.cancel() - task = Task(priority: priority) { - await action() - } - } - .onDisappear { - task?.cancel() + return content.onChange(of: id, initial: true) { + task?.cancel() + task = Task(priority: priority) { + await action() } + }.onDisappear { + task?.cancel() + } } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift index 90ec013d16..3157f7f85c 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift @@ -23,21 +23,19 @@ struct PreferenceModifier: View { self.modification = modification } - func update( + func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - var result = defaultUpdate( + backend: Backend + ) -> ViewLayoutResult { + var result = defaultComputeLayout( widget, children: children, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend ) result.preferences = modification(result.preferences, environment) return result diff --git a/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift index 00d87b182c..0631e07443 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift @@ -64,20 +64,28 @@ struct SheetModifier: TypeSafeView { children.childNode.widget.into() } - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let childResult = children.childNode.update( + backend: Backend + ) -> ViewLayoutResult { + children.childNode.computeLayout( with: body.view0, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) + } + + func commit( + _ widget: Backend.Widget, + children: Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + _ = children.childNode.commit() if isPresented.wrappedValue && children.sheet == nil { let sheetViewGraphNode = ViewGraphNode( @@ -101,12 +109,12 @@ struct SheetModifier: TypeSafeView { .with(\.dismiss, dismissAction) .with(\.sheet, sheet) - let result = children.sheetContentNode!.update( + _ = children.sheetContentNode!.computeLayout( with: sheetContent(), - proposedSize: SIMD2(x: 10_000, y: 0), - environment: sheetEnvironment, - dryRun: false + proposedSize: SIMD2(10_000, 0), + environment: sheetEnvironment ) + let result = children.sheetContentNode!.commit() let window = environment.window! as! Backend.Window let preferences = result.preferences @@ -147,15 +155,6 @@ struct SheetModifier: TypeSafeView { children.parentSheet = nil children.sheetContentNode = nil } - - // Reset presentation preferences so that they don't leak to enclosing sheets. - var modifiedResult = childResult - modifiedResult.preferences.interactiveDismissDisabled = nil - modifiedResult.preferences.presentationBackground = nil - modifiedResult.preferences.presentationCornerRadius = nil - modifiedResult.preferences.presentationDetents = nil - modifiedResult.preferences.presentationDragIndicatorVisibility = nil - return modifiedResult } func handleDismiss(children: Children) { diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift index 1dd3b3ff0e..5fa3c43e49 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift @@ -30,14 +30,29 @@ struct CornerRadiusModifier: View { body.layoutableChildren(backend: backend, children: children) } - func update( + func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { + body.computeLayout( + widget, + children: children, + proposedSize: proposedSize, + environment: environment, + backend: backend + ) + } + + func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { // We used to wrap the child content in a container and then set the corner // radius on that, since it was the simplest approach. But Gtk3Backend has // extremely poor corner radius support and only applies the corner radius @@ -46,17 +61,13 @@ struct CornerRadiusModifier: View { // implement the modifier this way then you can at the very least set the // cornerRadius of a coloured rectangle, which is quite a common thing to // want to do. - let contentResult = body.update( + body.commit( widget, children: children, - proposedSize: proposedSize, + layout: layout, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend ) - if !dryRun { - backend.setCornerRadius(of: widget, to: cornerRadius) - } - return contentResult + backend.setCornerRadius(of: widget, to: cornerRadius) } } diff --git a/Sources/SwiftCrossUI/Views/OptionalView.swift b/Sources/SwiftCrossUI/Views/OptionalView.swift index 97394a0be1..7b9590b22b 100644 --- a/Sources/SwiftCrossUI/Views/OptionalView.swift +++ b/Sources/SwiftCrossUI/Views/OptionalView.swift @@ -39,23 +39,21 @@ extension OptionalView: TypeSafeView { return backend.createContainer() } - func update( + func computeLayout( _ widget: Backend.Widget, children: OptionalViewChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let hasToggled: Bool - let result: ViewUpdateResult + let result: ViewLayoutResult if let view = view { if let node = children.node { - result = node.update( + result = node.computeLayout( with: view, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) hasToggled = false } else { @@ -65,35 +63,42 @@ extension OptionalView: TypeSafeView { environment: environment ) children.node = node - result = node.update( + result = node.computeLayout( with: view, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) hasToggled = true } } else { hasToggled = children.node != nil children.node = nil - result = ViewUpdateResult.leafView(size: .hidden) + result = ViewLayoutResult.leafView(size: .hidden) } children.hasToggled = children.hasToggled || hasToggled - if !dryRun { - if children.hasToggled { - backend.removeAllChildren(of: widget) - if let node = children.node { - backend.addChild(node.widget.into(), to: widget) - backend.setPosition(ofChildAt: 0, in: widget, to: .zero) - } - children.hasToggled = false - } + return result + } - backend.setSize(of: widget, to: result.size.size) + func commit( + _ widget: Backend.Widget, + children: OptionalViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + if children.hasToggled { + backend.removeAllChildren(of: widget) + if let node = children.node { + backend.addChild(node.widget.into(), to: widget) + backend.setPosition(ofChildAt: 0, in: widget, to: .zero) + } + children.hasToggled = false } - return result + _ = children.node?.commit() + + backend.setSize(of: widget, to: layout.size.size) } } diff --git a/Sources/SwiftCrossUI/Views/Picker.swift b/Sources/SwiftCrossUI/Views/Picker.swift index 89efa8424d..9cfa8a4ea6 100644 --- a/Sources/SwiftCrossUI/Views/Picker.swift +++ b/Sources/SwiftCrossUI/Views/Picker.swift @@ -18,17 +18,18 @@ public struct Picker: ElementaryView, View { self.value = value } - public func asWidget(backend: Backend) -> Backend.Widget { + func asWidget(backend: Backend) -> Backend.Widget { return backend.createPicker() } - public func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { + // TODO: Implement picker sizing within SwiftCrossUI so that we can + // properly separate committing logic out into `commit`. backend.updatePicker( widget, options: options.map { "\($0)" }, @@ -48,11 +49,7 @@ public struct Picker: ElementaryView, View { // but it can and should be as large as reasonable let size = backend.naturalSize(of: widget) if size == SIMD2(-1, -1) { - if !dryRun { - backend.setSize(of: widget, to: proposedSize) - } - - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize( size: proposedSize, idealSize: SIMD2(10, 10), @@ -63,10 +60,18 @@ public struct Picker: ElementaryView, View { ) ) } else { - // TODO: Implement picker sizing within SwiftCrossUI so that we can properly implement `dryRun`. - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize(fixedSize: size) ) } } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/ProgressView.swift b/Sources/SwiftCrossUI/Views/ProgressView.swift index 1653410ba6..c0a44d608a 100644 --- a/Sources/SwiftCrossUI/Views/ProgressView.swift +++ b/Sources/SwiftCrossUI/Views/ProgressView.swift @@ -107,17 +107,23 @@ struct ProgressSpinnerView: ElementaryView { backend.createProgressSpinner() } - func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - ViewUpdateResult.leafView( + backend: Backend + ) -> ViewLayoutResult { + ViewLayoutResult.leafView( size: ViewSize(fixedSize: backend.naturalSize(of: widget)) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) {} } struct ProgressBarView: ElementaryView { @@ -131,25 +137,19 @@ struct ProgressBarView: ElementaryView { backend.createProgressBar() } - func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let height = backend.naturalSize(of: widget).y let size = SIMD2( proposedSize.x, height ) - if !dryRun { - backend.updateProgressBar(widget, progressFraction: value, environment: environment) - backend.setSize(of: widget, to: size) - } - - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize( size: size, idealSize: SIMD2(100, height), @@ -160,4 +160,14 @@ struct ProgressBarView: ElementaryView { ) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.updateProgressBar(widget, progressFraction: value, environment: environment) + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/ScrollView.swift b/Sources/SwiftCrossUI/Views/ScrollView.swift index b3d7f1bc51..c8178b893b 100644 --- a/Sources/SwiftCrossUI/Views/ScrollView.swift +++ b/Sources/SwiftCrossUI/Views/ScrollView.swift @@ -1,3 +1,5 @@ +import Foundation + /// A view that is scrollable when it would otherwise overflow available space. Use the /// ``View/frame`` modifier to constrain height if necessary. public struct ScrollView: TypeSafeView, View { @@ -41,20 +43,18 @@ public struct ScrollView: TypeSafeView, View { return backend.createScrollContainer(for: children.innerContainer.into()) } - func update( + func computeLayout( _ widget: Backend.Widget, children: ScrollViewChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { // Probe how big the child would like to be - let childResult = children.child.update( + let childResult = children.child.computeLayout( with: body, proposedSize: proposedSize, - environment: environment, - dryRun: true + environment: environment ) let contentSize = childResult.size @@ -64,6 +64,8 @@ public struct ScrollView: TypeSafeView, View { axes.contains(.horizontal) && contentSize.idealWidthForProposedHeight > proposedSize.x let hasVerticalScrollBar = axes.contains(.vertical) && contentSize.idealHeightForProposedWidth > proposedSize.y + children.hasHorizontalScrollBar = hasHorizontalScrollBar + children.hasVerticalScrollBar = hasVerticalScrollBar let verticalScrollBarWidth = hasVerticalScrollBar ? scrollBarWidth : 0 let horizontalScrollBarHeight = hasHorizontalScrollBar ? scrollBarWidth : 0 @@ -98,57 +100,27 @@ public struct ScrollView: TypeSafeView, View { scrollViewHeight ) - let finalResult: ViewUpdateResult - if !dryRun { - // TODO: scroll bar presence shouldn't affect whether we use current - // or ideal size. Only the presence of the given axis in the user's - // list of scroll axes should affect that. - let proposedContentSize = SIMD2( - hasHorizontalScrollBar - ? (hasVerticalScrollBar - ? contentSize.idealSize.x : contentSize.idealWidthForProposedHeight) - : min(contentSize.size.x, proposedSize.x - verticalScrollBarWidth), - hasVerticalScrollBar - ? (hasHorizontalScrollBar - ? contentSize.idealSize.y : contentSize.idealHeightForProposedWidth) - : min(contentSize.size.y, proposedSize.y - horizontalScrollBarHeight) - ) + // TODO: scroll bar presence shouldn't affect whether we use current + // or ideal size. Only the presence of the given axis in the user's + // list of scroll axes should affect that. + let proposedContentSize = SIMD2( + hasHorizontalScrollBar + ? (hasVerticalScrollBar + ? contentSize.idealSize.x : contentSize.idealWidthForProposedHeight) + : min(contentSize.size.x, proposedSize.x - verticalScrollBarWidth), + hasVerticalScrollBar + ? (hasHorizontalScrollBar + ? contentSize.idealSize.y : contentSize.idealHeightForProposedWidth) + : min(contentSize.size.y, proposedSize.y - horizontalScrollBarHeight) + ) - finalResult = children.child.update( - with: body, - proposedSize: proposedContentSize, - environment: environment, - dryRun: false - ) - let finalContentSize = finalResult.size - - let clipViewWidth = scrollViewSize.x - verticalScrollBarWidth - let clipViewHeight = scrollViewSize.y - horizontalScrollBarHeight - var childPosition: SIMD2 = .zero - var innerContainerSize: SIMD2 = finalContentSize.size - if axes.contains(.vertical) && finalContentSize.size.x < clipViewWidth { - childPosition.x = (clipViewWidth - finalContentSize.size.x) / 2 - innerContainerSize.x = clipViewWidth - } - if axes.contains(.horizontal) && finalContentSize.size.y < clipViewHeight { - childPosition.y = (clipViewHeight - finalContentSize.size.y) / 2 - innerContainerSize.y = clipViewHeight - } - - backend.setSize(of: widget, to: scrollViewSize) - backend.setSize(of: children.innerContainer.into(), to: innerContainerSize) - backend.setPosition(ofChildAt: 0, in: children.innerContainer.into(), to: childPosition) - backend.setScrollBarPresence( - ofScrollContainer: widget, - hasVerticalScrollBar: hasVerticalScrollBar, - hasHorizontalScrollBar: hasHorizontalScrollBar - ) - backend.updateScrollContainer(widget, environment: environment) - } else { - finalResult = childResult - } + let finalChildResult = children.child.computeLayout( + with: body, + proposedSize: proposedContentSize, + environment: environment + ) - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: scrollViewSize, idealSize: contentSize.idealSize, @@ -157,15 +129,58 @@ public struct ScrollView: TypeSafeView, View { maximumWidth: nil, maximumHeight: nil ), - childResults: [finalResult] + childResults: [finalChildResult] ) } + + func commit( + _ widget: Backend.Widget, + children: ScrollViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let scrollViewSize = layout.size.size + let finalContentSize = children.child.commit().size + + let verticalScrollBarWidth = + children.hasVerticalScrollBar + ? backend.scrollBarWidth : 0 + let horizontalScrollBarHeight = + children.hasHorizontalScrollBar + ? backend.scrollBarWidth : 0 + let clipViewWidth = scrollViewSize.x - verticalScrollBarWidth + let clipViewHeight = scrollViewSize.y - horizontalScrollBarHeight + var childPosition: SIMD2 = .zero + var innerContainerSize: SIMD2 = finalContentSize.size + if axes.contains(.vertical) && finalContentSize.size.x < clipViewWidth { + childPosition.x = (clipViewWidth - finalContentSize.size.x) / 2 + innerContainerSize.x = clipViewWidth + } + if axes.contains(.horizontal) && finalContentSize.size.y < clipViewHeight { + childPosition.y = (clipViewHeight - finalContentSize.size.y) / 2 + innerContainerSize.y = clipViewHeight + } + + backend.setSize(of: widget, to: scrollViewSize) + backend.setSize(of: children.innerContainer.into(), to: innerContainerSize) + backend.setPosition(ofChildAt: 0, in: children.innerContainer.into(), to: childPosition) + backend.setScrollBarPresence( + ofScrollContainer: widget, + hasVerticalScrollBar: children.hasVerticalScrollBar, + hasHorizontalScrollBar: children.hasHorizontalScrollBar + ) + backend.updateScrollContainer(widget, environment: environment) + } } class ScrollViewChildren: ViewGraphNodeChildren { var children: TupleView1>.Children var innerContainer: AnyWidget + var hasVerticalScrollBar = false + var hasHorizontalScrollBar = false + var child: AnyViewGraphNode> { children.child0 } diff --git a/Sources/SwiftCrossUI/Views/Shapes/Shape.swift b/Sources/SwiftCrossUI/Views/Shapes/Shape.swift index e266588cbe..cb90753e4c 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/Shape.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/Shape.swift @@ -82,51 +82,54 @@ extension Shape { } @MainActor - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - let storage = children as! ShapeStorage + backend: Backend + ) -> ViewLayoutResult { let size = size(fitting: proposedSize) + return ViewLayoutResult.leafView(size: size) + } + @MainActor + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { let bounds = Path.Rect( x: 0.0, y: 0.0, - width: Double(size.size.x), - height: Double(size.size.y) + width: Double(layout.size.size.x), + height: Double(layout.size.size.y) ) let path = path(in: bounds) - storage.pointsChanged = - storage.pointsChanged || storage.oldPath?.actions != path.actions + let storage = children as! ShapeStorage + let pointsChanged = storage.oldPath?.actions != path.actions storage.oldPath = path let backendPath = storage.backendPath as! Backend.Path - if !dryRun { - backend.updatePath( - backendPath, - path, - bounds: bounds, - pointsChanged: storage.pointsChanged, - environment: environment - ) - storage.pointsChanged = false - - backend.setSize(of: widget, to: size.size) - backend.renderPath( - backendPath, - container: widget, - strokeColor: .clear, - fillColor: environment.suggestedForegroundColor, - overrideStrokeStyle: nil - ) - } + backend.updatePath( + backendPath, + path, + bounds: bounds, + pointsChanged: pointsChanged, + environment: environment + ) - return ViewUpdateResult.leafView(size: size) + backend.setSize(of: widget, to: layout.size.size) + backend.renderPath( + backendPath, + container: widget, + strokeColor: .clear, + fillColor: environment.suggestedForegroundColor, + overrideStrokeStyle: nil + ) } } @@ -135,5 +138,4 @@ final class ShapeStorage: ViewGraphNodeChildren { let erasedNodes: [ErasedViewGraphNode] = [] var backendPath: Any! var oldPath: Path? - var pointsChanged = false } diff --git a/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift b/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift index 1a09c59fc0..7850cbff99 100644 --- a/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift +++ b/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift @@ -53,51 +53,53 @@ extension Shape { extension StyledShape { @MainActor - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - // TODO: Don't duplicate this between Shape and StyledShape - let storage = children as! ShapeStorage + backend: Backend + ) -> ViewLayoutResult { let size = size(fitting: proposedSize) + return ViewLayoutResult.leafView(size: size) + } + @MainActor + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { let bounds = Path.Rect( x: 0.0, y: 0.0, - width: Double(size.size.x), - height: Double(size.size.y) + width: Double(layout.size.size.x), + height: Double(layout.size.size.y) ) let path = path(in: bounds) - storage.pointsChanged = - storage.pointsChanged || storage.oldPath?.actions != path.actions + let storage = children as! ShapeStorage + let pointsChanged = storage.oldPath?.actions != path.actions storage.oldPath = path let backendPath = storage.backendPath as! Backend.Path - if !dryRun { - backend.updatePath( - backendPath, - path, - bounds: bounds, - pointsChanged: storage.pointsChanged, - environment: environment - ) - storage.pointsChanged = false - - backend.setSize(of: widget, to: size.size) - backend.renderPath( - backendPath, - container: widget, - strokeColor: strokeColor ?? .clear, - fillColor: fillColor ?? .clear, - overrideStrokeStyle: strokeStyle - ) - } + backend.updatePath( + backendPath, + path, + bounds: bounds, + pointsChanged: pointsChanged, + environment: environment + ) - return ViewUpdateResult.leafView(size: size) + backend.setSize(of: widget, to: layout.size.size) + backend.renderPath( + backendPath, + container: widget, + strokeColor: strokeColor ?? .clear, + fillColor: fillColor ?? .clear, + overrideStrokeStyle: strokeStyle + ) } } diff --git a/Sources/SwiftCrossUI/Views/Slider.swift b/Sources/SwiftCrossUI/Views/Slider.swift index 1745400aa5..f06ee294ad 100644 --- a/Sources/SwiftCrossUI/Views/Slider.swift +++ b/Sources/SwiftCrossUI/Views/Slider.swift @@ -86,46 +86,23 @@ public struct Slider: ElementaryView, View { decimalPlaces = 2 } - public func asWidget(backend: Backend) -> Backend.Widget { + func asWidget(backend: Backend) -> Backend.Widget { return backend.createSlider() } - public func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - if !dryRun { - backend.updateSlider( - widget, - minimum: minimum, - maximum: maximum, - decimalPlaces: decimalPlaces, - environment: environment - ) { newValue in - if let value { - value.wrappedValue = newValue - } - } - - if let value = value?.wrappedValue { - backend.setValue(ofSlider: widget, to: value) - } - } - - // TODO: Don't rely on naturalSize for minimum size so that we can get Slider sizes without - // relying on the widget. + backend: Backend + ) -> ViewLayoutResult { + // TODO: Don't rely on naturalSize for minimum size so that we can get + // Slider sizes without relying on the widget. let naturalSize = backend.naturalSize(of: widget) let size = SIMD2(proposedSize.x, naturalSize.y) - if !dryRun { - backend.setSize(of: widget, to: size) - } - // TODO: Allow backends to specify their own ideal slider widths. - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize( size: size, idealSize: SIMD2(100, naturalSize.y), @@ -136,4 +113,29 @@ public struct Slider: ElementaryView, View { ) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.updateSlider( + widget, + minimum: minimum, + maximum: maximum, + decimalPlaces: decimalPlaces, + environment: environment + ) { newValue in + if let value { + value.wrappedValue = newValue + } + } + + if let value = value?.wrappedValue { + backend.setValue(ofSlider: widget, to: value) + } + + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/Spacer.swift b/Sources/SwiftCrossUI/Views/Spacer.swift index 913b3b6fa4..91765a2ff0 100644 --- a/Sources/SwiftCrossUI/Views/Spacer.swift +++ b/Sources/SwiftCrossUI/Views/Spacer.swift @@ -11,19 +11,16 @@ public struct Spacer: ElementaryView, View { self.minLength = minLength } - public func asWidget( - backend: Backend - ) -> Backend.Widget { + func asWidget(backend: Backend) -> Backend.Widget { return backend.createContainer() } - public func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let minLength = minLength ?? 0 let size: SIMD2 @@ -46,10 +43,7 @@ public struct Spacer: ElementaryView, View { maximumHeight = nil } - if !dryRun { - backend.setSize(of: widget, to: size) - } - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize( size: size, idealSize: SIMD2(minimumWidth, minimumHeight), @@ -60,4 +54,13 @@ public struct Spacer: ElementaryView, View { ) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + // Spacers are invisible so we don't have to update anything. + } } diff --git a/Sources/SwiftCrossUI/Views/SplitView.swift b/Sources/SwiftCrossUI/Views/SplitView.swift index 86a0e28844..a0d70cbadb 100644 --- a/Sources/SwiftCrossUI/Views/SplitView.swift +++ b/Sources/SwiftCrossUI/Views/SplitView.swift @@ -38,39 +38,31 @@ struct SplitView: TypeSafeView, View { ) } - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let leadingWidth = backend.sidebarWidth(ofSplitView: widget) - if !dryRun { - backend.setResizeHandler(ofSplitView: widget) { - environment.onResize(.empty) - } - } // Update pane children - let leadingResult = children.leadingChild.update( + let leadingResult = children.leadingChild.computeLayout( with: body.view0, proposedSize: SIMD2( leadingWidth, proposedSize.y ), - environment: environment, - dryRun: dryRun + environment: environment ) - let trailingResult = children.trailingChild.update( + let trailingResult = children.trailingChild.computeLayout( with: body.view1, proposedSize: SIMD2( proposedSize.x - max(leadingWidth, leadingResult.size.minimumWidth), proposedSize.y ), - environment: environment, - dryRun: dryRun + environment: environment ) // Update split view size and sidebar width bounds @@ -80,37 +72,8 @@ struct SplitView: TypeSafeView, View { max(proposedSize.x, leadingContentSize.size.x + trailingContentSize.size.x), max(proposedSize.y, max(leadingContentSize.size.y, trailingContentSize.size.y)) ) - if !dryRun { - backend.setSize(of: widget, to: size) - backend.setSidebarWidthBounds( - ofSplitView: widget, - minimum: leadingContentSize.minimumWidth, - maximum: max( - leadingContentSize.minimumWidth, - proposedSize.x - trailingContentSize.minimumWidth - ) - ) - - // Center pane children - backend.setPosition( - ofChildAt: 0, - in: children.leadingPaneContainer.into(), - to: SIMD2( - leadingWidth - leadingContentSize.size.x, - proposedSize.y - leadingContentSize.size.y - ) / 2 - ) - backend.setPosition( - ofChildAt: 0, - in: children.trailingPaneContainer.into(), - to: SIMD2( - proposedSize.x - leadingWidth - trailingContentSize.size.x, - proposedSize.y - trailingContentSize.size.y - ) / 2 - ) - } - return ViewUpdateResult( + return ViewLayoutResult( size: ViewSize( size: size, idealSize: leadingContentSize.idealSize &+ trailingContentSize.idealSize, @@ -123,6 +86,50 @@ struct SplitView: TypeSafeView, View { childResults: [leadingResult, trailingResult] ) } + + func commit( + _ widget: Backend.Widget, + children: Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.setResizeHandler(ofSplitView: widget) { + environment.onResize(.empty) + } + + let leadingWidth = backend.sidebarWidth(ofSplitView: widget) + let leadingResult = children.leadingChild.commit() + let trailingResult = children.trailingChild.commit() + + backend.setSize(of: widget, to: layout.size.size) + backend.setSidebarWidthBounds( + ofSplitView: widget, + minimum: leadingResult.size.minimumWidth, + maximum: max( + leadingResult.size.minimumWidth, + layout.size.size.x - trailingResult.size.minimumWidth + ) + ) + + // Center pane children + backend.setPosition( + ofChildAt: 0, + in: children.leadingPaneContainer.into(), + to: SIMD2( + leadingWidth - leadingResult.size.size.x, + layout.size.size.y - leadingResult.size.size.y + ) / 2 + ) + backend.setPosition( + ofChildAt: 0, + in: children.trailingPaneContainer.into(), + to: SIMD2( + layout.size.size.x - leadingWidth - trailingResult.size.size.x, + layout.size.size.y - trailingResult.size.size.y + ) / 2 + ) + } } class SplitViewChildren: ViewGraphNodeChildren { diff --git a/Sources/SwiftCrossUI/Views/Table.swift b/Sources/SwiftCrossUI/Views/Table.swift index 30669177eb..b18b7281be 100644 --- a/Sources/SwiftCrossUI/Views/Table.swift +++ b/Sources/SwiftCrossUI/Views/Table.swift @@ -32,37 +32,26 @@ public struct Table>: TypeSafeVi return backend.createTable() } - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let size = proposedSize - var cellResults: [ViewUpdateResult] = [] - let rowContent = rows.map(columns.content(for:)).map(RowView.init(_:)) - - for (node, content) in zip(children.rowNodes, rowContent) { - // Updating a RowView simply updates the view stored within its node, so the proposedSize - // is irrelevant. We can just set it to `.zero`. - _ = node.update( - with: content, - proposedSize: .zero, - environment: environment, - dryRun: dryRun - ) - } - + var cellResults: [ViewLayoutResult] = [] + children.rowContent = rows.map(columns.content(for:)).map(RowView.init(_:)) let columnLabels = columns.labels let columnCount = columnLabels.count - let remainder = rowContent.count - children.rowNodes.count + + // Create and destroy row nodes + let remainder = children.rowContent.count - children.rowNodes.count if remainder < 0 { children.rowNodes.removeLast(-remainder) children.cellContainerWidgets.removeLast(-remainder * columnCount) } else if remainder > 0 { - for row in rowContent[children.rowNodes.count...] { + for row in children.rowContent[children.rowNodes.count...] { let rowNode = AnyViewGraphNode( for: row, backend: backend, @@ -77,62 +66,52 @@ public struct Table>: TypeSafeVi } } - if !dryRun { - backend.setRowCount(ofTable: widget, to: rows.count) - backend.setColumnLabels(ofTable: widget, to: columnLabels, environment: environment) + // Update row nodes + let columnWidth = proposedSize.x / columnCount + for (node, content) in zip(children.rowNodes, children.rowContent) { + // TODO: Figure out if this is required + // This doesn't update the row's cells. It just updates the view + // instance stored in the row's ViewGraphNode + _ = node.computeLayout( + with: content, + proposedSize: .zero, + environment: environment + ) } - let columnWidth = proposedSize.x / columnCount + // Compute cell layouts. Really only done during this initial layout + // step to propagate cell preference values. Otherwise we'd do it + // during commit. var rowHeights: [Int] = [] - for (rowIndex, (rowNode, content)) in zip(children.rowNodes, rowContent).enumerated() { + let rows = zip(children.rowNodes, children.rowContent) + for (rowNode, content) in rows { let rowCells = content.layoutableChildren( backend: backend, children: rowNode.getChildren() ) - var cellHeights: [Int] = [] + var rowCellHeights: [Int] = [] for rowCell in rowCells { - let cellResult = rowCell.update( + let cellResult = rowCell.computeLayout( proposedSize: SIMD2(columnWidth, backend.defaultTableRowContentHeight), - environment: environment, - dryRun: dryRun + environment: environment ) cellResults.append(cellResult) - cellHeights.append(cellResult.size.size.y) + rowCellHeights.append(cellResult.size.size.y) } let rowHeight = - max(cellHeights.max() ?? 0, backend.defaultTableRowContentHeight) - + backend.defaultTableCellVerticalPadding * 2 - rowHeights.append(rowHeight) - - for (columnIndex, cellHeight) in zip(0..>: TypeSafeVi childResults: cellResults ) } + + func commit( + _ widget: Backend.Widget, + children: TableViewChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let columnLabels = columns.labels + backend.setRowCount(ofTable: widget, to: rows.count) + backend.setColumnLabels(ofTable: widget, to: columnLabels, environment: environment) + + // TODO: Avoid overhead of converting `cellContainerWidgets` to + // `[AnyWidget]` and back again all the time. + backend.setCells( + ofTable: widget, + to: children.cellContainerWidgets.map { $0.into() }, + withRowHeights: children.rowHeights + ) + + let columnCount = columnLabels.count + for (rowIndex, rowHeight) in children.rowHeights.enumerated() { + let rowCells = children.rowContent[rowIndex].layoutableChildren( + backend: backend, + children: children.rowNodes[rowIndex].getChildren() + ) + + for (columnIndex, cell) in rowCells.enumerated() { + let index = rowIndex * columnCount + columnIndex + let cellSize = cell.commit() + backend.setPosition( + ofChildAt: 0, + in: children.cellContainerWidgets[index].into(), + to: SIMD2( + 0, + (rowHeight - cellSize.size.size.y) / 2 + ) + ) + } + } + + backend.setSize(of: widget, to: layout.size.size) + } } class TableViewChildren: ViewGraphNodeChildren { var rowNodes: [AnyViewGraphNode>] = [] var cellContainerWidgets: [AnyWidget] = [] + var rowHeights: [Int] = [] + var rowContent: [RowView] = [] /// Not used, just a protocol requirement. var widgets: [AnyWidget] { @@ -195,13 +219,21 @@ struct RowView: View { return backend.createContainer() } - func update( + func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, backend: Backend - ) -> ViewUpdateResult { - return ViewUpdateResult.leafView(size: .empty) + ) -> ViewLayoutResult { + return ViewLayoutResult.leafView(size: .empty) } + + func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) {} } diff --git a/Sources/SwiftCrossUI/Views/Text.swift b/Sources/SwiftCrossUI/Views/Text.swift index ade3737162..906343a4b6 100644 --- a/Sources/SwiftCrossUI/Views/Text.swift +++ b/Sources/SwiftCrossUI/Views/Text.swift @@ -19,14 +19,13 @@ extension Text: ElementaryView { return backend.createTextView() } - public func update( + public func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - // TODO: Avoid this + backend: Backend + ) -> ViewLayoutResult { + // TODO: Avoid this. Move it to commit // Even in dry runs we must update the underlying text view widget // because GtkBackend currently relies on querying the widget for text // properties and such (via Pango). @@ -38,9 +37,6 @@ extension Text: ElementaryView { proposedFrame: proposedSize, environment: environment ) - if !dryRun { - backend.setSize(of: widget, to: size) - } let idealSize = backend.size( of: string, @@ -62,7 +58,7 @@ extension Text: ElementaryView { environment: environment ).y - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize( size: size, idealSize: idealSize, @@ -75,4 +71,13 @@ extension Text: ElementaryView { ) ) } + + public func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/TextEditor.swift b/Sources/SwiftCrossUI/Views/TextEditor.swift index 7070d6075d..960094730c 100644 --- a/Sources/SwiftCrossUI/Views/TextEditor.swift +++ b/Sources/SwiftCrossUI/Views/TextEditor.swift @@ -10,25 +10,15 @@ public struct TextEditor: ElementaryView { backend.createTextEditor() } - func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { // Avoid evaluating the binding multiple times let content = text - if !dryRun { - backend.updateTextEditor(widget, environment: environment) { newValue in - self.text = newValue - } - if content != backend.getContent(ofTextEditor: widget) { - backend.setContent(ofTextEditor: widget, to: content) - } - } - let idealHeight = backend.size( of: content, whenDisplayedIn: widget, @@ -40,11 +30,7 @@ public struct TextEditor: ElementaryView { max(proposedSize.y, idealHeight) ) - if !dryRun { - backend.setSize(of: widget, to: size) - } - - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize( size: size, idealSize: SIMD2(10, 10), @@ -57,4 +43,23 @@ public struct TextEditor: ElementaryView { ) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + // Avoid evaluating the binding multiple times + let content = self.text + + backend.updateTextEditor(widget, environment: environment) { newValue in + self.text = newValue + } + if text != backend.getContent(ofTextEditor: widget) { + backend.setContent(ofTextEditor: widget, to: content) + } + + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/TextField.swift b/Sources/SwiftCrossUI/Views/TextField.swift index 8086f18fc4..1bb883eda2 100644 --- a/Sources/SwiftCrossUI/Views/TextField.swift +++ b/Sources/SwiftCrossUI/Views/TextField.swift @@ -23,43 +23,24 @@ public struct TextField: ElementaryView, View { self.value = value ?? Binding(get: { dummy }, set: { dummy = $0 }) } - public func asWidget(backend: Backend) -> Backend.Widget { + func asWidget(backend: Backend) -> Backend.Widget { return backend.createTextField() } - public func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - if !dryRun { - backend.updateTextField( - widget, - placeholder: placeholder, - environment: environment, - onChange: { newValue in - self.value?.wrappedValue = newValue - }, - onSubmit: environment.onSubmit ?? {} - ) - if let value = value?.wrappedValue, value != backend.getContent(ofTextField: widget) { - backend.setContent(ofTextField: widget, to: value) - } - } - + backend: Backend + ) -> ViewLayoutResult { let naturalHeight = backend.naturalSize(of: widget).y let size = SIMD2( proposedSize.x, naturalHeight ) - if !dryRun { - backend.setSize(of: widget, to: size) - } // TODO: Allow backends to set their own ideal text field width - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize( size: size, idealSize: SIMD2(100, naturalHeight), @@ -70,4 +51,26 @@ public struct TextField: ElementaryView, View { ) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.updateTextField( + widget, + placeholder: placeholder, + environment: environment, + onChange: { newValue in + self.value?.wrappedValue = newValue + }, + onSubmit: environment.onSubmit ?? {} + ) + if let value = value?.wrappedValue, value != backend.getContent(ofTextField: widget) { + backend.setContent(ofTextField: widget, to: value) + } + + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/ToggleButton.swift b/Sources/SwiftCrossUI/Views/ToggleButton.swift index b5488c4da8..8cf2c91a3b 100644 --- a/Sources/SwiftCrossUI/Views/ToggleButton.swift +++ b/Sources/SwiftCrossUI/Views/ToggleButton.swift @@ -11,24 +11,31 @@ struct ToggleButton: ElementaryView, View { self.active = active } - public func asWidget(backend: Backend) -> Backend.Widget { + func asWidget(backend: Backend) -> Backend.Widget { return backend.createToggle() } - public func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - // TODO: Implement toggle button sizing within SwiftCrossUI so that we can properly implement `dryRun`. + backend: Backend + ) -> ViewLayoutResult { + // TODO: Implement toggle button sizing within SwiftCrossUI so that we + // can delay updating the underlying widget until `commit`. backend.setState(ofToggle: widget, to: active.wrappedValue) backend.updateToggle(widget, label: label, environment: environment) { newActiveState in active.wrappedValue = newActiveState } - return ViewUpdateResult.leafView( + return ViewLayoutResult.leafView( size: ViewSize(fixedSize: backend.naturalSize(of: widget)) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) {} } diff --git a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift b/Sources/SwiftCrossUI/Views/ToggleSwitch.swift index d59d58b908..a500d0509b 100644 --- a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift +++ b/Sources/SwiftCrossUI/Views/ToggleSwitch.swift @@ -8,25 +8,30 @@ struct ToggleSwitch: ElementaryView, View { self.active = active } - public func asWidget(backend: Backend) -> Backend.Widget { + func asWidget(backend: Backend) -> Backend.Widget { return backend.createSwitch() } - public func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - if !dryRun { - backend.updateSwitch(widget, environment: environment) { newActiveState in - active.wrappedValue = newActiveState - } - backend.setState(ofSwitch: widget, to: active.wrappedValue) - } - return ViewUpdateResult.leafView( + backend: Backend + ) -> ViewLayoutResult { + return ViewLayoutResult.leafView( size: ViewSize(fixedSize: backend.naturalSize(of: widget)) ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + backend.updateSwitch(widget, environment: environment) { newActiveState in + active.wrappedValue = newActiveState + } + backend.setState(ofSwitch: widget, to: active.wrappedValue) + } } diff --git a/Sources/SwiftCrossUI/Views/TupleView.swift b/Sources/SwiftCrossUI/Views/TupleView.swift index edd1d644fd..0dab664be3 100644 --- a/Sources/SwiftCrossUI/Views/TupleView.swift +++ b/Sources/SwiftCrossUI/Views/TupleView.swift @@ -7,14 +7,16 @@ private func layoutableChild( view: V ) -> LayoutSystem.LayoutableChild { LayoutSystem.LayoutableChild( - update: { proposedSize, environment, dryRun in - node.update( + computeLayout: { proposedSize, environment in + node.computeLayout( with: view, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) }, + commit: { + node.commit() + }, tag: "\(type(of: view))" ) } @@ -34,22 +36,38 @@ extension TupleView { } @MainActor - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let group = Group(content: self) - return group.update( + return group.computeLayout( widget, children: children, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + @MainActor + func commit( + _ widget: Backend.Widget, + children: Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let group = Group(content: self) + group.commit( + widget, + children: children, + layout: layout, + environment: environment, + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/TupleView.swift.gyb b/Sources/SwiftCrossUI/Views/TupleView.swift.gyb index 38396af12d..6c9533bd7e 100644 --- a/Sources/SwiftCrossUI/Views/TupleView.swift.gyb +++ b/Sources/SwiftCrossUI/Views/TupleView.swift.gyb @@ -10,14 +10,16 @@ private func layoutableChild( view: V ) -> LayoutSystem.LayoutableChild { LayoutSystem.LayoutableChild( - update: { proposedSize, environment, dryRun in - node.update( + computeLayout: { proposedSize, environment in + node.computeLayout( with: view, proposedSize: proposedSize, - environment: environment, - dryRun: dryRun + environment: environment ) }, + commit: { + node.commit() + }, tag: "\(type(of: view))" ) } @@ -37,22 +39,38 @@ extension TupleView { } @MainActor - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let group = Group(content: self) - return group.update( + return group.computeLayout( widget, children: children, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + @MainActor + func commit( + _ widget: Backend.Widget, + children: Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let group = Group(content: self) + group.commit( + widget, + children: children, + layout: layout, + environment: environment, + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/TypeSafeView.swift b/Sources/SwiftCrossUI/Views/TypeSafeView.swift index ef99bd4b19..3724688e92 100644 --- a/Sources/SwiftCrossUI/Views/TypeSafeView.swift +++ b/Sources/SwiftCrossUI/Views/TypeSafeView.swift @@ -22,14 +22,21 @@ protocol TypeSafeView: View { backend: Backend ) -> Backend.Widget - func update( + func computeLayout( _ widget: Backend.Widget, children: Children, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult + backend: Backend + ) -> ViewLayoutResult + + func commit( + _ widget: Backend.Widget, + children: Children, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) } extension TypeSafeView { @@ -69,21 +76,35 @@ extension TypeSafeView { return asWidget(children as! Children, backend: backend) } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - update( + backend: Backend + ) -> ViewLayoutResult { + computeLayout( widget, children: children as! Children, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + commit( + widget, + children: children as! Children, + layout: layout, + environment: environment, + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/VStack.swift b/Sources/SwiftCrossUI/Views/VStack.swift index b3b9973fd6..e621aee0f0 100644 --- a/Sources/SwiftCrossUI/Views/VStack.swift +++ b/Sources/SwiftCrossUI/Views/VStack.swift @@ -39,15 +39,14 @@ public struct VStack: View { return vStack } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - return LayoutSystem.updateStackLayout( + backend: Backend + ) -> ViewLayoutResult { + return LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), proposedSize: proposedSize, @@ -56,8 +55,27 @@ public struct VStack: View { .with(\.layoutOrientation, .vertical) .with(\.layoutAlignment, alignment.asStackAlignment) .with(\.layoutSpacing, spacing), - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + LayoutSystem.commitStackLayout( + container: widget, + children: layoutableChildren(backend: backend, children: children), + layout: layout, + environment: + environment + .with(\.layoutOrientation, .vertical) + .with(\.layoutAlignment, alignment.asStackAlignment) + .with(\.layoutSpacing, spacing), + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/View.swift b/Sources/SwiftCrossUI/Views/View.swift index abeca203fe..e6a4fbfb64 100644 --- a/Sources/SwiftCrossUI/Views/View.swift +++ b/Sources/SwiftCrossUI/Views/View.swift @@ -42,24 +42,25 @@ public protocol View { backend: Backend ) -> Backend.Widget - /// Updates the view's widget after a state change occurs (although the - /// change isn't guaranteed to have affected this particular view). - /// `proposedSize` is the size suggested by the parent container, but child - /// views always get the final call on their own size. - /// - /// Always called once immediately after creating the view's widget with. - /// This helps reduce code duplication between `asWidget` and `update`. - /// - Parameter dryRun: If `true`, avoids updating the UI and only computes - /// sizing. - /// - Returns: The view's new size. - func update( + /// 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. + func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult + backend: Backend + ) -> ViewLayoutResult + + func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) } extension View { @@ -119,42 +120,71 @@ extension View { return vStack.asWidget(children, backend: backend) } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - defaultUpdate( + backend: Backend + ) -> ViewLayoutResult { + defaultComputeLayout( widget, children: children, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend ) } - /// The default `View.update` implementation. Haters may see this as a + /// The default `View.computeLayout` implementation. Haters may see this as a /// composition lover re-implementing inheritance; I see it as innovation. - public func defaultUpdate( + public func defaultComputeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { + backend: Backend + ) -> ViewLayoutResult { let vStack = VStack(content: body) - return vStack.update( + return vStack.computeLayout( widget, children: children, proposedSize: proposedSize, environment: environment, - backend: backend, - dryRun: dryRun + backend: backend + ) + } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + defaultCommit( + widget, + children: children, + layout: layout, + environment: environment, + backend: backend + ) + } + + public func defaultCommit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let vStack = VStack(content: body) + return vStack.commit( + widget, + children: children, + layout: layout, + environment: environment, + backend: backend ) } } diff --git a/Sources/SwiftCrossUI/Views/WebView.swift b/Sources/SwiftCrossUI/Views/WebView.swift index 0b9e1dae4e..116e990da6 100644 --- a/Sources/SwiftCrossUI/Views/WebView.swift +++ b/Sources/SwiftCrossUI/Views/WebView.swift @@ -13,26 +13,13 @@ public struct WebView: ElementaryView { backend.createWebView() } - func update( + func computeLayout( _ widget: Backend.Widget, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - if !dryRun { - if url != currentURL { - backend.navigateWebView(widget, to: url) - currentURL = url - } - backend.updateWebView(widget, environment: environment) { destination in - currentURL = destination - url = destination - } - backend.setSize(of: widget, to: proposedSize) - } - - return ViewUpdateResult( + backend: Backend + ) -> ViewLayoutResult { + return ViewLayoutResult( size: ViewSize( size: proposedSize, idealSize: SIMD2(10, 10), @@ -44,4 +31,21 @@ public struct WebView: ElementaryView { childResults: [] ) } + + func commit( + _ widget: Backend.Widget, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + if url != currentURL { + backend.navigateWebView(widget, to: url) + currentURL = url + } + backend.updateWebView(widget, environment: environment) { destination in + currentURL = destination + url = destination + } + backend.setSize(of: widget, to: layout.size.size) + } } diff --git a/Sources/SwiftCrossUI/Views/ZStack.swift b/Sources/SwiftCrossUI/Views/ZStack.swift index 5cd5657c51..3bc815a6db 100644 --- a/Sources/SwiftCrossUI/Views/ZStack.swift +++ b/Sources/SwiftCrossUI/Views/ZStack.swift @@ -28,23 +28,20 @@ public struct ZStack: View { return zStack } - public func update( + public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend: Backend, - dryRun: Bool - ) -> ViewUpdateResult { - var childResults: [ViewUpdateResult] = [] - for child in layoutableChildren(backend: backend, children: children) { - let childResult = child.update( - proposedSize: proposedSize, - environment: environment, - dryRun: dryRun - ) - childResults.append(childResult) - } + backend: Backend + ) -> ViewLayoutResult { + let childResults = layoutableChildren(backend: backend, children: children) + .map { child in + child.computeLayout( + proposedSize: proposedSize, + environment: environment + ) + } let childSizes = childResults.map(\.size) let size = ViewSize( @@ -62,17 +59,30 @@ public struct ZStack: View { maximumHeight: childSizes.map(\.maximumHeight).max() ?? 0 ) - if !dryRun { - for (i, childSize) in childSizes.enumerated() { - let position = alignment.position( - ofChild: childSize.size, - in: size.size - ) - backend.setPosition(ofChildAt: i, in: widget, to: position) + return ViewLayoutResult(size: size, childResults: childResults) + } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + let size = layout.size + let children = layoutableChildren(backend: backend, children: children) + .map { child in + child.commit() } - backend.setSize(of: widget, to: size.size) + + for (i, child) in children.enumerated() { + let position = alignment.position( + ofChild: child.size.size, + in: size.size + ) + backend.setPosition(ofChildAt: i, in: widget, to: position) } - return ViewUpdateResult(size: size, childResults: childResults) + backend.setSize(of: widget, to: size.size) } } diff --git a/Sources/UIKitBackend/UIViewRepresentable.swift b/Sources/UIKitBackend/UIViewRepresentable.swift index 5b20929cd9..110fcd05b5 100644 --- a/Sources/UIKitBackend/UIViewRepresentable.swift +++ b/Sources/UIKitBackend/UIViewRepresentable.swift @@ -135,13 +135,12 @@ where Self: UIViewRepresentable { } } - public func update( + public func computeLayout( _ widget: Backend.Widget, children _: any ViewGraphNodeChildren, proposedSize: SIMD2, environment: EnvironmentValues, - backend _: Backend, - dryRun: Bool + backend _: Backend ) -> ViewUpdateResult { let representingWidget = widget as! ViewRepresentingWidget representingWidget.update(with: environment) @@ -153,13 +152,19 @@ where Self: UIViewRepresentable { context: representingWidget.context! ) - if !dryRun { - representingWidget.width = size.size.x - representingWidget.height = size.size.y - } - return ViewUpdateResult.leafView(size: size) } + + public func commit( + _ widget: Backend.Widget, + children: any ViewGraphNodeChildren, + layout: ViewLayoutResult, + environment: EnvironmentValues, + backend: Backend + ) { + representingWidget.width = layout.size.size.x + representingWidget.height = layout.size.size.y + } } extension UIViewRepresentable From 66334b333d9a63ada621b2768203fbfe644e3daf Mon Sep 17 00:00:00 2001 From: stackotter Date: Thu, 11 Dec 2025 11:34:44 +1000 Subject: [PATCH 2/2] Implement SwiftUI's layout system (worse behaviour, but better performance) Some of the bad behaviour from SwiftUI's layout system can probably be mitigated, but other undesirable behaviour is probably the best we can do, cause some things are impossible to compute nicely without laying out child views more than once (a no go for performant layout) --- .../LayoutPerformanceBenchmark.swift | 8 +- Sources/AppKitBackend/AppKitBackend.swift | 2 +- .../AppKitBackend/NSViewRepresentable.swift | 466 +++---- Sources/DummyBackend/DummyBackend.swift | 2 +- .../SwiftCrossUI/Builders/SceneBuilder.swift | 239 +--- .../Builders/TableRowBuilder.swift | 379 ------ .../Builders/TableRowBuilder.swift.gyb | 27 - .../SwiftCrossUI/Builders/ViewBuilder.swift | 254 +--- .../Environment/EnvironmentValues.swift | 6 +- Sources/SwiftCrossUI/HotReloadingMacros.swift | 22 +- .../SwiftCrossUI/Layout/LayoutSystem.swift | 297 +++-- Sources/SwiftCrossUI/Layout/Position.swift | 53 + .../Layout/ProposedViewSize.swift | 69 + .../Layout/ViewLayoutResult.swift | 53 + Sources/SwiftCrossUI/Layout/ViewSize.swift | 159 +-- .../Layout/ViewUpdateResult.swift | 33 - Sources/SwiftCrossUI/Scenes/TupleScene.swift | 399 ++---- .../SwiftCrossUI/Scenes/WindowGroupNode.swift | 142 +-- Sources/SwiftCrossUI/Values/Axis.swift | 22 +- Sources/SwiftCrossUI/Values/Color.swift | 16 +- Sources/SwiftCrossUI/Values/Orientation.swift | 10 + .../ViewGraph/AnyViewGraphNode.swift | 4 +- .../ViewGraph/ErasedViewGraphNode.swift | 4 +- .../SwiftCrossUI/ViewGraph/ViewGraph.swift | 51 +- .../ViewGraph/ViewGraphNode.swift | 53 +- Sources/SwiftCrossUI/Views/AnyView.swift | 139 -- Sources/SwiftCrossUI/Views/Button.swift | 6 +- Sources/SwiftCrossUI/Views/Checkbox.swift | 37 - Sources/SwiftCrossUI/Views/Divider.swift | 25 - Sources/SwiftCrossUI/Views/EitherView.swift | 4 +- .../SwiftCrossUI/Views/ElementaryView.swift | 4 +- Sources/SwiftCrossUI/Views/EmptyView.swift | 4 +- Sources/SwiftCrossUI/Views/ForEach.swift | 6 +- .../SwiftCrossUI/Views/GeometryReader.swift | 129 -- Sources/SwiftCrossUI/Views/Group.swift | 14 +- Sources/SwiftCrossUI/Views/HStack.swift | 14 +- .../Views/HotReloadableView.swift | 139 -- Sources/SwiftCrossUI/Views/Image.swift | 182 --- Sources/SwiftCrossUI/Views/List.swift | 244 ---- Sources/SwiftCrossUI/Views/Menu.swift | 13 +- .../Views/Modifiers/AlertModifier.swift | 137 -- .../ConditionalApplicationModifier.swift | 23 - .../Views/Modifiers/DisabledModifier.swift | 9 - .../Views/Modifiers/EnvironmentModifier.swift | 2 +- .../Modifiers/Handlers/OnChangeModifier.swift | 53 - .../Modifiers/Handlers/OnHoverModifier.swift | 64 - .../Handlers/OnOpenURLModifier.swift | 18 - .../Modifiers/Handlers/OnSubmitModifier.swift | 23 - .../Handlers/OnTapGestureModifier.swift | 6 +- .../Layout/AspectRatioModifier.swift | 153 --- .../Modifiers/Layout/BackgroundModifier.swift | 46 +- .../Modifiers/Layout/FixedSizeModifier.swift | 41 +- .../Modifiers/Layout/FrameModifier.swift | 166 +-- .../MultilineTextAlignmentModifier.swift | 9 - .../Modifiers/Layout/OverlayModifier.swift | 98 -- .../Modifiers/Layout/PaddingModifier.swift | 44 +- .../Lifecycle/OnAppearModifier.swift | 25 - .../Lifecycle/OnDisappearModifier.swift | 107 -- .../Modifiers/Lifecycle/TaskModifier.swift | 59 - .../Views/Modifiers/PreferenceModifier.swift | 43 - .../Modifiers/PresentationModifiers.swift | 64 - .../ScrollDismissesKeyboardModifier.swift | 41 - .../Views/Modifiers/SheetModifier.swift | 198 --- .../Style/CornerRadiusModifier.swift | 2 +- .../Modifiers/Style/ToggleStyleModifier.swift | 8 - .../Modifiers/TextContentTypeModifier.swift | 11 - .../Modifiers/TextSelectionModifier.swift | 10 - .../SwiftCrossUI/Views/NavigationLink.swift | 29 - .../SwiftCrossUI/Views/NavigationPath.swift | 150 --- .../Views/NavigationSplitView.swift | 51 - .../SwiftCrossUI/Views/NavigationStack.swift | 122 -- Sources/SwiftCrossUI/Views/OptionalView.swift | 6 +- Sources/SwiftCrossUI/Views/Picker.swift | 77 -- Sources/SwiftCrossUI/Views/ProgressView.swift | 173 --- Sources/SwiftCrossUI/Views/ScrollView.swift | 150 +-- .../SwiftCrossUI/Views/Shapes/Capsule.swift | 9 - .../SwiftCrossUI/Views/Shapes/Circle.swift | 23 - .../SwiftCrossUI/Views/Shapes/Ellipse.swift | 19 - .../SwiftCrossUI/Views/Shapes/Rectangle.swift | 7 - .../Views/Shapes/RoundedRectangle.swift | 449 ------- Sources/SwiftCrossUI/Views/Shapes/Shape.swift | 141 --- .../Views/Shapes/StyledShape.swift | 105 -- Sources/SwiftCrossUI/Views/Slider.swift | 141 --- Sources/SwiftCrossUI/Views/Spacer.swift | 43 +- Sources/SwiftCrossUI/Views/SplitView.swift | 173 --- Sources/SwiftCrossUI/Views/Table.swift | 239 ---- Sources/SwiftCrossUI/Views/TableColumn.swift | 25 - .../SwiftCrossUI/Views/TableRowContent.swift | 1121 ----------------- .../Views/TableRowContent.swift.gyb | 53 - Sources/SwiftCrossUI/Views/Text.swift | 52 +- Sources/SwiftCrossUI/Views/TextEditor.swift | 65 - Sources/SwiftCrossUI/Views/TextField.swift | 76 -- Sources/SwiftCrossUI/Views/Toggle.swift | 58 - Sources/SwiftCrossUI/Views/ToggleButton.swift | 41 - Sources/SwiftCrossUI/Views/ToggleSwitch.swift | 37 - Sources/SwiftCrossUI/Views/TupleView.swift | 253 +--- .../SwiftCrossUI/Views/TupleView.swift.gyb | 2 +- .../Views/TupleViewChildren.swift | 457 ++----- .../Views/TupleViewChildren.swift.gyb | 15 +- Sources/SwiftCrossUI/Views/TypeSafeView.swift | 4 +- Sources/SwiftCrossUI/Views/VStack.swift | 17 +- Sources/SwiftCrossUI/Views/View.swift | 8 +- Sources/SwiftCrossUI/Views/WebView.swift | 51 - Sources/SwiftCrossUI/Views/ZStack.swift | 23 +- 104 files changed, 1363 insertions(+), 8292 deletions(-) delete mode 100644 Sources/SwiftCrossUI/Builders/TableRowBuilder.swift delete mode 100644 Sources/SwiftCrossUI/Builders/TableRowBuilder.swift.gyb create mode 100644 Sources/SwiftCrossUI/Layout/Position.swift create mode 100644 Sources/SwiftCrossUI/Layout/ProposedViewSize.swift create mode 100644 Sources/SwiftCrossUI/Layout/ViewLayoutResult.swift delete mode 100644 Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift delete mode 100644 Sources/SwiftCrossUI/Views/AnyView.swift delete mode 100644 Sources/SwiftCrossUI/Views/Checkbox.swift delete mode 100644 Sources/SwiftCrossUI/Views/Divider.swift delete mode 100644 Sources/SwiftCrossUI/Views/GeometryReader.swift delete mode 100644 Sources/SwiftCrossUI/Views/HotReloadableView.swift delete mode 100644 Sources/SwiftCrossUI/Views/Image.swift delete mode 100644 Sources/SwiftCrossUI/Views/List.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/ConditionalApplicationModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/ScrollDismissesKeyboardModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/TextContentTypeModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/Modifiers/TextSelectionModifier.swift delete mode 100644 Sources/SwiftCrossUI/Views/NavigationLink.swift delete mode 100644 Sources/SwiftCrossUI/Views/NavigationPath.swift delete mode 100644 Sources/SwiftCrossUI/Views/NavigationSplitView.swift delete mode 100644 Sources/SwiftCrossUI/Views/NavigationStack.swift delete mode 100644 Sources/SwiftCrossUI/Views/Picker.swift delete mode 100644 Sources/SwiftCrossUI/Views/ProgressView.swift delete mode 100644 Sources/SwiftCrossUI/Views/Shapes/Capsule.swift delete mode 100644 Sources/SwiftCrossUI/Views/Shapes/Circle.swift delete mode 100644 Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift delete mode 100644 Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift delete mode 100644 Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift delete mode 100644 Sources/SwiftCrossUI/Views/Shapes/Shape.swift delete mode 100644 Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift delete mode 100644 Sources/SwiftCrossUI/Views/Slider.swift delete mode 100644 Sources/SwiftCrossUI/Views/SplitView.swift delete mode 100644 Sources/SwiftCrossUI/Views/Table.swift delete mode 100644 Sources/SwiftCrossUI/Views/TableColumn.swift delete mode 100644 Sources/SwiftCrossUI/Views/TableRowContent.swift delete mode 100644 Sources/SwiftCrossUI/Views/TableRowContent.swift.gyb delete mode 100644 Sources/SwiftCrossUI/Views/TextEditor.swift delete mode 100644 Sources/SwiftCrossUI/Views/TextField.swift delete mode 100644 Sources/SwiftCrossUI/Views/Toggle.swift delete mode 100644 Sources/SwiftCrossUI/Views/ToggleButton.swift delete mode 100644 Sources/SwiftCrossUI/Views/ToggleSwitch.swift delete mode 100644 Sources/SwiftCrossUI/Views/WebView.swift diff --git a/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift b/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift index d19d5ae0ba..dc34438718 100644 --- a/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift +++ b/Benchmarks/LayoutPerformanceBenchmark/LayoutPerformanceBenchmark.swift @@ -34,7 +34,7 @@ struct Benchmarks { } @MainActor - func updateNode(_ node: ViewGraphNode, _ size: SIMD2) { + func updateNode(_ node: ViewGraphNode, _ size: ProposedViewSize) { _ = node.computeLayout(proposedSize: size, environment: environment) _ = node.commit() } @@ -44,7 +44,7 @@ struct Benchmarks { #endif @MainActor - func benchmarkLayout(of viewType: V.Type, _ size: SIMD2, _ label: String) { + func benchmarkLayout(of viewType: V.Type, _ size: ProposedViewSize, _ label: String) { #if BENCHMARK_VIZ benchmarkVisualizations.append(( label, @@ -62,8 +62,8 @@ struct Benchmarks { } // Register benchmarks - benchmarkLayout(of: GridView.self, SIMD2(800, 800), "grid") - benchmarkLayout(of: ScrollableMessageListView.self, SIMD2(800, 800), "message list") + benchmarkLayout(of: GridView.self, ProposedViewSize(800, 800), "grid") + benchmarkLayout(of: ScrollableMessageListView.self, ProposedViewSize(800, 800), "message list") #if BENCHMARK_VIZ let names = benchmarkVisualizations.map(\.name).joined(separator: " | ") diff --git a/Sources/AppKitBackend/AppKitBackend.swift b/Sources/AppKitBackend/AppKitBackend.swift index aa5becc71a..6dbc4b7be3 100644 --- a/Sources/AppKitBackend/AppKitBackend.swift +++ b/Sources/AppKitBackend/AppKitBackend.swift @@ -530,7 +530,7 @@ public final class AppKitBackend: AppBackend { ) -> SIMD2 { if let proposedFrame, proposedFrame.x == 0 { // We want the text to have the same height as it would have if it were - // one pixel wide so that the layout doesn't suddely jump when the text + // one pixel wide so that the layout doesn't suddenly jump when the text // reaches zero width. let size = size( of: text, diff --git a/Sources/AppKitBackend/NSViewRepresentable.swift b/Sources/AppKitBackend/NSViewRepresentable.swift index 31238dce7b..07fdf356e9 100644 --- a/Sources/AppKitBackend/NSViewRepresentable.swift +++ b/Sources/AppKitBackend/NSViewRepresentable.swift @@ -1,233 +1,233 @@ -import AppKit -import SwiftCrossUI - -public struct NSViewRepresentableContext { - public let coordinator: Coordinator - public internal(set) var environment: EnvironmentValues -} - -/// A wrapper that you use to integrate an AppKit view into your SwiftCrossUI -/// view hierarchy. -public protocol NSViewRepresentable: View where Content == Never { - /// The underlying AppKit view. - associatedtype NSViewType: NSView - /// A type providing persistent storage for representable implementations. - associatedtype Coordinator = Void - - /// Create the initial NSView instance. - @MainActor - func makeNSView(context: NSViewRepresentableContext) -> NSViewType - - /// Update the view with new values. - /// - Parameters: - /// - nsView: The view to update. - /// - context: The context, including the coordinator and potentially new - /// environment values. - /// - Note: This may be called even when `context` has not changed. - @MainActor - func updateNSView( - _ nsView: NSViewType, - context: NSViewRepresentableContext - ) - - /// Make the coordinator for this view. - /// - /// The coordinator is used when the view needs to communicate changes to - /// the rest of the view hierarchy (i.e. through bindings), and is often the - /// view's delegate. - @MainActor - func makeCoordinator() -> Coordinator - - /// Compute the view's size. - /// - /// 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). - func determineViewSize( - for proposal: SIMD2, - nsView: NSViewType, - context: NSViewRepresentableContext - ) -> ViewSize - - /// Called to clean up the view when it's removed. - /// - /// This method is called after all AppKit lifecycle methods, such as - /// `nsView.didMoveToSuperview()`. The default implementation does nothing. - /// - Parameters: - /// - nsVIew: The view being dismantled. - /// - coordinator: The coordinator. - static func dismantleNSView(_ nsView: NSViewType, coordinator: Coordinator) -} - -extension NSViewRepresentable { - public static func dismantleNSView(_: NSViewType, coordinator _: Coordinator) { - // no-op - } - - public func determineViewSize( - for proposal: SIMD2, nsView: NSViewType, - context _: NSViewRepresentableContext - ) -> ViewSize { - let intrinsicSize = nsView.intrinsicContentSize - let sizeThatFits = nsView.fittingSize - - let roundedSizeThatFits = SIMD2( - Int(sizeThatFits.width.rounded(.up)), - Int(sizeThatFits.height.rounded(.up))) - let roundedIntrinsicSize = SIMD2( - Int(intrinsicSize.width.rounded(.awayFromZero)), - Int(intrinsicSize.height.rounded(.awayFromZero))) - - return ViewSize( - size: SIMD2( - intrinsicSize.width < 0.0 ? proposal.x : roundedSizeThatFits.x, - intrinsicSize.height < 0.0 ? proposal.y : roundedSizeThatFits.y - ), - // The 10 here is a somewhat arbitrary constant value so that it's always the same. - // See also `Color` and `Picker`, which use the same constant. - idealSize: SIMD2( - intrinsicSize.width < 0.0 ? 10 : roundedIntrinsicSize.x, - intrinsicSize.height < 0.0 ? 10 : roundedIntrinsicSize.y - ), - minimumWidth: max(0, roundedIntrinsicSize.x), - minimumHeight: max(0, roundedIntrinsicSize.x), - maximumWidth: nil, - maximumHeight: nil - ) - } -} - -extension View where Self: NSViewRepresentable { - public var body: Never { - preconditionFailure("This should never be called") - } - - public func children( - backend _: Backend, - snapshots _: [ViewGraphSnapshotter.NodeSnapshot]?, - environment _: EnvironmentValues - ) -> any ViewGraphNodeChildren { - EmptyViewChildren() - } - - public func layoutableChildren( - backend _: Backend, - children _: any ViewGraphNodeChildren - ) -> [LayoutSystem.LayoutableChild] { - [] - } - - public func asWidget( - _: any ViewGraphNodeChildren, - backend _: Backend - ) -> Backend.Widget { - if let widget = RepresentingWidget(representable: self) as? Backend.Widget { - return widget - } else { - fatalError("NSViewRepresentable requested by \(Backend.self)") - } - } - - public func computeLayout( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - guard backend is AppKitBackend else { - fatalError("NSViewRepresentable updated by \(Backend.self)") - } - - let representingWidget = widget as! RepresentingWidget - representingWidget.update(with: environment) - - let size = representingWidget.representable.determineViewSize( - for: proposedSize, - nsView: representingWidget.subview, - context: representingWidget.context! - ) - - return ViewLayoutResult.leafView(size: size) - } - - public func commit( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.setSize(of: widget, to: layout.size.size) - } -} - -extension NSViewRepresentable where Coordinator == Void { - public func makeCoordinator() { - return () - } -} - -/// Exists to handle `deinit`, the rest of the stuff is just in here cause -/// it's a convenient location. -final class RepresentingWidget: NSView { - var representable: Representable - var context: NSViewRepresentableContext? - - init(representable: Representable) { - self.representable = representable - super.init(frame: .zero) - - self.translatesAutoresizingMaskIntoConstraints = false - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) is not used for this view") - } - - lazy var subview: Representable.NSViewType = { - let view = representable.makeNSView(context: context!) - - self.addSubview(view) - - view.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - view.topAnchor.constraint(equalTo: self.topAnchor), - view.leadingAnchor.constraint(equalTo: self.leadingAnchor), - view.trailingAnchor.constraint(equalTo: self.trailingAnchor), - view.bottomAnchor.constraint(equalTo: self.bottomAnchor), - ]) - - return view - }() - - func update(with environment: EnvironmentValues) { - if context == nil { - context = .init( - coordinator: representable.makeCoordinator(), - environment: environment - ) - } else { - context!.environment = environment - representable.updateNSView(subview, context: context!) - } - } - - deinit { - if let context { - Representable.dismantleNSView(subview, coordinator: context.coordinator) - } - } -} +// import AppKit +// import SwiftCrossUI + +// public struct NSViewRepresentableContext { +// public let coordinator: Coordinator +// public internal(set) var environment: EnvironmentValues +// } + +// /// A wrapper that you use to integrate an AppKit view into your SwiftCrossUI +// /// view hierarchy. +// public protocol NSViewRepresentable: View where Content == Never { +// /// The underlying AppKit view. +// associatedtype NSViewType: NSView +// /// A type providing persistent storage for representable implementations. +// associatedtype Coordinator = Void + +// /// Create the initial NSView instance. +// @MainActor +// func makeNSView(context: NSViewRepresentableContext) -> NSViewType + +// /// Update the view with new values. +// /// - Parameters: +// /// - nsView: The view to update. +// /// - context: The context, including the coordinator and potentially new +// /// environment values. +// /// - Note: This may be called even when `context` has not changed. +// @MainActor +// func updateNSView( +// _ nsView: NSViewType, +// context: NSViewRepresentableContext +// ) + +// /// Make the coordinator for this view. +// /// +// /// The coordinator is used when the view needs to communicate changes to +// /// the rest of the view hierarchy (i.e. through bindings), and is often the +// /// view's delegate. +// @MainActor +// func makeCoordinator() -> Coordinator + +// /// Compute the view's size. +// /// +// /// 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). +// func determineViewSize( +// for proposal: SIMD2, +// nsView: NSViewType, +// context: NSViewRepresentableContext +// ) -> ViewSize + +// /// Called to clean up the view when it's removed. +// /// +// /// This method is called after all AppKit lifecycle methods, such as +// /// `nsView.didMoveToSuperview()`. The default implementation does nothing. +// /// - Parameters: +// /// - nsVIew: The view being dismantled. +// /// - coordinator: The coordinator. +// static func dismantleNSView(_ nsView: NSViewType, coordinator: Coordinator) +// } + +// extension NSViewRepresentable { +// public static func dismantleNSView(_: NSViewType, coordinator _: Coordinator) { +// // no-op +// } + +// public func determineViewSize( +// for proposal: SIMD2, nsView: NSViewType, +// context _: NSViewRepresentableContext +// ) -> ViewSize { +// let intrinsicSize = nsView.intrinsicContentSize +// let sizeThatFits = nsView.fittingSize + +// let roundedSizeThatFits = SIMD2( +// Int(sizeThatFits.width.rounded(.up)), +// Int(sizeThatFits.height.rounded(.up))) +// let roundedIntrinsicSize = SIMD2( +// Int(intrinsicSize.width.rounded(.awayFromZero)), +// Int(intrinsicSize.height.rounded(.awayFromZero))) + +// return ViewSize( +// size: SIMD2( +// intrinsicSize.width < 0.0 ? proposal.x : roundedSizeThatFits.x, +// intrinsicSize.height < 0.0 ? proposal.y : roundedSizeThatFits.y +// ), +// // The 10 here is a somewhat arbitrary constant value so that it's always the same. +// // See also `Color` and `Picker`, which use the same constant. +// idealSize: SIMD2( +// intrinsicSize.width < 0.0 ? 10 : roundedIntrinsicSize.x, +// intrinsicSize.height < 0.0 ? 10 : roundedIntrinsicSize.y +// ), +// minimumWidth: max(0, roundedIntrinsicSize.x), +// minimumHeight: max(0, roundedIntrinsicSize.x), +// maximumWidth: nil, +// maximumHeight: nil +// ) +// } +// } + +// extension View where Self: NSViewRepresentable { +// public var body: Never { +// preconditionFailure("This should never be called") +// } + +// public func children( +// backend _: Backend, +// snapshots _: [ViewGraphSnapshotter.NodeSnapshot]?, +// environment _: EnvironmentValues +// ) -> any ViewGraphNodeChildren { +// EmptyViewChildren() +// } + +// public func layoutableChildren( +// backend _: Backend, +// children _: any ViewGraphNodeChildren +// ) -> [LayoutSystem.LayoutableChild] { +// [] +// } + +// public func asWidget( +// _: any ViewGraphNodeChildren, +// backend _: Backend +// ) -> Backend.Widget { +// if let widget = RepresentingWidget(representable: self) as? Backend.Widget { +// return widget +// } else { +// fatalError("NSViewRepresentable requested by \(Backend.self)") +// } +// } + +// public func computeLayout( +// _ widget: Backend.Widget, +// children: any ViewGraphNodeChildren, +// proposedSize: SIMD2, +// environment: EnvironmentValues, +// backend: Backend +// ) -> ViewLayoutResult { +// guard backend is AppKitBackend else { +// fatalError("NSViewRepresentable updated by \(Backend.self)") +// } + +// let representingWidget = widget as! RepresentingWidget +// representingWidget.update(with: environment) + +// let size = representingWidget.representable.determineViewSize( +// for: proposedSize, +// nsView: representingWidget.subview, +// context: representingWidget.context! +// ) + +// return ViewLayoutResult.leafView(size: size) +// } + +// public func commit( +// _ widget: Backend.Widget, +// children: any ViewGraphNodeChildren, +// layout: ViewLayoutResult, +// environment: EnvironmentValues, +// backend: Backend +// ) { +// backend.setSize(of: widget, to: layout.size.size) +// } +// } + +// extension NSViewRepresentable where Coordinator == Void { +// public func makeCoordinator() { +// return () +// } +// } + +// /// Exists to handle `deinit`, the rest of the stuff is just in here cause +// /// it's a convenient location. +// final class RepresentingWidget: NSView { +// var representable: Representable +// var context: NSViewRepresentableContext? + +// init(representable: Representable) { +// self.representable = representable +// super.init(frame: .zero) + +// self.translatesAutoresizingMaskIntoConstraints = false +// } + +// @available(*, unavailable) +// required init?(coder: NSCoder) { +// fatalError("init(coder:) is not used for this view") +// } + +// lazy var subview: Representable.NSViewType = { +// let view = representable.makeNSView(context: context!) + +// self.addSubview(view) + +// view.translatesAutoresizingMaskIntoConstraints = false +// NSLayoutConstraint.activate([ +// view.topAnchor.constraint(equalTo: self.topAnchor), +// view.leadingAnchor.constraint(equalTo: self.leadingAnchor), +// view.trailingAnchor.constraint(equalTo: self.trailingAnchor), +// view.bottomAnchor.constraint(equalTo: self.bottomAnchor), +// ]) + +// return view +// }() + +// func update(with environment: EnvironmentValues) { +// if context == nil { +// context = .init( +// coordinator: representable.makeCoordinator(), +// environment: environment +// ) +// } else { +// context!.environment = environment +// representable.updateNSView(subview, context: context!) +// } +// } + +// deinit { +// if let context { +// Representable.dismantleNSView(subview, coordinator: context.coordinator) +// } +// } +// } diff --git a/Sources/DummyBackend/DummyBackend.swift b/Sources/DummyBackend/DummyBackend.swift index ccc6cb9f72..2d007aebd0 100644 --- a/Sources/DummyBackend/DummyBackend.swift +++ b/Sources/DummyBackend/DummyBackend.swift @@ -386,7 +386,7 @@ public final class DummyBackend: AppBackend { let charactersPerLine = max(1, proposedFrame.x / characterWidth) let lineCount = (text.count + charactersPerLine - 1) / charactersPerLine return SIMD2( - characterWidth * charactersPerLine, + characterWidth * min(charactersPerLine, text.count), lineHeight * lineCount ) } diff --git a/Sources/SwiftCrossUI/Builders/SceneBuilder.swift b/Sources/SwiftCrossUI/Builders/SceneBuilder.swift index ee690388e7..c40e832119 100644 --- a/Sources/SwiftCrossUI/Builders/SceneBuilder.swift +++ b/Sources/SwiftCrossUI/Builders/SceneBuilder.swift @@ -9,258 +9,79 @@ public struct SceneBuilder { return content } - public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1) - -> TupleScene2 - { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1) -> TupleScene2 { return TupleScene2(scene0, scene1) } - public static func buildBlock( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2 - ) -> TupleScene3 { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2) -> TupleScene3 { return TupleScene3(scene0, scene1, scene2) } - public static func buildBlock( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3 - ) -> TupleScene4 { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3) -> TupleScene4 { return TupleScene4(scene0, scene1, scene2, scene3) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene - >(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4) - -> TupleScene5 - { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4) -> TupleScene5 { return TupleScene5(scene0, scene1, scene2, scene3, scene4) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5 - ) -> TupleScene6 { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5) -> TupleScene6 { return TupleScene6(scene0, scene1, scene2, scene3, scene4, scene5) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6 - ) -> TupleScene7 { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6) -> TupleScene7 { return TupleScene7(scene0, scene1, scene2, scene3, scene4, scene5, scene6) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7 - ) -> TupleScene8 { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7) -> TupleScene8 { return TupleScene8(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8 - ) -> TupleScene9 { + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8) -> TupleScene9 { return TupleScene9(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9 - ) -> TupleScene10< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9 - > { - return TupleScene10( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9) -> TupleScene10 { + return TupleScene10(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10 - ) -> TupleScene11< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10 - > { - return TupleScene11( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10) -> TupleScene11 { + return TupleScene11(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11 - ) -> TupleScene12< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11 - > { - return TupleScene12( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11) -> TupleScene12 { + return TupleScene12(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12 - ) -> TupleScene13< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12 - > { - return TupleScene13( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12) -> TupleScene13 { + return TupleScene13(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13 - ) -> TupleScene14< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13 - > { - return TupleScene14( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12, scene13) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13) -> TupleScene14 { + return TupleScene14(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12, scene13) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14 - ) -> TupleScene15< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14 - > { - return TupleScene15( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12, scene13, scene14) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14) -> TupleScene15 { + return TupleScene15(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12, scene13, scene14) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15 - ) -> TupleScene16< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15 - > { - return TupleScene16( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12, scene13, scene14, scene15) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15) -> TupleScene16 { + return TupleScene16(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12, scene13, scene14, scene15) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16 - ) -> TupleScene17< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16 - > { - return TupleScene17( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12, scene13, scene14, scene15, scene16) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16) -> TupleScene17 { + return TupleScene17(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12, scene13, scene14, scene15, scene16) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, - Scene17: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17 - ) -> TupleScene18< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17 - > { - return TupleScene18( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12, scene13, scene14, scene15, scene16, scene17) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17) -> TupleScene18 { + return TupleScene18(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12, scene13, scene14, scene15, scene16, scene17) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, - Scene17: Scene, Scene18: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, - _ scene18: Scene18 - ) -> TupleScene19< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17, Scene18 - > { - return TupleScene19( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12, scene13, scene14, scene15, scene16, scene17, scene18) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, _ scene18: Scene18) -> TupleScene19 { + return TupleScene19(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12, scene13, scene14, scene15, scene16, scene17, scene18) } - public static func buildBlock< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, - Scene17: Scene, Scene18: Scene, Scene19: Scene - >( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, - _ scene18: Scene18, _ scene19: Scene19 - ) -> TupleScene20< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17, Scene18, Scene19 - > { - return TupleScene20( - scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, - scene11, scene12, scene13, scene14, scene15, scene16, scene17, scene18, scene19) + public static func buildBlock(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, _ scene18: Scene18, _ scene19: Scene19) -> TupleScene20 { + return TupleScene20(scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8, scene9, scene10, scene11, scene12, scene13, scene14, scene15, scene16, scene17, scene18, scene19) } } diff --git a/Sources/SwiftCrossUI/Builders/TableRowBuilder.swift b/Sources/SwiftCrossUI/Builders/TableRowBuilder.swift deleted file mode 100644 index 2db59f9fb2..0000000000 --- a/Sources/SwiftCrossUI/Builders/TableRowBuilder.swift +++ /dev/null @@ -1,379 +0,0 @@ -// This file was generated using gyb. Do not edit it directly. Edit -// TableRowBuilder.swift.gyb instead. - -/// A result builder for constructing a collection of table columns. -@resultBuilder -public struct TableRowBuilder { - public static func buildBlock() -> EmptyTableRowContent { - EmptyTableRowContent() - } - - public static func buildBlock< - Content0: View - >( - _ column0: TableColumn - ) -> TupleTableRowContent1< - RowValue, Content0 - > { - TupleTableRowContent1( - column0 - ) - } - public static func buildBlock< - Content0: View, Content1: View - >( - _ column0: TableColumn, _ column1: TableColumn - ) -> TupleTableRowContent2< - RowValue, Content0, Content1 - > { - TupleTableRowContent2( - column0, column1 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn - ) -> TupleTableRowContent3< - RowValue, Content0, Content1, Content2 - > { - TupleTableRowContent3( - column0, column1, column2 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn - ) -> TupleTableRowContent4< - RowValue, Content0, Content1, Content2, Content3 - > { - TupleTableRowContent4( - column0, column1, column2, column3 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn - ) -> TupleTableRowContent5< - RowValue, Content0, Content1, Content2, Content3, Content4 - > { - TupleTableRowContent5( - column0, column1, column2, column3, column4 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn - ) -> TupleTableRowContent6< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5 - > { - TupleTableRowContent6( - column0, column1, column2, column3, column4, column5 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn - ) -> TupleTableRowContent7< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6 - > { - TupleTableRowContent7( - column0, column1, column2, column3, column4, column5, column6 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn - ) -> TupleTableRowContent8< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7 - > { - TupleTableRowContent8( - column0, column1, column2, column3, column4, column5, column6, column7 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn - ) -> TupleTableRowContent9< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8 - > { - TupleTableRowContent9( - column0, column1, column2, column3, column4, column5, column6, column7, column8 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn - ) -> TupleTableRowContent10< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9 - > { - TupleTableRowContent10( - column0, column1, column2, column3, column4, column5, column6, column7, column8, column9 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn - ) -> TupleTableRowContent11< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10 - > { - TupleTableRowContent11( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn - ) -> TupleTableRowContent12< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11 - > { - TupleTableRowContent12( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn - ) -> TupleTableRowContent13< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12 - > { - TupleTableRowContent13( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View, Content13: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn - ) -> TupleTableRowContent14< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12, Content13 - > { - TupleTableRowContent14( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12, column13 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View, Content13: View, Content14: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn - ) -> TupleTableRowContent15< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12, Content13, Content14 - > { - TupleTableRowContent15( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12, column13, column14 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View, Content13: View, Content14: View, - Content15: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn - ) -> TupleTableRowContent16< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12, Content13, Content14, Content15 - > { - TupleTableRowContent16( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12, column13, column14, column15 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View, Content13: View, Content14: View, - Content15: View, Content16: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn - ) -> TupleTableRowContent17< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12, Content13, Content14, Content15, - Content16 - > { - TupleTableRowContent17( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12, column13, column14, column15, column16 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View, Content13: View, Content14: View, - Content15: View, Content16: View, Content17: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn, _ column17: TableColumn - ) -> TupleTableRowContent18< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12, Content13, Content14, Content15, - Content16, Content17 - > { - TupleTableRowContent18( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12, column13, column14, column15, column16, column17 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View, Content13: View, Content14: View, - Content15: View, Content16: View, Content17: View, Content18: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn, _ column17: TableColumn, - _ column18: TableColumn - ) -> TupleTableRowContent19< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12, Content13, Content14, Content15, - Content16, Content17, Content18 - > { - TupleTableRowContent19( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12, column13, column14, column15, column16, column17, - column18 - ) - } - public static func buildBlock< - Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, - Content10: View, Content11: View, Content12: View, Content13: View, Content14: View, - Content15: View, Content16: View, Content17: View, Content18: View, Content19: View - >( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn, _ column17: TableColumn, - _ column18: TableColumn, _ column19: TableColumn - ) -> TupleTableRowContent20< - RowValue, Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, - Content8, Content9, Content10, Content11, Content12, Content13, Content14, Content15, - Content16, Content17, Content18, Content19 - > { - TupleTableRowContent20( - column0, column1, column2, column3, column4, column5, column6, column7, column8, - column9, column10, column11, column12, column13, column14, column15, column16, column17, - column18, column19 - ) - } -} diff --git a/Sources/SwiftCrossUI/Builders/TableRowBuilder.swift.gyb b/Sources/SwiftCrossUI/Builders/TableRowBuilder.swift.gyb deleted file mode 100644 index f47814a32c..0000000000 --- a/Sources/SwiftCrossUI/Builders/TableRowBuilder.swift.gyb +++ /dev/null @@ -1,27 +0,0 @@ -// This file was generated using gyb. Do not edit it directly. Edit -// TableRowBuilder.swift.gyb instead. -%{ -maximum_column_count = 20 -}% - -/// A result builder for constructing a collection of table columns. -@resultBuilder -public struct TableRowBuilder { - public static func buildBlock() -> EmptyTableRowContent { - EmptyTableRowContent() - } - - %for i in range(1, maximum_column_count + 1): - public static func buildBlock< - ${", ".join("Content%d: View" % j for j in range(i))} - >( - ${", ".join("_ column%d: TableColumn" % (j, j) for j in range(i))} - ) -> TupleTableRowContent${i}< - RowValue, ${", ".join("Content%d" % j for j in range(i))} - > { - TupleTableRowContent${i}( - ${", ".join("column%d" % j for j in range(i))} - ) - } - %end -} diff --git a/Sources/SwiftCrossUI/Builders/ViewBuilder.swift b/Sources/SwiftCrossUI/Builders/ViewBuilder.swift index 2b5252d367..81d052d982 100644 --- a/Sources/SwiftCrossUI/Builders/ViewBuilder.swift +++ b/Sources/SwiftCrossUI/Builders/ViewBuilder.swift @@ -13,216 +13,80 @@ public struct ViewBuilder { return TupleView1(view0) } - public static func buildBlock(_ view0: V0, _ view1: V1) -> TupleView2< - V0, V1 - > { + public static func buildBlock(_ view0: V0, _ view1: V1) -> TupleView2 { return TupleView2(view0, view1) } - public static func buildBlock( - _ view0: V0, _ view1: V1, _ view2: V2 - ) -> TupleView3 { + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2) -> TupleView3 { return TupleView3(view0, view1, view2) } - public static func buildBlock( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3 - ) -> TupleView4 { + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3) -> TupleView4 { return TupleView4(view0, view1, view2, view3) } - public static func buildBlock( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4 - ) -> TupleView5 { + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4) -> TupleView5 { return TupleView5(view0, view1, view2, view3, view4) } - public static func buildBlock( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5 - ) -> TupleView6 { + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5) -> TupleView6 { return TupleView6(view0, view1, view2, view3, view4, view5) } - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View - >(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6) - -> TupleView7 - { - return TupleView7( - view0, view1, view2, view3, view4, view5, view6) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7 - ) -> TupleView8 { - return TupleView8( - view0, view1, view2, view3, view4, view5, view6, view7) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8 - ) -> TupleView9 { - return TupleView9( - view0, view1, view2, view3, view4, view5, view6, view7, view8) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9 - ) -> TupleView10 { - return TupleView10( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10 - ) -> TupleView11 { - return TupleView11( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11 - ) -> TupleView12 { - return TupleView12( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12 - ) -> TupleView13 { - return TupleView13( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View, V13: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, - _ view13: V13 - ) -> TupleView14 { - return TupleView14( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View, V13: View, V14: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, - _ view13: V13, _ view14: V14 - ) -> TupleView15 { - return TupleView15( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View, V13: View, V14: View, V15: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, - _ view13: V13, _ view14: V14, _ view15: V15 - ) -> TupleView16 { - return TupleView16( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View, V13: View, V14: View, V15: View, V16: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, - _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16 - ) -> TupleView17 { - return TupleView17< - V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16 - >( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View, V13: View, V14: View, V15: View, V16: View, - V17: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, - _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16, _ view17: V17 - ) -> TupleView18 - { - return TupleView18< - V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17 - >( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16, view17) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View, V13: View, V14: View, V15: View, V16: View, - V17: View, V18: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, - _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16, _ view17: V17, _ view18: V18 - ) -> TupleView19< - V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18 - > { - return TupleView19< - V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18 - >( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16, view17, view18) - } - - public static func buildBlock< - V0: View, V1: View, V2: View, V3: View, V4: View, V5: View, V6: View, V7: View, V8: View, - V9: View, V10: View, V11: View, V12: View, V13: View, V14: View, V15: View, V16: View, - V17: View, V18: View, V19: View - >( - _ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, - _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, - _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16, _ view17: V17, _ view18: V18, - _ view19: V19 - ) -> TupleView20< - V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19 - > { - return TupleView20< - V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19 - >( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16, view17, view18, view19) + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6) -> TupleView7 { + return TupleView7(view0, view1, view2, view3, view4, view5, view6) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7) -> TupleView8 { + return TupleView8(view0, view1, view2, view3, view4, view5, view6, view7) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8) -> TupleView9 { + return TupleView9(view0, view1, view2, view3, view4, view5, view6, view7, view8) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9) -> TupleView10 { + return TupleView10(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10) -> TupleView11 { + return TupleView11(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11) -> TupleView12 { + return TupleView12(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12) -> TupleView13 { + return TupleView13(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, _ view13: V13) -> TupleView14 { + return TupleView14(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, _ view13: V13, _ view14: V14) -> TupleView15 { + return TupleView15(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, _ view13: V13, _ view14: V14, _ view15: V15) -> TupleView16 { + return TupleView16(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16) -> TupleView17 { + return TupleView17(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16, _ view17: V17) -> TupleView18 { + return TupleView18(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16, view17) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16, _ view17: V17, _ view18: V18) -> TupleView19 { + return TupleView19(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16, view17, view18) + } + + public static func buildBlock(_ view0: V0, _ view1: V1, _ view2: V2, _ view3: V3, _ view4: V4, _ view5: V5, _ view6: V6, _ view7: V7, _ view8: V8, _ view9: V9, _ view10: V10, _ view11: V11, _ view12: V12, _ view13: V13, _ view14: V14, _ view15: V15, _ view16: V16, _ view17: V17, _ view18: V18, _ view19: V19) -> TupleView20 { + return TupleView20(view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16, view17, view18, view19) } public static func buildEither(first component: A) -> EitherView { diff --git a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift index cafe0dea63..264e8e741a 100644 --- a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift +++ b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift @@ -92,8 +92,8 @@ public struct EnvironmentValues { /// The style of list to use. package var listStyle: ListStyle - /// The style of toggle to use. - public var toggleStyle: ToggleStyle + // /// The style of toggle to use. + // public var toggleStyle: ToggleStyle /// Whether the text should be selectable. Set by ``View/textSelectionEnabled(_:)``. public var isTextSelectionEnabled: Bool @@ -213,7 +213,7 @@ public struct EnvironmentValues { window = nil extraValues = [:] listStyle = .default - toggleStyle = .button + // toggleStyle = .button isEnabled = true scrollDismissesKeyboardMode = .automatic isTextSelectionEnabled = false diff --git a/Sources/SwiftCrossUI/HotReloadingMacros.swift b/Sources/SwiftCrossUI/HotReloadingMacros.swift index 80a8e22baa..4c6daa8534 100644 --- a/Sources/SwiftCrossUI/HotReloadingMacros.swift +++ b/Sources/SwiftCrossUI/HotReloadingMacros.swift @@ -1,17 +1,17 @@ import Foundation -@attached( - peer, - names: named(hotReloadingExportedEntryPoint), - named(hotReloadingImportedEntryPoint), - named(hotReloadingHasConnectedToServer)) -@attached(member, names: named(entryPoint), named(hotReloadingExprIds)) -public macro HotReloadable() = - #externalMacro(module: "HotReloadingMacrosPlugin", type: "HotReloadableAppMacro") +// @attached( +// peer, +// names: named(hotReloadingExportedEntryPoint), +// named(hotReloadingImportedEntryPoint), +// named(hotReloadingHasConnectedToServer)) +// @attached(member, names: named(entryPoint), named(hotReloadingExprIds)) +// public macro HotReloadable() = +// #externalMacro(module: "HotReloadingMacrosPlugin", type: "HotReloadableAppMacro") -@freestanding(expression) -public macro hotReloadable(@ViewBuilder _ expr: () -> T) -> HotReloadableView = - #externalMacro(module: "HotReloadingMacrosPlugin", type: "HotReloadableExprMacro") +// @freestanding(expression) +// public macro hotReloadable(@ViewBuilder _ expr: () -> T) -> HotReloadableView = +// #externalMacro(module: "HotReloadingMacrosPlugin", type: "HotReloadableExprMacro") @_documentation(visibility: internal) public struct ExprLocation: Hashable { diff --git a/Sources/SwiftCrossUI/Layout/LayoutSystem.swift b/Sources/SwiftCrossUI/Layout/LayoutSystem.swift index bd0de8df00..89cd3a5399 100644 --- a/Sources/SwiftCrossUI/Layout/LayoutSystem.swift +++ b/Sources/SwiftCrossUI/Layout/LayoutSystem.swift @@ -8,7 +8,25 @@ public enum LayoutSystem { } static func roundSize(_ size: Double) -> Int { - Int(size.rounded(.towardZero)) + let size = size.rounded(.towardZero) + return if size >= Double(Int.max) { + Int.max + } else if size <= Double(Int.min) { + Int.min + } else { + Int(size) + } + } + + static func clamp(_ value: Double, minimum: Double?, maximum: Double?) -> Double { + var value = value + if let minimum { + value = max(minimum, value) + } + if let maximum { + value = min(maximum, value) + } + return value } static func aspectRatio(of frame: SIMD2) -> Double { @@ -47,14 +65,14 @@ public enum LayoutSystem { public struct LayoutableChild { private var computeLayout: @MainActor ( - _ proposedSize: SIMD2, + _ proposedSize: ProposedViewSize, _ environment: EnvironmentValues ) -> ViewLayoutResult private var _commit: @MainActor () -> ViewLayoutResult var tag: String? public init( - computeLayout: @escaping @MainActor (SIMD2, EnvironmentValues) -> ViewLayoutResult, + computeLayout: @escaping @MainActor (ProposedViewSize, EnvironmentValues) -> ViewLayoutResult, commit: @escaping @MainActor () -> ViewLayoutResult, tag: String? = nil ) { @@ -65,7 +83,7 @@ public enum LayoutSystem { @MainActor public func computeLayout( - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, dryRun: Bool = false ) -> ViewLayoutResult { @@ -84,50 +102,94 @@ public enum LayoutSystem { /// ``Group`` to avoid changing stack layout participation (since ``Group`` /// is meant to appear completely invisible to the layout system). @MainActor - public static func computeStackLayout( + static func computeStackLayout( container: Backend.Widget, children: [LayoutableChild], - proposedSize: SIMD2, + cache: inout StackLayoutCache, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend, inheritStackLayoutParticipation: Bool = false ) -> ViewLayoutResult { let spacing = environment.layoutSpacing let orientation = environment.layoutOrientation + let perpendicularOrientation = orientation.perpendicular var renderedChildren: [ViewLayoutResult] = Array( - repeating: ViewLayoutResult.leafView(size: .empty), + repeating: ViewLayoutResult.leafView(size: .zero), count: children.count ) + let stackLength = proposedSize[component: orientation] + if stackLength == 0 || stackLength == .infinity || stackLength == nil { + var resultLength: Double = 0 + var resultWidth: Double = 0 + var results: [ViewLayoutResult] = [] + for child in children { + let result = child.computeLayout( + proposedSize: proposedSize, + environment: environment + ) + resultLength += result.size[component: orientation] + resultWidth = max(resultWidth, result.size[component: perpendicularOrientation]) + results.append(result) + } + var size = ViewSize.zero + size[component: orientation] = resultLength + size[component: perpendicularOrientation] = resultWidth + + // In this case, flexibility doesn't matter. We set the ordering to + // nil to signal to commitStackLayout that it can ignore flexibility. + cache.lastFlexibilityOrdering = nil + cache.lastHiddenChildren = results.map(\.participatesInStackLayouts).map(!) + cache.redistributeSpaceOnCommit = false + + return ViewLayoutResult( + size: size, + childResults: results, + participateInStackLayoutsWhenEmpty: + results.contains(where: \.participateInStackLayoutsWhenEmpty), + preferencesOverlay: nil + ) + } + + guard let stackLength else { + fatalError("unreachable") + } + // My thanks go to this great article for investigating and explaining // how SwiftUI determines child view 'flexibility': // https://www.objc.io/blog/2020/11/10/hstacks-child-ordering/ var isHidden = [Bool](repeating: false, count: children.count) + var minimumProposedSize = proposedSize + minimumProposedSize[component: orientation] = 0 + var maximumProposedSize = proposedSize + maximumProposedSize[component: orientation] = .infinity let flexibilities = children.enumerated().map { i, child in - let result = child.computeLayout( - proposedSize: proposedSize, + let minimumResult = child.computeLayout( + proposedSize: minimumProposedSize, environment: environment ) - isHidden[i] = !result.participatesInStackLayouts - return switch orientation { - case .horizontal: - result.size.maximumWidth - Double(result.size.minimumWidth) - case .vertical: - result.size.maximumHeight - Double(result.size.minimumHeight) - } + let maximumResult = child.computeLayout( + proposedSize: maximumProposedSize, + environment: environment + ) + isHidden[i] = !minimumResult.participatesInStackLayouts + let maximum = maximumResult.size[component: orientation] + let minimum = minimumResult.size[component: orientation] + return maximum - minimum } let visibleChildrenCount = isHidden.filter { hidden in !hidden }.count - let totalSpacing = max(visibleChildrenCount - 1, 0) * spacing + let totalSpacing = Double(max(visibleChildrenCount - 1, 0) * spacing) let sortedChildren = zip(children.enumerated(), flexibilities) .sorted { first, second in first.1 <= second.1 } .map(\.0) - var spaceUsedAlongStackAxis = 0 + var spaceUsedAlongStackAxis: Double = 0 var childrenRemaining = visibleChildrenCount for (index, child) in sortedChildren { // No need to render visible children. @@ -148,135 +210,105 @@ public enum LayoutSystem { ) } renderedChildren[index] = result - renderedChildren[index].size = .hidden + renderedChildren[index].participateInStackLayoutsWhenEmpty = false + renderedChildren[index].size = .zero continue } - let proposedWidth: Double - let proposedHeight: Double - switch orientation { - case .horizontal: - proposedWidth = - Double(max(proposedSize.x - spaceUsedAlongStackAxis - totalSpacing, 0)) - / Double(childrenRemaining) - proposedHeight = Double(proposedSize.y) - case .vertical: - proposedHeight = - Double(max(proposedSize.y - spaceUsedAlongStackAxis - totalSpacing, 0)) - / Double(childrenRemaining) - proposedWidth = Double(proposedSize.x) - } + var proposedChildSize = proposedSize + proposedChildSize[component: orientation] = + max(stackLength - spaceUsedAlongStackAxis - totalSpacing, 0) / Double(childrenRemaining) let childResult = child.computeLayout( - proposedSize: SIMD2( - Int(proposedWidth.rounded(.towardZero)), - Int(proposedHeight.rounded(.towardZero)) - ), + proposedSize: proposedChildSize, environment: environment ) renderedChildren[index] = childResult childrenRemaining -= 1 - switch orientation { - case .horizontal: - spaceUsedAlongStackAxis += childResult.size.size.x - case .vertical: - spaceUsedAlongStackAxis += childResult.size.size.y - } + spaceUsedAlongStackAxis += childResult.size[component: orientation] } - let size: SIMD2 - let idealSize: SIMD2 - let idealWidthForProposedHeight: Int - let idealHeightForProposedWidth: Int - let minimumWidth: Int - let minimumHeight: Int - let maximumWidth: Double? - let maximumHeight: Double? - switch orientation { - case .horizontal: - size = SIMD2( - renderedChildren.map(\.size.size.x).reduce(0, +) + totalSpacing, - renderedChildren.map(\.size.size.y).max() ?? 0 - ) - idealSize = SIMD2( - renderedChildren.map(\.size.idealSize.x).reduce(0, +) + totalSpacing, - renderedChildren.map(\.size.idealSize.y).max() ?? 0 - ) - minimumWidth = renderedChildren.map(\.size.minimumWidth).reduce(0, +) + totalSpacing - minimumHeight = renderedChildren.map(\.size.minimumHeight).max() ?? 0 - maximumWidth = - renderedChildren.map(\.size.maximumWidth).reduce(0, +) + Double(totalSpacing) - maximumHeight = renderedChildren.map(\.size.maximumHeight).max() - idealWidthForProposedHeight = - renderedChildren.map(\.size.idealWidthForProposedHeight).reduce(0, +) - + totalSpacing - idealHeightForProposedWidth = - renderedChildren.map(\.size.idealHeightForProposedWidth).max() ?? 0 - case .vertical: - size = SIMD2( - renderedChildren.map(\.size.size.x).max() ?? 0, - renderedChildren.map(\.size.size.y).reduce(0, +) + totalSpacing - ) - idealSize = SIMD2( - renderedChildren.map(\.size.idealSize.x).max() ?? 0, - renderedChildren.map(\.size.idealSize.y).reduce(0, +) + totalSpacing - ) - minimumWidth = renderedChildren.map(\.size.minimumWidth).max() ?? 0 - minimumHeight = - renderedChildren.map(\.size.minimumHeight).reduce(0, +) + totalSpacing - maximumWidth = renderedChildren.map(\.size.maximumWidth).max() - maximumHeight = - renderedChildren.map(\.size.maximumHeight).reduce(0, +) + Double(totalSpacing) - idealWidthForProposedHeight = - renderedChildren.map(\.size.idealWidthForProposedHeight).max() ?? 0 - idealHeightForProposedWidth = - renderedChildren.map(\.size.idealHeightForProposedWidth).reduce(0, +) - + totalSpacing - } + var size = ViewSize.zero + size[component: orientation] = + renderedChildren.map(\.size[component: orientation]).reduce(0, +) + totalSpacing + size[component: perpendicularOrientation] = + renderedChildren.map(\.size[component: orientation]).max() ?? 0 + + cache.lastFlexibilityOrdering = sortedChildren.map(\.offset) + cache.lastHiddenChildren = isHidden - // If the stack has been told to inherit its stack layout participation - // and all of its children are hidden, then the stack itself also - // shouldn't participate in stack layouts. - let shouldGetIgnoredInStackLayouts = - inheritStackLayoutParticipation && isHidden.allSatisfy { $0 } + // When the length along the stacking axis is concrete (i.e. flexibility + // matters) and the perpendicular axis is unspecified (nil), then we need + // to re-run the space distribution algorithm with our final size during + // the commit phase. This opens the door to certain edge cases, but SwiftUI + // has them too, and there's not a good general solution to these edge + // cases, even if you assume that you have unlimited compute. + cache.redistributeSpaceOnCommit = + proposedSize[component: orientation] != nil + && proposedSize[component: perpendicularOrientation] == nil return ViewLayoutResult( - size: ViewSize( - size: size, - idealSize: idealSize, - idealWidthForProposedHeight: idealWidthForProposedHeight, - idealHeightForProposedWidth: idealHeightForProposedWidth, - minimumWidth: minimumWidth, - minimumHeight: minimumHeight, - maximumWidth: maximumWidth, - maximumHeight: maximumHeight, - participateInStackLayoutsWhenEmpty: !shouldGetIgnoredInStackLayouts - ), - childResults: renderedChildren + size: size, + childResults: renderedChildren, + participateInStackLayoutsWhenEmpty: + renderedChildren.contains(where: \.participateInStackLayoutsWhenEmpty), ) } @MainActor - public static func commitStackLayout( + static func commitStackLayout( container: Backend.Widget, children: [LayoutableChild], + cache: inout StackLayoutCache, layout: ViewLayoutResult, environment: EnvironmentValues, backend: Backend ) { - let size = layout.size.size - backend.setSize(of: container, to: size) - - let renderedChildren = children.map { $0.commit() } + let size = layout.size + backend.setSize(of: container, to: size.vector) let alignment = environment.layoutAlignment let spacing = environment.layoutSpacing let orientation = environment.layoutOrientation + let perpendicularOrientation = orientation.perpendicular + + if cache.redistributeSpaceOnCommit { + guard let ordering = cache.lastFlexibilityOrdering else { + fatalError("Expected flexibility ordering in order to redistribute space during commit") + } + + var spaceUsedAlongStackAxis: Double = 0 + let visibleChildrenCount = cache.lastHiddenChildren.filter { isHidden in + !isHidden + }.count + let totalSpacing = Double(visibleChildrenCount * spacing) + var childrenRemaining = visibleChildrenCount - var x = 0 - var y = 0 + // TODO: Reuse the corresponding loop from computeStackLayout if + // possible to avoid the possibility for a behaviour mismatch. + for index in ordering { + if cache.lastHiddenChildren[index] { + continue + } + + var proposedChildSize = layout.size + proposedChildSize[component: orientation] -= spaceUsedAlongStackAxis + totalSpacing + proposedChildSize[component: orientation] /= Double(childrenRemaining) + let result = children[index].computeLayout( + proposedSize: ProposedViewSize(proposedChildSize), + environment: environment + ) + + spaceUsedAlongStackAxis += result.size[component: orientation] + childrenRemaining -= 1 + } + } + + let renderedChildren = children.map { $0.commit() } + + var position = Position.zero for (index, child) in renderedChildren.enumerated() { // Avoid the whole iteration if the child is hidden. If there // are weird positioning issues for views that do strange things @@ -286,29 +318,22 @@ public enum LayoutSystem { } // Compute alignment - switch (orientation, alignment) { - case (.vertical, .leading): - x = 0 - case (.horizontal, .leading): - y = 0 - case (.vertical, .center): - x = (size.x - child.size.size.x) / 2 - case (.horizontal, .center): - y = (size.y - child.size.size.y) / 2 - case (.vertical, .trailing): - x = (size.x - child.size.size.x) - case (.horizontal, .trailing): - y = (size.y - child.size.size.y) + switch alignment { + case .leading: + position[component: perpendicularOrientation] = 0 + case .center: + let outer = size[component: perpendicularOrientation] + let inner = child.size[component: perpendicularOrientation] + position[component: perpendicularOrientation] = (outer - inner) / 2 + case .trailing: + let outer = size[component: perpendicularOrientation] + let inner = child.size[component: perpendicularOrientation] + position[component: perpendicularOrientation] = outer - inner } - backend.setPosition(ofChildAt: index, in: container, to: SIMD2(x, y)) + backend.setPosition(ofChildAt: index, in: container, to: position.vector) - switch orientation { - case .horizontal: - x += child.size.size.x + spacing - case .vertical: - y += child.size.size.y + spacing - } + position[component: orientation] += child.size[component: orientation] + Double(spacing) } } } diff --git a/Sources/SwiftCrossUI/Layout/Position.swift b/Sources/SwiftCrossUI/Layout/Position.swift new file mode 100644 index 0000000000..1d9682397b --- /dev/null +++ b/Sources/SwiftCrossUI/Layout/Position.swift @@ -0,0 +1,53 @@ +/// A positon. +struct Position: Hashable, Sendable { + /// The zero position (aka the origin). + static let zero = Self(0, 0) + + /// The position's x component. + var x: Double + /// The position's y component. + var y: Double + + var vector: SIMD2 { + SIMD2( + LayoutSystem.roundSize(x), + LayoutSystem.roundSize(y) + ) + } + + /// Creates a new position. + init(_ x: Double, _ y: Double) { + self.x = x + self.y = y + } + + /// The position component associated with the given orientation's main axis. + public subscript(component orientation: Orientation) -> Double { + get { + switch orientation { + case .horizontal: + x + case .vertical: + y + } + } + set { + switch orientation { + case .horizontal: + x = newValue + case .vertical: + y = newValue + } + } + } + + /// The position component associated with the given axis. + public subscript(component axis: Axis) -> Double { + get { + self[component: axis.orientation] + } + set { + self[component: axis.orientation] = newValue + } + } +} diff --git a/Sources/SwiftCrossUI/Layout/ProposedViewSize.swift b/Sources/SwiftCrossUI/Layout/ProposedViewSize.swift new file mode 100644 index 0000000000..df85e8f8ac --- /dev/null +++ b/Sources/SwiftCrossUI/Layout/ProposedViewSize.swift @@ -0,0 +1,69 @@ +/// The proposed size for a view. `nil` signifies an unspecified dimension. +public struct ProposedViewSize: Hashable, Sendable { + /// The zero proposal. + public static let zero = Self(0, 0) + /// The infinite proposal. + public static let infinity = Self(.infinity, .infinity) + /// The unspecified/ideal proposal. + public static let unspecified = Self(nil, nil) + + /// The proposed width (if any). + public var width: Double? + /// The proposed height (if any). + public var height: Double? + + /// Creates a view size proposal. + public init(_ width: Double?, _ height: Double?) { + self.width = width + self.height = height + } + + public init(_ viewSize: ViewSize) { + self.width = viewSize.width + self.height = viewSize.height + } + + init(_ vector: SIMD2) { + self.width = Double(vector.x) + self.height = Double(vector.y) + } + + /// Replaces unspecified dimensions of a proposed view size with dimensions + /// from a concrete view size to get a concrete proposal. + public func replacingUnspecifiedDimensions(by size: ViewSize) -> ViewSize { + ViewSize( + width ?? size.width, + height ?? size.height + ) + } + + /// The component associated with the given orientation. + public subscript(component orientation: Orientation) -> Double? { + get { + switch orientation { + case .horizontal: + width + case .vertical: + height + } + } + set { + switch orientation { + case .horizontal: + width = newValue + case .vertical: + height = newValue + } + } + } + + /// The component associated with the given axis. + public subscript(component axis: Axis) -> Double? { + get { + self[component: axis.orientation] + } + set { + self[component: axis.orientation] = newValue + } + } +} diff --git a/Sources/SwiftCrossUI/Layout/ViewLayoutResult.swift b/Sources/SwiftCrossUI/Layout/ViewLayoutResult.swift new file mode 100644 index 0000000000..7dc1e01596 --- /dev/null +++ b/Sources/SwiftCrossUI/Layout/ViewLayoutResult.swift @@ -0,0 +1,53 @@ +/// The result of a call to ``View/computeLayout(_:children:proposedSize:environment:backend:)``. +public struct ViewLayoutResult { + /// The size that the view has chosen for itself based off of the proposed view size. + public var size: ViewSize + /// Whether the view participates in stack layouts when empty (i.e. has its own spacing). + /// + /// This will be removed once we properly support dynamic alignment and spacing. + public var participateInStackLayoutsWhenEmpty: Bool + /// The preference values produced by the view and its children. + public var preferences: PreferenceValues + + public init( + size: ViewSize, + participateInStackLayoutsWhenEmpty: Bool = false, + preferences: PreferenceValues + ) { + self.size = size + self.participateInStackLayoutsWhenEmpty = participateInStackLayoutsWhenEmpty + self.preferences = preferences + } + + /// Creates a layout result by combining a parent view's sizing and its + /// children's preference values. + public init( + size: ViewSize, + childResults: [ViewLayoutResult], + participateInStackLayoutsWhenEmpty: Bool = false, + preferencesOverlay: PreferenceValues? = nil + ) { + self.size = size + self.participateInStackLayoutsWhenEmpty = participateInStackLayoutsWhenEmpty + + preferences = PreferenceValues( + merging: childResults.map(\.preferences) + + [preferencesOverlay].compactMap { $0 } + ) + } + + /// Creates the layout result of a leaf view (one with no children and no + /// special preference behaviour). Uses ``PreferenceValues/default``. + public static func leafView(size: ViewSize) -> Self { + ViewLayoutResult( + size: size, + participateInStackLayoutsWhenEmpty: true, + preferences: .default + ) + } + + /// Whether the view should participate in stack layouts (i.e. get its own spacing). + public var participatesInStackLayouts: Bool { + size != .zero || participateInStackLayoutsWhenEmpty + } +} diff --git a/Sources/SwiftCrossUI/Layout/ViewSize.swift b/Sources/SwiftCrossUI/Layout/ViewSize.swift index cd1a478979..aae94422e2 100644 --- a/Sources/SwiftCrossUI/Layout/ViewSize.swift +++ b/Sources/SwiftCrossUI/Layout/ViewSize.swift @@ -1,117 +1,60 @@ -/// The size of a view. Includes ideal size, and minimum/maximum width and height -/// along with the size you'd expect. -/// -/// The width and height components of the view's minimum and maximum sizes are -/// stored separately to make it extra clear that they don't always form some -/// sort of achievable minimum/maximum size. The provided minimum/maximum bounds -/// may only be achievable along a single axis at a time. -public struct ViewSize: Equatable, Sendable { - /// The view update result for an empty view. - public static let empty = ViewSize( - size: .zero, - idealSize: .zero, - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: 0, - maximumHeight: 0 - ) +/// The size of a view. +public struct ViewSize: Hashable, Sendable { + /// The zero view size. + public static let zero = Self(0, 0) - /// The view update result for a hidden view. Differs from ``ViewSize/empty`` - /// by stopping hidden views from participating in stack layouts (i.e. - /// getting spacing between the previous child and the hidden child). - public static let hidden = ViewSize( - size: .zero, - idealSize: .zero, - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: 0, - maximumHeight: 0, - participateInStackLayoutsWhenEmpty: false - ) + /// The view's width. + public var width: Double + /// The view's height. + public var height: Double - /// The size that the view now takes up. - public var size: SIMD2 - /// The size that the view ideally wants to take up. - public var idealSize: SIMD2 - /// The width that the view ideally wants to take up assuming that the - /// proposed height doesn't change. Only really differs from `idealSize` for - /// views that have a trade-off between width and height (such as `Text`). - public var idealWidthForProposedHeight: Int - /// The height that the view ideally wants to take up assuming that the - /// proposed width doesn't change. Only really differs from `idealSize` for - /// views that have a trade-off between width and height (such as `Text`). - public var idealHeightForProposedWidth: Int - /// The minimum width that the view can take (if its height remains the same). - public var minimumWidth: Int - /// The minimum height that the view can take (if its width remains the same). - public var minimumHeight: Int - /// The maximum width that the view can take (if its height remains the same). - public var maximumWidth: Double - /// The maximum height that the view can take (if its width remains the same). - public var maximumHeight: Double - /// Whether the view should participate in stack layouts when empty. - /// - /// If `false`, the view won't get any spacing before or after it in stack - /// layouts. For example, this is used by ``OptionalView`` when its - /// underlying view is `nil` to avoid having spacing between views that are - /// semantically 'not present'. - /// - /// Only takes effect when ``ViewSize/size`` is zero, to avoid any ambiguity - /// when the view has non-zero size as this option is really only intended - /// to be used for visually hidden views (what would it mean for a non-empty - /// view to not participate in the layout? would the spacing between the - /// previous view and the next go before or after the view? would the view - /// get forced to zero size?). - public var participateInStackLayoutsWhenEmpty: Bool + /// Creates a view size. + public init(_ width: Double, _ height: Double) { + self.width = width + self.height = height + } + + /// Creates a view size from an integer vector. + init(_ vector: SIMD2) { + width = Double(vector.x) + height = Double(vector.y) + } - /// The view's ideal aspect ratio, computed from ``ViewSize/idealSize``. If - /// either of the view's ideal dimensions are 0, then the aspect ratio - /// defaults to 1. - public var idealAspectRatio: Double { - LayoutSystem.aspectRatio(of: SIMD2(idealSize)) + /// Gets the view size as a vector. + var vector: SIMD2 { + SIMD2( + LayoutSystem.roundSize(width), + LayoutSystem.roundSize(height) + ) } - public init( - size: SIMD2, - idealSize: SIMD2, - idealWidthForProposedHeight: Int? = nil, - idealHeightForProposedWidth: Int? = nil, - minimumWidth: Int, - minimumHeight: Int, - maximumWidth: Double?, - maximumHeight: Double?, - participateInStackLayoutsWhenEmpty: Bool = true - ) { - self.size = size - self.idealSize = idealSize - self.idealWidthForProposedHeight = idealWidthForProposedHeight ?? idealSize.x - self.idealHeightForProposedWidth = idealHeightForProposedWidth ?? idealSize.y - self.minimumWidth = minimumWidth - self.minimumHeight = minimumHeight - // Using `Double(1 << 53)` as the default allows us to differentiate between views - // with unlimited size and different minimum sizes when calculating view flexibility. - // If we use `Double.infinity` then all views with unlimited size have infinite - // flexibility, meaning that there's no difference when sorting, even though the - // minimum size should still affect view layout. Similarly, if we use - // `Double.greatestFiniteMagnitude` we don't have enough precision to get different results - // when subtracting reasonable minimum dimensions. The chosen value for 'unlimited' - // width/height is in the range where the gap between consecutive Doubles is `1`, which - // I believe is a good compromise. - self.maximumWidth = maximumWidth ?? Double(1 << 53) - self.maximumHeight = maximumHeight ?? Double(1 << 53) - self.participateInStackLayoutsWhenEmpty = - participateInStackLayoutsWhenEmpty + /// The size component associated with the given orientation. + public subscript(component orientation: Orientation) -> Double { + get { + switch orientation { + case .horizontal: + width + case .vertical: + height + } + } + set { + switch orientation { + case .horizontal: + width = newValue + case .vertical: + height = newValue + } + } } - public init(fixedSize: SIMD2) { - size = fixedSize - idealSize = fixedSize - idealWidthForProposedHeight = fixedSize.x - idealHeightForProposedWidth = fixedSize.y - minimumWidth = fixedSize.x - minimumHeight = fixedSize.y - maximumWidth = Double(fixedSize.x) - maximumHeight = Double(fixedSize.y) - participateInStackLayoutsWhenEmpty = true + /// The size component associated with the given axis. + public subscript(component axis: Axis) -> Double { + get { + self[component: axis.orientation] + } + set { + self[component: axis.orientation] = newValue + } } } diff --git a/Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift b/Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift deleted file mode 100644 index 246bbf4fce..0000000000 --- a/Sources/SwiftCrossUI/Layout/ViewUpdateResult.swift +++ /dev/null @@ -1,33 +0,0 @@ -public struct ViewLayoutResult { - public var size: ViewSize - public var preferences: PreferenceValues - - public init( - size: ViewSize, - preferences: PreferenceValues - ) { - self.size = size - self.preferences = preferences - } - - public init( - size: ViewSize, - childResults: [ViewLayoutResult], - preferencesOverlay: PreferenceValues? = nil - ) { - self.size = size - - preferences = PreferenceValues( - merging: childResults.map(\.preferences) - + [preferencesOverlay].compactMap { $0 } - ) - } - - public static func leafView(size: ViewSize) -> Self { - ViewLayoutResult(size: size, preferences: .default) - } - - public var participatesInStackLayouts: Bool { - size.size != .zero || size.participateInStackLayoutsWhenEmpty - } -} diff --git a/Sources/SwiftCrossUI/Scenes/TupleScene.swift b/Sources/SwiftCrossUI/Scenes/TupleScene.swift index e5301fc3ba..7578d95757 100644 --- a/Sources/SwiftCrossUI/Scenes/TupleScene.swift +++ b/Sources/SwiftCrossUI/Scenes/TupleScene.swift @@ -115,9 +115,7 @@ public struct TupleScene4: - SceneGraphNode -{ +public final class TupleSceneNode4: SceneGraphNode { public typealias NodeScene = TupleScene4 var node0: Scene0.Node @@ -147,9 +145,7 @@ public final class TupleSceneNode4: Scene { +public struct TupleScene5: Scene { public typealias Node = TupleSceneNode5 var scene0: Scene0 @@ -160,9 +156,7 @@ public struct TupleScene5< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -178,9 +172,7 @@ public struct TupleScene5< } } -public final class TupleSceneNode5< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene ->: SceneGraphNode { +public final class TupleSceneNode5: SceneGraphNode { public typealias NodeScene = TupleScene5 var node0: Scene0.Node @@ -213,9 +205,7 @@ public final class TupleSceneNode5< node4.update(newScene?.scene4, backend: backend, environment: environment) } } -public struct TupleScene6< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene ->: Scene { +public struct TupleScene6: Scene { public typealias Node = TupleSceneNode6 var scene0: Scene0 @@ -227,10 +217,7 @@ public struct TupleScene6< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -248,9 +235,7 @@ public struct TupleScene6< } } -public final class TupleSceneNode6< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene ->: SceneGraphNode { +public final class TupleSceneNode6: SceneGraphNode { public typealias NodeScene = TupleScene6 var node0: Scene0.Node @@ -286,10 +271,7 @@ public final class TupleSceneNode6< node5.update(newScene?.scene5, backend: backend, environment: environment) } } -public struct TupleScene7< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene ->: Scene { +public struct TupleScene7: Scene { public typealias Node = TupleSceneNode7 var scene0: Scene0 @@ -302,10 +284,7 @@ public struct TupleScene7< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -325,10 +304,7 @@ public struct TupleScene7< } } -public final class TupleSceneNode7< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene ->: SceneGraphNode { +public final class TupleSceneNode7: SceneGraphNode { public typealias NodeScene = TupleScene7 var node0: Scene0.Node @@ -367,13 +343,8 @@ public final class TupleSceneNode7< node6.update(newScene?.scene6, backend: backend, environment: environment) } } -public struct TupleScene8< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene ->: Scene { - public typealias Node = TupleSceneNode8< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7 - > +public struct TupleScene8: Scene { + public typealias Node = TupleSceneNode8 var scene0: Scene0 var scene1: Scene1 @@ -386,10 +357,7 @@ public struct TupleScene8< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -411,13 +379,8 @@ public struct TupleScene8< } } -public final class TupleSceneNode8< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene8< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7 - > +public final class TupleSceneNode8: SceneGraphNode { + public typealias NodeScene = TupleScene8 var node0: Scene0.Node var node1: Scene1.Node @@ -458,13 +421,8 @@ public final class TupleSceneNode8< node7.update(newScene?.scene7, backend: backend, environment: environment) } } -public struct TupleScene9< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene ->: Scene { - public typealias Node = TupleSceneNode9< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8 - > +public struct TupleScene9: Scene { + public typealias Node = TupleSceneNode9 var scene0: Scene0 var scene1: Scene1 @@ -478,10 +436,7 @@ public struct TupleScene9< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -505,13 +460,8 @@ public struct TupleScene9< } } -public final class TupleSceneNode9< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene9< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8 - > +public final class TupleSceneNode9: SceneGraphNode { + public typealias NodeScene = TupleScene9 var node0: Scene0.Node var node1: Scene1.Node @@ -555,13 +505,8 @@ public final class TupleSceneNode9< node8.update(newScene?.scene8, backend: backend, environment: environment) } } -public struct TupleScene10< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene ->: Scene { - public typealias Node = TupleSceneNode10< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9 - > +public struct TupleScene10: Scene { + public typealias Node = TupleSceneNode10 var scene0: Scene0 var scene1: Scene1 @@ -576,10 +521,7 @@ public struct TupleScene10< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -605,13 +547,8 @@ public struct TupleScene10< } } -public final class TupleSceneNode10< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene10< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9 - > +public final class TupleSceneNode10: SceneGraphNode { + public typealias NodeScene = TupleScene10 var node0: Scene0.Node var node1: Scene1.Node @@ -658,13 +595,8 @@ public final class TupleSceneNode10< node9.update(newScene?.scene9, backend: backend, environment: environment) } } -public struct TupleScene11< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene ->: Scene { - public typealias Node = TupleSceneNode11< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10 - > +public struct TupleScene11: Scene { + public typealias Node = TupleSceneNode11 var scene0: Scene0 var scene1: Scene1 @@ -680,11 +612,7 @@ public struct TupleScene11< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -712,13 +640,8 @@ public struct TupleScene11< } } -public final class TupleSceneNode11< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene11< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10 - > +public final class TupleSceneNode11: SceneGraphNode { + public typealias NodeScene = TupleScene11 var node0: Scene0.Node var node1: Scene1.Node @@ -768,14 +691,8 @@ public final class TupleSceneNode11< node10.update(newScene?.scene10, backend: backend, environment: environment) } } -public struct TupleScene12< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene ->: Scene { - public typealias Node = TupleSceneNode12< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11 - > +public struct TupleScene12: Scene { + public typealias Node = TupleSceneNode12 var scene0: Scene0 var scene1: Scene1 @@ -792,11 +709,7 @@ public struct TupleScene12< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -826,14 +739,8 @@ public struct TupleScene12< } } -public final class TupleSceneNode12< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene12< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11 - > +public final class TupleSceneNode12: SceneGraphNode { + public typealias NodeScene = TupleScene12 var node0: Scene0.Node var node1: Scene1.Node @@ -886,15 +793,8 @@ public final class TupleSceneNode12< node11.update(newScene?.scene11, backend: backend, environment: environment) } } -public struct TupleScene13< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene ->: Scene { - public typealias Node = TupleSceneNode13< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12 - > +public struct TupleScene13: Scene { + public typealias Node = TupleSceneNode13 var scene0: Scene0 var scene1: Scene1 @@ -912,11 +812,7 @@ public struct TupleScene13< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -948,15 +844,8 @@ public struct TupleScene13< } } -public final class TupleSceneNode13< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene13< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12 - > +public final class TupleSceneNode13: SceneGraphNode { + public typealias NodeScene = TupleScene13 var node0: Scene0.Node var node1: Scene1.Node @@ -1012,15 +901,8 @@ public final class TupleSceneNode13< node12.update(newScene?.scene12, backend: backend, environment: environment) } } -public struct TupleScene14< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene ->: Scene { - public typealias Node = TupleSceneNode14< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13 - > +public struct TupleScene14: Scene { + public typealias Node = TupleSceneNode14 var scene0: Scene0 var scene1: Scene1 @@ -1039,11 +921,7 @@ public struct TupleScene14< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -1077,15 +955,8 @@ public struct TupleScene14< } } -public final class TupleSceneNode14< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene14< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13 - > +public final class TupleSceneNode14: SceneGraphNode { + public typealias NodeScene = TupleScene14 var node0: Scene0.Node var node1: Scene1.Node @@ -1144,15 +1015,8 @@ public final class TupleSceneNode14< node13.update(newScene?.scene13, backend: backend, environment: environment) } } -public struct TupleScene15< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene ->: Scene { - public typealias Node = TupleSceneNode15< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14 - > +public struct TupleScene15: Scene { + public typealias Node = TupleSceneNode15 var scene0: Scene0 var scene1: Scene1 @@ -1172,12 +1036,7 @@ public struct TupleScene15< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -1213,15 +1072,8 @@ public struct TupleScene15< } } -public final class TupleSceneNode15< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene15< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14 - > +public final class TupleSceneNode15: SceneGraphNode { + public typealias NodeScene = TupleScene15 var node0: Scene0.Node var node1: Scene1.Node @@ -1283,15 +1135,8 @@ public final class TupleSceneNode15< node14.update(newScene?.scene14, backend: backend, environment: environment) } } -public struct TupleScene16< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene ->: Scene { - public typealias Node = TupleSceneNode16< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15 - > +public struct TupleScene16: Scene { + public typealias Node = TupleSceneNode16 var scene0: Scene0 var scene1: Scene1 @@ -1312,12 +1157,7 @@ public struct TupleScene16< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -1355,15 +1195,8 @@ public struct TupleScene16< } } -public final class TupleSceneNode16< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene16< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15 - > +public final class TupleSceneNode16: SceneGraphNode { + public typealias NodeScene = TupleScene16 var node0: Scene0.Node var node1: Scene1.Node @@ -1428,15 +1261,8 @@ public final class TupleSceneNode16< node15.update(newScene?.scene15, backend: backend, environment: environment) } } -public struct TupleScene17< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene ->: Scene { - public typealias Node = TupleSceneNode17< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16 - > +public struct TupleScene17: Scene { + public typealias Node = TupleSceneNode17 var scene0: Scene0 var scene1: Scene1 @@ -1458,12 +1284,7 @@ public struct TupleScene17< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -1503,15 +1324,8 @@ public struct TupleScene17< } } -public final class TupleSceneNode17< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene17< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16 - > +public final class TupleSceneNode17: SceneGraphNode { + public typealias NodeScene = TupleScene17 var node0: Scene0.Node var node1: Scene1.Node @@ -1579,15 +1393,8 @@ public final class TupleSceneNode17< node16.update(newScene?.scene16, backend: backend, environment: environment) } } -public struct TupleScene18< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, Scene17: Scene ->: Scene { - public typealias Node = TupleSceneNode18< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17 - > +public struct TupleScene18: Scene { + public typealias Node = TupleSceneNode18 var scene0: Scene0 var scene1: Scene1 @@ -1610,12 +1417,7 @@ public struct TupleScene18< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -1657,15 +1459,8 @@ public struct TupleScene18< } } -public final class TupleSceneNode18< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, Scene17: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene18< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17 - > +public final class TupleSceneNode18: SceneGraphNode { + public typealias NodeScene = TupleScene18 var node0: Scene0.Node var node1: Scene1.Node @@ -1736,16 +1531,8 @@ public final class TupleSceneNode18< node17.update(newScene?.scene17, backend: backend, environment: environment) } } -public struct TupleScene19< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, Scene17: Scene, - Scene18: Scene ->: Scene { - public typealias Node = TupleSceneNode19< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17, Scene18 - > +public struct TupleScene19: Scene { + public typealias Node = TupleSceneNode19 var scene0: Scene0 var scene1: Scene1 @@ -1769,13 +1556,7 @@ public struct TupleScene19< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, - _ scene18: Scene18 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, _ scene18: Scene18) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -1819,16 +1600,8 @@ public struct TupleScene19< } } -public final class TupleSceneNode19< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, Scene17: Scene, - Scene18: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene19< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17, Scene18 - > +public final class TupleSceneNode19: SceneGraphNode { + public typealias NodeScene = TupleScene19 var node0: Scene0.Node var node1: Scene1.Node @@ -1902,16 +1675,8 @@ public final class TupleSceneNode19< node18.update(newScene?.scene18, backend: backend, environment: environment) } } -public struct TupleScene20< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, Scene17: Scene, - Scene18: Scene, Scene19: Scene ->: Scene { - public typealias Node = TupleSceneNode20< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17, Scene18, Scene19 - > +public struct TupleScene20: Scene { + public typealias Node = TupleSceneNode20 var scene0: Scene0 var scene1: Scene1 @@ -1936,13 +1701,7 @@ public struct TupleScene20< public var commands: Commands - public init( - _ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, - _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, - _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, - _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, - _ scene18: Scene18, _ scene19: Scene19 - ) { + public init(_ scene0: Scene0, _ scene1: Scene1, _ scene2: Scene2, _ scene3: Scene3, _ scene4: Scene4, _ scene5: Scene5, _ scene6: Scene6, _ scene7: Scene7, _ scene8: Scene8, _ scene9: Scene9, _ scene10: Scene10, _ scene11: Scene11, _ scene12: Scene12, _ scene13: Scene13, _ scene14: Scene14, _ scene15: Scene15, _ scene16: Scene16, _ scene17: Scene17, _ scene18: Scene18, _ scene19: Scene19) { self.scene0 = scene0 self.scene1 = scene1 self.scene2 = scene2 @@ -1988,16 +1747,8 @@ public struct TupleScene20< } } -public final class TupleSceneNode20< - Scene0: Scene, Scene1: Scene, Scene2: Scene, Scene3: Scene, Scene4: Scene, Scene5: Scene, - Scene6: Scene, Scene7: Scene, Scene8: Scene, Scene9: Scene, Scene10: Scene, Scene11: Scene, - Scene12: Scene, Scene13: Scene, Scene14: Scene, Scene15: Scene, Scene16: Scene, Scene17: Scene, - Scene18: Scene, Scene19: Scene ->: SceneGraphNode { - public typealias NodeScene = TupleScene20< - Scene0, Scene1, Scene2, Scene3, Scene4, Scene5, Scene6, Scene7, Scene8, Scene9, Scene10, - Scene11, Scene12, Scene13, Scene14, Scene15, Scene16, Scene17, Scene18, Scene19 - > +public final class TupleSceneNode20: SceneGraphNode { + public typealias NodeScene = TupleScene20 var node0: Scene0.Node var node1: Scene1.Node diff --git a/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift b/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift index 02dbae4141..191d831c40 100644 --- a/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift +++ b/Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift @@ -136,119 +136,52 @@ public final class WindowGroupNode: SceneGraphNode { } .with(\.window, window) - let dryRunResult: ViewLayoutResult? - if !windowSizeIsFinal { - // Perform a dry-run update of the root view to check if the window - // needs to change size. - let contentResult = viewGraph.update( + let finalContentResult: ViewLayoutResult + if scene.resizability.isResizable { + let minimumWindowSize = viewGraph.computeLayout( with: newScene?.body, - proposedSize: proposedWindowSize, - environment: environment, - dryRun: true - ) - dryRunResult = contentResult - - let newWindowSize = computeNewWindowSize( - currentProposedSize: proposedWindowSize, - backend: backend, - contentSize: contentResult.size, + proposedSize: .zero, environment: environment + ).size + + let clampedWindowSize = ViewSize( + max(minimumWindowSize.width, Double(proposedWindowSize.x)), + max(minimumWindowSize.height, Double(proposedWindowSize.y)) ) - // Restart the window update if the content has caused the window to - // change size. To avoid infinite recursion, we take the view's word - // and assume that it will take on the minimum/maximum size it claimed. - if let newWindowSize { + if clampedWindowSize.vector != proposedWindowSize && !windowSizeIsFinal { + // Restart the window update if the content has caused the window to + // change size. return update( scene, - proposedWindowSize: newWindowSize, + proposedWindowSize: clampedWindowSize.vector, backend: backend, environment: environment, - windowSizeIsFinal: false + windowSizeIsFinal: true ) } - } else { - dryRunResult = nil - } - let finalContentResult = viewGraph.update( - with: newScene?.body, - proposedSize: proposedWindowSize, - environment: environment, - dryRun: false - ) + // Set this even if the window isn't programmatically resizable + // because the window may still be user resizable. + backend.setMinimumSize(ofWindow: window, to: minimumWindowSize.vector) - // The Gtk 3 backend has some broken sizing code that can't really be - // fixed due to the design of Gtk 3. Our layout system underestimates - // the size of the new view due to the button not being in the Gtk 3 - // widget hierarchy yet (which prevents Gtk 3 from computing the - // natural sizes of the new buttons). One fix seems to be removing - // view size reuse (currently the second check in ViewGraphNode.update) - // and I'm not exactly sure why, but that makes things awfully slow. - // The other fix is to add an alternative path to - // Gtk3Backend.naturalSize(of:) for buttons that moves non-realized - // buttons to a secondary window before measuring their natural size, - // but that's super janky, easy to break if the button in the real - // window is inheriting styles from its ancestors, and I'm not sure - // how to hide the window (it's probably terrible for performance too). - // - // I still have no clue why this size underestimation (and subsequent - // mis-sizing of the window) had the symptom of all buttons losing - // their labels temporarily; Gtk 3 is a temperamental beast. - // - // Anyway, Gtk3Backend isn't really intended to be a recommended - // backend so I think this is a fine solution for now (people should - // only use Gtk3Backend if they can't use GtkBackend). - if let dryRunResult, finalContentResult.size != dryRunResult.size { - print( - """ - warning: Final window content size didn't match dry-run size. This is a sign that - either view size caching is broken or that backend.naturalSize(of:) is - broken (or both). - -> dryRunResult.size: \(dryRunResult.size) - -> finalContentResult.size: \(finalContentResult.size) - """ + finalContentResult = viewGraph.computeLayout( + proposedSize: ProposedViewSize(proposedWindowSize), + environment: environment ) - - // Give the view graph one more chance to sort itself out to fail - // as gracefully as possible. - let newWindowSize = computeNewWindowSize( - currentProposedSize: proposedWindowSize, - backend: backend, - contentSize: finalContentResult.size, + } else { + finalContentResult = viewGraph.computeLayout( + proposedSize: .unspecified, environment: environment ) - - if let newWindowSize { - return update( - scene, - proposedWindowSize: newWindowSize, - backend: backend, - environment: environment, - windowSizeIsFinal: true - ) - } } - // Set this even if the window isn't programmatically resizable - // because the window may still be user resizable. - if scene.resizability.isResizable { - backend.setMinimumSize( - ofWindow: window, - to: SIMD2( - finalContentResult.size.minimumWidth, - finalContentResult.size.minimumHeight - ) - ) - } + viewGraph.commit() backend.setPosition( ofChildAt: 0, in: containerWidget.into(), - to: SIMD2( - (proposedWindowSize.x - finalContentResult.size.size.x) / 2, - (proposedWindowSize.y - finalContentResult.size.size.y) / 2 - ) + to: (proposedWindowSize &- finalContentResult.size.vector) / 2 ) let currentWindowSize = backend.size(ofWindow: window) @@ -263,29 +196,4 @@ public final class WindowGroupNode: SceneGraphNode { return finalContentResult } - - public func computeNewWindowSize( - currentProposedSize: SIMD2, - backend: Backend, - contentSize: ViewSize, - environment: EnvironmentValues - ) -> SIMD2? { - if scene.resizability.isResizable { - if currentProposedSize.x < contentSize.minimumWidth - || currentProposedSize.y < contentSize.minimumHeight - { - let newSize = SIMD2( - max(currentProposedSize.x, contentSize.minimumWidth), - max(currentProposedSize.y, contentSize.minimumHeight) - ) - return newSize - } else { - return nil - } - } else if contentSize.idealSize != currentProposedSize { - return contentSize.idealSize - } else { - return nil - } - } } diff --git a/Sources/SwiftCrossUI/Values/Axis.swift b/Sources/SwiftCrossUI/Values/Axis.swift index 16f14a00e6..eaf1a95a0c 100644 --- a/Sources/SwiftCrossUI/Values/Axis.swift +++ b/Sources/SwiftCrossUI/Values/Axis.swift @@ -1,10 +1,20 @@ /// An axis in a 2D coordinate system. -public enum Axis: Sendable { +public enum Axis: Sendable, CaseIterable { /// The horizontal axis. case horizontal /// The vertical axis. case vertical + /// Gets the orientation with this axis as its main axis. + var orientation: Orientation { + switch self { + case .horizontal: + .horizontal + case .vertical: + .vertical + } + } + /// A set of axes represented as an efficient bit field. public struct Set: OptionSet, Sendable { /// The horizontal axis. @@ -17,5 +27,15 @@ public enum Axis: Sendable { public init(rawValue: UInt8) { self.rawValue = rawValue } + + /// Gets whether a given member is a member of the option set. + public func contains(_ member: Axis) -> Bool { + switch member { + case .horizontal: + contains(Axis.Set.horizontal) + case .vertical: + contains(Axis.Set.vertical) + } + } } } diff --git a/Sources/SwiftCrossUI/Values/Color.swift b/Sources/SwiftCrossUI/Values/Color.swift index dcd95dc80f..ef67f26d41 100644 --- a/Sources/SwiftCrossUI/Values/Color.swift +++ b/Sources/SwiftCrossUI/Values/Color.swift @@ -1,5 +1,8 @@ /// An RGBA representation of a color. public struct Color: Sendable, Equatable, Hashable { + /// The ideal size of a color view. + private static let idealSize = ViewSize(10, 10) + /// The red component (from 0 to 1). public var red: Float /// The green component (from 0 to 1). @@ -71,19 +74,12 @@ extension Color: ElementaryView { func computeLayout( _ widget: Backend.Widget, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { ViewLayoutResult.leafView( - size: ViewSize( - size: proposedSize, - idealSize: SIMD2(10, 10), - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: nil, - maximumHeight: nil - ) + size: proposedSize.replacingUnspecifiedDimensions(by: Self.idealSize) ) } @@ -93,7 +89,7 @@ extension Color: ElementaryView { environment: EnvironmentValues, backend: Backend ) { - backend.setSize(of: widget, to: layout.size.size) + backend.setSize(of: widget, to: layout.size.vector) backend.setColor(ofColorableRectangle: widget, to: self) } } diff --git a/Sources/SwiftCrossUI/Values/Orientation.swift b/Sources/SwiftCrossUI/Values/Orientation.swift index 0139a6d721..01ac11dac3 100644 --- a/Sources/SwiftCrossUI/Values/Orientation.swift +++ b/Sources/SwiftCrossUI/Values/Orientation.swift @@ -2,4 +2,14 @@ public enum Orientation: Sendable { case horizontal case vertical + + /// The orientation perpendicular to this one. + var perpendicular: Orientation { + switch self { + case .horizontal: + .vertical + case .vertical: + .horizontal + } + } } diff --git a/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift index 9fa4b09bcc..886a140baf 100644 --- a/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/AnyViewGraphNode.swift @@ -16,7 +16,7 @@ public class AnyViewGraphNode { private var _computeLayoutWithNewView: ( _ newView: NodeView?, - _ proposedSize: SIMD2, + _ proposedSize: ProposedViewSize, _ environment: EnvironmentValues ) -> ViewLayoutResult /// The node's type-erased commit method. @@ -70,7 +70,7 @@ public class AnyViewGraphNode { /// the given size proposal already has a cached result. public func computeLayout( with newView: NodeView?, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues ) -> ViewLayoutResult { _computeLayoutWithNewView(newView, proposedSize, environment) diff --git a/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift index b84853a9d1..0293bd32d9 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ErasedViewGraphNode.swift @@ -11,7 +11,7 @@ public struct ErasedViewGraphNode { public var computeLayoutWithNewView: ( _ newView: Any?, - _ proposedSize: SIMD2, + _ proposedSize: ProposedViewSize, _ environment: EnvironmentValues ) -> (viewTypeMatched: Bool, size: ViewLayoutResult) /// The underlying view graph node's commit method. @@ -46,7 +46,7 @@ public struct ErasedViewGraphNode { computeLayoutWithNewView = { view, proposedSize, environment in if let view { guard let view = view as? V else { - return (false, ViewLayoutResult.leafView(size: .empty)) + return (false, ViewLayoutResult.leafView(size: .zero)) } let size = node.computeLayout( with: view, diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift index a14bf62ef1..10d2c7908b 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift @@ -18,9 +18,11 @@ public class ViewGraph { private var cancellable: Cancellable? /// The root view being managed by this view graph. private var view: Root - /// The most recent size of the window (used when updated the root view due to a state - /// change as opposed to a window resizing event). - private var windowSize: SIMD2 + /// The latest size proposal. + private var latestProposal: ProposedViewSize + /// The latest proposal as of the last commit (used when updated the root + /// view due to a state change as opposed to a window resizing event). + private var committedProposal: ProposedViewSize /// The current size of the root view. private var currentRootViewResult: ViewLayoutResult @@ -39,45 +41,44 @@ public class ViewGraph { rootNode = AnyViewGraphNode(for: view, backend: backend, environment: environment) self.view = view - windowSize = .zero + latestProposal = .zero + committedProposal = .zero parentEnvironment = environment - currentRootViewResult = ViewLayoutResult.leafView(size: .empty) + currentRootViewResult = ViewLayoutResult.leafView(size: .zero) setIncomingURLHandler = backend.setIncomingURLHandler(to:) } /// 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 update( + public func computeLayout( with newView: Root? = nil, - proposedSize: SIMD2, - environment: EnvironmentValues, - dryRun: Bool + proposedSize: ProposedViewSize, + environment: EnvironmentValues ) -> ViewLayoutResult { parentEnvironment = environment - windowSize = proposedSize - - // TODO: Refactor view graph node to be computeLayout+commit based - // instead of update based. - let result: ViewLayoutResult - if dryRun { - result = rootNode.computeLayout( - with: newView ?? view, - proposedSize: proposedSize, - environment: parentEnvironment - ) - } else { - result = rootNode.commit() - } + latestProposal = proposedSize + let result = rootNode.computeLayout( + with: newView ?? view, + proposedSize: proposedSize, + environment: parentEnvironment + ) self.currentRootViewResult = result - if isFirstUpdate, !dryRun { + return result + } + + /// Commits the result of the last computeLayout call to the underlying + /// widget hierarchy. + public func commit() { + committedProposal = latestProposal + self.currentRootViewResult = rootNode.commit() + if isFirstUpdate { setIncomingURLHandler { url in self.currentRootViewResult.preferences.onOpenURL?(url) } isFirstUpdate = false } - return result } public func snapshot() -> ViewGraphSnapshotter.NodeSnapshot { diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift index 1f698b32f0..d79a6b4333 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift @@ -1,6 +1,7 @@ import Foundation -/// A view graph node storing a view, its widget, and its children (likely a collection of more nodes). +/// A view graph node storing a view, its widget, and its children (likely a +/// collection of more nodes). /// /// This is where updates are initiated when a view's state updates, and where state is persisted /// even when a view gets recomputed by its parent. @@ -17,9 +18,9 @@ public class ViewGraphNode: Sendable { /// The view's children (usually just contains more view graph nodes, but can handle extra logic /// such as figuring out how to update variable length array of children efficiently). /// - /// It's type-erased because otherwise complex implementation details would be forced to the user - /// or other compromises would have to be made. I believe that this is the best option with Swift's - /// current generics landscape. + /// It's type-erased because otherwise complex implementation details would + /// be forced to the user or other compromises would have to be made. I + /// believe that this is the best option with Swift's current generics landscape. public var children: any ViewGraphNodeChildren { get { _children! @@ -40,10 +41,10 @@ public class ViewGraphNode: Sendable { public var currentLayout: ViewLayoutResult? /// A cache of update results keyed by the proposed size they were for. Gets cleared before the /// results' sizes become invalid. - var resultCache: [SIMD2: ViewLayoutResult] + var resultCache: [ProposedViewSize: ViewLayoutResult] /// The most recent size proposed by the parent view. Used when updating the wrapped /// view as a result of a state change rather than the parent view updating. - private var lastProposedSize: SIMD2 + private var lastProposedSize: ProposedViewSize /// A cancellable handle to the view's state property observations. private var cancellables: [Cancellable] @@ -164,7 +165,7 @@ public class ViewGraphNode: Sendable { /// state. public func computeLayout( with newView: NodeView? = nil, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues ) -> ViewLayoutResult { // Defensively ensure that all future scene implementations obey this @@ -181,42 +182,6 @@ public class ViewGraphNode: Sendable { return cachedResult } - // Attempt to cleverly reuse the current size if we can know that it - // won't change. We must of course be in a dry run, have a known - // current size, and must've run at least one proper dry run update - // since the last update cycle (checked via`!sizeCache.isEmpty`) to - // ensure that the view has been updated at least once with the - // current view state. - if let currentLayout, !resultCache.isEmpty { - // If both the previous and current proposed sizes are larger than - // the view's previously computed maximum size, reuse the previous - // result (currentResult). - if ((Double(lastProposedSize.x) >= currentLayout.size.maximumWidth - && Double(proposedSize.x) >= currentLayout.size.maximumWidth) - || proposedSize.x == lastProposedSize.x) - && ((Double(lastProposedSize.y) >= currentLayout.size.maximumHeight - && Double(proposedSize.y) >= currentLayout.size.maximumHeight) - || proposedSize.y == lastProposedSize.y) - { - return currentLayout - } - - // If the view has already been updated this update cycle and claims - // to be fixed size (maximumSize == minimumSize) then reuse the current - // result. - let maximumSize = SIMD2( - currentLayout.size.maximumWidth, - currentLayout.size.maximumHeight - ) - let minimumSize = SIMD2( - Double(currentLayout.size.minimumWidth), - Double(currentLayout.size.minimumHeight) - ) - if maximumSize == minimumSize { - return currentLayout - } - } - parentEnvironment = environment lastProposedSize = proposedSize @@ -263,7 +228,7 @@ public class ViewGraphNode: Sendable { guard let currentLayout else { print("warning: layout committed before being computed, ignoring") - return .leafView(size: .empty) + return .leafView(size: .zero) } view.commit( diff --git a/Sources/SwiftCrossUI/Views/AnyView.swift b/Sources/SwiftCrossUI/Views/AnyView.swift deleted file mode 100644 index c80ada76b9..0000000000 --- a/Sources/SwiftCrossUI/Views/AnyView.swift +++ /dev/null @@ -1,139 +0,0 @@ -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. -public struct AnyView: TypeSafeView { - typealias Children = AnyViewChildren - - public var body = EmptyView() - - var child: any View - - public init(_ child: any View) { - self.child = child - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> AnyViewChildren { - let snapshot = snapshots?.count == 1 ? snapshots?.first : nil - return AnyViewChildren( - from: self, - backend: backend, - snapshot: snapshot, - environment: environment - ) - } - - func layoutableChildren( - backend: Backend, - children: AnyViewChildren - ) -> [LayoutSystem.LayoutableChild] { - // TODO: Figure out a convention for views like this where ``layoutableChildren`` will - // never get used unless something has already gone pretty wrong. - body.layoutableChildren(backend: backend, children: children) - } - - func asWidget( - _ children: AnyViewChildren, - backend: Backend - ) -> Backend.Widget { - let container = backend.createContainer() - backend.addChild(children.node.getWidget().into(), to: container) - backend.setPosition(ofChildAt: 0, in: container, to: .zero) - return container - } - - /// Attempts to update the child. If the initial update fails then it means that the child's - /// concrete type has changed and we must recreate the child node and swap out our current - /// child widget with the new view's widget. - func computeLayout( - _ widget: Backend.Widget, - children: AnyViewChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - var (viewTypesMatched, result) = children.node.computeLayoutWithNewView( - child, - proposedSize, - environment - ) - - // If the new view's type doesn't match the old view's type then we need to create a new - // view graph node for the new view. - if !viewTypesMatched { - children.widgetToReplace = children.node.getWidget() - children.node = ErasedViewGraphNode( - for: child, - backend: backend, - environment: environment - ) - - // We can just assume that the update succeeded because we just created the node - // a few lines earlier (so it's guaranteed that the view types match). - let (_, newResult) = children.node.computeLayoutWithNewView( - child, - proposedSize, - environment - ) - result = newResult - } - - return result - } - - func commit( - _ widget: Backend.Widget, - children: AnyViewChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - if let widgetToReplace = children.widgetToReplace { - backend.removeChild(widgetToReplace.into(), from: widget) - backend.addChild(children.node.getWidget().into(), to: widget) - backend.setPosition(ofChildAt: 0, in: widget, to: .zero) - children.widgetToReplace = nil - } - - _ = children.node.commit() - - backend.setSize(of: widget, to: layout.size.size) - } -} - -class AnyViewChildren: ViewGraphNodeChildren { - /// The erased underlying node. - var node: ErasedViewGraphNode - /// If the displayed view changed during a dry-run update then this stores the widget of the replaced view. - var widgetToReplace: AnyWidget? - - var widgets: [AnyWidget] { - return [node.getWidget()] - } - - var erasedNodes: [ErasedViewGraphNode] { - [node] - } - - /// Creates the erased child node and wraps the child's widget in a single-child container. - init( - from view: AnyView, - backend: Backend, - snapshot: ViewGraphSnapshotter.NodeSnapshot?, - environment: EnvironmentValues - ) { - node = ErasedViewGraphNode( - for: view.child, - backend: backend, - snapshot: snapshot, - environment: environment - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/Button.swift b/Sources/SwiftCrossUI/Views/Button.swift index 2145d94cd3..dc86fdc694 100644 --- a/Sources/SwiftCrossUI/Views/Button.swift +++ b/Sources/SwiftCrossUI/Views/Button.swift @@ -31,7 +31,7 @@ extension Button: ElementaryView { public func computeLayout( _ widget: Backend.Widget, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -57,7 +57,7 @@ extension Button: ElementaryView { naturalSize.y ) - return ViewLayoutResult.leafView(size: ViewSize(fixedSize: size)) + return ViewLayoutResult.leafView(size: ViewSize(size)) } public func commit( @@ -66,6 +66,6 @@ extension Button: ElementaryView { environment: EnvironmentValues, backend: Backend ) { - backend.setSize(of: widget, to: layout.size.size) + backend.setSize(of: widget, to: layout.size.vector) } } diff --git a/Sources/SwiftCrossUI/Views/Checkbox.swift b/Sources/SwiftCrossUI/Views/Checkbox.swift deleted file mode 100644 index 7ddd8a1766..0000000000 --- a/Sources/SwiftCrossUI/Views/Checkbox.swift +++ /dev/null @@ -1,37 +0,0 @@ -/// A checkbox control that is either on or off. -struct Checkbox: ElementaryView, View { - /// Whether the checkbox is active or not. - private var active: Binding - - /// Creates a checkbox. - public init(active: Binding) { - self.active = active - } - - public func asWidget(backend: Backend) -> Backend.Widget { - return backend.createCheckbox() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - return ViewLayoutResult.leafView( - size: ViewSize(fixedSize: backend.naturalSize(of: widget)) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.updateCheckbox(widget, environment: environment) { newActiveState in - active.wrappedValue = newActiveState - } - backend.setState(ofCheckbox: widget, to: active.wrappedValue) - } -} diff --git a/Sources/SwiftCrossUI/Views/Divider.swift b/Sources/SwiftCrossUI/Views/Divider.swift deleted file mode 100644 index f57182703b..0000000000 --- a/Sources/SwiftCrossUI/Views/Divider.swift +++ /dev/null @@ -1,25 +0,0 @@ -/// 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. -public struct Divider: View { - @Environment(\.colorScheme) var colorScheme - @Environment(\.layoutOrientation) var layoutOrientation - - var color: Color { - switch colorScheme { - case .dark: - Color(1, 1, 1, 0.1) - case .light: - Color(0, 0, 0, 0.1) - } - } - - public init() {} - - public var body: some View { - color.frame( - width: layoutOrientation == .horizontal ? 1 : nil, - height: layoutOrientation == .vertical ? 1 : nil - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/EitherView.swift b/Sources/SwiftCrossUI/Views/EitherView.swift index 916b4d8872..685ca68246 100644 --- a/Sources/SwiftCrossUI/Views/EitherView.swift +++ b/Sources/SwiftCrossUI/Views/EitherView.swift @@ -50,7 +50,7 @@ extension EitherView: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: EitherViewChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -125,7 +125,7 @@ extension EitherView: TypeSafeView { _ = children.node.erasedNode.commit() - backend.setSize(of: widget, to: layout.size.size) + backend.setSize(of: widget, to: layout.size.vector) } } diff --git a/Sources/SwiftCrossUI/Views/ElementaryView.swift b/Sources/SwiftCrossUI/Views/ElementaryView.swift index cf55fd78fa..3e93b86f22 100644 --- a/Sources/SwiftCrossUI/Views/ElementaryView.swift +++ b/Sources/SwiftCrossUI/Views/ElementaryView.swift @@ -9,7 +9,7 @@ protocol ElementaryView: View where Content == EmptyView { func computeLayout( _ widget: Backend.Widget, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult @@ -39,7 +39,7 @@ extension ElementaryView { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { diff --git a/Sources/SwiftCrossUI/Views/EmptyView.swift b/Sources/SwiftCrossUI/Views/EmptyView.swift index 44ff860c8d..90cc881507 100644 --- a/Sources/SwiftCrossUI/Views/EmptyView.swift +++ b/Sources/SwiftCrossUI/Views/EmptyView.swift @@ -39,11 +39,11 @@ public struct EmptyView: View, Sendable { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - ViewLayoutResult.leafView(size: .empty) + ViewLayoutResult.leafView(size: .zero) } public func commit( diff --git a/Sources/SwiftCrossUI/Views/ForEach.swift b/Sources/SwiftCrossUI/Views/ForEach.swift index 29572a95ea..b050be0658 100644 --- a/Sources/SwiftCrossUI/Views/ForEach.swift +++ b/Sources/SwiftCrossUI/Views/ForEach.swift @@ -81,7 +81,7 @@ extension ForEach: TypeSafeView, View where Child: View { func computeLayout( _ widget: Backend.Widget, children: ForEachViewChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -159,6 +159,7 @@ extension ForEach: TypeSafeView, View where Child: View { return LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren, + cache: &children.stackLayoutCache, proposedSize: proposedSize, environment: environment, backend: backend @@ -196,6 +197,7 @@ extension ForEach: TypeSafeView, View where Child: View { commit: node.commit ) }, + cache: &children.stackLayoutCache, layout: layout, environment: environment, backend: backend @@ -237,6 +239,8 @@ class ForEachViewChildren< nodes.map(ErasedViewGraphNode.init(wrapping:)) } + var stackLayoutCache = StackLayoutCache() + /// Gets a variable length view's children as view graph node children. init( from view: ForEach, diff --git a/Sources/SwiftCrossUI/Views/GeometryReader.swift b/Sources/SwiftCrossUI/Views/GeometryReader.swift deleted file mode 100644 index 7f49e52587..0000000000 --- a/Sources/SwiftCrossUI/Views/GeometryReader.swift +++ /dev/null @@ -1,129 +0,0 @@ -/// A container view that allows its content to read the size proposed to it. -/// -/// Geometry readers always take up the size proposed to them; no more, no less. -/// This is to decouple the geometry reader's size from the size of its content -/// in order to avoid feedback loops. -/// -/// ```swift -/// struct MeasurementView: View { -/// var body: some View { -/// GeometryReader { proxy in -/// Text("Width: \(proxy.size.x)") -/// Text("Height: \(proxy.size.y)") -/// } -/// } -/// } -/// ``` -/// -/// > Note: Geometry reader content may get evaluated multiple times with various -/// > sizes before the layout system settles on a size. Do not depend on the size -/// > proposal always being final. -public struct GeometryReader: TypeSafeView, View { - var content: (GeometryProxy) -> Content - - public var body = EmptyView() - - public init(@ViewBuilder content: @escaping (GeometryProxy) -> Content) { - self.content = content - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> GeometryReaderChildren { - GeometryReaderChildren() - } - - func layoutableChildren( - backend: Backend, - children: GeometryReaderChildren - ) -> [LayoutSystem.LayoutableChild] { - [] - } - - func asWidget( - _ children: GeometryReaderChildren, - backend: Backend - ) -> Backend.Widget { - // This is a little different to our usual wrapper implementations - // because we want to avoid calling the user's content closure before - // we actually have to. - return backend.createContainer() - } - - func computeLayout( - _ widget: Backend.Widget, - children: GeometryReaderChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let view = content(GeometryProxy(size: proposedSize)) - - let environment = environment.with(\.layoutAlignment, .leading) - - let contentNode: AnyViewGraphNode - if let node = children.node { - contentNode = node - } else { - contentNode = AnyViewGraphNode( - for: view, - backend: backend, - environment: environment - ) - children.node = contentNode - - // It's ok to add the child here even though it's not a dry run - // because this is guaranteed to only happen once. Dry runs are - // more about 'commit' actions that happen every single update. - backend.addChild(contentNode.widget.into(), to: widget) - } - - // TODO: Look into moving this to the final non-dry run update. In order - // to do so we'd have to give up on preferences being allowed to affect - // layout (which is probably something we don't want to support anyway - // because it sounds like feedback loop central). - let contentResult = contentNode.computeLayout( - with: view, - proposedSize: proposedSize, - environment: environment - ) - - return ViewLayoutResult( - size: ViewSize( - size: proposedSize, - idealSize: SIMD2(10, 10), - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: nil, - maximumHeight: nil - ), - childResults: [contentResult] - ) - } - - func commit( - _ widget: Backend.Widget, - children: GeometryReaderChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - _ = children.node?.commit() - backend.setPosition(ofChildAt: 0, in: widget, to: .zero) - backend.setSize(of: widget, to: layout.size.size) - } -} - -class GeometryReaderChildren: ViewGraphNodeChildren { - var node: AnyViewGraphNode? - - var widgets: [AnyWidget] { - [node?.widget].compactMap { $0 } - } - - var erasedNodes: [ErasedViewGraphNode] { - [node.map(ErasedViewGraphNode.init(wrapping:))].compactMap { $0 } - } -} diff --git a/Sources/SwiftCrossUI/Views/Group.swift b/Sources/SwiftCrossUI/Views/Group.swift index 41a2786b50..1e92224e47 100644 --- a/Sources/SwiftCrossUI/Views/Group.swift +++ b/Sources/SwiftCrossUI/Views/Group.swift @@ -26,18 +26,25 @@ public struct Group: View { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - LayoutSystem.computeStackLayout( + if !(children is TupleViewChildren) { + print("warning: VStack will not function correctly non-TupleView Content") + } + var cache = (children as? TupleViewChildren)?.stackLayoutCache ?? StackLayoutCache() + let result = LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), + cache: &cache, proposedSize: proposedSize, environment: environment, backend: backend, inheritStackLayoutParticipation: true ) + (children as? TupleViewChildren)?.stackLayoutCache = cache + return result } public func commit( @@ -47,12 +54,15 @@ public struct Group: View { environment: EnvironmentValues, backend: Backend ) { + var cache = (children as? TupleViewChildren)?.stackLayoutCache ?? StackLayoutCache() LayoutSystem.commitStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), + cache: &cache, layout: layout, environment: environment, backend: backend ) + (children as? TupleViewChildren)?.stackLayoutCache = cache } } diff --git a/Sources/SwiftCrossUI/Views/HStack.swift b/Sources/SwiftCrossUI/Views/HStack.swift index 85756c9919..36fe1f788d 100644 --- a/Sources/SwiftCrossUI/Views/HStack.swift +++ b/Sources/SwiftCrossUI/Views/HStack.swift @@ -32,13 +32,18 @@ public struct HStack: View { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - return LayoutSystem.computeStackLayout( + if !(children is TupleViewChildren) { + print("warning: VStack will not function correctly non-TupleView Content") + } + var cache = (children as? TupleViewChildren)?.stackLayoutCache ?? StackLayoutCache() + let result = LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), + cache: &cache, proposedSize: proposedSize, environment: environment @@ -47,6 +52,8 @@ public struct HStack: View { .with(\.layoutSpacing, spacing), backend: backend ) + (children as? TupleViewChildren)?.stackLayoutCache = cache + return result } public func commit( @@ -56,9 +63,11 @@ public struct HStack: View { environment: EnvironmentValues, backend: Backend ) { + var cache = (children as? TupleViewChildren)?.stackLayoutCache ?? StackLayoutCache() LayoutSystem.commitStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), + cache: &cache, layout: layout, environment: environment @@ -67,5 +76,6 @@ public struct HStack: View { .with(\.layoutSpacing, spacing), backend: backend ) + (children as? TupleViewChildren)?.stackLayoutCache = cache } } diff --git a/Sources/SwiftCrossUI/Views/HotReloadableView.swift b/Sources/SwiftCrossUI/Views/HotReloadableView.swift deleted file mode 100644 index 44d46dc527..0000000000 --- a/Sources/SwiftCrossUI/Views/HotReloadableView.swift +++ /dev/null @@ -1,139 +0,0 @@ -import Foundation - -/// A view which attempts to persist the state of its view subtree even -/// when the subtree's structure changes. Uses state serialization (via -/// view graph snapshotting) to persist view state even when a child -/// view's implementation gets swapped out with an implementation from -/// a newly-loaded dylib (this is what makes this useful for hot reloading). -/// -/// Only expected to be used directly by SwiftCrossUI itself or third -/// party libraries extending SwiftCrossUI's hot reloading capabilities. -public struct HotReloadableView: TypeSafeView { - typealias Children = HotReloadableViewChildren - - public var body = EmptyView() - - var child: any View - - public init(_ child: any View) { - self.child = child - } - - public init(@ViewBuilder _ child: () -> some View) { - self.child = child() - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> HotReloadableViewChildren { - let snapshot = snapshots?.count == 1 ? snapshots?.first : nil - return HotReloadableViewChildren( - from: self, - backend: backend, - snapshot: snapshot, - environment: environment - ) - } - - func asWidget( - _ children: HotReloadableViewChildren, - backend: Backend - ) -> Backend.Widget { - backend.createContainer() - } - - /// Attempts to update the child. If the initial update succeeds then the child's concrete type - /// hasn't changed and the ViewGraph has handled state persistence on our behalf. Otherwise, - /// we must recreate the child node and swap out our current child widget with the new view's - /// widget. Before displaying the child, we also attempt to transfer a snapshot of the old - /// view graph sub tree's state onto the new view graph sub tree. This is not possible to do - /// perfectly by definition, so if we can't successfully transfer the state of the sub tree - /// we just fall back on the failing view's default state. - func computeLayout( - _ widget: Backend.Widget, - children: HotReloadableViewChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - var (viewTypeMatched, result) = children.node.computeLayoutWithNewView( - child, - proposedSize, - environment - ) - - if !viewTypeMatched { - let snapshotter = ViewGraphSnapshotter() - let snapshot = children.node.transform(with: snapshotter) - children.node = ErasedViewGraphNode( - for: child, - backend: backend, - snapshot: snapshot, - environment: environment - ) - - // We can assume that the view types match since we just recreated the view - // on the line above. - let (_, newResult) = children.node.computeLayoutWithNewView( - child, - proposedSize, - environment - ) - result = newResult - children.hasChangedChild = true - } - - return result - } - - func commit( - _ widget: Backend.Widget, - children: HotReloadableViewChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - if children.hasChangedChild { - backend.removeAllChildren(of: widget) - backend.addChild(children.node.getWidget().into(), to: widget) - backend.setPosition(ofChildAt: 0, in: widget, to: .zero) - children.hasChangedChild = false - } - - _ = children.node.commit() - - backend.setSize(of: widget, to: layout.size.size) - } -} - -class HotReloadableViewChildren: ViewGraphNodeChildren { - /// The erased underlying node. - var node: ErasedViewGraphNode - - var widgets: [AnyWidget] { - [node.getWidget()] - } - - var erasedNodes: [ErasedViewGraphNode] { - [node] - } - - var hasChangedChild = true - - /// Creates the erased child node and wraps the child's widget in a single-child container. - init( - from view: HotReloadableView, - backend: Backend, - snapshot: ViewGraphSnapshotter.NodeSnapshot?, - environment: EnvironmentValues - ) { - node = ErasedViewGraphNode( - for: view.child, - backend: backend, - snapshot: snapshot, - environment: environment - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/Image.swift b/Sources/SwiftCrossUI/Views/Image.swift deleted file mode 100644 index de02903564..0000000000 --- a/Sources/SwiftCrossUI/Views/Image.swift +++ /dev/null @@ -1,182 +0,0 @@ -import Foundation -import ImageFormats - -/// A view that displays an image. -public struct Image: Sendable { - private var isResizable = false - private var source: Source - - enum Source: Equatable { - case url(URL, useFileExtension: Bool) - case image(ImageFormats.Image) - } - - /// Displays an image file. `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. - 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) - } - - /// Makes the image resize to fit the available space. - public func resizable() -> Self { - var image = self - image.isResizable = true - return image - } - - init(_ source: Source, resizable: Bool) { - self.source = source - self.isResizable = resizable - } -} - -extension Image: View { - public var body: some View { return EmptyView() } -} - -extension Image: TypeSafeView { - func layoutableChildren( - backend: Backend, - children: _ImageChildren - ) -> [LayoutSystem.LayoutableChild] { - [] - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> _ImageChildren { - _ImageChildren(backend: backend) - } - - func asWidget( - _ children: _ImageChildren, - backend: Backend - ) -> Backend.Widget { - children.container.into() - } - - func computeLayout( - _ widget: Backend.Widget, - children: _ImageChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let image: ImageFormats.Image? - if source != children.cachedImageSource { - switch source { - case .url(let url, let useFileExtension): - if let data = try? Data(contentsOf: url) { - let bytes = Array(data) - if useFileExtension { - image = try? ImageFormats.Image.load( - from: bytes, - usingFileExtension: url.pathExtension - ) - } else { - image = try? ImageFormats.Image.load(from: bytes) - } - } else { - image = nil - } - case .image(let sourceImage): - image = sourceImage - } - - children.cachedImageSource = source - children.cachedImage = image - children.imageChanged = true - } else { - image = children.cachedImage - } - - let idealSize = SIMD2(image?.width ?? 0, image?.height ?? 0) - let size: ViewSize - if isResizable { - size = ViewSize( - size: image == nil ? .zero : proposedSize, - idealSize: idealSize, - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: image == nil ? 0 : nil, - maximumHeight: image == nil ? 0 : nil - ) - } else { - size = ViewSize(fixedSize: idealSize) - } - - return ViewLayoutResult.leafView(size: size) - } - - func commit( - _ widget: Backend.Widget, - children: _ImageChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - let size = layout.size.size - let hasResized = children.cachedImageDisplaySize != size - children.cachedImageDisplaySize = layout.size.size - if children.imageChanged - || hasResized - || (backend.requiresImageUpdateOnScaleFactorChange - && children.lastScaleFactor != environment.windowScaleFactor) - { - if let image = children.cachedImage { - backend.updateImageView( - children.imageWidget.into(), - rgbaData: image.bytes, - width: image.width, - height: image.height, - targetWidth: size.x, - targetHeight: size.y, - dataHasChanged: children.imageChanged, - environment: environment - ) - if children.isContainerEmpty { - backend.addChild(children.imageWidget.into(), to: children.container.into()) - backend.setPosition(ofChildAt: 0, in: children.container.into(), to: .zero) - } - children.isContainerEmpty = false - } else { - backend.removeAllChildren(of: children.container.into()) - children.isContainerEmpty = true - } - children.imageChanged = false - children.lastScaleFactor = environment.windowScaleFactor - } - backend.setSize(of: children.container.into(), to: size) - backend.setSize(of: children.imageWidget.into(), to: size) - } -} - -class _ImageChildren: ViewGraphNodeChildren { - var cachedImageSource: Image.Source? = nil - var cachedImage: ImageFormats.Image? = nil - var cachedImageDisplaySize: SIMD2 = .zero - var container: AnyWidget - var imageWidget: AnyWidget - var imageChanged = false - var isContainerEmpty = true - var lastScaleFactor: Double = 1 - - init(backend: Backend) { - container = AnyWidget(backend.createContainer()) - imageWidget = AnyWidget(backend.createImageView()) - } - - var widgets: [AnyWidget] = [] - var erasedNodes: [ErasedViewGraphNode] = [] -} diff --git a/Sources/SwiftCrossUI/Views/List.swift b/Sources/SwiftCrossUI/Views/List.swift deleted file mode 100644 index 6a988d4862..0000000000 --- a/Sources/SwiftCrossUI/Views/List.swift +++ /dev/null @@ -1,244 +0,0 @@ -public struct List: TypeSafeView, View { - typealias Children = ListViewChildren> - - public let body = EmptyView() - - var selection: Binding - var rowContent: (Int) -> RowView - var associatedSelectionValue: (Int) -> SelectionValue - var find: (SelectionValue) -> Int? - var rowCount: Int - - public init( - _ data: Data, - selection: Binding, - @ViewBuilder rowContent: @escaping (Data.Element) -> RowView - ) where Data.Element: Identifiable, Data.Element.ID == SelectionValue, Data.Index == Int { - self.init(data, id: \.id, selection: selection, rowContent: rowContent) - } - - public init( - _ data: Data, - selection: Binding - ) - where - Data.Element: CustomStringConvertible & Identifiable, - Data.Element.ID == SelectionValue, - Data.Index == Int, - RowView == Text - { - self.init(data, selection: selection) { item in - return Text(item.description) - } - } - - public init( - _ data: Data, - id: @escaping (Data.Element) -> SelectionValue, - selection: Binding - ) where Data.Element: CustomStringConvertible, RowView == Text, Data.Index == Int { - self.init(data, id: id, selection: selection) { item in - return Text(item.description) - } - } - - public init( - _ data: Data, - id: KeyPath, - selection: Binding - ) where Data.Element: CustomStringConvertible, RowView == Text, Data.Index == Int { - self.init(data, id: id, selection: selection) { item in - return Text(item.description) - } - } - - public init( - _ data: Data, - id: KeyPath, - selection: Binding, - @ViewBuilder rowContent: @escaping (Data.Element) -> RowView - ) where Data.Index == Int { - self.init(data, id: { $0[keyPath: id] }, selection: selection, rowContent: rowContent) - } - - public init( - _ data: Data, - id: @escaping (Data.Element) -> SelectionValue, - selection: Binding, - @ViewBuilder rowContent: @escaping (Data.Element) -> RowView - ) where Data.Index == Int { - self.selection = selection - self.rowContent = { index in - rowContent(data[index]) - } - associatedSelectionValue = { index in - id(data[index]) - } - find = { selection in - data.firstIndex { item in - id(item) == selection - } - } - rowCount = data.count - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> Children { - // TODO: Implement snapshotting - Children() - } - - func asWidget( - _ children: Children, - backend: Backend - ) -> Backend.Widget { - backend.createSelectableListView() - } - - func computeLayout( - _ widget: Backend.Widget, - children: Children, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - // Padding that the backend could not remove (some frameworks have a small - // constant amount of required padding within each row). - let baseRowPadding = backend.baseItemPadding(ofSelectableListView: widget) - let minimumRowSize = backend.minimumRowSize(ofSelectableListView: widget) - let horizontalBasePadding = baseRowPadding.axisTotals.x - let verticalBasePadding = baseRowPadding.axisTotals.y - - let rowViews = (0.. children.nodes.count { - for rowView in rowViews.dropFirst(children.nodes.count) { - let node = AnyViewGraphNode( - for: rowView, - backend: backend, - environment: environment - ) - children.nodes.append(node) - } - } else if children.nodes.count > rowCount { - children.nodes.removeLast(children.nodes.count - rowCount) - } - - var childResults: [ViewLayoutResult] = [] - for (rowView, node) in zip(rowViews, children.nodes) { - let preferredSize = node.computeLayout( - with: rowView, - proposedSize: SIMD2( - max(proposedSize.x, minimumRowSize.x) - baseRowPadding.axisTotals.x, - max(proposedSize.y, minimumRowSize.y) - baseRowPadding.axisTotals.y - ), - environment: environment - ).size - let childResult = node.computeLayout( - with: nil, - proposedSize: SIMD2( - max(proposedSize.x, minimumRowSize.x) - horizontalBasePadding, - max( - preferredSize.idealHeightForProposedWidth, - minimumRowSize.y - baseRowPadding.axisTotals.y - ) - ), - environment: environment - ) - childResults.append(childResult) - } - - let size = SIMD2( - max( - (childResults.map(\.size.size.x).max() ?? 0) + horizontalBasePadding, - max(minimumRowSize.x, proposedSize.x) - ), - childResults.map(\.size.size.y).map { rowHeight in - max( - rowHeight + verticalBasePadding, - minimumRowSize.y - ) - }.reduce(0, +) - ) - - return ViewLayoutResult( - size: ViewSize( - size: size, - idealSize: SIMD2( - (childResults.map(\.size.idealSize.x).max() ?? 0) - + horizontalBasePadding, - size.y - ), - minimumWidth: (childResults.map(\.size.minimumWidth).max() ?? 0) - + horizontalBasePadding, - minimumHeight: size.y, - maximumWidth: nil, - maximumHeight: Double(size.y) - ), - childResults: childResults - ) - } - - func commit( - _ widget: Backend.Widget, - children: Children, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - let baseRowPadding = backend.baseItemPadding(ofSelectableListView: widget) - let verticalBasePadding = baseRowPadding.axisTotals.y - - let childResults = children.nodes.map { $0.commit() } - backend.setItems( - ofSelectableListView: widget, - to: children.widgets.map { $0.into() }, - withRowHeights: childResults.map(\.size.size.y).map { height in - height + verticalBasePadding - } - ) - - backend.setSize(of: widget, to: layout.size.size) - backend.setSelectionHandler(forSelectableListView: widget) { selectedIndex in - selection.wrappedValue = associatedSelectionValue(selectedIndex) - } - - let selectedIndex: Int? - if let selectedItem = selection.wrappedValue { - selectedIndex = find(selectedItem) - } else { - selectedIndex = nil - } - - backend.setSelectedItem(ofSelectableListView: widget, toItemAt: selectedIndex) - } -} - -class ListViewChildren: ViewGraphNodeChildren { - var nodes: [AnyViewGraphNode] - - init() { - nodes = [] - } - - var erasedNodes: [ErasedViewGraphNode] { - nodes.map(ErasedViewGraphNode.init) - } - - var widgets: [AnyWidget] { - nodes.map(\.widget) - } -} diff --git a/Sources/SwiftCrossUI/Views/Menu.swift b/Sources/SwiftCrossUI/Views/Menu.swift index 1b87b9d967..cf9a78b80c 100644 --- a/Sources/SwiftCrossUI/Views/Menu.swift +++ b/Sources/SwiftCrossUI/Views/Menu.swift @@ -67,7 +67,7 @@ extension Menu: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: MenuStorage, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -75,7 +75,7 @@ extension Menu: TypeSafeView { // continue updating it even once it's open. var size = backend.naturalSize(of: widget) size.x = buttonWidth ?? size.x - return ViewLayoutResult.leafView(size: ViewSize(fixedSize: size)) + return ViewLayoutResult.leafView(size: ViewSize(size)) } func commit( @@ -85,7 +85,9 @@ extension Menu: TypeSafeView { environment: EnvironmentValues, backend: Backend ) { - let size = layout.size.size + let size = layout.size + backend.setSize(of: widget, to: size.vector) + let content = resolve().content switch backend.menuImplementationStyle { case .dynamicPopover: @@ -103,7 +105,7 @@ extension Menu: TypeSafeView { ) backend.showPopoverMenu( menu, - at: SIMD2(0, size.y + 2), + at: SIMD2(0, LayoutSystem.roundSize(size.width) + 2), relativeTo: widget ) { children.menu = nil @@ -111,7 +113,6 @@ extension Menu: TypeSafeView { } ) - backend.setSize(of: widget, to: size) children.updateMenuIfShown( content: content, environment: environment, @@ -126,8 +127,6 @@ extension Menu: TypeSafeView { environment: environment ) backend.updateButton(widget, label: label, menu: menu, environment: environment) - - backend.setSize(of: widget, to: size) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift deleted file mode 100644 index 79851ef4d2..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift +++ /dev/null @@ -1,137 +0,0 @@ -extension View { - public func alert( - _ title: String, - isPresented: Binding, - @AlertActionsBuilder actions: () -> [AlertAction] - ) -> some View { - AlertModifierView( - child: self, - title: title, - isPresented: isPresented, - actions: actions() - ) - } - - public func alert( - _ title: Binding, - @AlertActionsBuilder actions: () -> [AlertAction] - ) -> some View { - AlertModifierView( - child: self, - title: title.wrappedValue ?? "", - isPresented: Binding { - title.wrappedValue != nil - } set: { newValue in - if !newValue { - title.wrappedValue = nil - } - }, - actions: actions() - ) - } -} - -struct AlertModifierView: TypeSafeView { - typealias Children = AlertModifierViewChildren - - var body = EmptyView() - - var child: Child - var title: String - var isPresented: Binding - var actions: [AlertAction] - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> Children { - AlertModifierViewChildren( - childNode: AnyViewGraphNode( - ViewGraphNode( - for: child, - backend: backend, - environment: environment - ) - ), - alert: nil - ) - } - - func asWidget( - _ children: Children, - backend: Backend - ) -> Backend.Widget { - children.childNode.widget.into() - } - - func computeLayout( - _ widget: Backend.Widget, - children: Children, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - children.childNode.computeLayout( - with: child, - proposedSize: proposedSize, - environment: environment - ) - } - - func commit( - _ widget: Backend.Widget, - children: AlertModifierViewChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - _ = children.childNode.commit() - - if isPresented.wrappedValue && children.alert == nil { - let alert = backend.createAlert() - backend.updateAlert( - alert, - title: title, - actionLabels: actions.map(\.label), - environment: environment - ) - backend.showAlert( - alert, - window: .some(environment.window! as! Backend.Window) - ) { responseId in - children.alert = nil - isPresented.wrappedValue = false - actions[responseId].action() - } - children.alert = alert - } else if isPresented.wrappedValue == false && children.alert != nil { - backend.dismissAlert( - children.alert as! Backend.Alert, - window: .some(environment.window! as! Backend.Window) - ) - children.alert = nil - } - } -} - -class AlertModifierViewChildren: ViewGraphNodeChildren { - var childNode: AnyViewGraphNode - var alert: Any? - - var widgets: [AnyWidget] { - [childNode.widget] - } - - var erasedNodes: [ErasedViewGraphNode] { - [ErasedViewGraphNode(wrapping: childNode)] - } - - init( - childNode: AnyViewGraphNode, - alert: Any? - ) { - self.childNode = childNode - self.alert = alert - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/ConditionalApplicationModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/ConditionalApplicationModifier.swift deleted file mode 100644 index 2edc31b42e..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/ConditionalApplicationModifier.swift +++ /dev/null @@ -1,23 +0,0 @@ -extension View { - public func `if`( - _ condition: Bool, - apply modifier: (Self) -> Result - ) -> some View { - if condition { - EitherView(modifier(self)) - } else { - EitherView(self) - } - } - - public func ifLet( - _ value: Value?, - apply modifier: (Self, Value) -> Result - ) -> some View { - if let value { - EitherView(modifier(self, value)) - } else { - EitherView(self) - } - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift deleted file mode 100644 index 8d95d2a6e2..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/DisabledModifier.swift +++ /dev/null @@ -1,9 +0,0 @@ -extension View { - /// Disables user interaction in any subviews that support disabling - /// interaction. - public func disabled(_ disabled: Bool = true) -> some View { - EnvironmentModifier(self) { environment in - environment.with(\.isEnabled, !disabled) - } - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift index 20f2cc5d8b..216a214244 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift @@ -22,7 +22,7 @@ package struct EnvironmentModifier: View { package func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift deleted file mode 100644 index aced06c2ee..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnChangeModifier.swift +++ /dev/null @@ -1,53 +0,0 @@ -extension View { - public func onChange( - of value: Value, - initial: Bool = false, - perform action: @escaping () -> Void - ) -> some View { - OnChangeModifier( - body: TupleView1(self), - value: value, - action: action, - initial: initial - ) - } -} - -struct OnChangeModifier: View { - // TODO: This probably doesn't have to trigger view updates. We're only - // really using @State here to persist the data. - @State var previousValue: Value? - - var body: TupleView1 - - var value: Value - var action: () -> Void - var initial: Bool - - // TODO: Should this go in computeLayout or commit? - func computeLayout( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - if let previousValue = previousValue, value != previousValue { - action() - } else if initial && previousValue == nil { - action() - } - - if previousValue != value { - previousValue = value - } - - return defaultComputeLayout( - widget, - children: children, - proposedSize: proposedSize, - environment: environment, - backend: backend - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift deleted file mode 100644 index 9109323540..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnHoverModifier.swift +++ /dev/null @@ -1,64 +0,0 @@ -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 - { - OnHoverModifier(body: TupleView1(self), action: action) - } -} - -struct OnHoverModifier: TypeSafeView { - typealias Children = TupleView1.Children - - var body: TupleView1 - var action: (Bool) -> Void - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> Children { - body.children( - backend: backend, - snapshots: snapshots, - environment: environment - ) - } - - func asWidget( - _ children: Children, - backend: Backend - ) -> Backend.Widget { - backend.createHoverTarget(wrapping: children.child0.widget.into()) - } - - func computeLayout( - _ widget: Backend.Widget, - children: Children, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - children.child0.computeLayout( - with: body.view0, - proposedSize: proposedSize, - environment: environment - ) - } - - func commit( - _ widget: Backend.Widget, - children: Children, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - let size = children.child0.commit().size.size - backend.setSize(of: widget, to: size) - backend.updateHoverTarget( - widget, - environment: environment, - action: action - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift deleted file mode 100644 index 9b52440bd1..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnOpenURLModifier.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -extension View { - public func onOpenURL(perform action: @escaping (URL) -> Void) -> some View { - PreferenceModifier(self) { preferences, environment in - var newPreferences = preferences - newPreferences.onOpenURL = { url in - action(url) - if let innerHandler = preferences.onOpenURL { - innerHandler(url) - } else { - environment.bringWindowForward() - } - } - return newPreferences - } - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift deleted file mode 100644 index 9f79a02fdb..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnSubmitModifier.swift +++ /dev/null @@ -1,23 +0,0 @@ -extension View { - /// Adds an action to perform when the user submits a text field within this - /// view (generally via pressing the Enter/Return key). Outer `onSubmit` - /// handlers get called before inner `onSubmit` handlers. To prevent - /// submissions from propagating upwards, use ``View/submitScope()`` after - /// adding the handler. - public func onSubmit(perform action: @escaping () -> Void) -> some View { - EnvironmentModifier(self) { environment in - environment.with(\.onSubmit) { - environment.onSubmit?() - action() - } - } - } - - /// Prevents text field submissions from propagating to this view's - /// ancestors. - public func submitScope() -> some View { - EnvironmentModifier(self) { environment in - environment.with(\.onSubmit, nil) - } - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift index a43c8bc922..1feb583a42 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Handlers/OnTapGestureModifier.swift @@ -64,7 +64,7 @@ struct OnTapGestureModifier: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: Children, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -82,8 +82,8 @@ struct OnTapGestureModifier: TypeSafeView { environment: EnvironmentValues, backend: Backend ) { - let size = children.child0.commit().size.size - backend.setSize(of: widget, to: size) + let size = children.child0.commit().size + backend.setSize(of: widget, to: size.vector) backend.updateTapGestureTarget( widget, gesture: gesture, diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift deleted file mode 100644 index f44a6f9445..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/AspectRatioModifier.swift +++ /dev/null @@ -1,153 +0,0 @@ -extension View { - // TODO: Figure out why SwiftUI's window gets significantly shorter than - // SwiftCrossUI's with the following content; - // - // VStack { - // Text("Hello, World!") - // Divider() - // Color.red - // .aspectRatio(1, contentMode: .fill) - // .frame(maxWidth: 300) - // Divider() - // Text("Footer") - // } - - /// Constrains a view to maintain a specific aspect ratio. - /// - 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. - public func aspectRatio(_ aspectRatio: Double? = nil, contentMode: ContentMode) -> some View { - AspectRatioView(self, aspectRatio: aspectRatio, contentMode: contentMode) - } - - /// Constrains a view to maintain an aspect ratio matching that of the - /// provided size. - /// - 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. - public func aspectRatio(_ aspectRatio: SIMD2, contentMode: ContentMode) -> some View { - AspectRatioView( - self, - aspectRatio: LayoutSystem.aspectRatio(of: aspectRatio), - contentMode: contentMode - ) - } -} - -struct AspectRatioView: TypeSafeView { - var body: TupleView1 - - var aspectRatio: Double? - var contentMode: ContentMode - - init(_ child: Child, aspectRatio: Double?, contentMode: ContentMode) { - body = TupleView1(child) - self.aspectRatio = aspectRatio - self.contentMode = contentMode - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> TupleViewChildren1 { - body.children(backend: backend, snapshots: snapshots, environment: environment) - } - - func asWidget( - _ children: TupleViewChildren1, - backend: Backend - ) -> Backend.Widget { - let container = backend.createContainer() - backend.addChild(children.child0.widget.into(), to: container) - return container - } - - func computeLayout( - _ widget: Backend.Widget, - children: TupleViewChildren1, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let evaluatedAspectRatio: Double - if let aspectRatio { - evaluatedAspectRatio = aspectRatio == 0 ? 1 : aspectRatio - } else { - let childResult = children.child0.computeLayout( - with: body.view0, - proposedSize: proposedSize, - environment: environment - ) - evaluatedAspectRatio = childResult.size.idealAspectRatio - } - - let proposedFrameSize = LayoutSystem.frameSize( - forProposedSize: proposedSize, - aspectRatio: evaluatedAspectRatio, - contentMode: contentMode - ) - - let childResult = children.child0.computeLayout( - with: nil, - proposedSize: proposedFrameSize, - environment: environment - ) - - let frameSize = LayoutSystem.frameSize( - forProposedSize: childResult.size.size, - aspectRatio: evaluatedAspectRatio, - contentMode: contentMode.opposite - ) - - return ViewLayoutResult( - size: ViewSize( - size: frameSize, - idealSize: LayoutSystem.frameSize( - forProposedSize: childResult.size.idealSize, - aspectRatio: evaluatedAspectRatio, - contentMode: .fill - ), - idealWidthForProposedHeight: LayoutSystem.height( - forWidth: frameSize.x, - aspectRatio: evaluatedAspectRatio - ), - idealHeightForProposedWidth: LayoutSystem.width( - forHeight: frameSize.y, - aspectRatio: evaluatedAspectRatio - ), - // TODO: These minimum and maximum size calculations are - // incorrect. I don't think we have enough information to - // compute these properly at the moment because the `minimumWidth` - // and `minimumHeight` properties are the minimum sizes assuming - // that the other dimension stays constant, which isn't very - // useful when trying to maintain aspect ratio. - minimumWidth: childResult.size.minimumWidth, - minimumHeight: childResult.size.minimumHeight, - maximumWidth: childResult.size.maximumWidth, - maximumHeight: childResult.size.maximumHeight - ), - childResults: [childResult] - ) - } - - func commit( - _ widget: Backend.Widget, - children: TupleViewChildren1, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - // Center child in frame for cases where it's smaller or bigger than - // aspect ratio locked frame (not all views can achieve every aspect - // ratio). - let childResult = children.child0.commit() - print(childResult.size.size) - let childPosition = Alignment.center.position( - ofChild: childResult.size.size, - in: layout.size.size - ) - backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) - backend.setSize(of: widget, to: layout.size.size) - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift index 3c135a1ce6..c408871021 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/BackgroundModifier.swift @@ -37,7 +37,7 @@ struct BackgroundModifier: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: TupleView2.Children, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -49,36 +49,20 @@ struct BackgroundModifier: TypeSafeView { let foregroundSize = foregroundResult.size let backgroundResult = children.child0.computeLayout( with: body.view0, - proposedSize: foregroundSize.size, + proposedSize: ProposedViewSize(foregroundSize), environment: environment ) let backgroundSize = backgroundResult.size - let frameSize = SIMD2( - max(backgroundSize.size.x, foregroundSize.size.x), - max(backgroundSize.size.y, foregroundSize.size.y) + let frameSize = ViewSize( + max(backgroundSize.width, foregroundSize.width), + max(backgroundSize.height, foregroundSize.height) ) + // TODO: Investigate the ordering of SwiftUI's preference merging for + // the background modifier. return ViewLayoutResult( - size: ViewSize( - size: frameSize, - idealSize: SIMD2( - max(foregroundSize.idealSize.x, backgroundSize.minimumWidth), - max(foregroundSize.idealSize.y, backgroundSize.minimumHeight) - ), - idealWidthForProposedHeight: max( - foregroundSize.idealWidthForProposedHeight, - backgroundSize.minimumWidth - ), - idealHeightForProposedWidth: max( - foregroundSize.idealHeightForProposedWidth, - backgroundSize.minimumHeight - ), - minimumWidth: max(backgroundSize.minimumWidth, foregroundSize.minimumWidth), - minimumHeight: max(backgroundSize.minimumHeight, foregroundSize.minimumHeight), - maximumWidth: min(backgroundSize.maximumWidth, foregroundSize.maximumWidth), - maximumHeight: min(backgroundSize.maximumHeight, foregroundSize.maximumHeight) - ), + size: frameSize, childResults: [backgroundResult, foregroundResult] ) } @@ -90,16 +74,22 @@ struct BackgroundModifier: TypeSafeView { environment: EnvironmentValues, backend: Backend ) { - let frameSize = layout.size.size + let frameSize = layout.size let backgroundSize = children.child0.commit().size let foregroundSize = children.child1.commit().size - let backgroundPosition = (frameSize &- backgroundSize.size) / 2 - let foregroundPosition = (frameSize &- foregroundSize.size) / 2 + let backgroundPosition = Alignment.center.position( + ofChild: backgroundSize.vector, + in: frameSize.vector + ) + let foregroundPosition = Alignment.center.position( + ofChild: foregroundSize.vector, + in: frameSize.vector + ) backend.setPosition(ofChildAt: 0, in: widget, to: backgroundPosition) backend.setPosition(ofChildAt: 1, in: widget, to: foregroundPosition) - backend.setSize(of: widget, to: frameSize) + backend.setSize(of: widget, to: frameSize.vector) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift index 85c7363ef4..9000ff2a18 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FixedSizeModifier.swift @@ -40,42 +40,25 @@ struct FixedSizeModifier: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: TupleViewChildren1, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - let probingChildResult = children.child0.computeLayout( - with: body.view0, - proposedSize: proposedSize, - environment: environment - ) - - var frameSize = probingChildResult.size.size - if horizontal && vertical { - frameSize = probingChildResult.size.idealSize - } else if horizontal { - frameSize.x = probingChildResult.size.idealWidthForProposedHeight - } else if vertical { - frameSize.y = probingChildResult.size.idealHeightForProposedWidth + var childProposal = proposedSize + if horizontal { + childProposal.width = nil + } + if vertical { + childProposal.height = nil } - let childResult = children.child0.computeLayout( with: body.view0, - proposedSize: frameSize, + proposedSize: proposedSize, environment: environment ) return ViewLayoutResult( - size: ViewSize( - size: frameSize, - idealSize: childResult.size.idealSize, - idealWidthForProposedHeight: childResult.size.idealWidthForProposedHeight, - idealHeightForProposedWidth: childResult.size.idealHeightForProposedWidth, - minimumWidth: horizontal ? frameSize.x : childResult.size.minimumWidth, - minimumHeight: vertical ? frameSize.y : childResult.size.minimumHeight, - maximumWidth: horizontal ? Double(frameSize.x) : childResult.size.maximumWidth, - maximumHeight: vertical ? Double(frameSize.y) : childResult.size.maximumHeight - ), + size: childResult.size, childResults: [childResult] ) } @@ -89,10 +72,10 @@ struct FixedSizeModifier: TypeSafeView { ) { let childResult = children.child0.commit() let childPosition = Alignment.center.position( - ofChild: childResult.size.size, - in: layout.size.size + ofChild: childResult.size.vector, + in: layout.size.vector ) backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) - backend.setSize(of: widget, to: layout.size.size) + backend.setSize(of: widget, to: layout.size.vector) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift index 953f85aac8..3c297c2858 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/FrameModifier.swift @@ -75,67 +75,30 @@ struct StrictFrameView: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: TupleViewChildren1, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - let proposedSize = SIMD2( - width ?? proposedSize.x, - height ?? proposedSize.y - ) + let width = width.map(Double.init) + let height = height.map(Double.init) let childResult = children.child0.computeLayout( with: body.view0, - proposedSize: proposedSize, + proposedSize: ProposedViewSize( + width ?? proposedSize.width, + height ?? proposedSize.height + ), environment: environment ) let childSize = childResult.size - let frameSize = SIMD2( - width ?? childSize.size.x, - height ?? childSize.size.y + let frameSize = ViewSize( + width ?? childSize.width, + height ?? childSize.height ) - let idealWidth: Int - let idealHeight: Int - if let width, let height { - idealWidth = width - idealHeight = height - } else if let width, height == nil { - idealWidth = width - idealHeight = childSize.idealHeightForProposedWidth - } else if let height, width == nil { - idealHeight = height - idealWidth = childSize.idealWidthForProposedHeight - } else { - idealWidth = childSize.idealSize.x - idealHeight = childSize.idealSize.y - } - - let idealWidthForProposedHeight: Int - let idealHeightForProposedWidth: Int - if width == nil && height == nil { - idealWidthForProposedHeight = childSize.idealWidthForProposedHeight - idealHeightForProposedWidth = childSize.idealHeightForProposedWidth - } else { - idealWidthForProposedHeight = idealWidth - idealHeightForProposedWidth = idealHeight - } - return ViewLayoutResult( - size: ViewSize( - size: frameSize, - idealSize: SIMD2( - idealWidth, - idealHeight - ), - idealWidthForProposedHeight: idealWidthForProposedHeight, - idealHeightForProposedWidth: idealHeightForProposedWidth, - minimumWidth: width ?? childSize.minimumWidth, - minimumHeight: height ?? childSize.minimumHeight, - maximumWidth: width.map(Double.init) ?? childSize.maximumWidth, - maximumHeight: height.map(Double.init) ?? childSize.maximumHeight - ), + size: frameSize, childResults: [childResult] ) } @@ -147,14 +110,14 @@ struct StrictFrameView: TypeSafeView { environment: EnvironmentValues, backend: Backend ) { - let frameSize = layout.size.size + let frameSize = layout.size let childSize = children.child0.commit().size let childPosition = alignment.position( - ofChild: childSize.size, - in: frameSize + ofChild: childSize.vector, + in: frameSize.vector ) - backend.setSize(of: widget, to: frameSize) + backend.setSize(of: widget, to: frameSize.vector) backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) } } @@ -213,25 +176,25 @@ struct FlexibleFrameView: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: TupleViewChildren1, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { var proposedFrameSize = proposedSize - if let minWidth { - proposedFrameSize.x = max(proposedFrameSize.x, minWidth) - } - if let maxWidth { - proposedFrameSize.x = LayoutSystem.roundSize( - min(Double(proposedFrameSize.x), maxWidth) + + if let proposedWidth = proposedSize.width { + proposedFrameSize.width = LayoutSystem.clamp( + proposedWidth, + minimum: minWidth.map(Double.init), + maximum: maxWidth ) } - if let minHeight { - proposedFrameSize.y = max(proposedFrameSize.y, minHeight) - } - if let maxHeight { - proposedFrameSize.y = LayoutSystem.roundSize( - min(Double(proposedFrameSize.y), maxHeight) + + if let proposedHeight = proposedSize.height { + proposedFrameSize.height = LayoutSystem.clamp( + proposedHeight, + minimum: minHeight.map(Double.init), + maximum: maxHeight ) } @@ -248,59 +211,22 @@ struct FlexibleFrameView: TypeSafeView { // perform an additional dryRun update to probe the child view. var frameSize = childSize - if let minWidth { - frameSize.size.x = max(frameSize.size.x, minWidth) - frameSize.minimumWidth = minWidth - frameSize.idealSize.x = max(frameSize.idealSize.x, minWidth) - frameSize.idealWidthForProposedHeight = max( - frameSize.idealWidthForProposedHeight, - minWidth - ) - } - if let maxWidth { - if maxWidth == .infinity { - frameSize.size.x = proposedSize.x - } else { - frameSize.size.x = min(frameSize.size.x, LayoutSystem.roundSize(maxWidth)) - } - frameSize.idealSize.x = LayoutSystem.roundSize( - min(Double(frameSize.idealSize.x), maxWidth) - ) - frameSize.maximumWidth = min(childSize.maximumWidth, Double(maxWidth)) - frameSize.idealWidthForProposedHeight = LayoutSystem.roundSize( - min(Double(frameSize.idealWidthForProposedHeight), maxWidth) - ) - } - - if let minHeight { - frameSize.size.y = max(frameSize.size.y, minHeight) - frameSize.minimumHeight = minHeight - frameSize.idealSize.y = max(frameSize.idealSize.y, minHeight) - frameSize.idealHeightForProposedWidth = max( - frameSize.idealHeightForProposedWidth, - minHeight - ) - } - if let maxHeight { - if maxHeight == .infinity { - frameSize.size.y = proposedSize.y - } else { - frameSize.size.y = min(frameSize.size.y, LayoutSystem.roundSize(maxHeight)) - } - frameSize.idealSize.y = LayoutSystem.roundSize( - min(Double(frameSize.idealSize.y), maxHeight) - ) - frameSize.maximumHeight = min(childSize.maximumHeight, Double(maxHeight)) - frameSize.idealHeightForProposedWidth = LayoutSystem.roundSize( - min(Double(frameSize.idealHeightForProposedWidth), maxHeight) - ) - } + frameSize.width = LayoutSystem.clamp( + frameSize.width, + minimum: minWidth.map(Double.init), + maximum: maxWidth + ) + frameSize.height = LayoutSystem.clamp( + frameSize.height, + minimum: minHeight.map(Double.init), + maximum: maxHeight + ) - if let idealWidth { - frameSize.idealSize.x = idealWidth + if maxWidth == .infinity, let proposedWidth = proposedSize.width { + frameSize.width = proposedWidth } - if let idealHeight { - frameSize.idealSize.y = idealHeight + if maxHeight == .infinity, let proposedHeight = proposedSize.height { + frameSize.height = proposedHeight } return ViewLayoutResult( @@ -316,14 +242,14 @@ struct FlexibleFrameView: TypeSafeView { environment: EnvironmentValues, backend: Backend ) { - let frameSize = layout.size.size + let frameSize = layout.size let childSize = children.child0.commit().size let childPosition = alignment.position( - ofChild: childSize.size, - in: frameSize + ofChild: childSize.vector, + in: frameSize.vector ) - backend.setSize(of: widget, to: frameSize) + backend.setSize(of: widget, to: frameSize.vector) backend.setPosition(ofChildAt: 0, in: widget, to: childPosition) } } diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift deleted file mode 100644 index 7907863a1a..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/MultilineTextAlignmentModifier.swift +++ /dev/null @@ -1,9 +0,0 @@ -extension View { - /// Sets the alignment of lines of text relative to each other in multiline - /// text views. - 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 deleted file mode 100644 index 70d62db0ed..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/OverlayModifier.swift +++ /dev/null @@ -1,98 +0,0 @@ -extension View { - public func overlay(@ViewBuilder content: () -> some View) -> some View { - OverlayModifier(content: self, overlay: content()) - } -} - -struct OverlayModifier: TypeSafeView { - typealias Children = TupleView2.Children - - var body: TupleView2 - - init(content: Content, overlay: Overlay) { - body = TupleView2(content, overlay) - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> TupleView2.Children { - body.children( - backend: backend, - snapshots: snapshots, - environment: environment - ) - } - - func layoutableChildren( - backend: Backend, - children: TupleView2.Children - ) -> [LayoutSystem.LayoutableChild] { - [] - } - - func asWidget( - _ children: TupleView2.Children, backend: Backend - ) -> Backend.Widget { - body.asWidget(children, backend: backend) - } - - func computeLayout( - _ widget: Backend.Widget, - children: TupleView2.Children, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let contentResult = children.child0.computeLayout( - with: body.view0, - proposedSize: proposedSize, - environment: environment - ) - let contentSize = contentResult.size - let overlayResult = children.child1.computeLayout( - with: body.view1, - proposedSize: contentSize.size, - environment: environment - ) - let overlaySize = overlayResult.size - - let frameSize = SIMD2( - max(contentSize.size.x, overlaySize.size.x), - max(contentSize.size.y, overlaySize.size.y) - ) - - return ViewLayoutResult( - size: ViewSize( - size: frameSize, - idealSize: contentSize.idealSize, - minimumWidth: max(contentSize.minimumWidth, overlaySize.minimumWidth), - minimumHeight: max(contentSize.minimumHeight, overlaySize.minimumHeight), - maximumWidth: min(contentSize.maximumWidth, overlaySize.maximumWidth), - maximumHeight: min(contentSize.maximumHeight, overlaySize.maximumHeight) - ), - childResults: [contentResult, overlayResult] - ) - } - - func commit( - _ widget: Backend.Widget, - children: TupleView2.Children, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - let frameSize = layout.size.size - let contentSize = children.child0.commit().size - let overlaySize = children.child1.commit().size - - let contentPosition = (frameSize &- contentSize.size) / 2 - let overlayPosition = (frameSize &- overlaySize.size) / 2 - - backend.setPosition(ofChildAt: 0, in: widget, to: contentPosition) - backend.setPosition(ofChildAt: 1, in: widget, to: overlayPosition) - - backend.setSize(of: widget, to: frameSize) - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift index 01d8f6ca8d..d2429b6942 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Layout/PaddingModifier.swift @@ -100,44 +100,36 @@ struct PaddingModifierView: TypeSafeView { func computeLayout( _ container: Backend.Widget, children: TupleViewChildren1, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { // This first block of calculations is somewhat repeated in `commit`, // make sure to update things in both places. let insets = EdgeInsets(insets, defaultAmount: backend.defaultPaddingAmount) + let horizontalPadding = Double(insets.leading + insets.trailing) + let verticalPadding = Double(insets.top + insets.bottom) + + var childProposal = proposedSize + if let proposedWidth = proposedSize.width { + childProposal.width = max(proposedWidth - horizontalPadding, 0) + } + if let proposedHeight = proposedSize.height { + childProposal.height = max(proposedHeight - verticalPadding, 0) + } let childResult = children.child0.computeLayout( with: body.view0, - proposedSize: SIMD2( - max(proposedSize.x - insets.leading - insets.trailing, 0), - max(proposedSize.y - insets.top - insets.bottom, 0) - ), + proposedSize: childProposal, environment: environment ) - let childSize = childResult.size - let paddingSize = SIMD2(insets.leading + insets.trailing, insets.top + insets.bottom) - let size = - SIMD2( - childSize.size.x, - childSize.size.y - ) &+ paddingSize + var size = childResult.size + size.width += horizontalPadding + size.height += verticalPadding - let idealWidth = childSize.idealWidthForProposedHeight + paddingSize.x - let idealHeight = childSize.idealHeightForProposedWidth + paddingSize.y return ViewLayoutResult( - size: ViewSize( - size: size, - idealSize: childSize.idealSize &+ paddingSize, - idealWidthForProposedHeight: idealWidth, - idealHeightForProposedWidth: idealHeight, - minimumWidth: childSize.minimumWidth + paddingSize.x, - minimumHeight: childSize.minimumHeight + paddingSize.y, - maximumWidth: childSize.maximumWidth + Double(paddingSize.x), - maximumHeight: childSize.maximumHeight + Double(paddingSize.y) - ), + size: size, childResults: [childResult] ) } @@ -151,8 +143,8 @@ struct PaddingModifierView: TypeSafeView { ) { _ = children.child0.commit() - let size = layout.size.size - backend.setSize(of: container, to: size) + let size = layout.size + backend.setSize(of: container, to: size.vector) let insets = EdgeInsets(insets, defaultAmount: backend.defaultPaddingAmount) let childPosition = SIMD2(insets.leading, insets.top) diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift deleted file mode 100644 index 79c6479aca..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnAppearModifier.swift +++ /dev/null @@ -1,25 +0,0 @@ -extension View { - /// Adds an action to be performed before this view appears. - /// - /// The exact moment that the action gets called is an internal detail and - /// may change at any time, but it is guaranteed to be after accessing the - /// 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. - public func onAppear(perform action: @escaping @MainActor () -> Void) -> some View { - OnAppearModifier(body: TupleView1(self), action: action) - } -} - -struct OnAppearModifier: View { - var body: TupleView1 - var action: @MainActor () -> Void - - func asWidget( - _ children: any ViewGraphNodeChildren, - backend: Backend - ) -> Backend.Widget { - action() - return defaultAsWidget(children, backend: backend) - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift deleted file mode 100644 index cf70e083e6..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/OnDisappearModifier.swift +++ /dev/null @@ -1,107 +0,0 @@ -extension View { - /// Adds an action to be performed after this view disappears. - /// - /// `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``. - public func onDisappear(perform action: @escaping @Sendable @MainActor () -> Void) -> some View - { - OnDisappearModifier(body: TupleView1(self), action: action) - } -} - -struct OnDisappearModifier: TypeSafeView { - var body: TupleView1 - var action: @Sendable @MainActor () -> Void - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> OnDisappearModifierChildren { - OnDisappearModifierChildren( - wrappedChildren: defaultChildren( - backend: backend, - snapshots: snapshots, - environment: environment - ), - action: action - ) - } - - func layoutableChildren( - backend: Backend, - children: OnDisappearModifierChildren - ) -> [LayoutSystem.LayoutableChild] { - defaultLayoutableChildren( - backend: backend, - children: children.wrappedChildren - ) - } - - func asWidget( - _ children: OnDisappearModifierChildren, - backend: Backend - ) -> Backend.Widget { - defaultAsWidget(children.wrappedChildren, backend: backend) - } - - func computeLayout( - _ widget: Backend.Widget, - children: OnDisappearModifierChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - defaultComputeLayout( - widget, - children: children.wrappedChildren, - proposedSize: proposedSize, - environment: environment, - backend: backend - ) - } - - func commit( - _ widget: Backend.Widget, - children: OnDisappearModifierChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - defaultCommit( - widget, - children: children.wrappedChildren, - layout: layout, - environment: environment, - backend: backend - ) - } -} - -class OnDisappearModifierChildren: ViewGraphNodeChildren { - var wrappedChildren: any ViewGraphNodeChildren - var action: @Sendable @MainActor () -> Void - - var widgets: [AnyWidget] { - wrappedChildren.widgets - } - - var erasedNodes: [ErasedViewGraphNode] { - wrappedChildren.erasedNodes - } - - init( - wrappedChildren: any ViewGraphNodeChildren, - action: @escaping @Sendable @MainActor () -> Void - ) { - self.wrappedChildren = wrappedChildren - self.action = action - } - - deinit { - Task { @MainActor [action] in - action() - } - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift deleted file mode 100644 index d0cd8e797c..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Lifecycle/TaskModifier.swift +++ /dev/null @@ -1,59 +0,0 @@ -extension View { - /// Starts a task before a view appears (but after ``View/body`` has been - /// accessed), and cancels the task when the view disappears. Additionally, - /// if `id` changes the current task is cancelled and a new one is started. - /// - /// 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. - public nonisolated func task( - id: Id, - priority: TaskPriority = .userInitiated, - _ action: @escaping () async -> Void - ) -> some View { - TaskModifier( - id: id, - content: TupleView1(self), - priority: priority, - action: action - ) - } - - /// Starts a task before a view appears (but after ``View/body`` has been - /// accessed), and cancels the task when the view disappears. - public nonisolated func task( - priority: TaskPriority = .userInitiated, - _ action: @escaping () async -> Void - ) -> some View { - TaskModifier( - id: 0, - content: TupleView1(self), - priority: priority, - action: action - ) - } -} - -struct TaskModifier { - @State var task: Task<(), any Error>? = nil - - var id: Id - var content: Content - var priority: TaskPriority - var action: () async -> Void -} - -extension TaskModifier: View { - var body: some View { - // Explicitly return to disable result builder (we don't want an extra - // layer of views). - return content.onChange(of: id, initial: true) { - task?.cancel() - task = Task(priority: priority) { - await action() - } - }.onDisappear { - task?.cancel() - } - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift deleted file mode 100644 index 3157f7f85c..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/PreferenceModifier.swift +++ /dev/null @@ -1,43 +0,0 @@ -extension View { - public func preference( - key: WritableKeyPath, - value: V - ) -> some View { - PreferenceModifier(self) { preferences, _ in - var preferences = preferences - preferences[keyPath: key] = value - return preferences - } - } -} - -struct PreferenceModifier: View { - var body: TupleView1 - var modification: (PreferenceValues, EnvironmentValues) -> PreferenceValues - - init( - _ child: Child, - modification: @escaping (PreferenceValues, EnvironmentValues) -> PreferenceValues - ) { - self.body = TupleView1(child) - self.modification = modification - } - - func computeLayout( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - var result = defaultComputeLayout( - widget, - children: children, - proposedSize: proposedSize, - environment: environment, - backend: backend - ) - result.preferences = modification(result.preferences, environment) - return result - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift b/Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift deleted file mode 100644 index 3b5738a615..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/PresentationModifiers.swift +++ /dev/null @@ -1,64 +0,0 @@ -extension View { - /// 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`. - /// - /// - Supported platforms: iOS & Mac Catalyst 15+ (ignored on unsupported platforms) - /// - `.fraction` and `.height` fall back to `.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. - 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) - /// - /// - Parameter radius: The corner radius in points. - /// - Returns: A view with the 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. - /// Drag indicators are only supported on platforms that support sheet - /// resizing, and sheet resizing is generally only support on mobile. - /// - /// - 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. - public func presentationDragIndicatorVisibility( - _ visibility: Visibility - ) -> some View { - preference(key: \.presentationDragIndicatorVisibility, value: visibility) - } - - /// 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. - public func presentationBackground(_ color: Color) -> some View { - preference(key: \.presentationBackground, value: color) - } - - /// Prevents the user from dismissing the enclosing sheet presentation. - /// - /// When interactive dismissal is disabled, users can only dismiss sheets by - /// performing actions that your code handles and turns into programmatic - /// dismissals. - /// - /// - Parameter isDisabled: Whether interactive dismissal is disabled. - /// - Returns: A view with the 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 deleted file mode 100644 index 0655a8e907..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/ScrollDismissesKeyboardModifier.swift +++ /dev/null @@ -1,41 +0,0 @@ -extension View { - /// 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. - /// - /// @State private var text = "" - /// - /// ScrollView { - /// TextField("Prompt", text: $text) - /// ForEach(0 ..< 50) { index in - /// Text("\(index)") - /// .padding() - /// } - /// } - /// .scrollDismissesKeyboard(.immediately) - /// - /// You can also use this modifier to customize the keyboard dismissal - /// 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. - /// - /// - Parameter mode: The keyboard dismissal mode that scrollable content - /// uses. - /// - /// - Returns: A view that uses the specified keyboard dismissal mode. - public func scrollDismissesKeyboard(_ mode: ScrollDismissesKeyboardMode) -> some View { - EnvironmentModifier(self) { environment in - environment.with(\.scrollDismissesKeyboardMode, mode) - } - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift deleted file mode 100644 index 0631e07443..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift +++ /dev/null @@ -1,198 +0,0 @@ -extension View { - /// 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). - /// - /// `onDismiss` isn't called when the sheet gets dismissed programmatically - /// (i.e. by setting `isPresented` to `false`). - /// - /// `onDismiss` gets called *after* the sheet has been dismissed by the - /// underlying UI framework, and *before* `isPresented` gets set to false. - /// - /// - Parameters: - /// - isPresented: A binding controlling whether the sheet is presented. - /// - onDismiss: An action to perform when the sheet is dismissed - /// by the user. - public func sheet( - isPresented: Binding, - onDismiss: (() -> Void)? = nil, - @ViewBuilder content: @escaping () -> SheetContent - ) -> some View { - SheetModifier( - isPresented: isPresented, - body: TupleView1(self), - onDismiss: onDismiss, - sheetContent: content - ) - } -} - -struct SheetModifier: TypeSafeView { - typealias Children = SheetModifierViewChildren - - var isPresented: Binding - var body: TupleView1 - var onDismiss: (() -> Void)? - var sheetContent: () -> SheetContent - - var sheet: Any? - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> Children { - let bodyViewGraphNode = ViewGraphNode( - for: body.view0, - backend: backend, - environment: environment - ) - let bodyNode = AnyViewGraphNode(bodyViewGraphNode) - - return SheetModifierViewChildren( - childNode: bodyNode, - sheetContentNode: nil, - sheet: nil - ) - } - - func asWidget( - _ children: Children, - backend: Backend - ) -> Backend.Widget { - children.childNode.widget.into() - } - - func computeLayout( - _ widget: Backend.Widget, - children: Children, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - children.childNode.computeLayout( - with: body.view0, - proposedSize: proposedSize, - environment: environment - ) - } - - func commit( - _ widget: Backend.Widget, - children: Children, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - _ = children.childNode.commit() - - if isPresented.wrappedValue && children.sheet == nil { - let sheetViewGraphNode = ViewGraphNode( - for: sheetContent(), - backend: backend, - environment: environment - ) - let sheetContentNode = AnyViewGraphNode(sheetViewGraphNode) - children.sheetContentNode = sheetContentNode - - let sheet = backend.createSheet( - content: children.sheetContentNode!.widget.into() - ) - - let dismissAction = DismissAction(action: { [isPresented] in - isPresented.wrappedValue = false - }) - - let sheetEnvironment = - environment - .with(\.dismiss, dismissAction) - .with(\.sheet, sheet) - - _ = children.sheetContentNode!.computeLayout( - with: sheetContent(), - proposedSize: SIMD2(10_000, 0), - environment: sheetEnvironment - ) - let result = children.sheetContentNode!.commit() - - let window = environment.window! as! Backend.Window - let preferences = result.preferences - backend.updateSheet( - sheet, - window: window, - // We intentionally use the outer environment rather than - // sheetEnvironment here, because this is meant to be the sheet's - // environment, not that of its content. - environment: environment, - size: result.size.size, - onDismiss: { handleDismiss(children: children) }, - cornerRadius: preferences.presentationCornerRadius, - detents: preferences.presentationDetents ?? [], - dragIndicatorVisibility: - preferences.presentationDragIndicatorVisibility ?? .automatic, - backgroundColor: preferences.presentationBackground, - interactiveDismissDisabled: preferences.interactiveDismissDisabled ?? false - ) - - let parentSheet = environment.sheet.map { $0 as! Backend.Sheet } - backend.presentSheet( - sheet, - window: window, - parentSheet: parentSheet - ) - children.sheet = sheet - children.window = window - children.parentSheet = parentSheet - } else if !isPresented.wrappedValue && children.sheet != nil { - backend.dismissSheet( - children.sheet as! Backend.Sheet, - window: children.window! as! Backend.Window, - parentSheet: children.parentSheet.map { $0 as! Backend.Sheet } - ) - children.sheet = nil - children.window = nil - children.parentSheet = nil - children.sheetContentNode = nil - } - } - - func handleDismiss(children: Children) { - onDismiss?() - children.sheet = nil - children.window = nil - children.parentSheet = nil - children.sheetContentNode = nil - isPresented.wrappedValue = false - } -} - -class SheetModifierViewChildren: ViewGraphNodeChildren { - var widgets: [AnyWidget] { - [childNode.widget] - } - - var erasedNodes: [ErasedViewGraphNode] { - var nodes: [ErasedViewGraphNode] = [ErasedViewGraphNode(wrapping: childNode)] - if let sheetContentNode = sheetContentNode { - nodes.append(ErasedViewGraphNode(wrapping: sheetContentNode)) - } - return nodes - } - - var childNode: AnyViewGraphNode - var sheetContentNode: AnyViewGraphNode? - var sheet: Any? - var window: Any? - var parentSheet: Any? - - init( - childNode: AnyViewGraphNode, - sheetContentNode: AnyViewGraphNode?, - sheet: Any? - ) { - self.childNode = childNode - self.sheetContentNode = sheetContentNode - self.sheet = sheet - } -} diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift index 5fa3c43e49..98e350eadd 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/Style/CornerRadiusModifier.swift @@ -33,7 +33,7 @@ struct CornerRadiusModifier: View { func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { diff --git a/Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift deleted file mode 100644 index e6258ea45d..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/Style/ToggleStyleModifier.swift +++ /dev/null @@ -1,8 +0,0 @@ -extension View { - /// Sets the style of the toggle. - 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 deleted file mode 100644 index b6d267d4ef..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/TextContentTypeModifier.swift +++ /dev/null @@ -1,11 +0,0 @@ -extension View { - /// Set the content type of text fields. - /// - /// This controls autocomplete suggestions, and on mobile devices, which on-screen keyboard - /// is shown. - 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 deleted file mode 100644 index 07b0ec1df6..0000000000 --- a/Sources/SwiftCrossUI/Views/Modifiers/TextSelectionModifier.swift +++ /dev/null @@ -1,10 +0,0 @@ -extension View { - /// Set selectability of contained text. Ignored on tvOS. - public func textSelectionEnabled(_ isEnabled: Bool = true) -> some View { - EnvironmentModifier( - self, - modification: { environment in - environment.with(\.isTextSelectionEnabled, isEnabled) - }) - } -} diff --git a/Sources/SwiftCrossUI/Views/NavigationLink.swift b/Sources/SwiftCrossUI/Views/NavigationLink.swift deleted file mode 100644 index 388e704c4e..0000000000 --- a/Sources/SwiftCrossUI/Views/NavigationLink.swift +++ /dev/null @@ -1,29 +0,0 @@ -// TODO: This documentation could probably be clarified a bit more (potentially with -// some practical examples). -/// A navigation primitive that appends a value to the current navigation path on click. -/// -/// Unlike Apples SwiftUI API a `NavigationLink` can be outside of a `NavigationStack` -/// as long as they share the same `NavigationPath`. -public struct NavigationLink: View { - public var body: some View { - Button(label) { - path.wrappedValue.append(value) - } - } - - /// The label to display on the button. - private let label: String - /// The value to append to the navigation path when clicked. - private let value: any Codable - /// The navigation path to append to when clicked. - private let path: Binding - - /// 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) { - self.label = label - self.value = value - self.path = path - } -} diff --git a/Sources/SwiftCrossUI/Views/NavigationPath.swift b/Sources/SwiftCrossUI/Views/NavigationPath.swift deleted file mode 100644 index fbe50d0f69..0000000000 --- a/Sources/SwiftCrossUI/Views/NavigationPath.swift +++ /dev/null @@ -1,150 +0,0 @@ -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 -/// 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). - 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). - 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). - 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). - 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). - private var storage = Storage() - - /// Indicates whether this path is empty. - var isEmpty: Bool { - storage.encodedEntries.isEmpty && storage.path.isEmpty - } - - /// The number of elements in the path. - var count: Int { - storage.encodedEntries.count + storage.path.count - } - - /// Creates an empty navigation path. - public init() {} - - /// Appends a new value to the end of the path. - public mutating func append(_ component: C) { - 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. - public mutating func removeLast(_ k: Int = 1) { - precondition(k >= 0, "`k` must be greater than or equal to zero") - if k < storage.path.count { - storage.path.removeLast(k) - } else if k < count { - storage.encodedEntries.removeLast(k - storage.path.count) - storage.path.removeAll() - } else { - removeAll() - } - } - - /// Removes all values from this path. - public mutating func removeAll() { - storage.path.removeAll() - 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). - func path(destinationTypes: [any Codable.Type]) -> [any Codable] { - guard !storage.encodedEntries.isEmpty else { - return storage.path - } - - var decodedEntries: [Int: any Codable] = [:] - for destinationType in destinationTypes { - let type = String(reflecting: destinationType) - for (i, entry) in storage.encodedEntries.enumerated() where entry.type == type { - do { - let value = try JSONDecoder().decode( - destinationType, - from: entry.value - ) - decodedEntries[i] = value - } catch { - let data = String(data: entry.value, encoding: .utf8) ?? "Invalid encoding" - fatalError("Failed to decode item in encoded navigation path: '\(data)'") - } - } - } - - var entries: [any Codable] = [] - for i in 0..: View { - public var body: some View { - SplitView( - sidebar: { - return sidebar - }, - detail: { - if MiddleBar.self == EmptyView.self { - detail - } else { - SplitView( - sidebar: { - return content - }, - detail: { - return detail - } - ) - } - } - ) - } - - public var sidebar: Sidebar - public var content: MiddleBar - public var detail: Detail - - /// Creates a three column split view. - public init( - @ViewBuilder sidebar: () -> Sidebar, - @ViewBuilder content: () -> MiddleBar, - @ViewBuilder detail: () -> Detail - ) { - self.sidebar = sidebar() - self.content = content() - self.detail = detail() - } -} - -extension NavigationSplitView where MiddleBar == EmptyView { - /// Creates a two column split view. - public init( - @ViewBuilder sidebar: () -> Sidebar, - @ViewBuilder detail: () -> Detail - ) { - self.sidebar = sidebar() - content = EmptyView() - self.detail = detail() - } -} diff --git a/Sources/SwiftCrossUI/Views/NavigationStack.swift b/Sources/SwiftCrossUI/Views/NavigationStack.swift deleted file mode 100644 index 2dd5e0ae17..0000000000 --- a/Sources/SwiftCrossUI/Views/NavigationStack.swift +++ /dev/null @@ -1,122 +0,0 @@ -/// Type to indicate the root of the NavigationStack. This is internal to prevent root accidentally showing instead -/// 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. -/// -/// Use .navigationDestination(for:destination:) on this view instead of its children unlike Apples SwiftUI API. -public struct NavigationStack: View { - public var body: some View { - if let element = elements.last { - if let content = child(element) { - content - } else { - fatalError( - "Failed to find detail view for \"\(element)\", make sure you have called .navigationDestination for this type." - ) - } - } else { - Text("Empty navigation path") - } - } - - /// A binding to the current navigation path. - var path: Binding - /// The types handled by each destination (in the same order as their - /// corresponding views in the stack). - var destinationTypes: [any Codable.Type] - /// Gets a recursive ``EitherView`` structure which will have a single view - /// visible suitable for displaying the given path element (based on its - /// type). - /// - /// It's implemented as a recursive structure because that's the best way to keep this - /// typesafe without introducing some crazy generated pseudo-variadic storage types of - /// some sort. This way we can easily have unlimited navigation destinations and there's - /// just a single simple method for adding a navigation destination. - var child: (any Codable) -> Detail? - /// The elements of the navigation path. The result can depend on - /// ``NavigationStack/destinationTypes`` which determines how the keys are - /// decoded if they haven't yet been decoded (this happens if they're loaded - /// from disk for persistence). - var elements: [any Codable] { - let resolvedPath = path.wrappedValue.path( - destinationTypes: destinationTypes - ) - return [NavigationStackRootPath()] + resolvedPath - } - - /// Creates a navigation stack with heterogeneous navigation state that you can control. - /// - Parameters: - /// - path: A `Binding` to the navigation state for this stack. - /// - root: The view to display when the stack is empty. - public init( - path: Binding, - @ViewBuilder _ root: @escaping () -> Detail - ) { - self.path = path - destinationTypes = [] - child = { element in - if element is NavigationStackRootPath { - return root() - } else { - return nil - } - } - } - - /// 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. - /// - 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. - public func navigationDestination( - for data: D.Type, - @ViewBuilder destination: @escaping (D) -> C - ) -> NavigationStack> { - // Adds another detail view by adding to the recursive structure of either views created - // to display details in a type-safe manner. See NavigationStack.child for details. - return NavigationStack>( - previous: self, - destination: destination - ) - } - - /// Add a destination for a specific path element (by adding another layer of ``EitherView``). - private init( - previous: NavigationStack, - destination: @escaping (Component) -> NewDetail? - ) where Detail == EitherView { - path = previous.path - destinationTypes = previous.destinationTypes + [Component.self] - child = { - if let previous = previous.child($0) { - // Either root or previously defined destination returned a view - return EitherView(previous) - } else if let component = $0 as? Component, let new = destination(component) { - // This destination returned a detail view for the current element - return EitherView(new) - } else { - // Possibly a future .navigationDestination will handle this path element - return nil - } - } - } - - /// 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 { - guard let child = child(element) else { - fatalError( - "Failed to find detail view for \"\(element)\", make sure you have called .navigationDestination for this type." - ) - } - - return child - } -} diff --git a/Sources/SwiftCrossUI/Views/OptionalView.swift b/Sources/SwiftCrossUI/Views/OptionalView.swift index 7b9590b22b..c39dfcbd6b 100644 --- a/Sources/SwiftCrossUI/Views/OptionalView.swift +++ b/Sources/SwiftCrossUI/Views/OptionalView.swift @@ -42,7 +42,7 @@ extension OptionalView: TypeSafeView { func computeLayout( _ widget: Backend.Widget, children: OptionalViewChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -73,7 +73,7 @@ extension OptionalView: TypeSafeView { } else { hasToggled = children.node != nil children.node = nil - result = ViewLayoutResult.leafView(size: .hidden) + result = ViewLayoutResult.leafView(size: .zero) } children.hasToggled = children.hasToggled || hasToggled @@ -98,7 +98,7 @@ extension OptionalView: TypeSafeView { _ = children.node?.commit() - backend.setSize(of: widget, to: layout.size.size) + backend.setSize(of: widget, to: layout.size.vector) } } diff --git a/Sources/SwiftCrossUI/Views/Picker.swift b/Sources/SwiftCrossUI/Views/Picker.swift deleted file mode 100644 index 9cfa8a4ea6..0000000000 --- a/Sources/SwiftCrossUI/Views/Picker.swift +++ /dev/null @@ -1,77 +0,0 @@ -/// A control for selecting from a set of values. -public struct Picker: ElementaryView, View { - /// The options to be offered by the picker. - private var options: [Value] - /// The picker's selected option. - private var value: Binding - - /// The index of the selected option (if any). - private var selectedOptionIndex: Int? { - return options.firstIndex { option in - return option == value.wrappedValue - } - } - - /// Creates a new picker with the given options and a binding for the selected value. - public init(of options: [Value], selection value: Binding) { - self.options = options - self.value = value - } - - func asWidget(backend: Backend) -> Backend.Widget { - return backend.createPicker() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - // TODO: Implement picker sizing within SwiftCrossUI so that we can - // properly separate committing logic out into `commit`. - backend.updatePicker( - widget, - options: options.map { "\($0)" }, - environment: environment - ) { - selectedIndex in - guard let selectedIndex = selectedIndex else { - value.wrappedValue = nil - return - } - value.wrappedValue = options[selectedIndex] - } - backend.setSelectedOption(ofPicker: widget, to: selectedOptionIndex) - - // Special handling for UIKitBackend: - // When backed by a UITableView, its natural size is -1 x -1, - // but it can and should be as large as reasonable - let size = backend.naturalSize(of: widget) - if size == SIMD2(-1, -1) { - return ViewLayoutResult.leafView( - size: ViewSize( - size: proposedSize, - idealSize: SIMD2(10, 10), - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: nil, - maximumHeight: nil - ) - ) - } else { - return ViewLayoutResult.leafView( - size: ViewSize(fixedSize: size) - ) - } - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.setSize(of: widget, to: layout.size.size) - } -} diff --git a/Sources/SwiftCrossUI/Views/ProgressView.swift b/Sources/SwiftCrossUI/Views/ProgressView.swift deleted file mode 100644 index c0a44d608a..0000000000 --- a/Sources/SwiftCrossUI/Views/ProgressView.swift +++ /dev/null @@ -1,173 +0,0 @@ -import Foundation - -public struct ProgressView: View { - private var label: Label - private var progress: Double? - private var kind: Kind - - private enum Kind { - case spinner - case bar - } - - public var body: some View { - if label as? EmptyView == nil { - progressIndicator - label - } else { - progressIndicator - } - } - - @ViewBuilder - private var progressIndicator: some View { - switch kind { - case .spinner: - ProgressSpinnerView() - case .bar: - ProgressBarView(value: progress) - } - } - - public init(_ label: Label) { - self.label = label - self.kind = .spinner - } - - public init(_ label: Label, _ progress: Progress) { - self.label = label - self.kind = .bar - - if !progress.isIndeterminate { - self.progress = progress.fractionCompleted - } - } - - /// Creates a progress bar view. If `value` is `nil`, an indeterminate progress - /// bar will be shown. - public init(_ label: Label, value: Value?) { - self.label = label - self.kind = .bar - self.progress = value.map(Double.init) - } -} - -extension ProgressView where Label == EmptyView { - public init() { - self.label = EmptyView() - self.kind = .spinner - } - - public init(_ progress: Progress) { - self.label = EmptyView() - self.kind = .bar - - if !progress.isIndeterminate { - self.progress = progress.fractionCompleted - } - } - - /// Creates a progress bar view. If `value` is `nil`, an indeterminate progress - /// bar will be shown. - public init(value: Value?) { - self.label = EmptyView() - self.kind = .bar - self.progress = value.map(Double.init) - } -} - -extension ProgressView where Label == Text { - public init(_ label: String) { - self.label = Text(label) - self.kind = .spinner - } - - public init(_ label: String, _ progress: Progress) { - self.label = Text(label) - self.kind = .bar - - if !progress.isIndeterminate { - self.progress = progress.fractionCompleted - } - } - - /// Creates a progress bar view. If `value` is `nil`, an indeterminate progress - /// bar will be shown. - public init(_ label: String, value: Value?) { - self.label = Text(label) - self.kind = .bar - self.progress = value.map(Double.init) - } -} - -struct ProgressSpinnerView: ElementaryView { - init() {} - - func asWidget(backend: Backend) -> Backend.Widget { - backend.createProgressSpinner() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - ViewLayoutResult.leafView( - size: ViewSize(fixedSize: backend.naturalSize(of: widget)) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) {} -} - -struct ProgressBarView: ElementaryView { - var value: Double? - - init(value: Double?) { - self.value = value - } - - func asWidget(backend: Backend) -> Backend.Widget { - backend.createProgressBar() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let height = backend.naturalSize(of: widget).y - let size = SIMD2( - proposedSize.x, - height - ) - - return ViewLayoutResult.leafView( - size: ViewSize( - size: size, - idealSize: SIMD2(100, height), - minimumWidth: 0, - minimumHeight: height, - maximumWidth: nil, - maximumHeight: Double(height) - ) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.updateProgressBar(widget, progressFraction: value, environment: environment) - backend.setSize(of: widget, to: layout.size.size) - } -} diff --git a/Sources/SwiftCrossUI/Views/ScrollView.swift b/Sources/SwiftCrossUI/Views/ScrollView.swift index c8178b893b..af581480ed 100644 --- a/Sources/SwiftCrossUI/Views/ScrollView.swift +++ b/Sources/SwiftCrossUI/Views/ScrollView.swift @@ -46,89 +46,92 @@ public struct ScrollView: TypeSafeView, View { func computeLayout( _ widget: Backend.Widget, children: ScrollViewChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { // Probe how big the child would like to be + var childProposal = proposedSize + for axis in Axis.allCases where axes.contains(axis) { + childProposal[component: axis] = nil + } let childResult = children.child.computeLayout( with: body, - proposedSize: proposedSize, + proposedSize: childProposal, environment: environment ) - let contentSize = childResult.size - - let scrollBarWidth = backend.scrollBarWidth - let hasHorizontalScrollBar = - axes.contains(.horizontal) && contentSize.idealWidthForProposedHeight > proposedSize.x - let hasVerticalScrollBar = - axes.contains(.vertical) && contentSize.idealHeightForProposedWidth > proposedSize.y - children.hasHorizontalScrollBar = hasHorizontalScrollBar - children.hasVerticalScrollBar = hasVerticalScrollBar + // If all scroll axes are unspecified, then our size is exactly that of + // the child view. This includes when we have no scroll axes. + if Axis.allCases.allSatisfy({ axis in + !axes.contains(axis) || proposedSize[component: axis] == nil + }) { + return childResult + } - let verticalScrollBarWidth = hasVerticalScrollBar ? scrollBarWidth : 0 - let horizontalScrollBarHeight = hasHorizontalScrollBar ? scrollBarWidth : 0 + let contentSize = childResult.size - let scrollViewWidth: Int - let scrollViewHeight: Int - let minimumWidth: Int - let minimumHeight: Int - if axes.contains(.horizontal) { - scrollViewWidth = max(proposedSize.x, verticalScrollBarWidth) - minimumWidth = verticalScrollBarWidth + // An axis is present when its a scroll axis AND the corresponding + // child content size is bigger then the proposed size. If the proposed + // size along the axis is nil then we don't have a scroll bar. + let hasHorizontalScrollBar: Bool + if axes.contains(.horizontal), let proposedWidth = proposedSize.width { + hasHorizontalScrollBar = contentSize.width > proposedWidth } else { - scrollViewWidth = min( - contentSize.size.x + verticalScrollBarWidth, - max(proposedSize.x, contentSize.minimumWidth + verticalScrollBarWidth) - ) - minimumWidth = contentSize.minimumWidth + verticalScrollBarWidth + hasHorizontalScrollBar = false } - if axes.contains(.vertical) { - scrollViewHeight = max(proposedSize.y, horizontalScrollBarHeight) - minimumHeight = horizontalScrollBarHeight + + let hasVerticalScrollBar: Bool + if axes.contains(.vertical), let proposedHeight = proposedSize.height { + hasVerticalScrollBar = contentSize.height > proposedHeight } else { - scrollViewHeight = min( - contentSize.size.y + horizontalScrollBarHeight, - max(proposedSize.y, contentSize.minimumHeight + horizontalScrollBarHeight) - ) - minimumHeight = contentSize.minimumHeight + horizontalScrollBarHeight + hasVerticalScrollBar = false } - let scrollViewSize = SIMD2( - scrollViewWidth, - scrollViewHeight - ) + let scrollBarWidth = Double(backend.scrollBarWidth) + let verticalScrollBarWidth = hasVerticalScrollBar ? scrollBarWidth : 0 + let horizontalScrollBarHeight = hasHorizontalScrollBar ? scrollBarWidth : 0 - // TODO: scroll bar presence shouldn't affect whether we use current - // or ideal size. Only the presence of the given axis in the user's - // list of scroll axes should affect that. - let proposedContentSize = SIMD2( - hasHorizontalScrollBar - ? (hasVerticalScrollBar - ? contentSize.idealSize.x : contentSize.idealWidthForProposedHeight) - : min(contentSize.size.x, proposedSize.x - verticalScrollBarWidth), - hasVerticalScrollBar - ? (hasHorizontalScrollBar - ? contentSize.idealSize.y : contentSize.idealHeightForProposedWidth) - : min(contentSize.size.y, proposedSize.y - horizontalScrollBarHeight) - ) + // Compute the final size to propose to the child view. Subtract off + // scroll bar sizes from non-scrolling axes. + var finalContentSizeProposal = childProposal + if !axes.contains(.horizontal), let proposedWidth = childProposal.width { + finalContentSizeProposal.width = proposedWidth - verticalScrollBarWidth + } + if !axes.contains(.vertical), let proposedHeight = childProposal.height { + finalContentSizeProposal.height = proposedHeight - horizontalScrollBarHeight + } + + // Propose a final size to the child view. let finalChildResult = children.child.computeLayout( - with: body, - proposedSize: proposedContentSize, + with: nil, + proposedSize: finalContentSizeProposal, environment: environment ) + // Compute the outer size. + var outerSize = finalChildResult.size + if axes.contains(.horizontal) { + outerSize.width = max( + finalChildResult.size.width + verticalScrollBarWidth, + proposedSize.width ?? 0 + ) + } else { + outerSize.width += verticalScrollBarWidth + } + + if axes.contains(.vertical) { + outerSize.height = max( + finalChildResult.size.height + horizontalScrollBarHeight, + proposedSize.height ?? 0 + ) + } else { + outerSize.height += horizontalScrollBarHeight + } + return ViewLayoutResult( - size: ViewSize( - size: scrollViewSize, - idealSize: contentSize.idealSize, - minimumWidth: minimumWidth, - minimumHeight: minimumHeight, - maximumWidth: nil, - maximumHeight: nil - ), + size: outerSize, childResults: [finalChildResult] ) } @@ -140,31 +143,12 @@ public struct ScrollView: TypeSafeView, View { environment: EnvironmentValues, backend: Backend ) { - let scrollViewSize = layout.size.size + let scrollViewSize = layout.size let finalContentSize = children.child.commit().size - let verticalScrollBarWidth = - children.hasVerticalScrollBar - ? backend.scrollBarWidth : 0 - let horizontalScrollBarHeight = - children.hasHorizontalScrollBar - ? backend.scrollBarWidth : 0 - let clipViewWidth = scrollViewSize.x - verticalScrollBarWidth - let clipViewHeight = scrollViewSize.y - horizontalScrollBarHeight - var childPosition: SIMD2 = .zero - var innerContainerSize: SIMD2 = finalContentSize.size - if axes.contains(.vertical) && finalContentSize.size.x < clipViewWidth { - childPosition.x = (clipViewWidth - finalContentSize.size.x) / 2 - innerContainerSize.x = clipViewWidth - } - if axes.contains(.horizontal) && finalContentSize.size.y < clipViewHeight { - childPosition.y = (clipViewHeight - finalContentSize.size.y) / 2 - innerContainerSize.y = clipViewHeight - } - - backend.setSize(of: widget, to: scrollViewSize) - backend.setSize(of: children.innerContainer.into(), to: innerContainerSize) - backend.setPosition(ofChildAt: 0, in: children.innerContainer.into(), to: childPosition) + backend.setSize(of: widget, to: scrollViewSize.vector) + backend.setSize(of: children.innerContainer.into(), to: finalContentSize.vector) + backend.setPosition(ofChildAt: 0, in: children.innerContainer.into(), to: .zero) backend.setScrollBarPresence( ofScrollContainer: widget, hasVerticalScrollBar: children.hasVerticalScrollBar, diff --git a/Sources/SwiftCrossUI/Views/Shapes/Capsule.swift b/Sources/SwiftCrossUI/Views/Shapes/Capsule.swift deleted file mode 100644 index cdc02e05a1..0000000000 --- a/Sources/SwiftCrossUI/Views/Shapes/Capsule.swift +++ /dev/null @@ -1,9 +0,0 @@ -/// A rounded rectangle whose corner radius is equal to half the length of its shortest side. -public struct Capsule: Shape { - public nonisolated init() {} - - public nonisolated func path(in bounds: Path.Rect) -> Path { - let radius = min(bounds.width, bounds.height) / 2.0 - return RoundedRectangle(cornerRadius: radius).path(in: bounds) - } -} diff --git a/Sources/SwiftCrossUI/Views/Shapes/Circle.swift b/Sources/SwiftCrossUI/Views/Shapes/Circle.swift deleted file mode 100644 index 4eb1cfba9f..0000000000 --- a/Sources/SwiftCrossUI/Views/Shapes/Circle.swift +++ /dev/null @@ -1,23 +0,0 @@ -public struct Circle: Shape { - public nonisolated init() {} - - public nonisolated func path(in bounds: Path.Rect) -> Path { - Path() - .addCircle(center: bounds.center, radius: min(bounds.width, bounds.height) / 2.0) - } - - public nonisolated func size(fitting proposal: SIMD2) -> ViewSize { - let diameter = min(proposal.x, proposal.y) - - return ViewSize( - size: SIMD2(x: diameter, y: diameter), - idealSize: SIMD2(x: 10, y: 10), - idealWidthForProposedHeight: proposal.y, - idealHeightForProposedWidth: proposal.x, - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: nil, - maximumHeight: nil - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift b/Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift deleted file mode 100644 index eb6c6794ea..0000000000 --- a/Sources/SwiftCrossUI/Views/Shapes/Ellipse.swift +++ /dev/null @@ -1,19 +0,0 @@ -public struct Ellipse: Shape { - public nonisolated init() {} - - public nonisolated func path(in bounds: Path.Rect) -> Path { - Path() - .addCircle(center: .zero, radius: bounds.width / 2.0) - .applyTransform( - AffineTransform( - linearTransform: SIMD4( - x: 1.0, - y: 0.0, - z: 0.0, - w: bounds.height / bounds.width - ), - translation: bounds.center - ) - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift b/Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift deleted file mode 100644 index 21ce292fca..0000000000 --- a/Sources/SwiftCrossUI/Views/Shapes/Rectangle.swift +++ /dev/null @@ -1,7 +0,0 @@ -public struct Rectangle: Shape { - public nonisolated init() {} - - public nonisolated func path(in bounds: Path.Rect) -> Path { - Path().addRectangle(bounds) - } -} diff --git a/Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift b/Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift deleted file mode 100644 index 285249bcbc..0000000000 --- a/Sources/SwiftCrossUI/Views/Shapes/RoundedRectangle.swift +++ /dev/null @@ -1,449 +0,0 @@ -/// 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. -public struct RoundedRectangle { - public var cornerRadius: Double - - public init(cornerRadius: Double) { - assert( - cornerRadius >= 0.0 && cornerRadius.isFinite, - "Corner radius must be a positive finite value") - 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 - fileprivate static let points = ( - SIMD2(0.292893218813, 0.292893218813), - SIMD2(0.517, 0.0687864376269), - SIMD2(0.87, 0.0337), - SIMD2(1.13130356636, 0.0139677719414), - SIMD2(1.1973, 0.0089), - SIMD2(1.5038, 0.0002), - 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. - fileprivate static let rMin = 0.441968022436 -} - -extension RoundedRectangle: Shape { - public func path(in bounds: Path.Rect) -> Path { - // just to avoid `RoundedRectangle.` qualifiers - let rMin = RoundedRectangle.rMin - let points = RoundedRectangle.points - - let effectiveRadius = min(cornerRadius, bounds.width / 2.0, bounds.height / 2.0) - let xRatio = effectiveRadius / (bounds.width / 2.0) - let yRatio = effectiveRadius / (bounds.height / 2.0) - - // MARK: Early exits - // These code paths are guaranteed to not use the approximations of the quintic curves. - - // Optimization: just a circle - if bounds.width == bounds.height && bounds.width <= cornerRadius * 2.0 { - return Circle().path(in: bounds) - } - - // Optimization: just a rectangle - if effectiveRadius == 0.0 { - return Rectangle().path(in: bounds) - } - - // Optimization: corner radius is too large to use quintic curves - if xRatio >= rMin && yRatio >= rMin { - return Path() - .move(to: SIMD2(x: bounds.x + effectiveRadius, y: bounds.y)) - .addLine(to: SIMD2(x: bounds.maxX - effectiveRadius, y: bounds.y)) - .addArc( - center: SIMD2(x: bounds.maxX - effectiveRadius, y: bounds.y + effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 1.5, - endAngle: 0.0, - clockwise: true - ) - .addLine(to: SIMD2(x: bounds.maxX, y: bounds.maxY - effectiveRadius)) - .addArc( - center: SIMD2( - x: bounds.maxX - effectiveRadius, y: bounds.maxY - effectiveRadius), - radius: effectiveRadius, - startAngle: 0.0, - endAngle: .pi * 0.5, - clockwise: true - ) - .addLine(to: SIMD2(x: bounds.x + effectiveRadius, y: bounds.maxY)) - .addArc( - center: SIMD2(x: bounds.x + effectiveRadius, y: bounds.maxY - effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 0.5, - endAngle: .pi, - clockwise: true - ) - .addLine(to: SIMD2(x: bounds.x, y: bounds.y + effectiveRadius)) - .addArc( - center: SIMD2(x: bounds.x + effectiveRadius, y: bounds.y + effectiveRadius), - radius: effectiveRadius, - startAngle: .pi, - endAngle: .pi * 1.5, - clockwise: true - ) - } - - return Path() - // MARK: Top edge, right side - .move(to: SIMD2(x: bounds.center.x, y: bounds.y)) - .if(xRatio >= rMin) { - $0 - .addLine(to: SIMD2(x: bounds.maxX - effectiveRadius, y: bounds.y)) - .addArc( - center: SIMD2( - x: bounds.maxX - effectiveRadius, y: bounds.y + effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 1.5, - endAngle: .pi * 1.75, - clockwise: true - ) - } else: { - $0 - .addLine( - to: SIMD2( - x: bounds.maxX - points.6.x * effectiveRadius, - y: bounds.y + points.6.y * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.5.x * effectiveRadius, - y: bounds.y + points.5.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.4.x * effectiveRadius, - y: bounds.y + points.4.y * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.3.x * effectiveRadius, - y: bounds.y + points.3.y * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.2.x * effectiveRadius, - y: bounds.y + points.2.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.1.x * effectiveRadius, - y: bounds.y + points.1.y * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.0.x * effectiveRadius, - y: bounds.y + points.0.y * effectiveRadius - ) - ) - } - // MARK: Right edge - .if(yRatio >= rMin) { - $0 - .addArc( - center: SIMD2( - x: bounds.maxX - effectiveRadius, y: bounds.y + effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 1.75, - endAngle: 0.0, - clockwise: true - ) - .addLine(to: SIMD2(x: bounds.maxX, y: bounds.maxY - effectiveRadius)) - .addArc( - center: SIMD2( - x: bounds.maxX - effectiveRadius, y: bounds.maxY - effectiveRadius), - radius: effectiveRadius, - startAngle: 0.0, - endAngle: .pi * 0.25, - clockwise: true - ) - } else: { - $0 - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.1.y * effectiveRadius, - y: bounds.y + points.1.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.2.y * effectiveRadius, - y: bounds.y + points.2.x * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.3.y * effectiveRadius, - y: bounds.y + points.3.x * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.4.y * effectiveRadius, - y: bounds.y + points.4.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.5.y * effectiveRadius, - y: bounds.y + points.5.x * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.6.y * effectiveRadius, - y: bounds.y + points.6.x * effectiveRadius - ) - ) - .addLine( - to: SIMD2( - x: bounds.maxX - points.6.y * effectiveRadius, - y: bounds.maxY - points.6.x * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.5.y * effectiveRadius, - y: bounds.maxY - points.5.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.4.y * effectiveRadius, - y: bounds.maxY - points.4.x * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.3.y * effectiveRadius, - y: bounds.maxY - points.3.x * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.2.y * effectiveRadius, - y: bounds.maxY - points.2.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.1.y * effectiveRadius, - y: bounds.maxY - points.1.x * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.0.y * effectiveRadius, - y: bounds.maxY - points.0.x * effectiveRadius - ) - ) - } - // MARK: Bottom edge - .if(xRatio >= rMin) { - $0 - .addArc( - center: SIMD2( - x: bounds.maxX - effectiveRadius, y: bounds.maxY - effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 0.25, - endAngle: .pi * 0.5, - clockwise: true - ) - .addLine(to: SIMD2(x: bounds.x + effectiveRadius, y: bounds.maxY)) - .addArc( - center: SIMD2( - x: bounds.x + effectiveRadius, y: bounds.maxY - effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 0.5, - endAngle: .pi * 0.75, - clockwise: true - ) - } else: { - $0 - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.1.x * effectiveRadius, - y: bounds.maxY - points.1.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.2.x * effectiveRadius, - y: bounds.maxY - points.2.y * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.3.x * effectiveRadius, - y: bounds.maxY - points.3.y * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.maxX - points.4.x * effectiveRadius, - y: bounds.maxY - points.4.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.maxX - points.5.x * effectiveRadius, - y: bounds.maxY - points.5.y * effectiveRadius - ), - to: SIMD2( - x: bounds.maxX - points.6.x * effectiveRadius, - y: bounds.maxY - points.6.y * effectiveRadius - ) - ) - .addLine( - to: SIMD2( - x: bounds.x + points.6.x * effectiveRadius, - y: bounds.maxY - points.6.y * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.5.x * effectiveRadius, - y: bounds.maxY - points.5.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.4.x * effectiveRadius, - y: bounds.maxY - points.4.y * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.3.x * effectiveRadius, - y: bounds.maxY - points.3.y * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.2.x * effectiveRadius, - y: bounds.maxY - points.2.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.1.x * effectiveRadius, - y: bounds.maxY - points.1.y * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.0.x * effectiveRadius, - y: bounds.maxY - points.0.y * effectiveRadius - ) - ) - } - // MARK: Left edge - .if(yRatio >= rMin) { - $0 - .addArc( - center: SIMD2( - x: bounds.x + effectiveRadius, y: bounds.maxY - effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 0.75, - endAngle: .pi, - clockwise: true - ) - .addLine(to: SIMD2(x: bounds.x, y: bounds.y + effectiveRadius)) - .addArc( - center: SIMD2(x: bounds.x + effectiveRadius, y: bounds.y + effectiveRadius), - radius: effectiveRadius, - startAngle: .pi, - endAngle: .pi * 1.25, - clockwise: true - ) - } else: { - $0 - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.1.y * effectiveRadius, - y: bounds.maxY - points.1.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.2.y * effectiveRadius, - y: bounds.maxY - points.2.x * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.3.y * effectiveRadius, - y: bounds.maxY - points.3.x * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.4.y * effectiveRadius, - y: bounds.maxY - points.4.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.5.y * effectiveRadius, - y: bounds.maxY - points.5.x * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.6.y * effectiveRadius, - y: bounds.maxY - points.6.x * effectiveRadius - ) - ) - .addLine( - to: SIMD2( - x: bounds.x + points.6.y * effectiveRadius, - y: bounds.y + points.6.x * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.5.y * effectiveRadius, - y: bounds.y + points.5.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.4.y * effectiveRadius, - y: bounds.y + points.4.x * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.3.y * effectiveRadius, - y: bounds.y + points.3.x * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.2.y * effectiveRadius, - y: bounds.y + points.2.x * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.1.y * effectiveRadius, - y: bounds.y + points.1.x * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.0.y * effectiveRadius, - y: bounds.y + points.0.x * effectiveRadius - ) - ) - } - // MARK: Top edge, left side - .if(xRatio >= rMin) { - $0 - .addArc( - center: SIMD2(x: bounds.x + effectiveRadius, y: bounds.y + effectiveRadius), - radius: effectiveRadius, - startAngle: .pi * 1.25, - endAngle: .pi * 1.5, - clockwise: true - ) - } else: { - $0 - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.1.x * effectiveRadius, - y: bounds.y + points.1.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.2.x * effectiveRadius, - y: bounds.y + points.2.y * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.3.x * effectiveRadius, - y: bounds.y + points.3.y * effectiveRadius - ) - ) - .addCubicCurve( - control1: SIMD2( - x: bounds.x + points.4.x * effectiveRadius, - y: bounds.y + points.4.y * effectiveRadius - ), - control2: SIMD2( - x: bounds.x + points.5.x * effectiveRadius, - y: bounds.y + points.5.y * effectiveRadius - ), - to: SIMD2( - x: bounds.x + points.6.x * effectiveRadius, - y: bounds.y + points.6.y * effectiveRadius - ) - ) - } - .addLine(to: SIMD2(x: bounds.center.x, y: bounds.y)) - } -} diff --git a/Sources/SwiftCrossUI/Views/Shapes/Shape.swift b/Sources/SwiftCrossUI/Views/Shapes/Shape.swift deleted file mode 100644 index cb90753e4c..0000000000 --- a/Sources/SwiftCrossUI/Views/Shapes/Shape.swift +++ /dev/null @@ -1,141 +0,0 @@ -/// A 2-D 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. -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: - /// ```swift - /// func path(in bounds: Path.Rect) -> Path { - /// Path() - /// .addSubpath( - /// Rectangle().path( - /// in: Path.Rect( - /// x: bounds.x, - /// y: bounds.y, - /// width: bounds.width / 2.0, - /// height: bounds.height - /// ) - /// ) - /// ) - /// .addSubpath( - /// Ellipse().path( - /// in: Path.Rect( - /// x: bounds.center.x, - /// y: bounds.y, - /// width: bounds.width / 2.0, - /// height: bounds.height - /// ) - /// ) - /// ) - /// } - /// ``` - func path(in bounds: Path.Rect) -> Path - /// Determine the ideal size of this shape given the proposed bounds. - /// - /// The default implementation accepts the proposal and imposes no practical limit on - /// the shape's size. - /// - Returns: Information about the shape's size. The ``ViewSize/size`` property is what - /// frame the shape 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 shape can be. The ``ViewSize/idealSize`` property should not vary with - /// the `proposal`, and should only depend on the shape's contents. Pass `nil` for the - /// maximum width/height if the shape has no maximum size. - func size(fitting proposal: SIMD2) -> ViewSize -} - -extension Shape { - public var body: EmptyView { return EmptyView() } - - public func size(fitting proposal: SIMD2) -> ViewSize { - return ViewSize( - size: proposal, - idealSize: SIMD2(x: 10, y: 10), - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: nil, - maximumHeight: nil - ) - } - - @MainActor - public func children( - backend _: Backend, - snapshots _: [ViewGraphSnapshotter.NodeSnapshot]?, - environment _: EnvironmentValues - ) -> any ViewGraphNodeChildren { - ShapeStorage() - } - - @MainActor - public func asWidget( - _ children: any ViewGraphNodeChildren, backend: Backend - ) -> Backend.Widget { - let container = backend.createPathWidget() - let storage = children as! ShapeStorage - storage.backendPath = backend.createPath() - storage.oldPath = nil - return container - } - - @MainActor - public func computeLayout( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let size = size(fitting: proposedSize) - return ViewLayoutResult.leafView(size: size) - } - - @MainActor - public func commit( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - let bounds = Path.Rect( - x: 0.0, - y: 0.0, - width: Double(layout.size.size.x), - height: Double(layout.size.size.y) - ) - let path = path(in: bounds) - - let storage = children as! ShapeStorage - let pointsChanged = storage.oldPath?.actions != path.actions - storage.oldPath = path - - let backendPath = storage.backendPath as! Backend.Path - backend.updatePath( - backendPath, - path, - bounds: bounds, - pointsChanged: pointsChanged, - environment: environment - ) - - backend.setSize(of: widget, to: layout.size.size) - backend.renderPath( - backendPath, - container: widget, - strokeColor: .clear, - fillColor: environment.suggestedForegroundColor, - overrideStrokeStyle: nil - ) - } -} - -final class ShapeStorage: ViewGraphNodeChildren { - let widgets: [AnyWidget] = [] - let erasedNodes: [ErasedViewGraphNode] = [] - var backendPath: Any! - var oldPath: Path? -} diff --git a/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift b/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift deleted file mode 100644 index 7850cbff99..0000000000 --- a/Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift +++ /dev/null @@ -1,105 +0,0 @@ -/// A shape that has style information attached to it, including color and stroke style. -public protocol StyledShape: Shape { - var strokeColor: Color? { get } - var fillColor: Color? { get } - var strokeStyle: StrokeStyle? { get } -} - -struct StyledShapeImpl: Sendable { - var base: Base - var strokeColor: Color? - var fillColor: Color? - var strokeStyle: StrokeStyle? - - init( - base: Base, - strokeColor: Color? = nil, - fillColor: Color? = nil, - strokeStyle: StrokeStyle? = nil - ) { - self.base = base - - if let styledBase = base as? any StyledShape { - self.strokeColor = strokeColor ?? styledBase.strokeColor - self.fillColor = fillColor ?? styledBase.fillColor - self.strokeStyle = strokeStyle ?? styledBase.strokeStyle - } else { - self.strokeColor = strokeColor - self.fillColor = fillColor - self.strokeStyle = strokeStyle - } - } -} - -extension StyledShapeImpl: StyledShape { - func path(in bounds: Path.Rect) -> Path { - return base.path(in: bounds) - } - - func size(fitting proposal: SIMD2) -> ViewSize { - return base.size(fitting: proposal) - } -} - -extension Shape { - public func fill(_ color: Color) -> some StyledShape { - StyledShapeImpl(base: self, fillColor: color) - } - - public func stroke(_ color: Color, style: StrokeStyle? = nil) -> some StyledShape { - StyledShapeImpl(base: self, strokeColor: color, strokeStyle: style) - } -} - -extension StyledShape { - @MainActor - public func computeLayout( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let size = size(fitting: proposedSize) - return ViewLayoutResult.leafView(size: size) - } - - @MainActor - public func commit( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - let bounds = Path.Rect( - x: 0.0, - y: 0.0, - width: Double(layout.size.size.x), - height: Double(layout.size.size.y) - ) - let path = path(in: bounds) - - let storage = children as! ShapeStorage - let pointsChanged = storage.oldPath?.actions != path.actions - storage.oldPath = path - - let backendPath = storage.backendPath as! Backend.Path - backend.updatePath( - backendPath, - path, - bounds: bounds, - pointsChanged: pointsChanged, - environment: environment - ) - - backend.setSize(of: widget, to: layout.size.size) - backend.renderPath( - backendPath, - container: widget, - strokeColor: strokeColor ?? .clear, - fillColor: fillColor ?? .clear, - overrideStrokeStyle: strokeStyle - ) - } -} diff --git a/Sources/SwiftCrossUI/Views/Slider.swift b/Sources/SwiftCrossUI/Views/Slider.swift deleted file mode 100644 index f06ee294ad..0000000000 --- a/Sources/SwiftCrossUI/Views/Slider.swift +++ /dev/null @@ -1,141 +0,0 @@ -/// A value convertible to and from a ``Double``.` -public protocol DoubleConvertible { - /// Creates a value from a ``Double``.` - init(_ value: Double) - - /// Converts the value to a ``Double``.` - var doubleRepresentation: Double { get } -} - -/// A value represented by a ``BinaryFloatingPoint``. -struct FloatingPointValue: DoubleConvertible { - var value: Value - - init(_ value: Value) { - self.value = value - } - - init(_ value: Double) { - self.value = Value(value) - } - - var doubleRepresentation: Double { - return Double(value) - } -} - -/// A value represented by a ``BinaryInteger``. -struct IntegerValue: DoubleConvertible { - var value: Value - - init(_ value: Value) { - self.value = value - } - - init(_ value: Double) { - self.value = Value(value) - } - - var doubleRepresentation: Double { - return Double(value) - } -} - -/// A control for selecting a value from a bounded range of numerical values. -public struct Slider: ElementaryView, View { - /// A binding to the current value. - private var value: Binding? - /// The slider's minimum value. - private var minimum: Double - /// The slider's maximum value. - private var maximum: Double - /// The number of decimal places used when displaying the value. - private var decimalPlaces: Int - - /// Creates a slider to select a value between a minimum and maximum value. - public init(_ value: Binding? = nil, minimum: T, maximum: T) { - if let value = value { - self.value = Binding( - get: { - return Double(value.wrappedValue) - }, - set: { newValue in - value.wrappedValue = T(newValue.rounded()) - } - ) - } - self.minimum = Double(minimum) - self.maximum = Double(maximum) - decimalPlaces = 0 - } - - /// Creates a slider to select a value between a minimum and maximum value. - public init(_ value: Binding? = nil, minimum: T, maximum: T) { - if let value = value { - self.value = Binding( - get: { - return Double(value.wrappedValue) - }, - set: { newValue in - value.wrappedValue = T(newValue) - } - ) - } - self.minimum = Double(minimum) - self.maximum = Double(maximum) - decimalPlaces = 2 - } - - func asWidget(backend: Backend) -> Backend.Widget { - return backend.createSlider() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - // TODO: Don't rely on naturalSize for minimum size so that we can get - // Slider sizes without relying on the widget. - let naturalSize = backend.naturalSize(of: widget) - let size = SIMD2(proposedSize.x, naturalSize.y) - - // TODO: Allow backends to specify their own ideal slider widths. - return ViewLayoutResult.leafView( - size: ViewSize( - size: size, - idealSize: SIMD2(100, naturalSize.y), - minimumWidth: naturalSize.x, - minimumHeight: naturalSize.y, - maximumWidth: nil, - maximumHeight: Double(naturalSize.y) - ) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.updateSlider( - widget, - minimum: minimum, - maximum: maximum, - decimalPlaces: decimalPlaces, - environment: environment - ) { newValue in - if let value { - value.wrappedValue = newValue - } - } - - if let value = value?.wrappedValue { - backend.setValue(ofSlider: widget, to: value) - } - - backend.setSize(of: widget, to: layout.size.size) - } -} diff --git a/Sources/SwiftCrossUI/Views/Spacer.swift b/Sources/SwiftCrossUI/Views/Spacer.swift index 91765a2ff0..f0a22ce01a 100644 --- a/Sources/SwiftCrossUI/Views/Spacer.swift +++ b/Sources/SwiftCrossUI/Views/Spacer.swift @@ -1,6 +1,9 @@ /// A flexible space that expands along the major axis of its containing /// stack layout, or on both axes if not contained in a stack. public struct Spacer: ElementaryView, View { + /// The ideal length of a spacer. + static let idealLength: Double = 8 + /// The minimum length this spacer can be shrunk to, along the axis of /// expansion. package var minLength: Int? @@ -17,42 +20,18 @@ public struct Spacer: ElementaryView, View { func computeLayout( _ widget: Backend.Widget, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - let minLength = minLength ?? 0 - - let size: SIMD2 - let minimumWidth: Int - let minimumHeight: Int - let maximumWidth: Double? - let maximumHeight: Double? - switch environment.layoutOrientation { - case .horizontal: - size = SIMD2(max(minLength, proposedSize.x), 0) - minimumWidth = minLength - minimumHeight = 0 - maximumWidth = nil - maximumHeight = 0 - case .vertical: - size = SIMD2(0, max(minLength, proposedSize.y)) - minimumWidth = 0 - minimumHeight = minLength - maximumWidth = 0 - maximumHeight = nil - } - - return ViewLayoutResult.leafView( - size: ViewSize( - size: size, - idealSize: SIMD2(minimumWidth, minimumHeight), - minimumWidth: minimumWidth, - minimumHeight: minimumHeight, - maximumWidth: maximumWidth, - maximumHeight: maximumHeight - ) + var size = ViewSize.zero + let proposedLength = proposedSize[component: environment.layoutOrientation] + size[component: environment.layoutOrientation] = max( + Double(minLength ?? 0), + proposedLength ?? Self.idealLength ) + + return ViewLayoutResult.leafView(size: size) } func commit( diff --git a/Sources/SwiftCrossUI/Views/SplitView.swift b/Sources/SwiftCrossUI/Views/SplitView.swift deleted file mode 100644 index a0d70cbadb..0000000000 --- a/Sources/SwiftCrossUI/Views/SplitView.swift +++ /dev/null @@ -1,173 +0,0 @@ -import Foundation - -struct SplitView: TypeSafeView, View { - typealias Children = SplitViewChildren, Detail> - - var body: TupleView2, Detail> - - /// Creates a two column split view. - init(@ViewBuilder sidebar: () -> Sidebar, @ViewBuilder detail: () -> Detail) { - body = TupleView2( - EnvironmentModifier(sidebar()) { $0.with(\.listStyle, .sidebar) }, - detail() - ) - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> Children { - SplitViewChildren( - wrapping: body.children( - backend: backend, - snapshots: snapshots, - environment: environment - ), - backend: backend - ) - } - - func asWidget( - _ children: Children, - backend: Backend - ) -> Backend.Widget { - return backend.createSplitView( - leadingChild: children.leadingPaneContainer.into(), - trailingChild: children.trailingPaneContainer.into() - ) - } - - func computeLayout( - _ widget: Backend.Widget, - children: Children, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let leadingWidth = backend.sidebarWidth(ofSplitView: widget) - - // Update pane children - let leadingResult = children.leadingChild.computeLayout( - with: body.view0, - proposedSize: SIMD2( - leadingWidth, - proposedSize.y - ), - environment: environment - ) - let trailingResult = children.trailingChild.computeLayout( - with: body.view1, - proposedSize: SIMD2( - proposedSize.x - max(leadingWidth, leadingResult.size.minimumWidth), - proposedSize.y - ), - environment: environment - ) - - // Update split view size and sidebar width bounds - let leadingContentSize = leadingResult.size - let trailingContentSize = trailingResult.size - let size = SIMD2( - max(proposedSize.x, leadingContentSize.size.x + trailingContentSize.size.x), - max(proposedSize.y, max(leadingContentSize.size.y, trailingContentSize.size.y)) - ) - - return ViewLayoutResult( - size: ViewSize( - size: size, - idealSize: leadingContentSize.idealSize &+ trailingContentSize.idealSize, - minimumWidth: leadingContentSize.minimumWidth + trailingContentSize.minimumWidth, - minimumHeight: max( - leadingContentSize.minimumHeight, trailingContentSize.minimumHeight), - maximumWidth: nil, - maximumHeight: nil - ), - childResults: [leadingResult, trailingResult] - ) - } - - func commit( - _ widget: Backend.Widget, - children: Children, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.setResizeHandler(ofSplitView: widget) { - environment.onResize(.empty) - } - - let leadingWidth = backend.sidebarWidth(ofSplitView: widget) - let leadingResult = children.leadingChild.commit() - let trailingResult = children.trailingChild.commit() - - backend.setSize(of: widget, to: layout.size.size) - backend.setSidebarWidthBounds( - ofSplitView: widget, - minimum: leadingResult.size.minimumWidth, - maximum: max( - leadingResult.size.minimumWidth, - layout.size.size.x - trailingResult.size.minimumWidth - ) - ) - - // Center pane children - backend.setPosition( - ofChildAt: 0, - in: children.leadingPaneContainer.into(), - to: SIMD2( - leadingWidth - leadingResult.size.size.x, - layout.size.size.y - leadingResult.size.size.y - ) / 2 - ) - backend.setPosition( - ofChildAt: 0, - in: children.trailingPaneContainer.into(), - to: SIMD2( - layout.size.size.x - leadingWidth - trailingResult.size.size.x, - layout.size.size.y - trailingResult.size.size.y - ) / 2 - ) - } -} - -class SplitViewChildren: ViewGraphNodeChildren { - var paneChildren: TupleView2.Children - var leadingPaneContainer: AnyWidget - var trailingPaneContainer: AnyWidget - - init( - wrapping children: TupleView2.Children, - backend: Backend - ) { - self.paneChildren = children - - let leadingPaneContainer = backend.createContainer() - backend.addChild(paneChildren.child0.widget.into(), to: leadingPaneContainer) - let trailingPaneContainer = backend.createContainer() - backend.addChild(paneChildren.child1.widget.into(), to: trailingPaneContainer) - - self.leadingPaneContainer = AnyWidget(leadingPaneContainer) - self.trailingPaneContainer = AnyWidget(trailingPaneContainer) - } - - var erasedNodes: [ErasedViewGraphNode] { - paneChildren.erasedNodes - } - - var widgets: [AnyWidget] { - [ - leadingPaneContainer, - trailingPaneContainer, - ] - } - - var leadingChild: AnyViewGraphNode { - paneChildren.child0 - } - - var trailingChild: AnyViewGraphNode { - paneChildren.child1 - } -} diff --git a/Sources/SwiftCrossUI/Views/Table.swift b/Sources/SwiftCrossUI/Views/Table.swift deleted file mode 100644 index b18b7281be..0000000000 --- a/Sources/SwiftCrossUI/Views/Table.swift +++ /dev/null @@ -1,239 +0,0 @@ -/// A container that presents rows of data arranged in columns. -public struct Table>: TypeSafeView, View { - typealias Children = TableViewChildren - - public var body = EmptyView() - - /// The row data to display. - private var rows: [RowValue] - /// The columns to display (which each compute their cell values when given - /// ``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) { - self.rows = rows - self.columns = columns() - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> Children { - // TODO: Table snapshotting - TableViewChildren() - } - - func asWidget( - _ children: Children, - backend: Backend - ) -> Backend.Widget { - return backend.createTable() - } - - func computeLayout( - _ widget: Backend.Widget, - children: Children, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let size = proposedSize - var cellResults: [ViewLayoutResult] = [] - children.rowContent = rows.map(columns.content(for:)).map(RowView.init(_:)) - let columnLabels = columns.labels - let columnCount = columnLabels.count - - // Create and destroy row nodes - let remainder = children.rowContent.count - children.rowNodes.count - if remainder < 0 { - children.rowNodes.removeLast(-remainder) - children.cellContainerWidgets.removeLast(-remainder * columnCount) - } else if remainder > 0 { - for row in children.rowContent[children.rowNodes.count...] { - let rowNode = AnyViewGraphNode( - for: row, - backend: backend, - environment: environment - ) - children.rowNodes.append(rowNode) - for cellWidget in rowNode.getChildren().widgets(for: backend) { - let cellContainer = backend.createContainer() - backend.addChild(cellWidget, to: cellContainer) - children.cellContainerWidgets.append(AnyWidget(cellContainer)) - } - } - } - - // Update row nodes - let columnWidth = proposedSize.x / columnCount - for (node, content) in zip(children.rowNodes, children.rowContent) { - // TODO: Figure out if this is required - // This doesn't update the row's cells. It just updates the view - // instance stored in the row's ViewGraphNode - _ = node.computeLayout( - with: content, - proposedSize: .zero, - environment: environment - ) - } - - // Compute cell layouts. Really only done during this initial layout - // step to propagate cell preference values. Otherwise we'd do it - // during commit. - var rowHeights: [Int] = [] - let rows = zip(children.rowNodes, children.rowContent) - for (rowNode, content) in rows { - let rowCells = content.layoutableChildren( - backend: backend, - children: rowNode.getChildren() - ) - - var rowCellHeights: [Int] = [] - for rowCell in rowCells { - let cellResult = rowCell.computeLayout( - proposedSize: SIMD2(columnWidth, backend.defaultTableRowContentHeight), - environment: environment - ) - cellResults.append(cellResult) - rowCellHeights.append(cellResult.size.size.y) - } - - let rowHeight = - max( - rowCellHeights.max() ?? 0, - backend.defaultTableRowContentHeight - ) + backend.defaultTableCellVerticalPadding * 2 - - rowHeights.append(rowHeight) - } - children.rowHeights = rowHeights - - // TODO: Compute a proper ideal size for tables - return ViewLayoutResult( - size: ViewSize( - size: size, - idealSize: .zero, - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: nil, - maximumHeight: nil - ), - childResults: cellResults - ) - } - - func commit( - _ widget: Backend.Widget, - children: TableViewChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - let columnLabels = columns.labels - backend.setRowCount(ofTable: widget, to: rows.count) - backend.setColumnLabels(ofTable: widget, to: columnLabels, environment: environment) - - // TODO: Avoid overhead of converting `cellContainerWidgets` to - // `[AnyWidget]` and back again all the time. - backend.setCells( - ofTable: widget, - to: children.cellContainerWidgets.map { $0.into() }, - withRowHeights: children.rowHeights - ) - - let columnCount = columnLabels.count - for (rowIndex, rowHeight) in children.rowHeights.enumerated() { - let rowCells = children.rowContent[rowIndex].layoutableChildren( - backend: backend, - children: children.rowNodes[rowIndex].getChildren() - ) - - for (columnIndex, cell) in rowCells.enumerated() { - let index = rowIndex * columnCount + columnIndex - let cellSize = cell.commit() - backend.setPosition( - ofChildAt: 0, - in: children.cellContainerWidgets[index].into(), - to: SIMD2( - 0, - (rowHeight - cellSize.size.size.y) / 2 - ) - ) - } - } - - backend.setSize(of: widget, to: layout.size.size) - } -} - -class TableViewChildren: ViewGraphNodeChildren { - var rowNodes: [AnyViewGraphNode>] = [] - var cellContainerWidgets: [AnyWidget] = [] - var rowHeights: [Int] = [] - var rowContent: [RowView] = [] - - /// Not used, just a protocol requirement. - var widgets: [AnyWidget] { - rowNodes.map(\.widget) - } - - var erasedNodes: [ErasedViewGraphNode] { - rowNodes.map(ErasedViewGraphNode.init(wrapping:)) - } - - init() { - rowNodes = [] - cellContainerWidgets = [] - } -} - -/// An empty view that simply manages a row's children. Not intended to be rendered directly. -struct RowView: View { - var body: Content - - init(_ content: Content) { - self.body = content - } - - func layoutableChildren( - backend: Backend, - children: any ViewGraphNodeChildren - ) -> [LayoutSystem.LayoutableChild] { - body.layoutableChildren(backend: backend, children: children) - } - - func children( - backend: Backend, - snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, - environment: EnvironmentValues - ) -> any ViewGraphNodeChildren { - body.children(backend: backend, snapshots: snapshots, environment: environment) - } - - func asWidget( - _ children: any ViewGraphNodeChildren, - backend: Backend - ) -> Backend.Widget { - return backend.createContainer() - } - - func computeLayout( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - return ViewLayoutResult.leafView(size: .empty) - } - - func commit( - _ widget: Backend.Widget, - children: any ViewGraphNodeChildren, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) {} -} diff --git a/Sources/SwiftCrossUI/Views/TableColumn.swift b/Sources/SwiftCrossUI/Views/TableColumn.swift deleted file mode 100644 index 4f8ea085b8..0000000000 --- a/Sources/SwiftCrossUI/Views/TableColumn.swift +++ /dev/null @@ -1,25 +0,0 @@ -/// A labelled column with a view for each row in a table. -public struct TableColumn { - /// The label displayed at the top of the column (also known as the column title). - public var label: String - /// The content displayed for this column of each row of the table. - public var content: (RowValue) -> Content -} - -extension TableColumn { - /// Creates a column. - public init(_ label: String, @ViewBuilder content: @escaping (RowValue) -> Content) { - self.label = label - self.content = content - } -} - -extension TableColumn where Content == Text { - /// Creates a column with that displays a string property and has a text label. - public init(_ label: String, value keyPath: KeyPath) { - self.label = label - self.content = { row in - Text(row[keyPath: keyPath]) - } - } -} diff --git a/Sources/SwiftCrossUI/Views/TableRowContent.swift b/Sources/SwiftCrossUI/Views/TableRowContent.swift deleted file mode 100644 index f76429dcff..0000000000 --- a/Sources/SwiftCrossUI/Views/TableRowContent.swift +++ /dev/null @@ -1,1121 +0,0 @@ -// This file was generated using gyb. Do not edit it directly. Edit -// TableRowContent.swift.gyb instead. - -public protocol TableRowContent { - associatedtype RowValue - associatedtype RowContent: View - - var labels: [String] { get } - - func content(for row: RowValue) -> RowContent -} - -public struct EmptyTableRowContent: TableRowContent { - public typealias RowContent = EmptyView - - public var labels: [String] { - [] - } - - public init() {} - - public func content(for row: RowValue) -> EmptyView { - EmptyView() - } -} - -public struct TupleTableRowContent1: TableRowContent { - public typealias RowContent = TupleView1 - - public var column0: TableColumn - - public var labels: [String] { - [column0.label] - } - - public init(_ column0: TableColumn) { - self.column0 = column0 - } - - public func content(for row: RowValue) -> RowContent { - TupleView1(column0.content(row)) - } -} - -public struct TupleTableRowContent2: TableRowContent { - public typealias RowContent = TupleView2 - - public var column0: TableColumn - public var column1: TableColumn - - public var labels: [String] { - [column0.label, column1.label] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - } - - public func content(for row: RowValue) -> RowContent { - TupleView2(column0.content(row), column1.content(row)) - } -} - -public struct TupleTableRowContent3: - TableRowContent -{ - public typealias RowContent = TupleView3 - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - - public var labels: [String] { - [column0.label, column1.label, column2.label] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - } - - public func content(for row: RowValue) -> RowContent { - TupleView3(column0.content(row), column1.content(row), column2.content(row)) - } -} - -public struct TupleTableRowContent4< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View ->: TableRowContent { - public typealias RowContent = TupleView4 - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - - public var labels: [String] { - [column0.label, column1.label, column2.label, column3.label] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - } - - public func content(for row: RowValue) -> RowContent { - TupleView4( - column0.content(row), column1.content(row), column2.content(row), column3.content(row)) - } -} - -public struct TupleTableRowContent5< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View ->: TableRowContent { - public typealias RowContent = TupleView5 - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - - public var labels: [String] { - [column0.label, column1.label, column2.label, column3.label, column4.label] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - } - - public func content(for row: RowValue) -> RowContent { - TupleView5( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row)) - } -} - -public struct TupleTableRowContent6< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View ->: TableRowContent { - public typealias RowContent = TupleView6< - Content0, Content1, Content2, Content3, Content4, Content5 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - - public var labels: [String] { - [column0.label, column1.label, column2.label, column3.label, column4.label, column5.label] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - } - - public func content(for row: RowValue) -> RowContent { - TupleView6( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row)) - } -} - -public struct TupleTableRowContent7< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View ->: TableRowContent { - public typealias RowContent = TupleView7< - Content0, Content1, Content2, Content3, Content4, Content5, Content6 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - } - - public func content(for row: RowValue) -> RowContent { - TupleView7( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row)) - } -} - -public struct TupleTableRowContent8< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View ->: TableRowContent { - public typealias RowContent = TupleView8< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - } - - public func content(for row: RowValue) -> RowContent { - TupleView8( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row)) - } -} - -public struct TupleTableRowContent9< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View ->: TableRowContent { - public typealias RowContent = TupleView9< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - } - - public func content(for row: RowValue) -> RowContent { - TupleView9( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row)) - } -} - -public struct TupleTableRowContent10< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View ->: TableRowContent { - public typealias RowContent = TupleView10< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - } - - public func content(for row: RowValue) -> RowContent { - TupleView10( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row)) - } -} - -public struct TupleTableRowContent11< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View ->: TableRowContent { - public typealias RowContent = TupleView11< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - } - - public func content(for row: RowValue) -> RowContent { - TupleView11( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row)) - } -} - -public struct TupleTableRowContent12< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View ->: TableRowContent { - public typealias RowContent = TupleView12< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - } - - public func content(for row: RowValue) -> RowContent { - TupleView12( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), column11.content(row) - ) - } -} - -public struct TupleTableRowContent13< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View ->: TableRowContent { - public typealias RowContent = TupleView13< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - } - - public func content(for row: RowValue) -> RowContent { - TupleView13( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row)) - } -} - -public struct TupleTableRowContent14< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View, Content13: View ->: TableRowContent { - public typealias RowContent = TupleView14< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12, Content13 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - public var column13: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, column13.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - self.column13 = column13 - } - - public func content(for row: RowValue) -> RowContent { - TupleView14( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row), column13.content(row)) - } -} - -public struct TupleTableRowContent15< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View, Content13: View, Content14: View ->: TableRowContent { - public typealias RowContent = TupleView15< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12, Content13, Content14 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - public var column13: TableColumn - public var column14: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, column13.label, column14.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - self.column13 = column13 - self.column14 = column14 - } - - public func content(for row: RowValue) -> RowContent { - TupleView15( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row), column13.content(row), - column14.content(row)) - } -} - -public struct TupleTableRowContent16< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View, Content13: View, Content14: View, Content15: View ->: TableRowContent { - public typealias RowContent = TupleView16< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12, Content13, Content14, Content15 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - public var column13: TableColumn - public var column14: TableColumn - public var column15: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, column13.label, column14.label, - column15.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - self.column13 = column13 - self.column14 = column14 - self.column15 = column15 - } - - public func content(for row: RowValue) -> RowContent { - TupleView16( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row), column13.content(row), - column14.content(row), column15.content(row)) - } -} - -public struct TupleTableRowContent17< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View, Content13: View, Content14: View, Content15: View, - Content16: View ->: TableRowContent { - public typealias RowContent = TupleView17< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12, Content13, Content14, Content15, Content16 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - public var column13: TableColumn - public var column14: TableColumn - public var column15: TableColumn - public var column16: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, column13.label, column14.label, - column15.label, column16.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - self.column13 = column13 - self.column14 = column14 - self.column15 = column15 - self.column16 = column16 - } - - public func content(for row: RowValue) -> RowContent { - TupleView17( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row), column13.content(row), - column14.content(row), column15.content(row), column16.content(row)) - } -} - -public struct TupleTableRowContent18< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View, Content13: View, Content14: View, Content15: View, - Content16: View, Content17: View ->: TableRowContent { - public typealias RowContent = TupleView18< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12, Content13, Content14, Content15, Content16, - Content17 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - public var column13: TableColumn - public var column14: TableColumn - public var column15: TableColumn - public var column16: TableColumn - public var column17: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, column13.label, column14.label, - column15.label, column16.label, column17.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn, _ column17: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - self.column13 = column13 - self.column14 = column14 - self.column15 = column15 - self.column16 = column16 - self.column17 = column17 - } - - public func content(for row: RowValue) -> RowContent { - TupleView18( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row), column13.content(row), - column14.content(row), column15.content(row), column16.content(row), - column17.content(row)) - } -} - -public struct TupleTableRowContent19< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View, Content13: View, Content14: View, Content15: View, - Content16: View, Content17: View, Content18: View ->: TableRowContent { - public typealias RowContent = TupleView19< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12, Content13, Content14, Content15, Content16, - Content17, Content18 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - public var column13: TableColumn - public var column14: TableColumn - public var column15: TableColumn - public var column16: TableColumn - public var column17: TableColumn - public var column18: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, column13.label, column14.label, - column15.label, column16.label, column17.label, column18.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn, _ column17: TableColumn, - _ column18: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - self.column13 = column13 - self.column14 = column14 - self.column15 = column15 - self.column16 = column16 - self.column17 = column17 - self.column18 = column18 - } - - public func content(for row: RowValue) -> RowContent { - TupleView19( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row), column13.content(row), - column14.content(row), column15.content(row), column16.content(row), - column17.content(row), column18.content(row)) - } -} - -public struct TupleTableRowContent20< - RowValue, Content0: View, Content1: View, Content2: View, Content3: View, Content4: View, - Content5: View, Content6: View, Content7: View, Content8: View, Content9: View, Content10: View, - Content11: View, Content12: View, Content13: View, Content14: View, Content15: View, - Content16: View, Content17: View, Content18: View, Content19: View ->: TableRowContent { - public typealias RowContent = TupleView20< - Content0, Content1, Content2, Content3, Content4, Content5, Content6, Content7, Content8, - Content9, Content10, Content11, Content12, Content13, Content14, Content15, Content16, - Content17, Content18, Content19 - > - - public var column0: TableColumn - public var column1: TableColumn - public var column2: TableColumn - public var column3: TableColumn - public var column4: TableColumn - public var column5: TableColumn - public var column6: TableColumn - public var column7: TableColumn - public var column8: TableColumn - public var column9: TableColumn - public var column10: TableColumn - public var column11: TableColumn - public var column12: TableColumn - public var column13: TableColumn - public var column14: TableColumn - public var column15: TableColumn - public var column16: TableColumn - public var column17: TableColumn - public var column18: TableColumn - public var column19: TableColumn - - public var labels: [String] { - [ - column0.label, column1.label, column2.label, column3.label, column4.label, - column5.label, column6.label, column7.label, column8.label, column9.label, - column10.label, column11.label, column12.label, column13.label, column14.label, - column15.label, column16.label, column17.label, column18.label, column19.label, - ] - } - - public init( - _ column0: TableColumn, _ column1: TableColumn, - _ column2: TableColumn, _ column3: TableColumn, - _ column4: TableColumn, _ column5: TableColumn, - _ column6: TableColumn, _ column7: TableColumn, - _ column8: TableColumn, _ column9: TableColumn, - _ column10: TableColumn, _ column11: TableColumn, - _ column12: TableColumn, _ column13: TableColumn, - _ column14: TableColumn, _ column15: TableColumn, - _ column16: TableColumn, _ column17: TableColumn, - _ column18: TableColumn, _ column19: TableColumn - ) { - self.column0 = column0 - self.column1 = column1 - self.column2 = column2 - self.column3 = column3 - self.column4 = column4 - self.column5 = column5 - self.column6 = column6 - self.column7 = column7 - self.column8 = column8 - self.column9 = column9 - self.column10 = column10 - self.column11 = column11 - self.column12 = column12 - self.column13 = column13 - self.column14 = column14 - self.column15 = column15 - self.column16 = column16 - self.column17 = column17 - self.column18 = column18 - self.column19 = column19 - } - - public func content(for row: RowValue) -> RowContent { - TupleView20( - column0.content(row), column1.content(row), column2.content(row), column3.content(row), - column4.content(row), column5.content(row), column6.content(row), column7.content(row), - column8.content(row), column9.content(row), column10.content(row), - column11.content(row), column12.content(row), column13.content(row), - column14.content(row), column15.content(row), column16.content(row), - column17.content(row), column18.content(row), column19.content(row)) - } -} diff --git a/Sources/SwiftCrossUI/Views/TableRowContent.swift.gyb b/Sources/SwiftCrossUI/Views/TableRowContent.swift.gyb deleted file mode 100644 index aed9494119..0000000000 --- a/Sources/SwiftCrossUI/Views/TableRowContent.swift.gyb +++ /dev/null @@ -1,53 +0,0 @@ -// This file was generated using gyb. Do not edit it directly. Edit -// TableRowContent.swift.gyb instead. -%{ -maximum_column_count = 20 -}% - -public protocol TableRowContent { - associatedtype RowValue - associatedtype RowContent: View - - var labels: [String] { get } - - func content(for row: RowValue) -> RowContent -} - -public struct EmptyTableRowContent: TableRowContent { - public typealias RowContent = EmptyView - - public var labels: [String] { - [] - } - - public init() {} - - public func content(for row: RowValue) -> EmptyView { - EmptyView() - } -} - -%for i in range(1, maximum_column_count + 1): -public struct TupleTableRowContent${i}: TableRowContent { - public typealias RowContent = TupleView${i}<${", ".join("Content%d" % j for j in range(i))}> - - %for j in range(i): - public var column${j}: TableColumn - %end - - public var labels: [String] { - [${", ".join("column%d.label" % j for j in range(i))}] - } - - public init(${", ".join("_ column%d: TableColumn" % (j, j) for j in range(i))}) { - %for j in range(i): - self.column${j} = column${j} - %end - } - - public func content(for row: RowValue) -> RowContent { - TupleView${i}(${", ".join("column%d.content(row)" % j for j in range(i))}) - } -} - -%end diff --git a/Sources/SwiftCrossUI/Views/Text.swift b/Sources/SwiftCrossUI/Views/Text.swift index 906343a4b6..779899a7ca 100644 --- a/Sources/SwiftCrossUI/Views/Text.swift +++ b/Sources/SwiftCrossUI/Views/Text.swift @@ -21,55 +21,35 @@ extension Text: ElementaryView { public func computeLayout( _ widget: Backend.Widget, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - // TODO: Avoid this. Move it to commit + // TODO: Avoid this. Move it to commit once we figure out a solution for Gtk. // Even in dry runs we must update the underlying text view widget // because GtkBackend currently relies on querying the widget for text // properties and such (via Pango). backend.updateTextView(widget, content: string, environment: environment) - let size = backend.size( - of: string, - whenDisplayedIn: widget, - proposedFrame: proposedSize, - environment: environment - ) + let proposedFrame: SIMD2? + if let width = proposedSize.width { + proposedFrame = SIMD2( + LayoutSystem.roundSize(width), + // Backends don't care about our height proposal here at the moment. + proposedSize.height.map(LayoutSystem.roundSize) ?? 1 + ) + } else { + proposedFrame = nil + } - let idealSize = backend.size( + let size = backend.size( of: string, whenDisplayedIn: widget, - proposedFrame: nil, + proposedFrame: proposedFrame, environment: environment ) - let minimumWidth = backend.size( - of: string, - whenDisplayedIn: widget, - proposedFrame: SIMD2(1, proposedSize.y), - environment: environment - ).x - let minimumHeight = backend.size( - of: string, - whenDisplayedIn: widget, - proposedFrame: SIMD2(proposedSize.x, 1), - environment: environment - ).y - - return ViewLayoutResult.leafView( - size: ViewSize( - size: size, - idealSize: idealSize, - idealWidthForProposedHeight: idealSize.x, - idealHeightForProposedWidth: size.y, - minimumWidth: minimumWidth == 1 ? 0 : minimumWidth, - minimumHeight: minimumHeight, - maximumWidth: Double(idealSize.x), - maximumHeight: Double(size.y) - ) - ) + return ViewLayoutResult.leafView(size: ViewSize(size)) } public func commit( @@ -78,6 +58,6 @@ extension Text: ElementaryView { environment: EnvironmentValues, backend: Backend ) { - backend.setSize(of: widget, to: layout.size.size) + backend.setSize(of: widget, to: layout.size.vector) } } diff --git a/Sources/SwiftCrossUI/Views/TextEditor.swift b/Sources/SwiftCrossUI/Views/TextEditor.swift deleted file mode 100644 index 960094730c..0000000000 --- a/Sources/SwiftCrossUI/Views/TextEditor.swift +++ /dev/null @@ -1,65 +0,0 @@ -/// A control for editing multiline text. -public struct TextEditor: ElementaryView { - @Binding var text: String - - public init(text: Binding) { - _text = text - } - - func asWidget(backend: Backend) -> Backend.Widget { - backend.createTextEditor() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - // Avoid evaluating the binding multiple times - let content = text - - let idealHeight = backend.size( - of: content, - whenDisplayedIn: widget, - proposedFrame: SIMD2(proposedSize.x, 1), - environment: environment - ).y - let size = SIMD2( - proposedSize.x, - max(proposedSize.y, idealHeight) - ) - - return ViewLayoutResult.leafView( - size: ViewSize( - size: size, - idealSize: SIMD2(10, 10), - idealWidthForProposedHeight: 10, - idealHeightForProposedWidth: idealHeight, - minimumWidth: 0, - minimumHeight: idealHeight, - maximumWidth: nil, - maximumHeight: nil - ) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - // Avoid evaluating the binding multiple times - let content = self.text - - backend.updateTextEditor(widget, environment: environment) { newValue in - self.text = newValue - } - if text != backend.getContent(ofTextEditor: widget) { - backend.setContent(ofTextEditor: widget, to: content) - } - - backend.setSize(of: widget, to: layout.size.size) - } -} diff --git a/Sources/SwiftCrossUI/Views/TextField.swift b/Sources/SwiftCrossUI/Views/TextField.swift deleted file mode 100644 index 1bb883eda2..0000000000 --- a/Sources/SwiftCrossUI/Views/TextField.swift +++ /dev/null @@ -1,76 +0,0 @@ -/// A control that displays an editable text interface. -public struct TextField: ElementaryView, View { - /// The label to show when the field is empty. - private var placeholder: String - /// The field's content. - private var value: Binding? - - /// Creates an editable text field with a given placeholder. - public init(_ placeholder: String = "", text: Binding) { - self.placeholder = placeholder - self.value = text - } - - /// Creates an editable text field with a given placeholder. - @available( - *, deprecated, - message: "Use TextField(_:text:) instead", - renamed: "TextField.init(_:text:)" - ) - public init(_ placeholder: String = "", _ value: Binding? = nil) { - self.placeholder = placeholder - var dummy = "" - self.value = value ?? Binding(get: { dummy }, set: { dummy = $0 }) - } - - func asWidget(backend: Backend) -> Backend.Widget { - return backend.createTextField() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - let naturalHeight = backend.naturalSize(of: widget).y - let size = SIMD2( - proposedSize.x, - naturalHeight - ) - - // TODO: Allow backends to set their own ideal text field width - return ViewLayoutResult.leafView( - size: ViewSize( - size: size, - idealSize: SIMD2(100, naturalHeight), - minimumWidth: 0, - minimumHeight: naturalHeight, - maximumWidth: nil, - maximumHeight: Double(naturalHeight) - ) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.updateTextField( - widget, - placeholder: placeholder, - environment: environment, - onChange: { newValue in - self.value?.wrappedValue = newValue - }, - onSubmit: environment.onSubmit ?? {} - ) - if let value = value?.wrappedValue, value != backend.getContent(ofTextField: widget) { - backend.setContent(ofTextField: widget, to: value) - } - - backend.setSize(of: widget, to: layout.size.size) - } -} diff --git a/Sources/SwiftCrossUI/Views/Toggle.swift b/Sources/SwiftCrossUI/Views/Toggle.swift deleted file mode 100644 index dc787811ea..0000000000 --- a/Sources/SwiftCrossUI/Views/Toggle.swift +++ /dev/null @@ -1,58 +0,0 @@ -/// A control for toggling between two values (usually representing on and off). -public struct Toggle: View { - @Environment(\.backend) var backend - @Environment(\.toggleStyle) var toggleStyle - - /// The label to be shown on or beside the toggle. - var label: String - /// Whether the toggle is active or not. - var active: Binding - - /// Creates a toggle that displays a custom label. - public init(_ label: String, active: Binding) { - self.label = label - self.active = active - } - - public var body: some View { - switch toggleStyle.style { - case .switch: - HStack { - Text(label) - - if backend.requiresToggleSwitchSpacer { - Spacer() - } - - ToggleSwitch(active: active) - } - case .button: - ToggleButton(label, active: active) - case .checkbox: - HStack { - Text(label) - - Checkbox(active: active) - } - } - } -} - -/// A style of toggle. -public struct ToggleStyle: Sendable { - package var style: Style - - /// A toggle switch. - public static let `switch` = Self(style: .switch) - /// A toggle button. Generally looks like a regular button when off and an - /// accented button when on. - public static let button = Self(style: .button) - /// A checkbox. - public static let checkbox = Self(style: .checkbox) - - package enum Style { - case `switch` - case button - case checkbox - } -} diff --git a/Sources/SwiftCrossUI/Views/ToggleButton.swift b/Sources/SwiftCrossUI/Views/ToggleButton.swift deleted file mode 100644 index 8cf2c91a3b..0000000000 --- a/Sources/SwiftCrossUI/Views/ToggleButton.swift +++ /dev/null @@ -1,41 +0,0 @@ -/// A button style control that is either on or off. -struct ToggleButton: ElementaryView, View { - /// The label to show on the toggle button. - private var label: String - /// Whether the button is active or not. - private var active: Binding - - /// Creates a toggle button that displays a custom label. - public init(_ label: String, active: Binding) { - self.label = label - self.active = active - } - - func asWidget(backend: Backend) -> Backend.Widget { - return backend.createToggle() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - // TODO: Implement toggle button sizing within SwiftCrossUI so that we - // can delay updating the underlying widget until `commit`. - backend.setState(ofToggle: widget, to: active.wrappedValue) - backend.updateToggle(widget, label: label, environment: environment) { newActiveState in - active.wrappedValue = newActiveState - } - return ViewLayoutResult.leafView( - size: ViewSize(fixedSize: backend.naturalSize(of: widget)) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) {} -} diff --git a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift b/Sources/SwiftCrossUI/Views/ToggleSwitch.swift deleted file mode 100644 index a500d0509b..0000000000 --- a/Sources/SwiftCrossUI/Views/ToggleSwitch.swift +++ /dev/null @@ -1,37 +0,0 @@ -/// A light switch style control that is either on or off. -struct ToggleSwitch: ElementaryView, View { - /// Whether the switch is active or not. - private var active: Binding - - /// Creates a switch. - public init(active: Binding) { - self.active = active - } - - func asWidget(backend: Backend) -> Backend.Widget { - return backend.createSwitch() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - return ViewLayoutResult.leafView( - size: ViewSize(fixedSize: backend.naturalSize(of: widget)) - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - backend.updateSwitch(widget, environment: environment) { newActiveState in - active.wrappedValue = newActiveState - } - backend.setState(ofSwitch: widget, to: active.wrappedValue) - } -} diff --git a/Sources/SwiftCrossUI/Views/TupleView.swift b/Sources/SwiftCrossUI/Views/TupleView.swift index 0dab664be3..0c24ef041b 100644 --- a/Sources/SwiftCrossUI/Views/TupleView.swift +++ b/Sources/SwiftCrossUI/Views/TupleView.swift @@ -39,7 +39,7 @@ extension TupleView { func computeLayout( _ widget: Backend.Widget, children: Children, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -72,6 +72,7 @@ extension TupleView { } } + /// A view with exactly 1 children. Autogenerated as an alternative to Swift's not yet /// production ready variadic generics. /// @@ -110,7 +111,7 @@ extension TupleView1: TupleView { children: Children ) -> [LayoutSystem.LayoutableChild] { [ - layoutableChild(node: children.child0, view: view0) + layoutableChild(node: children.child0, view: view0), ] } } @@ -321,9 +322,7 @@ extension TupleView5: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView6< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View -> { +public struct TupleView6 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -334,10 +333,7 @@ public struct TupleView6< public var body = EmptyView() /// Wraps 6 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -384,9 +380,7 @@ extension TupleView6: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView7< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View -> { +public struct TupleView7 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -398,10 +392,7 @@ public struct TupleView7< public var body = EmptyView() /// Wraps 7 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -450,10 +441,7 @@ extension TupleView7: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView8< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View -> { +public struct TupleView8 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -466,10 +454,7 @@ public struct TupleView8< public var body = EmptyView() /// Wraps 8 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -520,10 +505,7 @@ extension TupleView8: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView9< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View -> { +public struct TupleView9 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -537,10 +519,7 @@ public struct TupleView9< public var body = EmptyView() /// Wraps 9 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -558,9 +537,7 @@ extension TupleView9: View { } extension TupleView9: TupleView { - typealias Children = TupleViewChildren9< - View0, View1, View2, View3, View4, View5, View6, View7, View8 - > + typealias Children = TupleViewChildren9 func children( backend: Backend, @@ -595,10 +572,7 @@ extension TupleView9: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView10< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View -> { +public struct TupleView10 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -613,10 +587,7 @@ public struct TupleView10< public var body = EmptyView() /// Wraps 10 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -635,9 +606,7 @@ extension TupleView10: View { } extension TupleView10: TupleView { - typealias Children = TupleViewChildren10< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9 - > + typealias Children = TupleViewChildren10 func children( backend: Backend, @@ -673,10 +642,7 @@ extension TupleView10: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView11< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View -> { +public struct TupleView11 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -692,11 +658,7 @@ public struct TupleView11< public var body = EmptyView() /// Wraps 11 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -716,9 +678,7 @@ extension TupleView11: View { } extension TupleView11: TupleView { - typealias Children = TupleViewChildren11< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10 - > + typealias Children = TupleViewChildren11 func children( backend: Backend, @@ -755,10 +715,7 @@ extension TupleView11: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView12< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View -> { +public struct TupleView12 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -775,11 +732,7 @@ public struct TupleView12< public var body = EmptyView() /// Wraps 12 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -800,9 +753,7 @@ extension TupleView12: View { } extension TupleView12: TupleView { - typealias Children = TupleViewChildren12< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11 - > + typealias Children = TupleViewChildren12 func children( backend: Backend, @@ -840,10 +791,7 @@ extension TupleView12: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView13< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View -> { +public struct TupleView13 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -861,11 +809,7 @@ public struct TupleView13< public var body = EmptyView() /// Wraps 13 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -887,9 +831,7 @@ extension TupleView13: View { } extension TupleView13: TupleView { - typealias Children = TupleViewChildren13< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, View12 - > + typealias Children = TupleViewChildren13 func children( backend: Backend, @@ -897,8 +839,7 @@ extension TupleView13: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, backend: backend, snapshots: snapshots, environment: environment ) } @@ -929,10 +870,7 @@ extension TupleView13: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView14< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View, View13: View -> { +public struct TupleView14 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -951,11 +889,7 @@ public struct TupleView14< public var body = EmptyView() /// Wraps 14 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -978,10 +912,7 @@ extension TupleView14: View { } extension TupleView14: TupleView { - typealias Children = TupleViewChildren14< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, - View12, View13 - > + typealias Children = TupleViewChildren14 func children( backend: Backend, @@ -989,8 +920,7 @@ extension TupleView14: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, backend: backend, snapshots: snapshots, environment: environment ) } @@ -1022,11 +952,7 @@ extension TupleView14: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView15< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View, View13: View, - View14: View -> { +public struct TupleView15 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -1046,11 +972,7 @@ public struct TupleView15< public var body = EmptyView() /// Wraps 15 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -1074,10 +996,7 @@ extension TupleView15: View { } extension TupleView15: TupleView { - typealias Children = TupleViewChildren15< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, - View12, View13, View14 - > + typealias Children = TupleViewChildren15 func children( backend: Backend, @@ -1085,8 +1004,7 @@ extension TupleView15: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, backend: backend, snapshots: snapshots, environment: environment ) } @@ -1119,11 +1037,7 @@ extension TupleView15: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView16< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View, View13: View, - View14: View, View15: View -> { +public struct TupleView16 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -1144,12 +1058,7 @@ public struct TupleView16< public var body = EmptyView() /// Wraps 16 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, - _ view15: View15 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, _ view15: View15) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -1174,10 +1083,7 @@ extension TupleView16: View { } extension TupleView16: TupleView { - typealias Children = TupleViewChildren16< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, - View12, View13, View14, View15 - > + typealias Children = TupleViewChildren16 func children( backend: Backend, @@ -1185,8 +1091,7 @@ extension TupleView16: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, backend: backend, snapshots: snapshots, environment: environment ) } @@ -1220,11 +1125,7 @@ extension TupleView16: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView17< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View, View13: View, - View14: View, View15: View, View16: View -> { +public struct TupleView17 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -1246,12 +1147,7 @@ public struct TupleView17< public var body = EmptyView() /// Wraps 17 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, - _ view15: View15, _ view16: View16 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, _ view15: View15, _ view16: View16) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -1277,10 +1173,7 @@ extension TupleView17: View { } extension TupleView17: TupleView { - typealias Children = TupleViewChildren17< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, - View12, View13, View14, View15, View16 - > + typealias Children = TupleViewChildren17 func children( backend: Backend, @@ -1288,8 +1181,7 @@ extension TupleView17: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16, backend: backend, snapshots: snapshots, environment: environment ) } @@ -1324,11 +1216,7 @@ extension TupleView17: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView18< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View, View13: View, - View14: View, View15: View, View16: View, View17: View -> { +public struct TupleView18 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -1351,12 +1239,7 @@ public struct TupleView18< public var body = EmptyView() /// Wraps 18 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, - _ view15: View15, _ view16: View16, _ view17: View17 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, _ view15: View15, _ view16: View16, _ view17: View17) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -1383,10 +1266,7 @@ extension TupleView18: View { } extension TupleView18: TupleView { - typealias Children = TupleViewChildren18< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, - View12, View13, View14, View15, View16, View17 - > + typealias Children = TupleViewChildren18 func children( backend: Backend, @@ -1394,8 +1274,7 @@ extension TupleView18: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16, view17, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16, view17, backend: backend, snapshots: snapshots, environment: environment ) } @@ -1431,11 +1310,7 @@ extension TupleView18: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView19< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View, View13: View, - View14: View, View15: View, View16: View, View17: View, View18: View -> { +public struct TupleView19 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -1459,12 +1334,7 @@ public struct TupleView19< public var body = EmptyView() /// Wraps 19 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, - _ view15: View15, _ view16: View16, _ view17: View17, _ view18: View18 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, _ view15: View15, _ view16: View16, _ view17: View17, _ view18: View18) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -1492,10 +1362,7 @@ extension TupleView19: View { } extension TupleView19: TupleView { - typealias Children = TupleViewChildren19< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, - View12, View13, View14, View15, View16, View17, View18 - > + typealias Children = TupleViewChildren19 func children( backend: Backend, @@ -1503,8 +1370,7 @@ extension TupleView19: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16, view17, view18, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16, view17, view18, backend: backend, snapshots: snapshots, environment: environment ) } @@ -1541,11 +1407,7 @@ extension TupleView19: TupleView { /// production ready variadic generics. /// /// Has the same behaviour as ``Group`` when rendered directly. -public struct TupleView20< - View0: View, View1: View, View2: View, View3: View, View4: View, View5: View, View6: View, - View7: View, View8: View, View9: View, View10: View, View11: View, View12: View, View13: View, - View14: View, View15: View, View16: View, View17: View, View18: View, View19: View -> { +public struct TupleView20 { public var view0: View0 public var view1: View1 public var view2: View2 @@ -1570,12 +1432,7 @@ public struct TupleView20< public var body = EmptyView() /// Wraps 20 child views in a single container view. - public init( - _ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, - _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, - _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, - _ view15: View15, _ view16: View16, _ view17: View17, _ view18: View18, _ view19: View19 - ) { + public init(_ view0: View0, _ view1: View1, _ view2: View2, _ view3: View3, _ view4: View4, _ view5: View5, _ view6: View6, _ view7: View7, _ view8: View8, _ view9: View9, _ view10: View10, _ view11: View11, _ view12: View12, _ view13: View13, _ view14: View14, _ view15: View15, _ view16: View16, _ view17: View17, _ view18: View18, _ view19: View19) { self.view0 = view0 self.view1 = view1 self.view2 = view2 @@ -1604,10 +1461,7 @@ extension TupleView20: View { } extension TupleView20: TupleView { - typealias Children = TupleViewChildren20< - View0, View1, View2, View3, View4, View5, View6, View7, View8, View9, View10, View11, - View12, View13, View14, View15, View16, View17, View18, View19 - > + typealias Children = TupleViewChildren20 func children( backend: Backend, @@ -1615,8 +1469,7 @@ extension TupleView20: TupleView { environment: EnvironmentValues ) -> Children { return Children( - view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, - view12, view13, view14, view15, view16, view17, view18, view19, + view0, view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14, view15, view16, view17, view18, view19, backend: backend, snapshots: snapshots, environment: environment ) } diff --git a/Sources/SwiftCrossUI/Views/TupleView.swift.gyb b/Sources/SwiftCrossUI/Views/TupleView.swift.gyb index 6c9533bd7e..68bf66cec5 100644 --- a/Sources/SwiftCrossUI/Views/TupleView.swift.gyb +++ b/Sources/SwiftCrossUI/Views/TupleView.swift.gyb @@ -42,7 +42,7 @@ extension TupleView { func computeLayout( _ widget: Backend.Widget, children: Children, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { diff --git a/Sources/SwiftCrossUI/Views/TupleViewChildren.swift b/Sources/SwiftCrossUI/Views/TupleViewChildren.swift index acf995547a..55f5de387d 100644 --- a/Sources/SwiftCrossUI/Views/TupleViewChildren.swift +++ b/Sources/SwiftCrossUI/Views/TupleViewChildren.swift @@ -1,6 +1,17 @@ // This file was generated using gyb. Do not edit it directly. Edit // TupleViewChildren.swift.gyb instead. +struct StackLayoutCache { + var lastFlexibilityOrdering: [Int]? + var lastHiddenChildren: [Bool] = [] + var redistributeSpaceOnCommit = false +} + +protocol TupleViewChildren: ViewGraphNodeChildren { + @MainActor + var stackLayoutCache: StackLayoutCache { get nonmutating set } +} + /// A helper function to shorten node initialisations to a single line. This /// helps compress the generated code a bit and minimise the number of additions /// and deletions caused by updating the generator. @@ -19,19 +30,22 @@ private func node( ) } + /// A fixed-length strongly-typed collection of 1 child nodes. A counterpart to /// ``TupleView1``. -public struct TupleViewChildren1: ViewGraphNodeChildren { +public class TupleViewChildren1: TupleViewChildren { public var widgets: [AnyWidget] { return [child0.widget] } public var erasedNodes: [ErasedViewGraphNode] { return [ - ErasedViewGraphNode(wrapping: child0) + ErasedViewGraphNode(wrapping: child0), ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode @@ -52,7 +66,7 @@ public struct TupleViewChildren1: ViewGraphNodeChildren { /// A fixed-length strongly-typed collection of 2 child nodes. A counterpart to /// ``TupleView2``. -public struct TupleViewChildren2: ViewGraphNodeChildren { +public class TupleViewChildren2: TupleViewChildren { public var widgets: [AnyWidget] { return [child0.widget, child1.widget] } @@ -64,6 +78,8 @@ public struct TupleViewChildren2: ViewGraphNodeChild ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -77,7 +93,7 @@ public struct TupleViewChildren2: ViewGraphNodeChild environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -87,7 +103,7 @@ public struct TupleViewChildren2: ViewGraphNodeChild /// A fixed-length strongly-typed collection of 3 child nodes. A counterpart to /// ``TupleView3``. -public struct TupleViewChildren3: ViewGraphNodeChildren { +public class TupleViewChildren3: TupleViewChildren { public var widgets: [AnyWidget] { return [child0.widget, child1.widget, child2.widget] } @@ -100,6 +116,8 @@ public struct TupleViewChildren3: View ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -115,8 +133,7 @@ public struct TupleViewChildren3: View environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -127,9 +144,7 @@ public struct TupleViewChildren3: View /// A fixed-length strongly-typed collection of 4 child nodes. A counterpart to /// ``TupleView4``. -public struct TupleViewChildren4: - ViewGraphNodeChildren -{ +public class TupleViewChildren4: TupleViewChildren { public var widgets: [AnyWidget] { return [child0.widget, child1.widget, child2.widget, child3.widget] } @@ -143,6 +158,8 @@ public struct TupleViewChildren4 /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -160,8 +177,7 @@ public struct TupleViewChildren4: ViewGraphNodeChildren { +public class TupleViewChildren5: TupleViewChildren { public var widgets: [AnyWidget] { return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget] } @@ -190,6 +204,8 @@ public struct TupleViewChildren5< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -209,9 +225,7 @@ public struct TupleViewChildren5< environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -224,14 +238,9 @@ public struct TupleViewChildren5< /// A fixed-length strongly-typed collection of 6 child nodes. A counterpart to /// ``TupleView6``. -public struct TupleViewChildren6< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View ->: ViewGraphNodeChildren { +public class TupleViewChildren6: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -245,6 +254,8 @@ public struct TupleViewChildren6< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -260,16 +271,13 @@ public struct TupleViewChildren6< /// Creates the nodes for 6 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -283,14 +291,9 @@ public struct TupleViewChildren6< /// A fixed-length strongly-typed collection of 7 child nodes. A counterpart to /// ``TupleView7``. -public struct TupleViewChildren7< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, Child6: View ->: ViewGraphNodeChildren { +public class TupleViewChildren7: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -305,6 +308,8 @@ public struct TupleViewChildren7< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -322,17 +327,13 @@ public struct TupleViewChildren7< /// Creates the nodes for 7 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -347,15 +348,9 @@ public struct TupleViewChildren7< /// A fixed-length strongly-typed collection of 8 child nodes. A counterpart to /// ``TupleView8``. -public struct TupleViewChildren8< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View ->: ViewGraphNodeChildren { +public class TupleViewChildren8: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -371,6 +366,8 @@ public struct TupleViewChildren8< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -390,17 +387,13 @@ public struct TupleViewChildren8< /// Creates the nodes for 8 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -416,15 +409,9 @@ public struct TupleViewChildren8< /// A fixed-length strongly-typed collection of 9 child nodes. A counterpart to /// ``TupleView9``. -public struct TupleViewChildren9< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View ->: ViewGraphNodeChildren { +public class TupleViewChildren9: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -441,6 +428,8 @@ public struct TupleViewChildren9< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -462,18 +451,13 @@ public struct TupleViewChildren9< /// Creates the nodes for 9 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -490,15 +474,9 @@ public struct TupleViewChildren9< /// A fixed-length strongly-typed collection of 10 child nodes. A counterpart to /// ``TupleView10``. -public struct TupleViewChildren10< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View ->: ViewGraphNodeChildren { +public class TupleViewChildren10: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -516,6 +494,8 @@ public struct TupleViewChildren10< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -539,18 +519,13 @@ public struct TupleViewChildren10< /// Creates the nodes for 10 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -568,16 +543,9 @@ public struct TupleViewChildren10< /// A fixed-length strongly-typed collection of 11 child nodes. A counterpart to /// ``TupleView11``. -public struct TupleViewChildren11< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View ->: ViewGraphNodeChildren { +public class TupleViewChildren11: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -596,6 +564,8 @@ public struct TupleViewChildren11< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -621,20 +591,13 @@ public struct TupleViewChildren11< /// Creates the nodes for 11 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -653,16 +616,9 @@ public struct TupleViewChildren11< /// A fixed-length strongly-typed collection of 12 child nodes. A counterpart to /// ``TupleView12``. -public struct TupleViewChildren12< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View ->: ViewGraphNodeChildren { +public class TupleViewChildren12: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -682,6 +638,8 @@ public struct TupleViewChildren12< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -709,21 +667,13 @@ public struct TupleViewChildren12< /// Creates the nodes for 12 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -743,17 +693,9 @@ public struct TupleViewChildren12< /// A fixed-length strongly-typed collection of 13 child nodes. A counterpart to /// ``TupleView13``. -public struct TupleViewChildren13< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View ->: ViewGraphNodeChildren { +public class TupleViewChildren13: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -774,6 +716,8 @@ public struct TupleViewChildren13< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -803,22 +747,13 @@ public struct TupleViewChildren13< /// Creates the nodes for 13 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -839,17 +774,9 @@ public struct TupleViewChildren13< /// A fixed-length strongly-typed collection of 14 child nodes. A counterpart to /// ``TupleView14``. -public struct TupleViewChildren14< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View, Child13: View ->: ViewGraphNodeChildren { +public class TupleViewChildren14: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, child13.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget, child13.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -871,6 +798,8 @@ public struct TupleViewChildren14< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -902,23 +831,13 @@ public struct TupleViewChildren14< /// Creates the nodes for 14 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), - ViewGraphSnapshotter.name(of: Child13.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self), ViewGraphSnapshotter.name(of: Child13.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -940,17 +859,9 @@ public struct TupleViewChildren14< /// A fixed-length strongly-typed collection of 15 child nodes. A counterpart to /// ``TupleView15``. -public struct TupleViewChildren15< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View, Child13: View, Child14: View ->: ViewGraphNodeChildren { +public class TupleViewChildren15: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget, child13.widget, child14.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -973,6 +884,8 @@ public struct TupleViewChildren15< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -1006,25 +919,13 @@ public struct TupleViewChildren15< /// Creates the nodes for 15 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, - _ child14: Child14, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, _ child14: Child14, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), - ViewGraphSnapshotter.name(of: Child13.self), - ViewGraphSnapshotter.name(of: Child14.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self), ViewGraphSnapshotter.name(of: Child13.self), ViewGraphSnapshotter.name(of: Child14.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -1047,18 +948,9 @@ public struct TupleViewChildren15< /// A fixed-length strongly-typed collection of 16 child nodes. A counterpart to /// ``TupleView16``. -public struct TupleViewChildren16< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View, Child13: View, Child14: View, Child15: View ->: ViewGraphNodeChildren { +public class TupleViewChildren16: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, - child15.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, child15.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -1082,6 +974,8 @@ public struct TupleViewChildren16< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -1117,26 +1011,13 @@ public struct TupleViewChildren16< /// Creates the nodes for 16 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, - _ child14: Child14, _ child15: Child15, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, _ child14: Child14, _ child15: Child15, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), - ViewGraphSnapshotter.name(of: Child13.self), - ViewGraphSnapshotter.name(of: Child14.self), - ViewGraphSnapshotter.name(of: Child15.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self), ViewGraphSnapshotter.name(of: Child13.self), ViewGraphSnapshotter.name(of: Child14.self), ViewGraphSnapshotter.name(of: Child15.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -1160,18 +1041,9 @@ public struct TupleViewChildren16< /// A fixed-length strongly-typed collection of 17 child nodes. A counterpart to /// ``TupleView17``. -public struct TupleViewChildren17< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View, Child13: View, Child14: View, Child15: View, Child16: View ->: ViewGraphNodeChildren { +public class TupleViewChildren17: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, - child15.widget, child16.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, child15.widget, child16.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -1196,6 +1068,8 @@ public struct TupleViewChildren17< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -1233,27 +1107,13 @@ public struct TupleViewChildren17< /// Creates the nodes for 17 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, - _ child14: Child14, _ child15: Child15, _ child16: Child16, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, _ child14: Child14, _ child15: Child15, _ child16: Child16, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), - ViewGraphSnapshotter.name(of: Child13.self), - ViewGraphSnapshotter.name(of: Child14.self), - ViewGraphSnapshotter.name(of: Child15.self), - ViewGraphSnapshotter.name(of: Child16.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self), ViewGraphSnapshotter.name(of: Child13.self), ViewGraphSnapshotter.name(of: Child14.self), ViewGraphSnapshotter.name(of: Child15.self), ViewGraphSnapshotter.name(of: Child16.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -1278,18 +1138,9 @@ public struct TupleViewChildren17< /// A fixed-length strongly-typed collection of 18 child nodes. A counterpart to /// ``TupleView18``. -public struct TupleViewChildren18< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View, Child13: View, Child14: View, Child15: View, Child16: View, Child17: View ->: ViewGraphNodeChildren { +public class TupleViewChildren18: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, - child15.widget, child16.widget, child17.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, child15.widget, child16.widget, child17.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -1315,6 +1166,8 @@ public struct TupleViewChildren18< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -1354,28 +1207,13 @@ public struct TupleViewChildren18< /// Creates the nodes for 18 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, - _ child14: Child14, _ child15: Child15, _ child16: Child16, _ child17: Child17, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, _ child14: Child14, _ child15: Child15, _ child16: Child16, _ child17: Child17, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), - ViewGraphSnapshotter.name(of: Child13.self), - ViewGraphSnapshotter.name(of: Child14.self), - ViewGraphSnapshotter.name(of: Child15.self), - ViewGraphSnapshotter.name(of: Child16.self), - ViewGraphSnapshotter.name(of: Child17.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self), ViewGraphSnapshotter.name(of: Child13.self), ViewGraphSnapshotter.name(of: Child14.self), ViewGraphSnapshotter.name(of: Child15.self), ViewGraphSnapshotter.name(of: Child16.self), ViewGraphSnapshotter.name(of: Child17.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -1401,19 +1239,9 @@ public struct TupleViewChildren18< /// A fixed-length strongly-typed collection of 19 child nodes. A counterpart to /// ``TupleView19``. -public struct TupleViewChildren19< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View, Child13: View, Child14: View, Child15: View, Child16: View, Child17: View, - Child18: View ->: ViewGraphNodeChildren { +public class TupleViewChildren19: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, - child15.widget, child16.widget, child17.widget, child18.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, child15.widget, child16.widget, child17.widget, child18.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -1440,6 +1268,8 @@ public struct TupleViewChildren19< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -1481,30 +1311,13 @@ public struct TupleViewChildren19< /// Creates the nodes for 19 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, - _ child14: Child14, _ child15: Child15, _ child16: Child16, _ child17: Child17, - _ child18: Child18, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, _ child14: Child14, _ child15: Child15, _ child16: Child16, _ child17: Child17, _ child18: Child18, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), - ViewGraphSnapshotter.name(of: Child13.self), - ViewGraphSnapshotter.name(of: Child14.self), - ViewGraphSnapshotter.name(of: Child15.self), - ViewGraphSnapshotter.name(of: Child16.self), - ViewGraphSnapshotter.name(of: Child17.self), - ViewGraphSnapshotter.name(of: Child18.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self), ViewGraphSnapshotter.name(of: Child13.self), ViewGraphSnapshotter.name(of: Child14.self), ViewGraphSnapshotter.name(of: Child15.self), ViewGraphSnapshotter.name(of: Child16.self), ViewGraphSnapshotter.name(of: Child17.self), ViewGraphSnapshotter.name(of: Child18.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) @@ -1531,19 +1344,9 @@ public struct TupleViewChildren19< /// A fixed-length strongly-typed collection of 20 child nodes. A counterpart to /// ``TupleView20``. -public struct TupleViewChildren20< - Child0: View, Child1: View, Child2: View, Child3: View, Child4: View, Child5: View, - Child6: View, Child7: View, Child8: View, Child9: View, Child10: View, Child11: View, - Child12: View, Child13: View, Child14: View, Child15: View, Child16: View, Child17: View, - Child18: View, Child19: View ->: ViewGraphNodeChildren { +public class TupleViewChildren20: TupleViewChildren { public var widgets: [AnyWidget] { - return [ - child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, - child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, - child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, - child15.widget, child16.widget, child17.widget, child18.widget, child19.widget, - ] + return [child0.widget, child1.widget, child2.widget, child3.widget, child4.widget, child5.widget, child6.widget, child7.widget, child8.widget, child9.widget, child10.widget, child11.widget, child12.widget, child13.widget, child14.widget, child15.widget, child16.widget, child17.widget, child18.widget, child19.widget] } public var erasedNodes: [ErasedViewGraphNode] { @@ -1571,6 +1374,8 @@ public struct TupleViewChildren20< ] } + var stackLayoutCache = StackLayoutCache() + /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var child0: AnyViewGraphNode /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. @@ -1614,31 +1419,13 @@ public struct TupleViewChildren20< /// Creates the nodes for 20 child views. public init( - _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, - _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, - _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, - _ child14: Child14, _ child15: Child15, _ child16: Child16, _ child17: Child17, - _ child18: Child18, _ child19: Child19, + _ child0: Child0, _ child1: Child1, _ child2: Child2, _ child3: Child3, _ child4: Child4, _ child5: Child5, _ child6: Child6, _ child7: Child7, _ child8: Child8, _ child9: Child9, _ child10: Child10, _ child11: Child11, _ child12: Child12, _ child13: Child13, _ child14: Child14, _ child15: Child15, _ child16: Child16, _ child17: Child17, _ child18: Child18, _ child19: Child19, backend: Backend, snapshots: [ViewGraphSnapshotter.NodeSnapshot]?, environment: EnvironmentValues ) { let viewTypeNames = [ - ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), - ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), - ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), - ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), - ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), - ViewGraphSnapshotter.name(of: Child10.self), - ViewGraphSnapshotter.name(of: Child11.self), - ViewGraphSnapshotter.name(of: Child12.self), - ViewGraphSnapshotter.name(of: Child13.self), - ViewGraphSnapshotter.name(of: Child14.self), - ViewGraphSnapshotter.name(of: Child15.self), - ViewGraphSnapshotter.name(of: Child16.self), - ViewGraphSnapshotter.name(of: Child17.self), - ViewGraphSnapshotter.name(of: Child18.self), - ViewGraphSnapshotter.name(of: Child19.self), + ViewGraphSnapshotter.name(of: Child0.self), ViewGraphSnapshotter.name(of: Child1.self), ViewGraphSnapshotter.name(of: Child2.self), ViewGraphSnapshotter.name(of: Child3.self), ViewGraphSnapshotter.name(of: Child4.self), ViewGraphSnapshotter.name(of: Child5.self), ViewGraphSnapshotter.name(of: Child6.self), ViewGraphSnapshotter.name(of: Child7.self), ViewGraphSnapshotter.name(of: Child8.self), ViewGraphSnapshotter.name(of: Child9.self), ViewGraphSnapshotter.name(of: Child10.self), ViewGraphSnapshotter.name(of: Child11.self), ViewGraphSnapshotter.name(of: Child12.self), ViewGraphSnapshotter.name(of: Child13.self), ViewGraphSnapshotter.name(of: Child14.self), ViewGraphSnapshotter.name(of: Child15.self), ViewGraphSnapshotter.name(of: Child16.self), ViewGraphSnapshotter.name(of: Child17.self), ViewGraphSnapshotter.name(of: Child18.self), ViewGraphSnapshotter.name(of: Child19.self) ] let snapshots = ViewGraphSnapshotter.match(snapshots ?? [], to: viewTypeNames) self.child0 = node(for: child0, backend, snapshots[0], environment) diff --git a/Sources/SwiftCrossUI/Views/TupleViewChildren.swift.gyb b/Sources/SwiftCrossUI/Views/TupleViewChildren.swift.gyb index db3e4309f0..b494c5c266 100644 --- a/Sources/SwiftCrossUI/Views/TupleViewChildren.swift.gyb +++ b/Sources/SwiftCrossUI/Views/TupleViewChildren.swift.gyb @@ -4,6 +4,17 @@ maximum_child_count = 20 }% +struct StackLayoutCache { + var lastFlexibilityOrdering: [Int]? + var lastHiddenChildren: [Bool] = [] + var redistributeSpaceOnCommit = false +} + +protocol TupleViewChildren: ViewGraphNodeChildren { + @MainActor + var stackLayoutCache: StackLayoutCache { get nonmutating set } +} + /// A helper function to shorten node initialisations to a single line. This /// helps compress the generated code a bit and minimise the number of additions /// and deletions caused by updating the generator. @@ -34,7 +45,7 @@ variadic_type_parameters = ", ".join(children) /// A fixed-length strongly-typed collection of ${i + 1} child nodes. A counterpart to /// ``TupleView${i + 1}``. -public struct TupleViewChildren${i + 1}<${struct_type_parameters}>: ViewGraphNodeChildren { +public class TupleViewChildren${i + 1}<${struct_type_parameters}>: TupleViewChildren { public var widgets: [AnyWidget] { return [${", ".join("%s.widget" % child.lower() for child in children)}] } @@ -47,6 +58,8 @@ public struct TupleViewChildren${i + 1}<${struct_type_parameters}>: ViewGraphNod ] } + var stackLayoutCache = StackLayoutCache() + % for child in children: /// ``AnyViewGraphNode`` is used instead of ``ViewGraphNode`` because otherwise the backend leaks into views. public var ${child.lower()}: AnyViewGraphNode<${child}> diff --git a/Sources/SwiftCrossUI/Views/TypeSafeView.swift b/Sources/SwiftCrossUI/Views/TypeSafeView.swift index 3724688e92..fb218a76c1 100644 --- a/Sources/SwiftCrossUI/Views/TypeSafeView.swift +++ b/Sources/SwiftCrossUI/Views/TypeSafeView.swift @@ -25,7 +25,7 @@ protocol TypeSafeView: View { func computeLayout( _ widget: Backend.Widget, children: Children, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult @@ -79,7 +79,7 @@ extension TypeSafeView { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { diff --git a/Sources/SwiftCrossUI/Views/VStack.swift b/Sources/SwiftCrossUI/Views/VStack.swift index e621aee0f0..f3917a2a4b 100644 --- a/Sources/SwiftCrossUI/Views/VStack.swift +++ b/Sources/SwiftCrossUI/Views/VStack.swift @@ -42,13 +42,21 @@ public struct VStack: View { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { - return LayoutSystem.computeStackLayout( + if !(children is TupleViewChildren) { + // TODO: Make layout caching a ViewGraphNode feature so that we can handle + // these edge cases without a second thought. Would also make introducing + // a port of SwiftUI's Layout protocol much easier. + print("warning: VStack will not function correctly non-TupleView Content") + } + var cache = (children as? TupleViewChildren)?.stackLayoutCache ?? StackLayoutCache() + let result = LayoutSystem.computeStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), + cache: &cache, proposedSize: proposedSize, environment: environment @@ -57,6 +65,8 @@ public struct VStack: View { .with(\.layoutSpacing, spacing), backend: backend ) + (children as? TupleViewChildren)?.stackLayoutCache = cache + return result } public func commit( @@ -66,9 +76,11 @@ public struct VStack: View { environment: EnvironmentValues, backend: Backend ) { + var cache = (children as? TupleViewChildren)?.stackLayoutCache ?? StackLayoutCache() LayoutSystem.commitStackLayout( container: widget, children: layoutableChildren(backend: backend, children: children), + cache: &cache, layout: layout, environment: environment @@ -77,5 +89,6 @@ public struct VStack: View { .with(\.layoutSpacing, spacing), backend: backend ) + (children as? TupleViewChildren)?.stackLayoutCache = cache } } diff --git a/Sources/SwiftCrossUI/Views/View.swift b/Sources/SwiftCrossUI/Views/View.swift index e6a4fbfb64..562f025cfb 100644 --- a/Sources/SwiftCrossUI/Views/View.swift +++ b/Sources/SwiftCrossUI/Views/View.swift @@ -49,11 +49,13 @@ public protocol View { func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult + /// Commits the last computed layout to the underlying widget hierarchy. + /// `layout` is guaranteed to be the last value returned by ``computeLayout``. func commit( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, @@ -123,7 +125,7 @@ extension View { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -141,7 +143,7 @@ extension View { public func defaultComputeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { diff --git a/Sources/SwiftCrossUI/Views/WebView.swift b/Sources/SwiftCrossUI/Views/WebView.swift deleted file mode 100644 index 116e990da6..0000000000 --- a/Sources/SwiftCrossUI/Views/WebView.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Foundation - -@available(tvOS, unavailable) -public struct WebView: ElementaryView { - @State var currentURL: URL? - @Binding var url: URL - - public init(_ url: Binding) { - _url = url - } - - func asWidget(backend: Backend) -> Backend.Widget { - backend.createWebView() - } - - func computeLayout( - _ widget: Backend.Widget, - proposedSize: SIMD2, - environment: EnvironmentValues, - backend: Backend - ) -> ViewLayoutResult { - return ViewLayoutResult( - size: ViewSize( - size: proposedSize, - idealSize: SIMD2(10, 10), - minimumWidth: 0, - minimumHeight: 0, - maximumWidth: nil, - maximumHeight: nil - ), - childResults: [] - ) - } - - func commit( - _ widget: Backend.Widget, - layout: ViewLayoutResult, - environment: EnvironmentValues, - backend: Backend - ) { - if url != currentURL { - backend.navigateWebView(widget, to: url) - currentURL = url - } - backend.updateWebView(widget, environment: environment) { destination in - currentURL = destination - url = destination - } - backend.setSize(of: widget, to: layout.size.size) - } -} diff --git a/Sources/SwiftCrossUI/Views/ZStack.swift b/Sources/SwiftCrossUI/Views/ZStack.swift index 3bc815a6db..87884604bc 100644 --- a/Sources/SwiftCrossUI/Views/ZStack.swift +++ b/Sources/SwiftCrossUI/Views/ZStack.swift @@ -31,7 +31,7 @@ public struct ZStack: View { public func computeLayout( _ widget: Backend.Widget, children: any ViewGraphNodeChildren, - proposedSize: SIMD2, + proposedSize: ProposedViewSize, environment: EnvironmentValues, backend: Backend ) -> ViewLayoutResult { @@ -43,20 +43,9 @@ public struct ZStack: View { ) } - let childSizes = childResults.map(\.size) let size = ViewSize( - size: SIMD2( - childSizes.map(\.size.x).max() ?? 0, - childSizes.map(\.size.y).max() ?? 0 - ), - idealSize: SIMD2( - childSizes.map(\.idealSize.x).max() ?? 0, - childSizes.map(\.idealSize.y).max() ?? 0 - ), - minimumWidth: childSizes.map(\.minimumWidth).max() ?? 0, - minimumHeight: childSizes.map(\.minimumHeight).max() ?? 0, - maximumWidth: childSizes.map(\.maximumWidth).max() ?? 0, - maximumHeight: childSizes.map(\.maximumHeight).max() ?? 0 + childResults.map(\.size.width).max() ?? 0, + childResults.map(\.size.height).max() ?? 0 ) return ViewLayoutResult(size: size, childResults: childResults) @@ -77,12 +66,12 @@ public struct ZStack: View { for (i, child) in children.enumerated() { let position = alignment.position( - ofChild: child.size.size, - in: size.size + ofChild: child.size.vector, + in: size.vector ) backend.setPosition(ofChildAt: i, in: widget, to: position) } - backend.setSize(of: widget, to: size.size) + backend.setSize(of: widget, to: size.vector) } }