Skip to content

Commit f0ca64f

Browse files
authored
Merge pull request #38 from PureSwift/feature/style-inheritance
2 parents 2bc1ae7 + 2d251b3 commit f0ca64f

3 files changed

Lines changed: 99 additions & 5 deletions

File tree

Demo/App.swiftpm/Sources/Catalog.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct CatalogEntry: Identifiable {
2525

2626
static let all: [CatalogEntry] = [
2727
CatalogEntry(id: "text", title: "Text", screen: AnyCatalogScreen(TextPlayground())),
28+
CatalogEntry(id: "style", title: "Styling", screen: AnyCatalogScreen(StylePlayground())),
2829
CatalogEntry(id: "button", title: "Button", screen: AnyCatalogScreen(ButtonPlayground())),
2930
CatalogEntry(id: "toggle", title: "Toggle", screen: AnyCatalogScreen(TogglePlayground())),
3031
CatalogEntry(id: "slider", title: "Slider", screen: AnyCatalogScreen(SliderPlayground())),
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#if canImport(AndroidSwiftUI)
2+
import AndroidSwiftUI
3+
#else
4+
import SwiftUI
5+
#endif
6+
7+
struct StylePlayground: View {
8+
@State private var taps = 0
9+
var body: some View {
10+
ScrollView {
11+
VStack(alignment: .leading, spacing: 0) {
12+
Example("Inherited font") {
13+
VStack(alignment: .leading, spacing: 6) {
14+
Text("All three")
15+
Text("inherit the")
16+
Text("title font")
17+
}
18+
.font(.title)
19+
}
20+
Example("Inherited color") {
21+
VStack(alignment: .leading, spacing: 6) {
22+
Text("Both lines are")
23+
Text("blue from the container")
24+
}
25+
.foregroundColor(.blue)
26+
}
27+
Example("Child overrides parent") {
28+
VStack(alignment: .leading, spacing: 6) {
29+
Text("Title by inheritance")
30+
Text("Caption overrides").font(.caption)
31+
}
32+
.font(.title)
33+
}
34+
Example("Font and color together") {
35+
VStack(alignment: .leading, spacing: 6) {
36+
Text("Headline weight")
37+
Text("and purple color")
38+
}
39+
.font(.headline)
40+
.foregroundColor(.purple)
41+
}
42+
Example("Inherited disabled") {
43+
VStack(alignment: .leading, spacing: 8) {
44+
Text("Both buttons are disabled (taps: \(taps))")
45+
Button("First") { taps += 1 }
46+
Button("Second") { taps += 1 }
47+
}
48+
.disabled(true)
49+
}
50+
}
51+
}
52+
}
53+
}

Demo/swiftui/src/commonMain/kotlin/com/pureswift/swiftui/Render.kt

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ internal data class AnimSpec(val curve: String, val durationMs: Int)
138138

139139
internal val LocalAnimationSpec = compositionLocalOf<AnimSpec?> { null }
140140

141+
// Inherited style environment (SwiftUI's `.font`/`.foregroundColor`/`.disabled`
142+
// set values for a whole subtree). A node folds its own such modifiers into
143+
// these before rendering its children; leaves consume them as defaults that
144+
// their own modifiers still override.
145+
internal val LocalInheritedFontSize = compositionLocalOf { TextUnit.Unspecified }
146+
internal val LocalInheritedFontWeight = compositionLocalOf<FontWeight?> { null }
147+
internal val LocalInheritedColor = compositionLocalOf { Color.Unspecified }
148+
internal val LocalInheritedDisabled = compositionLocalOf { false }
149+
141150
/// Interprets a Swift-evaluated node tree into Material 3 composables.
142151
///
143152
/// One `when` per node type; unknown types render a diagnostic so schema
@@ -152,7 +161,36 @@ fun Render(node: ViewNode) {
152161
val spec = node.string("animationCurve")?.let {
153162
AnimSpec(it, (node.double("animationDurationMs") ?: 350.0).toInt())
154163
} ?: LocalAnimationSpec.current
155-
CompositionLocalProvider(LocalAnimationSpec provides spec) { RenderResolved(node) }
164+
165+
// Fold this node's own style modifiers into the inherited environment so
166+
// its subtree sees them.
167+
var fontSize = LocalInheritedFontSize.current
168+
var fontWeight = LocalInheritedFontWeight.current
169+
var color = LocalInheritedColor.current
170+
var disabled = LocalInheritedDisabled.current
171+
for (m in node.modifiers) {
172+
when (m.kind) {
173+
"font" -> {
174+
m.args.string("style")?.let {
175+
fontSize = fontSizeForStyle(it).sp
176+
fontWeight = defaultWeightForStyle(it)
177+
}
178+
m.args.double("size")?.let { fontSize = it.sp }
179+
m.args.string("weight")?.let { fontWeight = fontWeightFor(it) }
180+
}
181+
"fontWeight" -> m.args.string("weight")?.let { fontWeight = fontWeightFor(it) }
182+
"foregroundColor" -> m.args.long("color")?.let { color = Color(it.toInt()) }
183+
"disabled" -> if ((m.args["value"] as? kotlinx.serialization.json.JsonPrimitive)?.content == "true") disabled = true
184+
}
185+
}
186+
187+
CompositionLocalProvider(
188+
LocalAnimationSpec provides spec,
189+
LocalInheritedFontSize provides fontSize,
190+
LocalInheritedFontWeight provides fontWeight,
191+
LocalInheritedColor provides color,
192+
LocalInheritedDisabled provides disabled,
193+
) { RenderResolved(node) }
156194
}
157195

158196
@Composable
@@ -165,8 +203,9 @@ private fun RenderResolved(node: ViewNode) {
165203
"Button" -> {
166204
val onTap = node.long("onTap")
167205
Button(
206+
// own disabled and any inherited `.disabled` both apply
168207
onClick = { onTap?.let { SwiftBridge.sink.invokeVoid(it) } },
169-
enabled = !node.isDisabled(),
208+
enabled = !node.isDisabled() && !LocalInheritedDisabled.current,
170209
modifier = node.composeModifiers(),
171210
) {
172211
RenderChildren(node)
@@ -744,9 +783,10 @@ private fun materialIcon(name: String): ImageVector? = when (name) {
744783
// attributes. Layout modifiers in the same chain still apply via composeModifiers().
745784
@Composable
746785
private fun RenderText(node: ViewNode) {
747-
var color = Color.Unspecified
748-
var fontSize: TextUnit = TextUnit.Unspecified
749-
var weight: FontWeight? = null
786+
// Start from the inherited environment; the node's own modifiers override.
787+
var color = LocalInheritedColor.current
788+
var fontSize: TextUnit = LocalInheritedFontSize.current
789+
var weight: FontWeight? = LocalInheritedFontWeight.current
750790
var fontStyle: FontStyle? = null
751791
var maxLines = Int.MAX_VALUE
752792
var textAlign: TextAlign? = null

0 commit comments

Comments
 (0)