2
2
// FrameLayout.swift
3
3
// OpenSwiftUICore
4
4
//
5
- // Status: WIP
5
+ // Status: Complete
6
6
// ID: 73C64038119BBD0A6D8557B14379A404 (SwiftUICore)
7
7
8
8
public import Foundation
@@ -162,8 +162,7 @@ extension View {
162
162
/// - Returns: A view with fixed dimensions of `width` and `height`, for the
163
163
/// parameters that are non-`nil`.
164
164
@inlinable
165
- nonisolated
166
- public func frame( width: CGFloat ? = nil , height: CGFloat ? = nil , alignment: Alignment = . center) -> some View {
165
+ nonisolated public func frame( width: CGFloat ? = nil , height: CGFloat ? = nil , alignment: Alignment = . center) -> some View {
167
166
return modifier (
168
167
_FrameLayout ( width: width, height: height, alignment: alignment)
169
168
)
@@ -182,4 +181,279 @@ extension View {
182
181
}
183
182
}
184
183
185
- // MARK: - FlexFrameLayout [6.4.41] [WIP]
184
+ // MARK: - FlexFrameLayout [6.4.41]
185
+
186
+ /// A modifier that aligns its child in an invisible, flexible frame with size
187
+ /// limits and ideal size properties.
188
+ @frozen
189
+ public struct _FlexFrameLayout : UnaryLayout , FrameLayoutCommon {
190
+ let minWidth : CGFloat ?
191
+ let idealWidth : CGFloat ?
192
+ let maxWidth : CGFloat ?
193
+ let minHeight : CGFloat ?
194
+ let idealHeight : CGFloat ?
195
+ let maxHeight : CGFloat ?
196
+ let alignment : Alignment
197
+
198
+ /// Creates an instance with the given properties.
199
+ @usableFromInline
200
+ package init (
201
+ minWidth: CGFloat ? = nil ,
202
+ idealWidth: CGFloat ? = nil ,
203
+ maxWidth: CGFloat ? = nil ,
204
+ minHeight: CGFloat ? = nil ,
205
+ idealHeight: CGFloat ? = nil ,
206
+ maxHeight: CGFloat ? = nil ,
207
+ alignment: Alignment
208
+ ) {
209
+ let minW : CGFloat ? = if let minWidth {
210
+ max ( minWidth, . zero)
211
+ } else {
212
+ nil
213
+ }
214
+ let ideaW : CGFloat ? = if let idealWidth {
215
+ max ( minW ?? . zero, idealWidth)
216
+ } else {
217
+ nil
218
+ }
219
+ let maxW : CGFloat ? = if let maxWidth {
220
+ max ( ideaW ?? . zero, maxWidth)
221
+ } else {
222
+ nil
223
+ }
224
+ let minH : CGFloat ? = if let minHeight {
225
+ max ( minHeight, . zero)
226
+ } else {
227
+ nil
228
+ }
229
+ let ideaH : CGFloat ? = if let idealHeight {
230
+ max ( minH ?? . zero, idealHeight)
231
+ } else {
232
+ nil
233
+ }
234
+ let maxH : CGFloat ? = if let maxHeight {
235
+ max ( ideaH ?? . zero, maxHeight)
236
+ } else {
237
+ nil
238
+ }
239
+ let hasInvalidWidth = ( minWidth ?? . zero) > ( idealWidth ?? maxWidth ?? . infinity) ||
240
+ ( idealWidth ?? . zero) > ( maxWidth ?? . infinity) ||
241
+ ( minWidth ?? . zero) . isInfinite || ( minWidth ?? . zero) . isNaN
242
+
243
+ let hasInvalidHeight = ( minHeight ?? . zero) > ( idealHeight ?? maxHeight ?? . infinity) ||
244
+ ( idealHeight ?? 0.0 ) > ( maxHeight ?? . infinity) ||
245
+ ( minHeight ?? . zero) . isInfinite || ( minHeight ?? . zero) . isNaN
246
+
247
+ if ( hasInvalidWidth || hasInvalidHeight) && isLinkedOnOrAfter ( . v2) {
248
+ Log . runtimeIssues ( " Invalid frame dimension (negative or non-finite). " )
249
+ }
250
+
251
+ self . minWidth = minW
252
+ self . idealWidth = ideaW
253
+ self . maxWidth = maxW
254
+ self . minHeight = minH
255
+ self . idealHeight = ideaH
256
+ self . maxHeight = maxH
257
+ self . alignment = alignment
258
+ }
259
+
260
+ private func childProposal( myProposal: _ProposedSize ) -> _ProposedSize {
261
+ let width : CGFloat ? = if let idealWidth {
262
+ min ( max ( myProposal. width ?? idealWidth, minWidth ?? - . infinity) , maxWidth ?? . infinity)
263
+ } else {
264
+ nil
265
+ }
266
+ let height : CGFloat ? = if let idealHeight {
267
+ min ( max ( myProposal. height ?? idealHeight, minHeight ?? - . infinity) , maxHeight ?? . infinity)
268
+ } else {
269
+ nil
270
+ }
271
+ return _ProposedSize ( width: width, height: height)
272
+ }
273
+
274
+ package func sizeThatFits(
275
+ in proposedSize: _ProposedSize ,
276
+ context: SizeAndSpacingContext ,
277
+ child: LayoutProxy
278
+ ) -> CGSize {
279
+ let width : CGFloat ? = if let width = proposedSize. width {
280
+ if let minWidth, let maxWidth, minWidth <= maxWidth {
281
+ min ( max ( width, minWidth) , maxWidth)
282
+ } else {
283
+ nil
284
+ }
285
+ } else {
286
+ idealWidth
287
+ }
288
+ let height : CGFloat ? = if let height = proposedSize. height {
289
+ if let minHeight, let maxHeight, minHeight <= maxHeight {
290
+ min ( max ( height, minHeight) , maxHeight)
291
+ } else {
292
+ nil
293
+ }
294
+ } else {
295
+ idealHeight
296
+ }
297
+ guard let width, let height else {
298
+ let childProposal = childProposal ( myProposal: proposedSize)
299
+ let size = child. size ( in: childProposal)
300
+
301
+ let finalWidth = if let width {
302
+ width
303
+ } else {
304
+ switch ( minWidth, maxWidth) {
305
+ case let ( minW? , maxW? ) where minW <= maxW:
306
+ min ( max ( minW, size. width) , maxW)
307
+ case let ( minW? , nil ) :
308
+ max ( min ( childProposal. width ?? . infinity, size. width) , minW)
309
+ case let ( nil , maxW? ) :
310
+ min ( max ( childProposal. width ?? - . infinity, size. width) , maxW)
311
+ default :
312
+ size. width
313
+ }
314
+ }
315
+ let finalHeight = if let height {
316
+ height
317
+ } else {
318
+ switch ( minHeight, maxHeight) {
319
+ case let ( minH? , maxH? ) where minH <= maxH:
320
+ min ( max ( minH, size. height) , maxH)
321
+ case let ( minH? , nil ) :
322
+ max ( min ( childProposal. height ?? . infinity, size. height) , minH)
323
+ case let ( nil , maxH? ) :
324
+ min ( max ( childProposal. height ?? - . infinity, size. height) , maxH)
325
+ default :
326
+ size. height
327
+ }
328
+ }
329
+ return CGSize ( width: finalWidth, height: finalHeight)
330
+ }
331
+ return CGSize ( width: width, height: height)
332
+ }
333
+
334
+ private func childPlacementProposal( of child: LayoutProxy , context: PlacementContext ) -> _ProposedSize {
335
+ func proposedDimension(
336
+ _ axis: Axis ,
337
+ min: CGFloat ? = nil ,
338
+ ideal: CGFloat ? = nil ,
339
+ max: CGFloat ? = nil
340
+ ) -> CGFloat ? {
341
+ let value = context. size [ axis]
342
+ guard ideal == nil ,
343
+ context. proposedSize [ axis] == nil ,
344
+ ( min ?? - . infinity) < value, value < ( max ?? . infinity)
345
+ else {
346
+ return value
347
+ }
348
+ return nil
349
+ }
350
+ return _ProposedSize (
351
+ width: proposedDimension ( . horizontal, min: minWidth, ideal: idealWidth, max: maxWidth) ,
352
+ height: proposedDimension ( . vertical, min: minHeight, ideal: idealHeight, max: maxHeight)
353
+ )
354
+ }
355
+
356
+ package func placement( of child: LayoutProxy , in context: PlacementContext ) -> _Placement {
357
+ let childProposal = if Semantics . FlexFrameIdealSizing. isEnabled {
358
+ childPlacementProposal ( of: child, context: context)
359
+ } else {
360
+ _ProposedSize ( context. size)
361
+ }
362
+ return commonPlacement ( of: child, in: context, childProposal: childProposal)
363
+ }
364
+
365
+ package func spacing( in context: SizeAndSpacingContext , child: LayoutProxy ) -> Spacing {
366
+ if _SemanticFeature_v3. isEnabled, !child. requiresSpacingProjection {
367
+ var spacing = child. layoutComputer. spacing ( )
368
+ var edges : Edge . Set = [ ]
369
+ if minHeight != nil || idealHeight != nil || maxHeight != nil {
370
+ edges. formUnion ( . vertical)
371
+ }
372
+ if minWidth != nil || idealWidth != nil || maxWidth != nil {
373
+ edges. formUnion ( . horizontal)
374
+ }
375
+ spacing. reset ( . init( edges, layoutDirection: context. layoutDirection) )
376
+ return spacing
377
+ } else {
378
+ return child. layoutComputer. spacing ( )
379
+ }
380
+ }
381
+ }
382
+
383
+ extension View {
384
+ /// Positions this view within an invisible frame having the specified size
385
+ /// constraints.
386
+ ///
387
+ /// Always specify at least one size characteristic when calling this
388
+ /// method. Pass `nil` or leave out a characteristic to indicate that the
389
+ /// frame should adopt this view's sizing behavior, constrained by the other
390
+ /// non-`nil` arguments.
391
+ ///
392
+ /// The size proposed to this view is the size proposed to the frame,
393
+ /// limited by any constraints specified, and with any ideal dimensions
394
+ /// specified replacing any corresponding unspecified dimensions in the
395
+ /// proposal.
396
+ ///
397
+ /// If no minimum or maximum constraint is specified in a given dimension,
398
+ /// the frame adopts the sizing behavior of its child in that dimension. If
399
+ /// both constraints are specified in a dimension, the frame unconditionally
400
+ /// adopts the size proposed for it, clamped to the constraints. Otherwise,
401
+ /// the size of the frame in either dimension is:
402
+ ///
403
+ /// - If a minimum constraint is specified and the size proposed for the
404
+ /// frame by the parent is less than the size of this view, the proposed
405
+ /// size, clamped to that minimum.
406
+ /// - If a maximum constraint is specified and the size proposed for the
407
+ /// frame by the parent is greater than the size of this view, the
408
+ /// proposed size, clamped to that maximum.
409
+ /// - Otherwise, the size of this view.
410
+ ///
411
+ /// - Parameters:
412
+ /// - minWidth: The minimum width of the resulting frame.
413
+ /// - idealWidth: The ideal width of the resulting frame.
414
+ /// - maxWidth: The maximum width of the resulting frame.
415
+ /// - minHeight: The minimum height of the resulting frame.
416
+ /// - idealHeight: The ideal height of the resulting frame.
417
+ /// - maxHeight: The maximum height of the resulting frame.
418
+ /// - alignment: The alignment of this view inside the resulting frame.
419
+ /// Note that most alignment values have no apparent effect when the
420
+ /// size of the frame happens to match that of this view.
421
+ ///
422
+ /// - Returns: A view with flexible dimensions given by the call's non-`nil`
423
+ /// parameters.
424
+ @inlinable
425
+ nonisolated public func frame(
426
+ minWidth: CGFloat ? = nil ,
427
+ idealWidth: CGFloat ? = nil ,
428
+ maxWidth: CGFloat ? = nil ,
429
+ minHeight: CGFloat ? = nil ,
430
+ idealHeight: CGFloat ? = nil ,
431
+ maxHeight: CGFloat ? = nil ,
432
+ alignment: Alignment = . center
433
+ ) -> some View {
434
+ func areInNondecreasingOrder(
435
+ _ min: CGFloat ? , _ ideal: CGFloat ? , _ max: CGFloat ?
436
+ ) -> Bool {
437
+ let min = min ?? - . infinity
438
+ let ideal = ideal ?? min
439
+ let max = max ?? ideal
440
+ return min <= ideal && ideal <= max
441
+ }
442
+
443
+ if !areInNondecreasingOrder( minWidth, idealWidth, maxWidth)
444
+ || !areInNondecreasingOrder( minHeight, idealHeight, maxHeight)
445
+ {
446
+ Log . runtimeIssues ( " Contradictory frame constraints specified. " )
447
+ }
448
+
449
+ return modifier (
450
+ _FlexFrameLayout (
451
+ minWidth: minWidth,
452
+ idealWidth: idealWidth, maxWidth: maxWidth,
453
+ minHeight: minHeight,
454
+ idealHeight: idealHeight, maxHeight: maxHeight,
455
+ alignment: alignment
456
+ )
457
+ )
458
+ }
459
+ }
0 commit comments