@@ -17,55 +17,67 @@ import androidx.compose.ui.Modifier
17
17
18
18
/// Composable to handle sizing and layout in a SwiftUI-like way for containers that compose child content.
19
19
///
20
- /// Compose's behavior differs from SwiftUI's when dealing with filling space. A SwiftUI container will give each child the
21
- /// space it needs to display, then automatically divide the remainder between children that want to expand. In Compose, on
22
- /// the other hand, a single 'fillMaxWidth' child will consume all remaining space, pushing subsequent children out. To get
23
- /// SwiftUI's behavior, all children that want to expand must use the `weight` modifier, which is only available in a Row or
24
- /// Column scope. We've abstracted the fact that 'weight' for a given dimension may or may not be available depending on scope
25
- /// behind our `Modifier.fillWidth` and `Modifier.fillHeight` extension functions.
20
+ /// - Seealso: `ComposeFlexibleContainer(...)`
21
+ @Composable public func ComposeContainer( axis: Axis ? = nil , eraseAxis: Bool = false , scrollAxes: Axis . Set = [ ] , modifier: Modifier = Modifier, fixedWidth: Bool = false , fillWidth: Bool = false , fixedHeight: Bool = false , fillHeight: Bool = false , then: Modifier = Modifier, content: @Composable ( Modifier ) -> Void ) {
22
+ ComposeFlexibleContainer ( axis: axis, eraseAxis: eraseAxis, scrollAxes: scrollAxes, modifier: modifier, fixedWidth: fixedWidth, flexibleWidthMax: fillWidth ? Float . flexibleFill : nil , fixedHeight: fixedHeight, flexibleHeightMax: fillHeight ? Float . flexibleFill : nil , then: then, content: content)
23
+ }
24
+
25
+ /// Composable to handle sizing and layout in a SwiftUI-like way for containers that compose child content.
26
+ ///
27
+ /// In Compose, containers are not perfectly layout neutral. A container that wants to expand must use the proper
28
+ /// modifier, rather than relying on its content. Additionally, a single 'fillMaxWidth' child will consume all
29
+ /// remaining space, pushing subsequent children out.
26
30
///
27
- /// Having to explicitly set a certain modifier in order to expand within a parent is problematic for containers that want to
28
- /// fit content. The container only wants to expand if it has content that wants to expand. It can't know this until it composes
29
- /// its content. The code in this function sets triggers on the environment values that we use in 'fillWidth' and 'fillHeight' so
30
- /// that if the container content uses them, the container itself can recompose with the appropriate expansion to match its
31
- /// content. Note that this generally only affects final layout when an expanding child is in a container that is itself in a
32
- /// container, and it has to share space with other members of the parent container.
33
- @Composable public func ComposeContainer ( axis: Axis ? = nil , eraseAxis: Bool = false , scrollAxes: Axis . Set = [ ] , modifier: Modifier = Modifier, fillWidth : Bool = false , fixedWidth : Bool = false , minWidth : Bool = false , fillHeight : Bool = false , fixedHeight: Bool = false , minHeight : Bool = false , then: Modifier = Modifier, content: @Composable ( Modifier ) -> Void ) {
34
- // Use remembered expansion values to recompose on change
35
- let isFillWidth = remember { mutableStateOf ( fillWidth ) }
36
- let isFillHeight = remember { mutableStateOf ( fillHeight ) }
31
+ /// Having to explicitly set a modifier in order to expand within a parent in Compose is problematic for containers that
32
+ /// want to fit content. The container only wants to expand if it has content that wants to expand. It can't know this
33
+ /// until it composes its content. The code in this function sets triggers on the environment values that we use in
34
+ /// flexible layout so that if the container content uses them, the container itself can recompose with the appropriate
35
+ /// expansion to match its content. Note that this generally only affects final layout when an expanding child is in a
36
+ /// container that is itself in a container , and it has to share space with other members of the parent container.
37
+ @Composable public func ComposeFlexibleContainer ( axis: Axis ? = nil , eraseAxis: Bool = false , scrollAxes: Axis . Set = [ ] , modifier: Modifier = Modifier, fixedWidth : Bool = false , flexibleWidthIdeal : Float ? = nil , flexibleWidthMin : Float ? = nil , flexibleWidthMax : Float ? = nil , fixedHeight: Bool = false , flexibleHeightIdeal : Float ? = nil , flexibleHeightMin : Float ? = nil , flexibleHeightMax : Float ? = nil , then: Modifier = Modifier, content: @Composable ( Modifier ) -> Void ) {
38
+ // Use remembered flexible values to recompose on change
39
+ let contentFlexibleWidthMax = remember { mutableStateOf ( flexibleWidthMax ) }
40
+ let contentFlexibleHeightMax = remember { mutableStateOf ( flexibleHeightMax ) }
37
41
38
- // Create the correct modifier for the current values. We must use IntrinsicSize.Max for fills in a scroll direction
39
- // because Compose's fillMax modifiers have no effect in the scroll direction. We can't use IntrinsicSize for scrolling
40
- // containers, however
42
+ // Create the correct modifier for the current values and content
41
43
var modifier = modifier
42
44
let inheritedLayoutScrollAxes = EnvironmentValues . shared. _layoutScrollAxes
43
45
var totalLayoutScrollAxes = inheritedLayoutScrollAxes
44
- if fixedWidth || minWidth || axis == . vertical {
46
+ if fixedWidth || flexibleWidthMax ? . isFlexibleNonExpandingMax == true || flexibleWidthMin ? . isFlexibleNonExpandingMin == true || axis == . vertical {
45
47
totalLayoutScrollAxes. remove ( Axis . Set. horizontal)
46
48
}
47
- if !fixedWidth && isFillWidth. value {
48
- if fillWidth {
49
- modifier = modifier. fillWidth ( )
50
- } else if inheritedLayoutScrollAxes. contains ( Axis . Set. horizontal) {
51
- modifier = modifier. width ( IntrinsicSize . Max)
49
+ if !fixedWidth {
50
+ if flexibleWidthMax? . isFlexibleExpanding != true && contentFlexibleWidthMax. value? . isFlexibleExpanding == true && inheritedLayoutScrollAxes. contains ( Axis . Set. horizontal) {
51
+ // We must use IntrinsicSize.Max for fills in a scroll direction because Compose's fillMax modifiers
52
+ // have no effect in the scroll direction. Flexible values can influence intrinsic measurement
53
+ let minValue = flexibleWidthMin? . isFlexibleNonExpandingMin == true ? flexibleWidthMin : nil
54
+ let maxValue = flexibleWidthMax? . isFlexibleNonExpandingMax == true ? flexibleWidthMax : nil
55
+ modifier = modifier. flexibleWidth ( min: minValue, max: maxValue) . width ( IntrinsicSize . Max)
52
56
} else {
53
- modifier = modifier. fillWidth ( )
57
+ let max : Float ? = flexibleWidthMax ?? contentFlexibleWidthMax. value
58
+ if flexibleWidthIdeal != nil || flexibleWidthMin != nil || max != nil {
59
+ modifier = modifier. flexibleWidth ( ideal: flexibleWidthIdeal, min: flexibleWidthMin, max: max)
60
+ }
54
61
}
55
62
}
56
- if fixedHeight || minHeight || axis == . horizontal {
63
+ if fixedHeight || flexibleHeightMax ? . isFlexibleNonExpandingMax == true || flexibleHeightMin ? . isFlexibleNonExpandingMin == true || axis == . horizontal {
57
64
totalLayoutScrollAxes. remove ( Axis . Set. vertical)
58
65
}
59
- if !fixedHeight && isFillHeight. value {
60
- if fillHeight {
61
- modifier = modifier. fillHeight ( )
62
- } else if inheritedLayoutScrollAxes. contains ( Axis . Set. vertical) {
63
- modifier = modifier. height ( IntrinsicSize . Max)
66
+ if !fixedHeight {
67
+ if flexibleHeightMax? . isFlexibleExpanding != true && contentFlexibleHeightMax. value? . isFlexibleExpanding == true && inheritedLayoutScrollAxes. contains ( Axis . Set. vertical) {
68
+ // We must use IntrinsicSize.Max for fills in a scroll direction because Compose's fillMax modifiers
69
+ // have no effect in the scroll direction. Flexible values can influence intrinsic measurement
70
+ let minValue = flexibleHeightMin? . isFlexibleNonExpandingMin == true ? flexibleHeightMin : nil
71
+ let maxValue = flexibleHeightMax? . isFlexibleNonExpandingMax == true ? flexibleHeightMax : nil
72
+ modifier = modifier. flexibleHeight ( min: minValue, max: maxValue) . height ( IntrinsicSize . Max)
64
73
} else {
65
- modifier = modifier. fillHeight ( )
74
+ let max : Float ? = flexibleHeightMax ?? contentFlexibleHeightMax. value
75
+ if flexibleHeightIdeal != nil || flexibleHeightMin != nil || max != nil {
76
+ modifier = modifier. flexibleHeight ( ideal: flexibleHeightIdeal, min: flexibleHeightMin, max: max)
77
+ }
66
78
}
67
79
}
68
-
80
+
69
81
totalLayoutScrollAxes. formUnion ( scrollAxes)
70
82
let inheritedScrollAxes = EnvironmentValues . shared. _scrollAxes
71
83
let totalScrollAxes = inheritedScrollAxes. union ( scrollAxes)
@@ -87,26 +99,42 @@ import androidx.compose.ui.Modifier
87
99
88
100
// Reset the container layout because this is a new container. A directional container like 'HStack' or 'VStack' will set
89
101
// the correct layout before rendering in the content block below, so that its own children can distribute available space
90
- $0. set_fillWidthModifier ( nil )
91
- $0. set_fillHeightModifier ( nil )
102
+ $0. set_flexibleWidthModifier ( nil )
103
+ $0. set_flexibleHeightModifier ( nil )
92
104
93
- // Set the 'fillWidth' and 'fillHeight' blocks to trigger a side effect to update our container's expansion state, which can
94
- // cause it to recompose and recalculate its own modifier. We must use `SideEffect` or the recomposition never happens
95
- $0. set_fillWidth {
96
- if !isFillWidth. value {
97
- SideEffect {
98
- isFillWidth. value = true
105
+ // Set the 'flexibleWidth' and 'flexibleHeight' blocks to trigger a side effect to update our container's expansion state, which
106
+ // can cause it to recompose and recalculate its own modifier. We must use `SideEffect` or the recomposition never happens
107
+ $0. set_flexibleWidth { ideal, min, max in
108
+ var defaultModifier : Modifier = Modifier
109
+ if max? . isFlexibleExpanding == true {
110
+ if max == Float . flexibleFill {
111
+ SideEffect { contentFlexibleWidthMax. value = Float . flexibleFill }
112
+ } else if contentFlexibleWidthMax. value != Float . flexibleFill {
113
+ // max must be flexibleSpace or flexibleUnknownWithSpace
114
+ SideEffect { contentFlexibleWidthMax. value = Float . flexibleUnknownWithSpace }
99
115
}
116
+ defaultModifier = Modifier . fillMaxWidth ( )
117
+ } else if max != nil && contentFlexibleWidthMax. value == nil {
118
+ SideEffect { contentFlexibleWidthMax. value = Float . flexibleUnknownNonExpanding }
100
119
}
101
- return EnvironmentValues . shared. _fillWidthModifier ?? Modifier . fillMaxWidth ( )
120
+ return EnvironmentValues . shared. _flexibleWidthModifier ? ( ideal, min, max)
121
+ ?? defaultModifier. applyNonExpandingFlexibleWidth ( ideal: ideal, min: min, max: max)
102
122
}
103
- $0. set_fillHeight {
104
- if !isFillHeight. value {
105
- SideEffect {
106
- isFillHeight. value = true
123
+ $0. set_flexibleHeight { ideal, min, max in
124
+ var defaultModifier : Modifier = Modifier
125
+ if max? . isFlexibleExpanding == true {
126
+ if max == Float . flexibleFill {
127
+ SideEffect { contentFlexibleHeightMax. value = Float . flexibleFill }
128
+ } else if contentFlexibleHeightMax. value != Float . flexibleFill {
129
+ // max must be flexibleSpace or flexibleUnknownWithSpace
130
+ SideEffect { contentFlexibleHeightMax. value = Float . flexibleUnknownWithSpace }
107
131
}
132
+ defaultModifier = Modifier . fillMaxHeight ( )
133
+ } else if max != nil && contentFlexibleHeightMax. value == nil {
134
+ SideEffect { contentFlexibleHeightMax. value = Float . flexibleUnknownNonExpanding }
108
135
}
109
- return EnvironmentValues . shared. _fillHeightModifier ?? Modifier . fillMaxHeight ( )
136
+ return EnvironmentValues . shared. _flexibleHeightModifier ? ( ideal, min, max)
137
+ ?? defaultModifier. applyNonExpandingFlexibleHeight ( ideal: ideal, min: min, max: max)
110
138
}
111
139
return ComposeResult . ok
112
140
} in: {
0 commit comments