craft-native 0.0.187 → 0.0.189
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/android/templates/CraftNative.kt.template +34 -0
- package/dist/android/templates/CraftNativeCapabilities.kt.template +92 -9
- package/dist/android/templates/CraftNativeShell.kt.template +14 -2
- package/dist/android/templates/MainActivityNative.kt.template +312 -24
- package/dist/browser/dialogs.d.ts +20 -0
- package/dist/browser/storage.d.ts +31 -0
- package/dist/browser.d.ts +22 -0
- package/dist/browser.js +705 -0
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/templates/CraftHybrid.swift +36 -4
- package/dist/ios/templates/CraftNativeActions.swift +16 -4
- package/dist/ios/templates/CraftNativeFiles.swift +131 -25
- package/dist/ios/templates/CraftNativeScreen.swift +454 -3
- package/dist/macos/templates/native_layout.zig +299 -6
- package/package.json +9 -1
|
@@ -135,22 +135,105 @@ private enum CraftNativeTreeLayoutBridge {
|
|
|
135
135
|
var childCount: UInt32
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
private struct
|
|
138
|
+
private struct StyleInputV2 {
|
|
139
|
+
var direction: UInt32 = 0
|
|
140
|
+
var width: Float = .nan
|
|
141
|
+
var height: Float = .nan
|
|
142
|
+
var intrinsicWidth: Float = .nan
|
|
143
|
+
var intrinsicHeight: Float = .nan
|
|
144
|
+
var baseline: Float = .nan
|
|
145
|
+
var aspectRatio: Float = .nan
|
|
146
|
+
var flexBasis: Float = .nan
|
|
147
|
+
var flexGrow: Float = 0
|
|
148
|
+
var flexShrink: Float = 0
|
|
149
|
+
var minWidth: Float = .nan
|
|
150
|
+
var maxWidth: Float = .nan
|
|
151
|
+
var minHeight: Float = .nan
|
|
152
|
+
var maxHeight: Float = .nan
|
|
153
|
+
var gap: Float = 0
|
|
154
|
+
var justifyContent: UInt32 = 0
|
|
155
|
+
var alignItems: UInt32 = 3
|
|
156
|
+
var alignSelf: UInt32 = 0
|
|
157
|
+
var flexWrap: UInt32 = 0
|
|
158
|
+
var alignContent: UInt32 = 6
|
|
159
|
+
var marginTop: Float = 0
|
|
160
|
+
var marginRight: Float = 0
|
|
161
|
+
var marginBottom: Float = 0
|
|
162
|
+
var marginLeft: Float = 0
|
|
163
|
+
var paddingTop: Float = 0
|
|
164
|
+
var paddingRight: Float = 0
|
|
165
|
+
var paddingBottom: Float = 0
|
|
166
|
+
var paddingLeft: Float = 0
|
|
167
|
+
var grid: UInt32 = 0
|
|
168
|
+
var gridColumns: UInt32 = 1
|
|
169
|
+
var rowGap: Float = 0
|
|
170
|
+
var columnGap: Float = 0
|
|
171
|
+
var reverse: UInt32 = 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private struct NodeInputV2 {
|
|
175
|
+
var style: StyleInputV2
|
|
176
|
+
var firstChild: UInt32
|
|
177
|
+
var childCount: UInt32
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fileprivate struct Frame {
|
|
139
181
|
var x: Float
|
|
140
182
|
var y: Float
|
|
141
183
|
var width: Float
|
|
142
184
|
var height: Float
|
|
143
185
|
}
|
|
144
186
|
|
|
187
|
+
fileprivate struct TreeNode {
|
|
188
|
+
let view: UIView?
|
|
189
|
+
let style: CraftNativeLayoutStyle
|
|
190
|
+
let intrinsic: CGSize
|
|
191
|
+
let baseline: CGFloat
|
|
192
|
+
let direction: NSLayoutConstraint.Axis
|
|
193
|
+
let gap: CGFloat
|
|
194
|
+
let rowGap: CGFloat
|
|
195
|
+
let columnGap: CGFloat
|
|
196
|
+
let wrap: Bool
|
|
197
|
+
let justifyContent: String
|
|
198
|
+
let alignItems: String
|
|
199
|
+
let alignContent: String
|
|
200
|
+
let padding: UIEdgeInsets
|
|
201
|
+
let grid: Bool
|
|
202
|
+
let gridColumns: Int
|
|
203
|
+
let reverse: Bool
|
|
204
|
+
let wrapReverse: Bool
|
|
205
|
+
let children: [TreeNode]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
fileprivate struct TreeResult {
|
|
209
|
+
let nodes: [TreeNode]
|
|
210
|
+
let parents: [Int]
|
|
211
|
+
let frames: [Frame]
|
|
212
|
+
}
|
|
213
|
+
|
|
145
214
|
private typealias Function = @convention(c) (
|
|
146
215
|
Float, Float, UnsafeRawPointer, Int, UnsafeMutableRawPointer, Int
|
|
147
216
|
) -> Int32
|
|
148
217
|
|
|
218
|
+
private typealias FunctionV2 = @convention(c) (
|
|
219
|
+
Float, Float, UnsafeRawPointer, Int, UnsafeMutableRawPointer, Int
|
|
220
|
+
) -> Int32
|
|
221
|
+
|
|
149
222
|
private static let function: Function? = {
|
|
150
223
|
guard let symbol = dlsym(UnsafeMutableRawPointer(bitPattern: -2), "craft_native_layout_tree") else { return nil }
|
|
151
224
|
return unsafeBitCast(symbol, to: Function.self)
|
|
152
225
|
}()
|
|
153
226
|
|
|
227
|
+
private static let functionV2: FunctionV2? = {
|
|
228
|
+
guard let symbol = dlsym(UnsafeMutableRawPointer(bitPattern: -2), "craft_native_layout_tree_v2") else { return nil }
|
|
229
|
+
return unsafeBitCast(symbol, to: FunctionV2.self)
|
|
230
|
+
}()
|
|
231
|
+
|
|
232
|
+
private static let functionV3: FunctionV2? = {
|
|
233
|
+
guard let symbol = dlsym(UnsafeMutableRawPointer(bitPattern: -2), "craft_native_layout_tree_v3") else { return nil }
|
|
234
|
+
return unsafeBitCast(symbol, to: FunctionV2.self)
|
|
235
|
+
}()
|
|
236
|
+
|
|
154
237
|
private static func finite(_ value: CGFloat?) -> Float {
|
|
155
238
|
guard let value, value.isFinite else { return .nan }
|
|
156
239
|
return Float(value)
|
|
@@ -260,6 +343,199 @@ private enum CraftNativeTreeLayoutBridge {
|
|
|
260
343
|
(child.0, CGRect(x: CGFloat(frame.x), y: CGFloat(frame.y), width: CGFloat(frame.width), height: CGFloat(frame.height)))
|
|
261
344
|
}
|
|
262
345
|
}
|
|
346
|
+
|
|
347
|
+
fileprivate static func treeFrames(root: TreeNode, width: CGFloat, height: CGFloat) -> TreeResult? {
|
|
348
|
+
guard let function, width.isFinite, height.isFinite, width >= 0, height >= 0, !root.children.isEmpty else { return nil }
|
|
349
|
+
if containsWrapReverse(root) {
|
|
350
|
+
return treeFramesV2(root: root, width: width, height: height, function: functionV3)
|
|
351
|
+
}
|
|
352
|
+
if containsReverse(root) {
|
|
353
|
+
return treeFramesV2(root: root, width: width, height: height, function: functionV2)
|
|
354
|
+
}
|
|
355
|
+
var nodes: [TreeNode] = []
|
|
356
|
+
var parents: [Int] = []
|
|
357
|
+
func append(_ node: TreeNode, parent: Int) {
|
|
358
|
+
let index = nodes.count
|
|
359
|
+
nodes.append(node)
|
|
360
|
+
parents.append(parent)
|
|
361
|
+
node.children.forEach { append($0, parent: index) }
|
|
362
|
+
}
|
|
363
|
+
append(root, parent: -1)
|
|
364
|
+
|
|
365
|
+
func put(_ input: inout StyleInput, _ node: TreeNode, parentWidth: CGFloat, parentHeight: CGFloat, rootNode: Bool) {
|
|
366
|
+
let contentWidth = max(0, (rootNode ? width : node.intrinsic.width) - node.padding.left - node.padding.right)
|
|
367
|
+
let contentHeight = max(0, (rootNode ? height : node.intrinsic.height) - node.padding.top - node.padding.bottom)
|
|
368
|
+
let mainSize = node.direction == .horizontal ? contentWidth : contentHeight
|
|
369
|
+
input.direction = node.direction == .horizontal ? 0 : 1
|
|
370
|
+
input.width = rootNode ? Float(width) : length(node.style.width, base: parentWidth)
|
|
371
|
+
input.height = rootNode ? Float(height) : length(node.style.height, base: parentHeight)
|
|
372
|
+
input.intrinsicWidth = rootNode ? .nan : Float(max(0, node.intrinsic.width))
|
|
373
|
+
input.intrinsicHeight = rootNode ? .nan : Float(max(0, node.intrinsic.height))
|
|
374
|
+
input.baseline = rootNode ? .nan : Float(max(0, node.baseline))
|
|
375
|
+
input.aspectRatio = finite(node.style.aspectRatio)
|
|
376
|
+
input.flexBasis = length(node.style.flexBasis, base: mainSize)
|
|
377
|
+
input.flexGrow = rootNode ? 0 : Float(max(0, node.style.flexGrow))
|
|
378
|
+
let defaultShrink: CGFloat = node.view is UIScrollView || (node.view is UILabel && node.direction == .horizontal) ? 1 : 0
|
|
379
|
+
input.flexShrink = rootNode ? 0 : Float(max(0, node.style.flexShrink ?? defaultShrink))
|
|
380
|
+
input.minWidth = length(node.style.minWidth, base: contentWidth)
|
|
381
|
+
input.maxWidth = length(node.style.maxWidth, base: contentWidth)
|
|
382
|
+
input.minHeight = length(node.style.minHeight, base: contentHeight)
|
|
383
|
+
input.maxHeight = length(node.style.maxHeight, base: contentHeight)
|
|
384
|
+
input.gap = Float(max(0, node.gap))
|
|
385
|
+
input.justifyContent = justify(node.justifyContent)
|
|
386
|
+
input.alignItems = alignment(node.alignItems)
|
|
387
|
+
input.alignSelf = rootNode ? 0 : alignSelf(node.style.alignSelf)
|
|
388
|
+
input.flexWrap = node.wrap ? 1 : 0
|
|
389
|
+
input.alignContent = craftNativeAlignContentCode(node.alignContent)
|
|
390
|
+
input.marginTop = Float(max(0, node.style.marginTop))
|
|
391
|
+
input.marginRight = Float(max(0, node.style.marginRight))
|
|
392
|
+
input.marginBottom = Float(max(0, node.style.marginBottom))
|
|
393
|
+
input.marginLeft = Float(max(0, node.style.marginLeft))
|
|
394
|
+
input.paddingTop = Float(max(0, node.padding.top))
|
|
395
|
+
input.paddingRight = Float(max(0, node.padding.right))
|
|
396
|
+
input.paddingBottom = Float(max(0, node.padding.bottom))
|
|
397
|
+
input.paddingLeft = Float(max(0, node.padding.left))
|
|
398
|
+
input.grid = node.grid ? 1 : 0
|
|
399
|
+
input.gridColumns = UInt32(max(1, node.gridColumns))
|
|
400
|
+
input.rowGap = Float(max(0, node.rowGap))
|
|
401
|
+
input.columnGap = Float(max(0, node.columnGap))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
var styles = [StyleInput](repeating: StyleInput(), count: nodes.count)
|
|
405
|
+
var metadata = [Float](repeating: 0, count: nodes.count * 2)
|
|
406
|
+
var cursor = 0
|
|
407
|
+
func write(_ node: TreeNode, parentWidth: CGFloat, parentHeight: CGFloat, rootNode: Bool) {
|
|
408
|
+
let index = cursor
|
|
409
|
+
cursor += 1
|
|
410
|
+
put(&styles[index], node, parentWidth: parentWidth, parentHeight: parentHeight, rootNode: rootNode)
|
|
411
|
+
let contentWidth = max(0, (rootNode ? width : node.intrinsic.width) - node.padding.left - node.padding.right)
|
|
412
|
+
let contentHeight = max(0, (rootNode ? height : node.intrinsic.height) - node.padding.top - node.padding.bottom)
|
|
413
|
+
let firstChild = cursor
|
|
414
|
+
node.children.forEach { write($0, parentWidth: contentWidth, parentHeight: contentHeight, rootNode: false) }
|
|
415
|
+
metadata[index * 2] = Float(firstChild)
|
|
416
|
+
metadata[index * 2 + 1] = Float(cursor - firstChild)
|
|
417
|
+
}
|
|
418
|
+
write(root, parentWidth: width, parentHeight: height, rootNode: true)
|
|
419
|
+
let inputs = zip(styles, stride(from: 0, to: metadata.count, by: 2)).map { style, offset in
|
|
420
|
+
NodeInput(style: style, firstChild: UInt32(metadata[offset]), childCount: UInt32(metadata[offset + 1]))
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
var output = [Frame](repeating: Frame(x: 0, y: 0, width: 0, height: 0), count: nodes.count)
|
|
424
|
+
let frameCount = output.count
|
|
425
|
+
let status = inputs.withUnsafeBufferPointer { inputBuffer in
|
|
426
|
+
metadata.withUnsafeBufferPointer { metadataBuffer in
|
|
427
|
+
output.withUnsafeMutableBufferPointer { outputBuffer in
|
|
428
|
+
function(
|
|
429
|
+
Float(width), Float(height), UnsafeRawPointer(inputBuffer.baseAddress!), inputs.count,
|
|
430
|
+
UnsafeMutableRawPointer(outputBuffer.baseAddress!), frameCount
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
guard status == 0, output.allSatisfy({ frame in
|
|
436
|
+
frame.x.isFinite && frame.y.isFinite && frame.width.isFinite && frame.height.isFinite &&
|
|
437
|
+
frame.width >= 0 && frame.height >= 0
|
|
438
|
+
}) else { return nil }
|
|
439
|
+
return TreeResult(nodes: nodes, parents: parents, frames: output)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private static func containsReverse(_ node: TreeNode) -> Bool {
|
|
443
|
+
node.reverse || node.children.contains(where: containsReverse)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private static func containsWrapReverse(_ node: TreeNode) -> Bool {
|
|
447
|
+
node.wrapReverse || node.children.contains(where: containsWrapReverse)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private static func treeFramesV2(root: TreeNode, width: CGFloat, height: CGFloat, function: FunctionV2?) -> TreeResult? {
|
|
451
|
+
guard let function, width.isFinite, height.isFinite, width >= 0, height >= 0, !root.children.isEmpty else { return nil }
|
|
452
|
+
var nodes: [TreeNode] = []
|
|
453
|
+
var parents: [Int] = []
|
|
454
|
+
func append(_ node: TreeNode, parent: Int) {
|
|
455
|
+
let index = nodes.count
|
|
456
|
+
nodes.append(node)
|
|
457
|
+
parents.append(parent)
|
|
458
|
+
node.children.forEach { append($0, parent: index) }
|
|
459
|
+
}
|
|
460
|
+
append(root, parent: -1)
|
|
461
|
+
|
|
462
|
+
func put(_ input: inout StyleInputV2, _ node: TreeNode, parentWidth: CGFloat, parentHeight: CGFloat, rootNode: Bool) {
|
|
463
|
+
let contentWidth = max(0, (rootNode ? width : node.intrinsic.width) - node.padding.left - node.padding.right)
|
|
464
|
+
let contentHeight = max(0, (rootNode ? height : node.intrinsic.height) - node.padding.top - node.padding.bottom)
|
|
465
|
+
let mainSize = node.direction == .horizontal ? contentWidth : contentHeight
|
|
466
|
+
input.direction = node.direction == .horizontal ? 0 : 1
|
|
467
|
+
input.width = rootNode ? Float(width) : length(node.style.width, base: parentWidth)
|
|
468
|
+
input.height = rootNode ? Float(height) : length(node.style.height, base: parentHeight)
|
|
469
|
+
input.intrinsicWidth = rootNode ? .nan : Float(max(0, node.intrinsic.width))
|
|
470
|
+
input.intrinsicHeight = rootNode ? .nan : Float(max(0, node.intrinsic.height))
|
|
471
|
+
input.baseline = rootNode ? .nan : Float(max(0, node.baseline))
|
|
472
|
+
input.aspectRatio = finite(node.style.aspectRatio)
|
|
473
|
+
input.flexBasis = length(node.style.flexBasis, base: mainSize)
|
|
474
|
+
input.flexGrow = rootNode ? 0 : Float(max(0, node.style.flexGrow))
|
|
475
|
+
let defaultShrink: CGFloat = node.view is UIScrollView || (node.view is UILabel && node.direction == .horizontal) ? 1 : 0
|
|
476
|
+
input.flexShrink = rootNode ? 0 : Float(max(0, node.style.flexShrink ?? defaultShrink))
|
|
477
|
+
input.minWidth = length(node.style.minWidth, base: contentWidth)
|
|
478
|
+
input.maxWidth = length(node.style.maxWidth, base: contentWidth)
|
|
479
|
+
input.minHeight = length(node.style.minHeight, base: contentHeight)
|
|
480
|
+
input.maxHeight = length(node.style.maxHeight, base: contentHeight)
|
|
481
|
+
input.gap = Float(max(0, node.gap))
|
|
482
|
+
input.justifyContent = justify(node.justifyContent)
|
|
483
|
+
input.alignItems = alignment(node.alignItems)
|
|
484
|
+
input.alignSelf = rootNode ? 0 : alignSelf(node.style.alignSelf)
|
|
485
|
+
input.flexWrap = node.wrap ? 1 : 0
|
|
486
|
+
input.alignContent = craftNativeAlignContentCode(node.alignContent)
|
|
487
|
+
input.marginTop = Float(max(0, node.style.marginTop))
|
|
488
|
+
input.marginRight = Float(max(0, node.style.marginRight))
|
|
489
|
+
input.marginBottom = Float(max(0, node.style.marginBottom))
|
|
490
|
+
input.marginLeft = Float(max(0, node.style.marginLeft))
|
|
491
|
+
input.paddingTop = Float(max(0, node.padding.top))
|
|
492
|
+
input.paddingRight = Float(max(0, node.padding.right))
|
|
493
|
+
input.paddingBottom = Float(max(0, node.padding.bottom))
|
|
494
|
+
input.paddingLeft = Float(max(0, node.padding.left))
|
|
495
|
+
input.grid = node.grid ? 1 : 0
|
|
496
|
+
input.gridColumns = UInt32(max(1, node.gridColumns))
|
|
497
|
+
input.rowGap = Float(max(0, node.rowGap))
|
|
498
|
+
input.columnGap = Float(max(0, node.columnGap))
|
|
499
|
+
input.reverse = (node.reverse ? 1 : 0) | (node.wrapReverse ? 2 : 0)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
var styles = [StyleInputV2](repeating: StyleInputV2(), count: nodes.count)
|
|
503
|
+
var metadata = [Float](repeating: 0, count: nodes.count * 2)
|
|
504
|
+
var cursor = 0
|
|
505
|
+
func write(_ node: TreeNode, parentWidth: CGFloat, parentHeight: CGFloat, rootNode: Bool) {
|
|
506
|
+
let index = cursor
|
|
507
|
+
cursor += 1
|
|
508
|
+
put(&styles[index], node, parentWidth: parentWidth, parentHeight: parentHeight, rootNode: rootNode)
|
|
509
|
+
let contentWidth = max(0, (rootNode ? width : node.intrinsic.width) - node.padding.left - node.padding.right)
|
|
510
|
+
let contentHeight = max(0, (rootNode ? height : node.intrinsic.height) - node.padding.top - node.padding.bottom)
|
|
511
|
+
let firstChild = cursor
|
|
512
|
+
node.children.forEach { write($0, parentWidth: contentWidth, parentHeight: contentHeight, rootNode: false) }
|
|
513
|
+
metadata[index * 2] = Float(firstChild)
|
|
514
|
+
metadata[index * 2 + 1] = Float(cursor - firstChild)
|
|
515
|
+
}
|
|
516
|
+
write(root, parentWidth: width, parentHeight: height, rootNode: true)
|
|
517
|
+
let inputs = zip(styles, stride(from: 0, to: metadata.count, by: 2)).map { style, offset in
|
|
518
|
+
NodeInputV2(style: style, firstChild: UInt32(metadata[offset]), childCount: UInt32(metadata[offset + 1]))
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
var output = [Frame](repeating: Frame(x: 0, y: 0, width: 0, height: 0), count: nodes.count)
|
|
522
|
+
let frameCount = output.count
|
|
523
|
+
let status = inputs.withUnsafeBufferPointer { inputBuffer in
|
|
524
|
+
metadata.withUnsafeBufferPointer { metadataBuffer in
|
|
525
|
+
output.withUnsafeMutableBufferPointer { outputBuffer in
|
|
526
|
+
function(
|
|
527
|
+
Float(width), Float(height), UnsafeRawPointer(inputBuffer.baseAddress!), inputs.count,
|
|
528
|
+
UnsafeMutableRawPointer(outputBuffer.baseAddress!), frameCount
|
|
529
|
+
)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
guard status == 0, output.allSatisfy({ frame in
|
|
534
|
+
frame.x.isFinite && frame.y.isFinite && frame.width.isFinite && frame.height.isFinite &&
|
|
535
|
+
frame.width >= 0 && frame.height >= 0
|
|
536
|
+
}) else { return nil }
|
|
537
|
+
return TreeResult(nodes: nodes, parents: parents, frames: output)
|
|
538
|
+
}
|
|
263
539
|
}
|
|
264
540
|
|
|
265
541
|
private enum CraftNativeFlexLineBridge {
|
|
@@ -1548,6 +1824,137 @@ private class CraftNativeFlowView: UIStackView {
|
|
|
1548
1824
|
flowChildren.filter { $0.superview === self && !$0.isHidden && !($0 is CraftNativeModalView) }
|
|
1549
1825
|
}
|
|
1550
1826
|
|
|
1827
|
+
/// Builds the eligible portion of this container for the host-neutral
|
|
1828
|
+
/// tree ABI. The local algorithm remains authoritative for
|
|
1829
|
+
/// grid-auto-rows, absolute children, and any subtree whose measurement
|
|
1830
|
+
/// cannot be represented by the shared contract.
|
|
1831
|
+
fileprivate func sharedTreeNode(parentWidth: CGFloat, parentHeight: CGFloat) -> CraftNativeTreeLayoutBridge.TreeNode? {
|
|
1832
|
+
guard gridAutoRows == nil else { return nil }
|
|
1833
|
+
let children = visibleChildren
|
|
1834
|
+
let flow = children.filter { $0.craftLayoutStyle.position != "absolute" }
|
|
1835
|
+
guard flow.count == children.count else { return nil }
|
|
1836
|
+
let style = craftLayoutStyle
|
|
1837
|
+
let width = style.width?.resolve(parentWidth)
|
|
1838
|
+
let height = style.height?.resolve(parentHeight)
|
|
1839
|
+
let intrinsic = CraftNativeFlexLayout.measure(
|
|
1840
|
+
self, width: width, height: height,
|
|
1841
|
+
maxWidth: max(0, parentWidth), maxHeight: max(0, parentHeight)
|
|
1842
|
+
)
|
|
1843
|
+
let measuredWidth = width ?? intrinsic.width
|
|
1844
|
+
let measuredHeight = height ?? intrinsic.height
|
|
1845
|
+
let contentWidth = max(0, measuredWidth - effectivePadding.left - effectivePadding.right)
|
|
1846
|
+
let contentHeight = max(0, measuredHeight - effectivePadding.top - effectivePadding.bottom)
|
|
1847
|
+
let nodes = flow.map { child -> CraftNativeTreeLayoutBridge.TreeNode? in
|
|
1848
|
+
let childStyle = child.craftLayoutStyle
|
|
1849
|
+
let childWidth = childStyle.width?.resolve(contentWidth)
|
|
1850
|
+
let childHeight = childStyle.height?.resolve(contentHeight)
|
|
1851
|
+
let childIntrinsic = CraftNativeFlexLayout.measure(
|
|
1852
|
+
child, width: childWidth, height: childHeight,
|
|
1853
|
+
maxWidth: contentWidth, maxHeight: contentHeight
|
|
1854
|
+
)
|
|
1855
|
+
let nested = child as? CraftNativeFlowView
|
|
1856
|
+
let nestedNode = nested?.sharedTreeNode(
|
|
1857
|
+
parentWidth: contentWidth, parentHeight: contentHeight
|
|
1858
|
+
)
|
|
1859
|
+
if nested != nil && nestedNode == nil { return nil }
|
|
1860
|
+
return CraftNativeTreeLayoutBridge.TreeNode(
|
|
1861
|
+
view: child,
|
|
1862
|
+
style: childStyle,
|
|
1863
|
+
intrinsic: childIntrinsic,
|
|
1864
|
+
baseline: Self.firstBaseline(child, cross: childIntrinsic.height),
|
|
1865
|
+
direction: nested?.axis ?? .horizontal,
|
|
1866
|
+
gap: nested.map { $0.axis == .horizontal ? ($0.columnGap ?? $0.gap) : ($0.rowGap ?? $0.gap) } ?? 0,
|
|
1867
|
+
rowGap: nested?.rowGap ?? 0,
|
|
1868
|
+
columnGap: nested?.columnGap ?? 0,
|
|
1869
|
+
wrap: nested?.wrap ?? false,
|
|
1870
|
+
justifyContent: nested?.justifyContent ?? "flex-start",
|
|
1871
|
+
alignItems: nested?.alignItems ?? "stretch",
|
|
1872
|
+
alignContent: nested?.alignContent ?? "flex-start",
|
|
1873
|
+
padding: nested?.effectivePadding ?? .zero,
|
|
1874
|
+
grid: nested?.grid ?? false,
|
|
1875
|
+
gridColumns: nested?.gridColumns ?? 1,
|
|
1876
|
+
reverse: nested?.reverse ?? false,
|
|
1877
|
+
wrapReverse: nested?.wrapReverse ?? false,
|
|
1878
|
+
children: nestedNode?.children ?? []
|
|
1879
|
+
)
|
|
1880
|
+
}
|
|
1881
|
+
guard nodes.allSatisfy({ $0 != nil }) else { return nil }
|
|
1882
|
+
return CraftNativeTreeLayoutBridge.TreeNode(
|
|
1883
|
+
view: self,
|
|
1884
|
+
style: style,
|
|
1885
|
+
intrinsic: intrinsic,
|
|
1886
|
+
baseline: 0,
|
|
1887
|
+
direction: axis,
|
|
1888
|
+
gap: axis == .horizontal ? (columnGap ?? gap) : (rowGap ?? gap),
|
|
1889
|
+
rowGap: rowGap ?? 0,
|
|
1890
|
+
columnGap: columnGap ?? 0,
|
|
1891
|
+
wrap: wrap,
|
|
1892
|
+
justifyContent: justifyContent,
|
|
1893
|
+
alignItems: alignItems,
|
|
1894
|
+
alignContent: alignContent,
|
|
1895
|
+
padding: effectivePadding,
|
|
1896
|
+
grid: grid,
|
|
1897
|
+
gridColumns: gridColumns,
|
|
1898
|
+
reverse: reverse,
|
|
1899
|
+
wrapReverse: wrapReverse,
|
|
1900
|
+
children: nodes.compactMap { $0 }
|
|
1901
|
+
)
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
private func sharedTreeFrames(_ children: [UIView], width: CGFloat?, height: CGFloat?) -> CraftNativeTreeLayoutBridge.TreeResult? {
|
|
1905
|
+
guard let width, let height, gridAutoRows == nil,
|
|
1906
|
+
width.isFinite, height.isFinite, width >= 0, height >= 0 else { return nil }
|
|
1907
|
+
let nodes = children.map { child -> CraftNativeTreeLayoutBridge.TreeNode? in
|
|
1908
|
+
let style = child.craftLayoutStyle
|
|
1909
|
+
let intrinsic = CraftNativeFlexLayout.measure(child, width: style.width?.resolve(width), height: style.height?.resolve(height), maxWidth: width, maxHeight: height)
|
|
1910
|
+
let nested = child as? CraftNativeFlowView
|
|
1911
|
+
let nestedNode = nested?.sharedTreeNode(parentWidth: width, parentHeight: height)
|
|
1912
|
+
if nested != nil && nestedNode == nil { return nil }
|
|
1913
|
+
return CraftNativeTreeLayoutBridge.TreeNode(
|
|
1914
|
+
view: child,
|
|
1915
|
+
style: style,
|
|
1916
|
+
intrinsic: intrinsic,
|
|
1917
|
+
baseline: Self.firstBaseline(child, cross: intrinsic.height),
|
|
1918
|
+
direction: nested?.axis ?? .horizontal,
|
|
1919
|
+
gap: nested.map { $0.axis == .horizontal ? ($0.columnGap ?? $0.gap) : ($0.rowGap ?? $0.gap) } ?? 0,
|
|
1920
|
+
rowGap: nested?.rowGap ?? 0,
|
|
1921
|
+
columnGap: nested?.columnGap ?? 0,
|
|
1922
|
+
wrap: nested?.wrap ?? false,
|
|
1923
|
+
justifyContent: nested?.justifyContent ?? "flex-start",
|
|
1924
|
+
alignItems: nested?.alignItems ?? "stretch",
|
|
1925
|
+
alignContent: nested?.alignContent ?? "flex-start",
|
|
1926
|
+
padding: nested?.effectivePadding ?? .zero,
|
|
1927
|
+
grid: nested?.grid ?? false,
|
|
1928
|
+
gridColumns: nested?.gridColumns ?? 1,
|
|
1929
|
+
reverse: nested?.reverse ?? false,
|
|
1930
|
+
wrapReverse: nested?.wrapReverse ?? false,
|
|
1931
|
+
children: nestedNode?.children ?? []
|
|
1932
|
+
)
|
|
1933
|
+
}
|
|
1934
|
+
guard nodes.allSatisfy({ $0 != nil }) else { return nil }
|
|
1935
|
+
let root = CraftNativeTreeLayoutBridge.TreeNode(
|
|
1936
|
+
view: nil,
|
|
1937
|
+
style: .empty,
|
|
1938
|
+
intrinsic: .zero,
|
|
1939
|
+
baseline: 0,
|
|
1940
|
+
direction: axis,
|
|
1941
|
+
gap: axis == .horizontal ? (columnGap ?? gap) : (rowGap ?? gap),
|
|
1942
|
+
rowGap: rowGap ?? 0,
|
|
1943
|
+
columnGap: columnGap ?? 0,
|
|
1944
|
+
wrap: wrap,
|
|
1945
|
+
justifyContent: justifyContent,
|
|
1946
|
+
alignItems: alignItems,
|
|
1947
|
+
alignContent: alignContent,
|
|
1948
|
+
padding: effectivePadding,
|
|
1949
|
+
grid: grid,
|
|
1950
|
+
gridColumns: gridColumns,
|
|
1951
|
+
reverse: reverse,
|
|
1952
|
+
wrapReverse: wrapReverse,
|
|
1953
|
+
children: nodes.compactMap { $0 }
|
|
1954
|
+
)
|
|
1955
|
+
return CraftNativeTreeLayoutBridge.treeFrames(root: root, width: width, height: height)
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1551
1958
|
/// The flexbox algorithm, single pass. `width`/`height` are this view's
|
|
1552
1959
|
/// outer size when its parent fixes it, nil when it sizes to its content
|
|
1553
1960
|
/// within `maxWidth`/`maxHeight`. Returns its outer size and, when
|
|
@@ -1609,7 +2016,19 @@ private class CraftNativeFlowView: UIStackView {
|
|
|
1609
2016
|
frames: &frames
|
|
1610
2017
|
)
|
|
1611
2018
|
}
|
|
1612
|
-
if place, flow.count == children.count, let
|
|
2019
|
+
if place, flow.count == children.count, let sharedTree = sharedTreeFrames(flow, width: width, height: height), (sharedTree.nodes.count > flow.count + 1 || grid) {
|
|
2020
|
+
frames = []
|
|
2021
|
+
for index in 1..<sharedTree.nodes.count {
|
|
2022
|
+
guard let view = sharedTree.nodes[index].view else { continue }
|
|
2023
|
+
let offset = sharedTree.frames[index]
|
|
2024
|
+
let parent = sharedTree.parents[index]
|
|
2025
|
+
let parentFrame = sharedTree.frames[parent]
|
|
2026
|
+
frames.append((view, CGRect(
|
|
2027
|
+
x: CGFloat(offset.x - parentFrame.x), y: CGFloat(offset.y - parentFrame.y),
|
|
2028
|
+
width: CGFloat(offset.width), height: CGFloat(offset.height)
|
|
2029
|
+
)))
|
|
2030
|
+
}
|
|
2031
|
+
} else if place, flow.count == children.count, let shared = sharedDirectFrames(flow, width: width, height: height) {
|
|
1613
2032
|
frames = shared
|
|
1614
2033
|
}
|
|
1615
2034
|
let size = CGSize(width: width ?? contentSize.width + paddingWidth, height: height ?? contentSize.height + paddingHeight)
|
|
@@ -3321,6 +3740,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3321
3740
|
// a screen that hid its own.
|
|
3322
3741
|
navigationController?.setNavigationBarHidden(false, animated: animated)
|
|
3323
3742
|
}
|
|
3743
|
+
applyNavigationBarAppearance()
|
|
3324
3744
|
// Each screen says whether it wants the tab bar, so one that hid it
|
|
3325
3745
|
// never leaves it hidden for the next.
|
|
3326
3746
|
CraftChrome.shared.setHiddenByScreen(tabBarHidden)
|
|
@@ -3406,6 +3826,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3406
3826
|
|
|
3407
3827
|
private func refreshTraitDefaults() {
|
|
3408
3828
|
view.backgroundColor = screenBackgroundColor ?? config.resolvedBackgroundColor
|
|
3829
|
+
applyNavigationBarAppearance()
|
|
3409
3830
|
if let document = mutationDocument.node("root") {
|
|
3410
3831
|
renderCommitted(document)
|
|
3411
3832
|
} else {
|
|
@@ -3637,7 +4058,8 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3637
4058
|
/// - `craft.secureStorage.getSync(key)`: the Keychain item the web page's
|
|
3638
4059
|
/// `craft.secureStorage.set` wrote.
|
|
3639
4060
|
/// - `craft.navigation.setOptions({ title, largeTitle, hidden, backTitle,
|
|
3640
|
-
/// tabBarHidden, swipeBack, contentSwipeBack, tabSwipeEnabled,
|
|
4061
|
+
/// tabBarHidden, swipeBack, contentSwipeBack, tabSwipeEnabled,
|
|
4062
|
+
/// backgroundColor, navigationBarSolid, rightButtons })`;
|
|
3641
4063
|
/// a right button's tap is the event `navButton`.
|
|
3642
4064
|
///
|
|
3643
4065
|
/// A runtime that assigns `craft.storage` (or the others) afterwards keeps
|
|
@@ -3905,6 +4327,9 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3905
4327
|
/// own horizontal pager or chart gesture.
|
|
3906
4328
|
private(set) var tabSwipeEnabled = true
|
|
3907
4329
|
private var prefersLargeTitle: Bool?
|
|
4330
|
+
/// Whether this screen opts into an opaque navigation bar filled with its
|
|
4331
|
+
/// own background colour. The default keeps UIKit's material appearance.
|
|
4332
|
+
private var navigationBarSolid = false
|
|
3908
4333
|
/// The screen's own colour (`backgroundColor`), under the status bar and
|
|
3909
4334
|
/// the home indicator too, where its content never reaches: a black
|
|
3910
4335
|
/// full-screen timer is black edge to edge. nil is the app's.
|
|
@@ -3964,6 +4389,9 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3964
4389
|
screenBackgroundColor = color(options["backgroundColor"])
|
|
3965
4390
|
view.backgroundColor = screenBackgroundColor ?? config.resolvedBackgroundColor
|
|
3966
4391
|
}
|
|
4392
|
+
if let solid = options["navigationBarSolid"] as? Bool {
|
|
4393
|
+
navigationBarSolid = solid
|
|
4394
|
+
}
|
|
3967
4395
|
if let hidden = options["hidden"] as? Bool {
|
|
3968
4396
|
navigationBarHidden = hidden
|
|
3969
4397
|
if navigationController?.topViewController === self {
|
|
@@ -3974,6 +4402,9 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3974
4402
|
let buttons = (options["rightButtons"] as? [[String: Any]]) ?? []
|
|
3975
4403
|
navigationItem.rightBarButtonItems = buttons.compactMap(navigationButton).reversed()
|
|
3976
4404
|
}
|
|
4405
|
+
if options.keys.contains("backgroundColor") || options.keys.contains("navigationBarSolid") {
|
|
4406
|
+
applyNavigationBarAppearance()
|
|
4407
|
+
}
|
|
3977
4408
|
// Asked for after the bar was hidden for a bare root screen.
|
|
3978
4409
|
if navigationBarHidden == nil, wantsNavigationBar, navigationController?.topViewController === self,
|
|
3979
4410
|
navigationController?.isNavigationBarHidden == true {
|
|
@@ -3981,6 +4412,26 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3981
4412
|
}
|
|
3982
4413
|
}
|
|
3983
4414
|
|
|
4415
|
+
/// Applies the screen's optional opaque bar without changing the default
|
|
4416
|
+
/// UIKit material when the screen did not opt in.
|
|
4417
|
+
private func applyNavigationBarAppearance() {
|
|
4418
|
+
guard navigationBarSolid else {
|
|
4419
|
+
navigationItem.standardAppearance = nil
|
|
4420
|
+
navigationItem.scrollEdgeAppearance = nil
|
|
4421
|
+
navigationItem.compactAppearance = nil
|
|
4422
|
+
navigationItem.compactScrollEdgeAppearance = nil
|
|
4423
|
+
return
|
|
4424
|
+
}
|
|
4425
|
+
let appearance = UINavigationBarAppearance()
|
|
4426
|
+
appearance.configureWithOpaqueBackground()
|
|
4427
|
+
appearance.backgroundColor = screenBackgroundColor ?? config.resolvedBackgroundColor
|
|
4428
|
+
appearance.shadowColor = .clear
|
|
4429
|
+
navigationItem.standardAppearance = appearance
|
|
4430
|
+
navigationItem.scrollEdgeAppearance = appearance
|
|
4431
|
+
navigationItem.compactAppearance = appearance
|
|
4432
|
+
navigationItem.compactScrollEdgeAppearance = appearance
|
|
4433
|
+
}
|
|
4434
|
+
|
|
3984
4435
|
private func applyStatusBar(_ props: [String: Any]) {
|
|
3985
4436
|
let style: UIStatusBarStyle?
|
|
3986
4437
|
switch (props["barStyle"] as? String)?.lowercased() {
|