Skip to content

Commit

Permalink
Docs
Browse files Browse the repository at this point in the history
  • Loading branch information
kyleve committed Dec 10, 2020
1 parent 7c7b182 commit 7490aae
Showing 1 changed file with 71 additions and 53 deletions.
124 changes: 71 additions & 53 deletions BlueprintUI/Sources/Layout/Decorate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,38 @@ import UIKit
/// Places a decoration element behind or in front of the given `wrapped` element,
/// and positions it according to the `position` parameter.
///
/// The size of the element is determined only by the `wrapped` element –
/// that is, the `background` element will overflow the bounds of the element, but not
/// affect its layout or size. This is similar to how UIKit handles shadows: They live outside of the layout
/// rect of the view:
/// The size and position of the element is determined only by the `wrapped`
/// element, the `decoration` element does not affect the layout at all.
///
/// The arrows represent the measured size of the view for layout purposes.
/// Example
/// -------
/// The arrows represent the measured size of the element for layout purposes.
/// ```
/// ┌───────────────────┐
/// │ Background
/// │ ┏━━━━━━━━━━━━━━━┓ │ ▲
/// │ ┃ ┃ │ │
/// │ ┃ Wrapped ┃ │ │
/// │ ┃ ┃ │ │
/// │ ┗━━━━━━━━━━━━━━━┛ │ ▼
/// ┌───────────────────┐ ┌──────┐
/// │ Decoration │ │
/// │ ┏━━━━━━━━━━━━━━━┓ │ ▲ │ ┣━━━━━━━━━━┓ ▲
/// │ ┃ ┃ │ │ └─┳────┘ ┃ │
/// │ ┃ Wrapped ┃ │ │ ┃ Wrapped ┃ │
/// │ ┃ ┃ │ │ ┃ ┃ │
/// │ ┗━━━━━━━━━━━━━━━┛ │ ▼ ┗━━━━━━━━━━━━━━━┛ ▼
/// └───────────────────┘
/// ◀───────────────▶
/// ◀───────────────▶ ◀───────────────▶
/// ```
/// This is useful to render on-touch or selected states of elements, where you
/// want to provide a padded background that does otherwise affect the layout of the element,
/// which is likely controlled by its parent element.
///
public struct Decorate : ProxyElement {

/// The element which provides the sizing and measurement.
/// The sizing and position of the `Decorate` element is determined
/// by this element.
public var wrapped : Element

/// The element which is used to render the background.
/// It is stretched to fit the `wrapped` content, plus the `overflow` padding.
///
/// If you have a 100w x 50h element, and an overflow of (10, 10, 10, 10),
/// the measured sized will be 100w x 50h, and the background will be
/// sized to be 120w x 70h.
/// ```
/// ┌───────────────────┐
/// │ ┏━━━━━━━━━━━━━━━┓ │ ▲
/// │ ┃ ┃ │ │
/// │ ┃ Wrapped ┃ │ │
/// │ ┃ ┃ │ │
/// │ ┗━━━━━━━━━━━━━━━┛ │ ▼
/// └───────────────────┘
/// ◀───────────────▶
/// ```
/// The element which is used to draw the decoration.
/// It does not affect sizing or positioning.
public var decoration : Element

/// TODO
/// Where the decoration should be positioned in the z-axis: Above or below the wrapped element.
public var layering : Layering

/// TODO
/// How the `decoration` should be positioned in respect to the `wrapped` element.
public var position : Position

/// Creates a new instance with the provided overflow, background, and wrapped element.
Expand All @@ -82,29 +66,29 @@ public struct Decorate : ProxyElement {
public var elementRepresentation: Element {
LayoutWriter { context, layout in

let wrappedSize = self.wrapped.content.measure(
let contentSize = self.wrapped.content.measure(
in: context.size,
environment: context.environment
)

let wrappedFrame = CGRect(origin: .zero, size: wrappedSize)
let contentFrame = CGRect(origin: .zero, size: contentSize)

let decorationFrame = self.position.frame(
with: wrappedFrame,
with: contentFrame,
decoration: self.decoration,
environment: context.environment
)

layout.sizing = .fixed(wrappedSize)
layout.sizing = .fixed(contentSize)

switch self.layering {
case .above:
layout.add(with: wrappedFrame, child: self.wrapped)
layout.add(with: contentFrame, child: self.wrapped)
layout.add(with: decorationFrame, child: self.decoration)

case .below:
layout.add(with: decorationFrame, child: self.decoration)
layout.add(with: wrappedFrame, child: self.wrapped)
layout.add(with: contentFrame, child: self.wrapped)
}
}
}
Expand All @@ -113,59 +97,87 @@ public struct Decorate : ProxyElement {

extension Decorate {

/// If the decoration should be positioned above or below the content element.
public enum Layering : Equatable {

/// The decoration is displayed above the content element.
case above

/// The decoration is displayed below the content element.
case below
}

/// What corner the decoration element should be positioned in.
public enum Corner : Equatable {
case topLeft
case topRight
case bottomRight
case bottomLeft
}

/// How to position the decoration element relative to the content element.
public enum Position {

/// Insets the decoration element on each edge by the amount specified by
/// the `UIEdgeInsets` property.
///
/// A positive value for an edge expands the decoration outside of that edge,
/// whereas a negative inset pushes the the decoration inside that edge.
case inset(UIEdgeInsets)

/// Provides a `.inset` position where the decoration is inset by the
/// same amount on each side.
public static func inset(_ amount : CGFloat) -> Self {
.inset(UIEdgeInsets(top: amount, left: amount, bottom: amount, right: amount))
}

/// Provides a `.inset` position where the decoration is inset by the
/// `horizontal` amount on the left and right, and the `vertical` amount on the top and bottom.
public static func inset(horizontal : CGFloat = 0.0, vertical : CGFloat = 0.0) -> Self {
.inset(UIEdgeInsets(top: vertical, left: horizontal, bottom: vertical, right: horizontal))
}

case corner(Corner, CGPoint = .zero)
/// The decoration element is positioned in the given corner of the
/// content element, optionally offset by the provided amount.
case corner(Corner, UIOffset = .init())

/// Allows you to provide custom positioning for the decoration, based on the passed context.
case custom((CustomContext) -> CGRect)

/// Information provided to the `.custom` positioning type.
public struct CustomContext {
public var contentBounds : CGRect

/// The frame of the content element within the `Decorate` element.
public var contentFrame : CGRect

/// The environment the element is being rendered in.
public var environment : Environment
}

func frame(with wrappedFrame : CGRect, decoration : Element, environment : Environment) -> CGRect {
func frame(
with contentFrame : CGRect,
decoration : Element,
environment : Environment
) -> CGRect {

switch self {
case .inset(let inset):
return wrappedFrame.inset(by: inset.inverted)
return contentFrame.inset(by: inset.negated)

case .corner(let corner, let offset):

let size = decoration.content.measure(in: .init(wrappedFrame.size), environment: environment)
let size = decoration.content.measure(in: .init(contentFrame.size), environment: environment)

let center : CGPoint = {
switch corner {
case .topLeft:
return .zero
case .topRight:
return CGPoint(x: wrappedFrame.maxX, y: 0)
return CGPoint(x: contentFrame.maxX, y: 0)
case .bottomRight:
return CGPoint(x: wrappedFrame.maxX, y: wrappedFrame.maxY)
return CGPoint(x: contentFrame.maxX, y: contentFrame.maxY)
case .bottomLeft:
return CGPoint(x: 0, y: wrappedFrame.maxY)
return CGPoint(x: 0, y: contentFrame.maxY)
}
}()

Expand All @@ -175,10 +187,12 @@ extension Decorate {
y: center.y - (size.height / 2.0)
),
size: size
).offset(by: offset)
).offset(
by: CGPoint(x: offset.horizontal, y: offset.vertical)
)

case .custom(let provider):
return provider(.init(contentBounds: wrappedFrame, environment: environment))
return provider(.init(contentFrame: contentFrame, environment: environment))
}
}
}
Expand All @@ -187,7 +201,11 @@ extension Decorate {

extension Element {

/// TODO docs
/// Places a decoration element behind or in front of the given `wrapped` element,
/// and positions it according to the `position` parameter.
///
/// See the `Decorate` element for more.
///
public func decorate(
layering : Decorate.Layering,
position : Decorate.Position,
Expand All @@ -205,7 +223,7 @@ extension Element {


extension UIEdgeInsets {
fileprivate var inverted : UIEdgeInsets {
fileprivate var negated : UIEdgeInsets {
UIEdgeInsets(
top: -self.top,
left: -self.left,
Expand Down

0 comments on commit 7490aae

Please sign in to comment.