Skip to content

Instantly share code, notes, and snippets.

@VAndrJ
Last active August 1, 2023 07:58
Show Gist options
  • Save VAndrJ/7fef520b53d947ccc16375d945dc1d4e to your computer and use it in GitHub Desktop.
Save VAndrJ/7fef520b53d947ccc16375d945dc1d4e to your computer and use it in GitHub Desktop.
Texture Layout Spec in Swift
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