-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8) Infinite size elements #450
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import CoreGraphics | ||
|
||
extension CGSize { | ||
/// A size with `infinity` in both dimensions. | ||
public static let infinity = CGSize(width: CGFloat.infinity, height: .infinity) | ||
|
||
/// Returns a size with infinite dimensions replaced by the values from the given replacement. | ||
public func replacingInfinity(with replacement: CGSize) -> CGSize { | ||
assert(replacement.isFinite, "Infinity replacement value must be finite") | ||
|
||
return CGSize( | ||
width: width.replacingInfinity(with: replacement.width), | ||
height: height.replacingInfinity(with: replacement.height) | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Foundation | ||
|
||
extension FloatingPoint { | ||
/// Returns `replacement` if `self.isInfinite` is `true`, or `self` if `self` is finite. | ||
public func replacingInfinity(with replacement: Self) -> Self { | ||
assert(replacement.isFinite, "Infinity replacement value must be finite") | ||
|
||
return isInfinite ? replacement : self | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import CoreGraphics | ||
import UIKit | ||
|
||
extension CGSize { | ||
static func + (lhs: CGSize, rhs: CGSize) -> CGSize { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,9 @@ public struct Image: Element { | |
|
||
public var content: ElementContent { | ||
let measurer = Measurer(contentMode: contentMode, imageSize: image?.size) | ||
return ElementContent(measurable: measurer) | ||
return ElementContent { constraint, environment in | ||
measurer.measure(in: constraint, layoutMode: environment.layoutMode) | ||
} | ||
} | ||
|
||
public func backingViewDescription(with context: ViewDescriptionContext) -> ViewDescription? { | ||
|
@@ -106,18 +108,19 @@ extension CGSize { | |
|
||
extension Image { | ||
|
||
fileprivate struct Measurer: Measurable { | ||
fileprivate struct Measurer { | ||
|
||
var contentMode: ContentMode | ||
var imageSize: CGSize? | ||
|
||
func measure(in constraint: SizeConstraint) -> CGSize { | ||
func measure(in constraint: SizeConstraint, layoutMode: LayoutMode) -> CGSize { | ||
guard let imageSize = imageSize else { return .zero } | ||
|
||
enum Mode { | ||
case fitWidth(CGFloat) | ||
case fitHeight(CGFloat) | ||
case useImageSize | ||
case infinite | ||
} | ||
|
||
let mode: Mode | ||
|
@@ -137,7 +140,12 @@ extension Image { | |
} else if case .atMost(let height) = constraint.height { | ||
mode = .fitHeight(height) | ||
} else { | ||
mode = .useImageSize | ||
switch layoutMode { | ||
case .legacy: | ||
mode = .useImageSize | ||
case .caffeinated: | ||
mode = .infinite | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you unpack this a bit for me? Given an unconstrained measurement space, Or is it that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It does! Practically speaking, there's not that much impact, because the need to measure an element fully unconstrained is somewhat rare. There's perhaps a semantic change — if you were counting on unconstrained measurement to get you something like an intrinsic size, or SwiftUI's concept of "ideal size", that's no longer possible. But it was never guaranteed to work that way, and would be inconsistent depending on what you measured.
Nope, there's no way to opt out at the element level. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this because of the cache hinting optimizations? I guess I would expect for the measurement modes that don't stretch the image, I'd expect to return the size of image, even in an infinite constraint. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's required by the new layout contract, yeah.
Well, the aspect fit & fill modes already fill space today, even under finite constraints. Given one finite constraint, they'll fill it, no matter how large. So the reasoning is that if the size grows continuously with the constraint, why wouldn't it grow to infinity when the constraint grows to infinity? We could change the behavior of these modes to return the size of the image without filling space, and that would be compliant too, but it would be a much bigger breaking change to existing layouts. |
||
} | ||
} | ||
} | ||
|
||
|
@@ -154,6 +162,8 @@ extension Image { | |
) | ||
case .useImageSize: | ||
return imageSize | ||
case .infinite: | ||
return .infinity | ||
} | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import BlueprintUI | ||
|
||
extension LayoutMode { | ||
static let testModes: [LayoutMode] = [.legacy, .caffeinated] | ||
|
||
/// Run the given block with `self` as the default layout mode, restoring the previous default | ||
/// afterwards, and returning the result of the block. | ||
func performAsDefault<Result>(block: () throws -> Result) rethrows -> Result { | ||
let oldLayoutMode = LayoutMode.default | ||
defer { LayoutMode.default = oldLayoutMode } | ||
|
||
LayoutMode.default = self | ||
|
||
return try block() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One has to love it.