Last active
August 1, 2023 07:58
-
-
Save VAndrJ/7fef520b53d947ccc16375d945dc1d4e to your computer and use it in GitHub Desktop.
Texture Layout Spec in Swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public final class Row: ASStackLayoutSpec { | |
public convenience init( | |
spacing: CGFloat = 0, | |
main: ASStackLayoutJustifyContent = .start, | |
cross: ASStackLayoutAlignItems = .start, | |
wrap: ASStackLayoutFlexWrap = .noWrap, | |
alignContent: ASStackLayoutAlignContent = .start, | |
line: CGFloat = 0, | |
@LayoutSpecBuilder content: () -> [ASLayoutElement] | |
) { | |
self.init( | |
direction: .horizontal, | |
spacing: spacing, | |
justifyContent: main, | |
alignItems: cross, | |
flexWrap: wrap, | |
alignContent: alignContent, | |
lineSpacing: line, | |
children: content() | |
) | |
} | |
} | |
public final class Column: ASStackLayoutSpec { | |
public convenience init( | |
spacing: CGFloat = 0, | |
main: ASStackLayoutJustifyContent = .start, | |
cross: ASStackLayoutAlignItems = .start, | |
wrap: ASStackLayoutFlexWrap = .noWrap, | |
alignContent: ASStackLayoutAlignContent = .start, | |
line: CGFloat = 0, | |
@LayoutSpecBuilder content: () -> [ASLayoutElement] | |
) { | |
self.init( | |
direction: .vertical, | |
spacing: spacing, | |
justifyContent: main, | |
alignItems: cross, | |
flexWrap: wrap, | |
alignContent: alignContent, | |
lineSpacing: line, | |
children: content() | |
) | |
} | |
} | |
extension ASLayoutElement { | |
func flex(shrink: CGFloat? = nil, grow: CGFloat? = nil) -> Self { | |
assert(shrink != nil || grow != nil) | |
if let shrink { | |
style.flexShrink = shrink | |
} | |
if let grow { | |
style.flexGrow = grow | |
} | |
return self | |
} | |
func background(_ element: ASLayoutElement) -> ASBackgroundLayoutSpec { | |
ASBackgroundLayoutSpec( | |
child: self, | |
background: element | |
) | |
} | |
func overlay(_ element: ASLayoutElement) -> ASOverlayLayoutSpec { | |
ASOverlayLayoutSpec( | |
child: self, | |
overlay: element | |
) | |
} | |
func centered( | |
_ centering: ASCenterLayoutSpecCenteringOptions = .XY, | |
sizing: ASCenterLayoutSpecSizingOptions = .minimumXY | |
) -> ASCenterLayoutSpec { | |
ASCenterLayoutSpec( | |
centeringOptions: centering, | |
sizingOptions: sizing, | |
child: self | |
) | |
} | |
func corner( | |
_ element: ASLayoutElement, | |
location: ASCornerLayoutLocation = .topRight, | |
offset: CGPoint = .zero, | |
wrapsCorner: Bool = false | |
) -> ASCornerLayoutSpec { | |
let spec = ASCornerLayoutSpec( | |
child: self, | |
corner: element, | |
location: location | |
) | |
spec.offset = offset | |
spec.wrapsCorner = wrapsCorner | |
return spec | |
} | |
} | |
@resultBuilder | |
public struct LayoutSpecBuilder { | |
public static func buildBlock(_ components: ASLayoutElement...) -> [ASLayoutElement] { | |
components | |
} | |
} | |
public final class Stack: ASWrapperLayoutSpec { | |
public init(@LayoutSpecBuilder content: () -> [ASLayoutElement]) { | |
super.init(layoutElements: content()) | |
} | |
public override func calculateLayoutThatFits(_ constrainedSize: ASSizeRange) -> ASLayout { | |
var rawSubLayouts: [ASLayout] = [] | |
var size = constrainedSize.min | |
guard let children, !children.isEmpty else { | |
return ASLayout(layoutElement: self, size: size, sublayouts: rawSubLayouts) | |
} | |
for child in children { | |
let sublayout = child.layoutThatFits(constrainedSize, parentSize: constrainedSize.max) | |
sublayout.position = .zero | |
size.width = max(size.width, sublayout.size.width) | |
size.height = max(size.height, sublayout.size.height) | |
rawSubLayouts.append(sublayout) | |
} | |
for (i, child) in children.enumerated() { | |
if let centerSpec = child as? ASCenterLayoutSpec { | |
switch centerSpec.centeringOptions { | |
case .X: | |
let x = (size.width - rawSubLayouts[i].size.width) * proportionOfAxisFor(position: .center) | |
rawSubLayouts[i].position = CGPoint(x: x, y: 0) | |
case .Y: | |
let y = (size.height - rawSubLayouts[i].size.height) * proportionOfAxisFor(position: .center) | |
rawSubLayouts[i].position = CGPoint(x: 0, y: y) | |
case .XY: | |
let x = (size.width - rawSubLayouts[i].size.width) * proportionOfAxisFor(position: .center) | |
let y = (size.height - rawSubLayouts[i].size.height) * proportionOfAxisFor(position: .center) | |
rawSubLayouts[i].position = CGPoint(x: x, y: y) | |
default: | |
break | |
} | |
} | |
if let relativeSpec = child as? ASRelativeLayoutSpec { | |
let x = (size.width - rawSubLayouts[i].size.width) * proportionOfAxisFor(position: relativeSpec.horizontalPosition) | |
let y = (size.height - rawSubLayouts[i].size.height) * proportionOfAxisFor(position: relativeSpec.verticalPosition) | |
rawSubLayouts[i].position = CGPoint(x: x, y: y) | |
} | |
} | |
return ASLayout(layoutElement: self, size: size, sublayouts: rawSubLayouts) | |
} | |
private func proportionOfAxisFor(position: ASRelativeLayoutSpecPosition) -> CGFloat { | |
switch position { | |
case .center: | |
return 0.5 | |
case .end: | |
return 1 | |
default: | |
return 0 | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment