Skip to content

Commit e0ef2b1

Browse files
authored
Add background and overlay support (#336)
* Add ViewDimensions helper method * Add Background and Overlay modifier * Implemente makeSecondaryLayerView * Add missing Foundation import for CGSize on Linux
1 parent 1a6c9f3 commit e0ef2b1

File tree

6 files changed

+470
-29
lines changed

6 files changed

+470
-29
lines changed

Sources/OpenSwiftUICore/Data/Preference/PreferenceCombiner.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ package struct PreferenceCombiner<K>: Rule, AsyncAttribute, CustomStringConverti
5151

5252
package struct PairwisePreferenceCombinerVisitor: PreferenceKeyVisitor {
5353
package let outputs: (_ViewOutputs, _ViewOutputs)
54+
5455
package var result: _ViewOutputs = _ViewOutputs()
5556

5657
package init(outputs: (_ViewOutputs, _ViewOutputs)) {

Sources/OpenSwiftUICore/Layout/Stack/ZStack.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,7 @@ extension _ZStackLayout: Layout {
162162
}
163163
for subview in subviews {
164164
let dimensions = subview.dimensions(in: ProposedViewSize(bounds.size))
165-
let horizontalAlignmentValue = dimensions[alignment.horizontal]
166-
let verticalAlignmentValue = dimensions[alignment.vertical]
165+
let (horizontalAlignmentValue, verticalAlignmentValue) = dimensions[alignment]
167166
let geometry = ViewGeometry(
168167
origin: CGPoint(
169168
x: alignmentSize.width - horizontalAlignmentValue + bounds.origin.x,

Sources/OpenSwiftUICore/Layout/View/ViewDimensions.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ public struct ViewDimensions {
147147
self[guide.key]
148148
}
149149

150+
@inline(__always)
151+
subscript(guide: Alignment) -> (CGFloat, CGFloat) {
152+
(self[guide.horizontal], self[guide.vertical])
153+
}
154+
150155
/// Gets the explicit value of the given horizontal alignment guide.
151156
///
152157
/// Find the horizontal offset of a particular guide in the corresponding
@@ -184,6 +189,11 @@ public struct ViewDimensions {
184189
public subscript(explicit guide: VerticalAlignment) -> CGFloat? {
185190
self[explicit: guide.key]
186191
}
192+
193+
@inline(__always)
194+
subscript(explicit guide: Alignment) -> (CGFloat?, CGFloat?) {
195+
(self[explicit: guide.horizontal], self[explicit: guide.vertical])
196+
}
187197
}
188198

189199
@available(*, unavailable)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//
2+
// BackgroundModifier.swift
3+
// OpenSwiftUICore
4+
// Status: WIP
5+
6+
// MARK: - BackgroundModifier [6.4.41]
7+
8+
/// A modifier that layers a secondary view behind the primary content it
9+
/// modifies, while maintaining the layout characteristics of the primary view.
10+
@available(OpenSwiftUI_v1_0, *)
11+
@frozen
12+
public struct _BackgroundModifier<Background>: ViewModifier, MultiViewModifier, PrimitiveViewModifier where Background: View {
13+
public var background: Background
14+
15+
public var alignment: Alignment
16+
17+
/// Creates an instance that adds `background` as a secondary layer behind
18+
/// its primary content.
19+
@inlinable
20+
public init(background: Background, alignment: Alignment = .center) {
21+
self.background = background
22+
self.alignment = alignment
23+
}
24+
25+
nonisolated public static func _makeView(
26+
modifier: _GraphValue<Self>,
27+
inputs: _ViewInputs,
28+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
29+
) -> _ViewOutputs {
30+
makeSecondaryLayerView(
31+
secondaryLayer: modifier[offset: { .of(&$0.background) }].value,
32+
alignment: modifier[offset: { .of(&$0.alignment) }].value,
33+
inputs: inputs,
34+
body: body,
35+
flipOrder: true
36+
)
37+
}
38+
}
39+
40+
@available(*, unavailable)
41+
extension _BackgroundModifier : Sendable {}
42+
43+
// TODO
44+
45+
// MARK: - View + Background [6.4.41] [WIP]
46+
47+
@available(OpenSwiftUI_v3_0, *)
48+
extension View {
49+
/// Layers the views that you specify behind this view.
50+
///
51+
/// Use this modifier to place one or more views behind another view.
52+
/// For example, you can place a collection of stars beind a ``Text`` view:
53+
///
54+
/// Text("ABCDEF")
55+
/// .background(alignment: .leading) { Star(color: .red) }
56+
/// .background(alignment: .center) { Star(color: .green) }
57+
/// .background(alignment: .trailing) { Star(color: .blue) }
58+
///
59+
/// The example above assumes that you've defined a `Star` view with a
60+
/// parameterized color:
61+
///
62+
/// struct Star: View {
63+
/// var color: Color
64+
///
65+
/// var body: some View {
66+
/// Image(systemName: "star.fill")
67+
/// .foregroundStyle(color)
68+
/// }
69+
/// }
70+
///
71+
/// By setting different `alignment` values for each modifier, you make the
72+
/// stars appear in different places behind the text:
73+
///
74+
/// ![A screenshot of the letters A, B, C, D, E, and F written in front of
75+
/// three stars. The stars, from left to right, are red, green, and
76+
/// blue.](View-background-2)
77+
///
78+
/// If you specify more than one view in the `content` closure, the modifier
79+
/// collects all of the views in the closure into an implicit ``ZStack``,
80+
/// taking them in order from back to front. For example, you can layer a
81+
/// vertical bar behind a circle, with both of those behind a horizontal
82+
/// bar:
83+
///
84+
/// Color.blue
85+
/// .frame(width: 200, height: 10) // Creates a horizontal bar.
86+
/// .background {
87+
/// Color.green
88+
/// .frame(width: 10, height: 100) // Creates a vertical bar.
89+
/// Circle()
90+
/// .frame(width: 50, height: 50)
91+
/// }
92+
///
93+
/// Both the background modifier and the implicit ``ZStack`` composed from
94+
/// the background content --- the circle and the vertical bar --- use a
95+
/// default ``Alignment/center`` alignment. The vertical bar appears
96+
/// centered behind the circle, and both appear as a composite view centered
97+
/// behind the horizontal bar:
98+
///
99+
/// ![A screenshot of a circle with a horizontal blue bar layered on top
100+
/// and a vertical green bar layered underneath. All of the items are center
101+
/// aligned.](View-background-3)
102+
///
103+
/// If you specify an alignment for the background, it applies to the
104+
/// implicit stack rather than to the individual views in the closure. You
105+
/// can see this if you add the ``Alignment/leading`` alignment:
106+
///
107+
/// Color.blue
108+
/// .frame(width: 200, height: 10)
109+
/// .background(alignment: .leading) {
110+
/// Color.green
111+
/// .frame(width: 10, height: 100)
112+
/// Circle()
113+
/// .frame(width: 50, height: 50)
114+
/// }
115+
///
116+
/// The vertical bar and the circle move as a unit to align the stack
117+
/// with the leading edge of the horizontal bar, while the
118+
/// vertical bar remains centered on the circle:
119+
///
120+
/// ![A screenshot of a horizontal blue bar in front of a circle, which
121+
/// is in front of a vertical green bar. The horizontal bar and the circle
122+
/// are center aligned with each other; the left edges of the circle
123+
/// and the horizontal are aligned.](View-background-3a)
124+
///
125+
/// To control the placement of individual items inside the `content`
126+
/// closure, either use a different background modifier for each item, as
127+
/// the earlier example of stars under text demonstrates, or add an explicit
128+
/// ``ZStack`` inside the content closure with its own alignment:
129+
///
130+
/// Color.blue
131+
/// .frame(width: 200, height: 10)
132+
/// .background(alignment: .leading) {
133+
/// ZStack(alignment: .leading) {
134+
/// Color.green
135+
/// .frame(width: 10, height: 100)
136+
/// Circle()
137+
/// .frame(width: 50, height: 50)
138+
/// }
139+
/// }
140+
///
141+
/// The stack alignment ensures that the circle's leading edge aligns with
142+
/// the vertical bar's, while the background modifier aligns the composite
143+
/// view with the horizontal bar:
144+
///
145+
/// ![A screenshot of a horizontal blue bar in front of a circle, which
146+
/// is in front of a vertical green bar. All items are aligned on their
147+
/// left edges.](View-background-4)
148+
///
149+
/// You can achieve layering without a background modifier by putting both
150+
/// the modified view and the background content into a ``ZStack``. This
151+
/// produces a simpler view hierarchy, but it changes the layout priority
152+
/// that OpenSwiftUI applies to the views. Use the background modifier when you
153+
/// want the modified view to dominate the layout.
154+
///
155+
/// If you want to specify a ``ShapeStyle`` like a
156+
/// ``HierarchicalShapeStyle`` or a ``Material`` as the background, use
157+
/// ``View/background(_:ignoresSafeAreaEdges:)`` instead.
158+
/// To specify a ``Shape`` or ``InsettableShape``, use
159+
/// ``View/background(_:in:fillStyle:)``.
160+
/// To configure the background of a presentation, like a sheet, use
161+
/// ``View/presentationBackground(alignment:content:)``.
162+
///
163+
/// - Parameters:
164+
/// - alignment: The alignment that the modifier uses to position the
165+
/// implicit ``ZStack`` that groups the background views. The default
166+
/// is ``Alignment/center``.
167+
/// - content: A ``ViewBuilder`` that you use to declare the views to draw
168+
/// behind this view, stacked in a cascading order from bottom to top.
169+
/// The last view that you list appears at the front of the stack.
170+
///
171+
/// - Returns: A view that uses the specified content as a background.
172+
@inlinable
173+
nonisolated public func background<V>(alignment: Alignment = .center, @ViewBuilder content: () -> V) -> some View where V: View {
174+
modifier(_BackgroundModifier(background: content(), alignment: alignment))
175+
}
176+
}

0 commit comments

Comments
 (0)