@thelacanians/vue-native-cli 0.4.15 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +329 -15
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
- package/native/android/gradle.properties +1 -0
- package/native/android/gradlew +1 -1
- package/native/ios/VueNativeCore/Package.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
// MARK: - Layout Value
|
|
5
|
+
|
|
6
|
+
/// Represents a layout dimension value that can be points, percent, or auto.
|
|
7
|
+
public enum LayoutValue: Equatable {
|
|
8
|
+
case points(CGFloat)
|
|
9
|
+
case percent(CGFloat)
|
|
10
|
+
case auto
|
|
11
|
+
case undefined
|
|
12
|
+
|
|
13
|
+
var isUndefined: Bool {
|
|
14
|
+
if case .undefined = self { return true }
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func resolve(relativeTo parent: CGFloat) -> CGFloat? {
|
|
19
|
+
switch self {
|
|
20
|
+
case .points(let v): return v
|
|
21
|
+
case .percent(let v): return parent * v / 100.0
|
|
22
|
+
case .auto, .undefined: return nil
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// MARK: - Layout Enums
|
|
28
|
+
|
|
29
|
+
public enum FlexDirection {
|
|
30
|
+
case column, row, columnReverse, rowReverse
|
|
31
|
+
|
|
32
|
+
var isRow: Bool {
|
|
33
|
+
self == .row || self == .rowReverse
|
|
34
|
+
}
|
|
35
|
+
var isReverse: Bool {
|
|
36
|
+
self == .columnReverse || self == .rowReverse
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public enum JustifyContent {
|
|
41
|
+
case flexStart, flexEnd, center, spaceBetween, spaceAround, spaceEvenly
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public enum AlignItems {
|
|
45
|
+
case stretch, flexStart, flexEnd, center, baseline
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public enum AlignSelf {
|
|
49
|
+
case auto, stretch, flexStart, flexEnd, center, baseline
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public enum PositionType {
|
|
53
|
+
case relative, absolute
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public enum DisplayType {
|
|
57
|
+
case flex, none
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public enum FlexWrap {
|
|
61
|
+
case noWrap, wrap, wrapReverse
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MARK: - Edges
|
|
65
|
+
|
|
66
|
+
/// Stores inset values (top, right, bottom, left).
|
|
67
|
+
public struct EdgeInsets: Equatable {
|
|
68
|
+
public var top: CGFloat
|
|
69
|
+
public var right: CGFloat
|
|
70
|
+
public var bottom: CGFloat
|
|
71
|
+
public var left: CGFloat
|
|
72
|
+
|
|
73
|
+
public static let zero = EdgeInsets(top: 0, right: 0, bottom: 0, left: 0)
|
|
74
|
+
|
|
75
|
+
public var horizontal: CGFloat { left + right }
|
|
76
|
+
public var vertical: CGFloat { top + bottom }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// MARK: - LayoutNode
|
|
80
|
+
|
|
81
|
+
/// A simplified flexbox layout node. Stores flex properties and computes frames
|
|
82
|
+
/// for its children. Associates with an NSView via objc_setAssociatedObject.
|
|
83
|
+
///
|
|
84
|
+
/// This implements a subset of the CSS Flexbox specification sufficient for
|
|
85
|
+
/// Phase 1 (counter app): direction, justify, align, padding, margin, gap,
|
|
86
|
+
/// flex grow/shrink, and basic dimensions.
|
|
87
|
+
@MainActor
|
|
88
|
+
public final class LayoutNode {
|
|
89
|
+
|
|
90
|
+
// MARK: - Flex container properties
|
|
91
|
+
|
|
92
|
+
public var flexDirection: FlexDirection = .column
|
|
93
|
+
public var justifyContent: JustifyContent = .flexStart
|
|
94
|
+
public var alignItems: AlignItems = .stretch
|
|
95
|
+
public var alignContent: AlignItems = .stretch
|
|
96
|
+
public var flexWrap: FlexWrap = .noWrap
|
|
97
|
+
|
|
98
|
+
// MARK: - Flex item properties
|
|
99
|
+
|
|
100
|
+
public var flexGrow: CGFloat = 0
|
|
101
|
+
public var flexShrink: CGFloat = 1
|
|
102
|
+
public var flexBasis: LayoutValue = .undefined
|
|
103
|
+
public var alignSelf: AlignSelf = .auto
|
|
104
|
+
|
|
105
|
+
// MARK: - Dimensions
|
|
106
|
+
|
|
107
|
+
public var width: LayoutValue = .undefined
|
|
108
|
+
public var height: LayoutValue = .undefined
|
|
109
|
+
public var minWidth: LayoutValue = .undefined
|
|
110
|
+
public var minHeight: LayoutValue = .undefined
|
|
111
|
+
public var maxWidth: LayoutValue = .undefined
|
|
112
|
+
public var maxHeight: LayoutValue = .undefined
|
|
113
|
+
public var aspectRatio: CGFloat?
|
|
114
|
+
|
|
115
|
+
// MARK: - Spacing
|
|
116
|
+
|
|
117
|
+
public var padding: EdgeInsets = .zero
|
|
118
|
+
public var margin: EdgeInsets = .zero
|
|
119
|
+
|
|
120
|
+
/// Main axis gap between children (columnGap for row, rowGap for column).
|
|
121
|
+
public var gap: CGFloat = 0
|
|
122
|
+
public var rowGap: CGFloat?
|
|
123
|
+
public var columnGap: CGFloat?
|
|
124
|
+
|
|
125
|
+
// MARK: - Position
|
|
126
|
+
|
|
127
|
+
public var positionType: PositionType = .relative
|
|
128
|
+
public var positionTop: LayoutValue = .undefined
|
|
129
|
+
public var positionRight: LayoutValue = .undefined
|
|
130
|
+
public var positionBottom: LayoutValue = .undefined
|
|
131
|
+
public var positionLeft: LayoutValue = .undefined
|
|
132
|
+
|
|
133
|
+
// MARK: - Display
|
|
134
|
+
|
|
135
|
+
public var display: DisplayType = .flex
|
|
136
|
+
public var isEnabled: Bool = true
|
|
137
|
+
|
|
138
|
+
// MARK: - Direction (RTL/LTR)
|
|
139
|
+
|
|
140
|
+
public var layoutDirection: NSUserInterfaceLayoutDirection = .leftToRight
|
|
141
|
+
|
|
142
|
+
// MARK: - View association
|
|
143
|
+
|
|
144
|
+
weak var view: NSView?
|
|
145
|
+
var isDirty: Bool = true
|
|
146
|
+
|
|
147
|
+
// MARK: - Computed results
|
|
148
|
+
|
|
149
|
+
/// The computed frame after layout. Relative to parent's content area.
|
|
150
|
+
public var computedFrame: CGRect = .zero
|
|
151
|
+
|
|
152
|
+
// MARK: - Children
|
|
153
|
+
|
|
154
|
+
/// Returns the LayoutNode children by inspecting the view's subviews.
|
|
155
|
+
var children: [LayoutNode] {
|
|
156
|
+
guard let view = view else { return [] }
|
|
157
|
+
return view.subviews.compactMap { $0.layoutNode }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// MARK: - Mark dirty
|
|
161
|
+
|
|
162
|
+
public func markDirty() {
|
|
163
|
+
isDirty = true
|
|
164
|
+
view?.needsLayout = true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// MARK: - Layout algorithm
|
|
168
|
+
|
|
169
|
+
/// Perform layout calculation. Sets frames on all child views recursively.
|
|
170
|
+
/// - Parameters:
|
|
171
|
+
/// - availableWidth: The width available from the parent.
|
|
172
|
+
/// - availableHeight: The height available from the parent.
|
|
173
|
+
public func layout(availableWidth: CGFloat, availableHeight: CGFloat) {
|
|
174
|
+
guard display != .none else { return }
|
|
175
|
+
|
|
176
|
+
let resolvedWidth = width.resolve(relativeTo: availableWidth) ?? availableWidth
|
|
177
|
+
let resolvedHeight = height.resolve(relativeTo: availableHeight) ?? availableHeight
|
|
178
|
+
|
|
179
|
+
let constrainedWidth = constrain(resolvedWidth, min: minWidth.resolve(relativeTo: availableWidth), max: maxWidth.resolve(relativeTo: availableWidth))
|
|
180
|
+
let constrainedHeight = constrain(resolvedHeight, min: minHeight.resolve(relativeTo: availableHeight), max: maxHeight.resolve(relativeTo: availableHeight))
|
|
181
|
+
|
|
182
|
+
let contentWidth = constrainedWidth - padding.horizontal
|
|
183
|
+
let contentHeight = constrainedHeight - padding.vertical
|
|
184
|
+
|
|
185
|
+
// Separate children into relative (flex) and absolute positioned
|
|
186
|
+
let allChildren = children
|
|
187
|
+
let relativeChildren = allChildren.filter { $0.positionType == .relative && $0.display != .none }
|
|
188
|
+
let absoluteChildren = allChildren.filter { $0.positionType == .absolute && $0.display != .none }
|
|
189
|
+
|
|
190
|
+
// Compute main axis gap
|
|
191
|
+
let mainGap: CGFloat
|
|
192
|
+
if flexDirection.isRow {
|
|
193
|
+
mainGap = columnGap ?? gap
|
|
194
|
+
} else {
|
|
195
|
+
mainGap = rowGap ?? gap
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Phase 1: Measure children along main axis (hypothetical size)
|
|
199
|
+
let isRow = flexDirection.isRow
|
|
200
|
+
let mainSize = isRow ? contentWidth : contentHeight
|
|
201
|
+
let crossSize = isRow ? contentHeight : contentWidth
|
|
202
|
+
|
|
203
|
+
struct ChildMeasure {
|
|
204
|
+
let node: LayoutNode
|
|
205
|
+
var mainHypothetical: CGFloat
|
|
206
|
+
var crossHypothetical: CGFloat
|
|
207
|
+
var flexBasis: CGFloat
|
|
208
|
+
var mainFinal: CGFloat = 0
|
|
209
|
+
var crossFinal: CGFloat = 0
|
|
210
|
+
var mainMarginBefore: CGFloat
|
|
211
|
+
var mainMarginAfter: CGFloat
|
|
212
|
+
var crossMarginBefore: CGFloat
|
|
213
|
+
var crossMarginAfter: CGFloat
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
var measures: [ChildMeasure] = relativeChildren.map { child in
|
|
217
|
+
// Resolve flex basis
|
|
218
|
+
let basis: CGFloat
|
|
219
|
+
if let b = child.flexBasis.resolve(relativeTo: mainSize), !child.flexBasis.isUndefined {
|
|
220
|
+
basis = b
|
|
221
|
+
} else if isRow, let w = child.width.resolve(relativeTo: contentWidth) {
|
|
222
|
+
basis = w
|
|
223
|
+
} else if !isRow, let h = child.height.resolve(relativeTo: contentHeight) {
|
|
224
|
+
basis = h
|
|
225
|
+
} else {
|
|
226
|
+
// Content-based sizing: use the view's fittingSize as an estimate
|
|
227
|
+
basis = 0
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let crossHyp: CGFloat
|
|
231
|
+
if isRow {
|
|
232
|
+
crossHyp = child.height.resolve(relativeTo: contentHeight) ?? crossSize
|
|
233
|
+
} else {
|
|
234
|
+
crossHyp = child.width.resolve(relativeTo: contentWidth) ?? crossSize
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let mainMarginBefore: CGFloat
|
|
238
|
+
let mainMarginAfter: CGFloat
|
|
239
|
+
let crossMarginBefore: CGFloat
|
|
240
|
+
let crossMarginAfter: CGFloat
|
|
241
|
+
if isRow {
|
|
242
|
+
mainMarginBefore = child.margin.left
|
|
243
|
+
mainMarginAfter = child.margin.right
|
|
244
|
+
crossMarginBefore = child.margin.top
|
|
245
|
+
crossMarginAfter = child.margin.bottom
|
|
246
|
+
} else {
|
|
247
|
+
mainMarginBefore = child.margin.top
|
|
248
|
+
mainMarginAfter = child.margin.bottom
|
|
249
|
+
crossMarginBefore = child.margin.left
|
|
250
|
+
crossMarginAfter = child.margin.right
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return ChildMeasure(
|
|
254
|
+
node: child,
|
|
255
|
+
mainHypothetical: basis,
|
|
256
|
+
crossHypothetical: crossHyp,
|
|
257
|
+
flexBasis: basis,
|
|
258
|
+
mainMarginBefore: mainMarginBefore,
|
|
259
|
+
mainMarginAfter: mainMarginAfter,
|
|
260
|
+
crossMarginBefore: crossMarginBefore,
|
|
261
|
+
crossMarginAfter: crossMarginAfter
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Phase 2: Flex grow/shrink
|
|
266
|
+
let totalGaps = measures.count > 1 ? mainGap * CGFloat(measures.count - 1) : 0
|
|
267
|
+
let totalMargins = measures.reduce(CGFloat(0)) { $0 + $1.mainMarginBefore + $1.mainMarginAfter }
|
|
268
|
+
let usedSpace = measures.reduce(CGFloat(0)) { $0 + $1.flexBasis } + totalGaps + totalMargins
|
|
269
|
+
let freeSpace = mainSize - usedSpace
|
|
270
|
+
|
|
271
|
+
let totalGrow = measures.reduce(CGFloat(0)) { $0 + $1.node.flexGrow }
|
|
272
|
+
let totalShrink = measures.reduce(CGFloat(0)) { $0 + ($1.node.flexShrink * $1.flexBasis) }
|
|
273
|
+
|
|
274
|
+
for i in measures.indices {
|
|
275
|
+
if freeSpace > 0 && totalGrow > 0 {
|
|
276
|
+
measures[i].mainFinal = measures[i].flexBasis + (freeSpace * measures[i].node.flexGrow / totalGrow)
|
|
277
|
+
} else if freeSpace < 0 && totalShrink > 0 {
|
|
278
|
+
let shrinkRatio = (measures[i].node.flexShrink * measures[i].flexBasis) / totalShrink
|
|
279
|
+
measures[i].mainFinal = measures[i].flexBasis + (freeSpace * shrinkRatio)
|
|
280
|
+
} else {
|
|
281
|
+
measures[i].mainFinal = measures[i].flexBasis
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Clamp to min/max
|
|
285
|
+
let child = measures[i].node
|
|
286
|
+
if isRow {
|
|
287
|
+
measures[i].mainFinal = constrain(measures[i].mainFinal, min: child.minWidth.resolve(relativeTo: contentWidth), max: child.maxWidth.resolve(relativeTo: contentWidth))
|
|
288
|
+
} else {
|
|
289
|
+
measures[i].mainFinal = constrain(measures[i].mainFinal, min: child.minHeight.resolve(relativeTo: contentHeight), max: child.maxHeight.resolve(relativeTo: contentHeight))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Ensure non-negative
|
|
293
|
+
measures[i].mainFinal = max(0, measures[i].mainFinal)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Phase 3: Cross axis sizing (alignItems)
|
|
297
|
+
for i in measures.indices {
|
|
298
|
+
let child = measures[i].node
|
|
299
|
+
let resolvedAlign = child.alignSelf == .auto ? alignItems : alignSelfToAlignItems(child.alignSelf)
|
|
300
|
+
|
|
301
|
+
if resolvedAlign == .stretch {
|
|
302
|
+
measures[i].crossFinal = crossSize - measures[i].crossMarginBefore - measures[i].crossMarginAfter
|
|
303
|
+
} else {
|
|
304
|
+
measures[i].crossFinal = measures[i].crossHypothetical
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Apply aspect ratio
|
|
308
|
+
if let ar = child.aspectRatio, ar > 0 {
|
|
309
|
+
if isRow {
|
|
310
|
+
measures[i].crossFinal = measures[i].mainFinal / ar
|
|
311
|
+
} else {
|
|
312
|
+
measures[i].crossFinal = measures[i].mainFinal * ar
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Clamp cross to min/max
|
|
317
|
+
if isRow {
|
|
318
|
+
measures[i].crossFinal = constrain(measures[i].crossFinal, min: child.minHeight.resolve(relativeTo: contentHeight), max: child.maxHeight.resolve(relativeTo: contentHeight))
|
|
319
|
+
} else {
|
|
320
|
+
measures[i].crossFinal = constrain(measures[i].crossFinal, min: child.minWidth.resolve(relativeTo: contentWidth), max: child.maxWidth.resolve(relativeTo: contentWidth))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
measures[i].crossFinal = max(0, measures[i].crossFinal)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Phase 4: Main axis positioning (justifyContent)
|
|
327
|
+
let totalChildMainSize = measures.reduce(CGFloat(0)) { $0 + $1.mainFinal + $1.mainMarginBefore + $1.mainMarginAfter }
|
|
328
|
+
let remainingMain = mainSize - totalChildMainSize - totalGaps
|
|
329
|
+
|
|
330
|
+
var mainOffset: CGFloat = 0
|
|
331
|
+
var mainSpacing: CGFloat = mainGap
|
|
332
|
+
|
|
333
|
+
switch justifyContent {
|
|
334
|
+
case .flexStart:
|
|
335
|
+
mainOffset = 0
|
|
336
|
+
case .flexEnd:
|
|
337
|
+
mainOffset = remainingMain
|
|
338
|
+
case .center:
|
|
339
|
+
mainOffset = remainingMain / 2
|
|
340
|
+
case .spaceBetween:
|
|
341
|
+
mainOffset = 0
|
|
342
|
+
if measures.count > 1 {
|
|
343
|
+
mainSpacing = mainGap + remainingMain / CGFloat(measures.count - 1)
|
|
344
|
+
}
|
|
345
|
+
case .spaceAround:
|
|
346
|
+
let space = remainingMain / CGFloat(measures.count)
|
|
347
|
+
mainOffset = space / 2
|
|
348
|
+
mainSpacing = mainGap + space
|
|
349
|
+
case .spaceEvenly:
|
|
350
|
+
let space = remainingMain / CGFloat(measures.count + 1)
|
|
351
|
+
mainOffset = space
|
|
352
|
+
mainSpacing = mainGap + space
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Phase 5: Position children
|
|
356
|
+
var currentMain = mainOffset
|
|
357
|
+
let orderedMeasures = flexDirection.isReverse ? measures.reversed() : Array(measures)
|
|
358
|
+
|
|
359
|
+
for measure in orderedMeasures {
|
|
360
|
+
let child = measure.node
|
|
361
|
+
currentMain += measure.mainMarginBefore
|
|
362
|
+
|
|
363
|
+
let resolvedAlign = child.alignSelf == .auto ? alignItems : alignSelfToAlignItems(child.alignSelf)
|
|
364
|
+
|
|
365
|
+
let crossOffset: CGFloat
|
|
366
|
+
switch resolvedAlign {
|
|
367
|
+
case .flexStart:
|
|
368
|
+
crossOffset = measure.crossMarginBefore
|
|
369
|
+
case .flexEnd:
|
|
370
|
+
crossOffset = crossSize - measure.crossFinal - measure.crossMarginAfter
|
|
371
|
+
case .center:
|
|
372
|
+
crossOffset = (crossSize - measure.crossFinal) / 2
|
|
373
|
+
case .stretch, .baseline:
|
|
374
|
+
crossOffset = measure.crossMarginBefore
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let x: CGFloat
|
|
378
|
+
let y: CGFloat
|
|
379
|
+
let w: CGFloat
|
|
380
|
+
let h: CGFloat
|
|
381
|
+
|
|
382
|
+
if isRow {
|
|
383
|
+
x = padding.left + currentMain
|
|
384
|
+
y = padding.top + crossOffset
|
|
385
|
+
w = measure.mainFinal
|
|
386
|
+
h = measure.crossFinal
|
|
387
|
+
} else {
|
|
388
|
+
x = padding.left + crossOffset
|
|
389
|
+
y = padding.top + currentMain
|
|
390
|
+
w = measure.crossFinal
|
|
391
|
+
h = measure.mainFinal
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
child.computedFrame = CGRect(x: x, y: y, width: w, height: h)
|
|
395
|
+
child.view?.frame = child.computedFrame
|
|
396
|
+
|
|
397
|
+
// Recurse into child
|
|
398
|
+
child.layout(availableWidth: w, availableHeight: h)
|
|
399
|
+
|
|
400
|
+
currentMain += measure.mainFinal + measure.mainMarginAfter + mainSpacing
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Phase 6: Absolute positioned children
|
|
404
|
+
for child in absoluteChildren {
|
|
405
|
+
let childW = child.width.resolve(relativeTo: constrainedWidth) ?? 0
|
|
406
|
+
let childH = child.height.resolve(relativeTo: constrainedHeight) ?? 0
|
|
407
|
+
|
|
408
|
+
var x: CGFloat = padding.left
|
|
409
|
+
var y: CGFloat = padding.top
|
|
410
|
+
|
|
411
|
+
if let left = child.positionLeft.resolve(relativeTo: constrainedWidth) {
|
|
412
|
+
x = left + child.margin.left
|
|
413
|
+
} else if let right = child.positionRight.resolve(relativeTo: constrainedWidth) {
|
|
414
|
+
x = constrainedWidth - right - childW - child.margin.right
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if let top = child.positionTop.resolve(relativeTo: constrainedHeight) {
|
|
418
|
+
y = top + child.margin.top
|
|
419
|
+
} else if let bottom = child.positionBottom.resolve(relativeTo: constrainedHeight) {
|
|
420
|
+
y = constrainedHeight - bottom - childH - child.margin.bottom
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
child.computedFrame = CGRect(x: x, y: y, width: childW, height: childH)
|
|
424
|
+
child.view?.frame = child.computedFrame
|
|
425
|
+
|
|
426
|
+
child.layout(availableWidth: childW, availableHeight: childH)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
isDirty = false
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// MARK: - Helpers
|
|
433
|
+
|
|
434
|
+
private func constrain(_ value: CGFloat, min: CGFloat?, max: CGFloat?) -> CGFloat {
|
|
435
|
+
var result = value
|
|
436
|
+
if let min = min { result = Swift.max(result, min) }
|
|
437
|
+
if let max = max { result = Swift.min(result, max) }
|
|
438
|
+
return result
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private func alignSelfToAlignItems(_ alignSelf: AlignSelf) -> AlignItems {
|
|
442
|
+
switch alignSelf {
|
|
443
|
+
case .auto: return .stretch
|
|
444
|
+
case .stretch: return .stretch
|
|
445
|
+
case .flexStart: return .flexStart
|
|
446
|
+
case .flexEnd: return .flexEnd
|
|
447
|
+
case .center: return .center
|
|
448
|
+
case .baseline: return .baseline
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// MARK: - NSView extension
|
|
454
|
+
|
|
455
|
+
private var layoutNodeKey: UInt8 = 0
|
|
456
|
+
|
|
457
|
+
extension NSView {
|
|
458
|
+
/// Access the LayoutNode associated with this view. Creates one on first access.
|
|
459
|
+
public var layoutNode: LayoutNode? {
|
|
460
|
+
get {
|
|
461
|
+
objc_getAssociatedObject(self, &layoutNodeKey) as? LayoutNode
|
|
462
|
+
}
|
|
463
|
+
set {
|
|
464
|
+
objc_setAssociatedObject(self, &layoutNodeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
465
|
+
newValue?.view = self
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/// Convenience: returns or creates a LayoutNode for this view.
|
|
470
|
+
@discardableResult
|
|
471
|
+
public func ensureLayoutNode() -> LayoutNode {
|
|
472
|
+
if let existing = layoutNode { return existing }
|
|
473
|
+
let node = LayoutNode()
|
|
474
|
+
self.layoutNode = node
|
|
475
|
+
return node
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// MARK: - Percentage postfix operator
|
|
480
|
+
|
|
481
|
+
postfix operator %
|
|
482
|
+
|
|
483
|
+
public postfix func % (value: CGFloat) -> LayoutValue {
|
|
484
|
+
return .percent(value)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
public postfix func % (value: Int) -> LayoutValue {
|
|
488
|
+
return .percent(CGFloat(value))
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
public postfix func % (value: Double) -> LayoutValue {
|
|
492
|
+
return .percent(CGFloat(value))
|
|
493
|
+
}
|