Skip to content

Commit f7ee050

Browse files
authored
Merge pull request #42 from PureSwift/feature/disabled-controls
2 parents ef6def4 + 23aaf2d commit f7ee050

2 files changed

Lines changed: 21 additions & 10 deletions

File tree

Demo/App.swiftpm/Sources/StylePlaygrounds.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct StylePlayground: View {
88
@State private var taps = 0
99
@State private var toggle = true
1010
@State private var slider = 0.5
11+
@State private var name = ""
1112
var body: some View {
1213
ScrollView {
1314
VStack(alignment: .leading, spacing: 0) {
@@ -52,9 +53,11 @@ struct StylePlayground: View {
5253
}
5354
Example("Inherited disabled") {
5455
VStack(alignment: .leading, spacing: 8) {
55-
Text("Both buttons are disabled (taps: \(taps))")
56-
Button("First") { taps += 1 }
57-
Button("Second") { taps += 1 }
56+
Text("Every control below is disabled (taps: \(taps))")
57+
Button("Button") { taps += 1 }
58+
Toggle("Toggle", isOn: $toggle)
59+
Slider(value: $slider, in: 0...1)
60+
TextField("TextField", text: $name)
5861
}
5962
.disabled(true)
6063
}

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,8 @@ private fun RenderResolved(node: ViewNode) {
224224
val onTap = node.long("onTap")
225225
val tint = LocalTint.current
226226
Button(
227-
// own disabled and any inherited `.disabled` both apply
228227
onClick = { onTap?.let { SwiftBridge.sink.invokeVoid(it) } },
229-
enabled = !node.isDisabled() && !LocalInheritedDisabled.current,
228+
enabled = node.isEnabled(),
230229
colors = if (tint != null) ButtonDefaults.buttonColors(containerColor = tint) else ButtonDefaults.buttonColors(),
231230
modifier = node.composeModifiers(),
232231
) {
@@ -242,6 +241,7 @@ private fun RenderResolved(node: ViewNode) {
242241
Switch(
243242
checked = node.bool("isOn") ?: false,
244243
onCheckedChange = { onChange?.let { id -> SwiftBridge.sink.invokeBool(id, it) } },
244+
enabled = node.isEnabled(),
245245
colors = if (tint != null) SwitchDefaults.colors(checkedTrackColor = tint) else SwitchDefaults.colors(),
246246
)
247247
}
@@ -349,6 +349,7 @@ private fun RenderResolved(node: ViewNode) {
349349
value = (node.double("value") ?: 0.0).toFloat(),
350350
onValueChange = { onChange?.let { id -> SwiftBridge.sink.invokeDouble(id, it.toDouble()) } },
351351
valueRange = min..max,
352+
enabled = node.isEnabled(),
352353
colors = if (tint != null) SliderDefaults.colors(thumbColor = tint, activeTrackColor = tint) else SliderDefaults.colors(),
353354
modifier = node.composeModifiers().fillMaxWidth(),
354355
)
@@ -432,6 +433,7 @@ private fun RenderTextField(node: ViewNode) {
432433
}
433434
},
434435
label = { Text(node.string("placeholder") ?: "") },
436+
enabled = node.isEnabled(),
435437
visualTransformation = if (node.bool("secure") == true) PasswordVisualTransformation() else VisualTransformation.None,
436438
modifier = node.composeModifiers().fillMaxWidth(),
437439
)
@@ -442,11 +444,12 @@ private fun RenderTextField(node: ViewNode) {
442444
private fun RenderStepper(node: ViewNode) {
443445
val onIncrement = node.long("onIncrement")
444446
val onDecrement = node.long("onDecrement")
447+
val enabled = node.isEnabled()
445448
Row(verticalAlignment = Alignment.CenterVertically, modifier = node.composeModifiers().fillMaxWidth()) {
446449
RenderChildren(node)
447450
Spacer(modifier = Modifier.weight(1f))
448-
TextButton(onClick = { onDecrement?.let { SwiftBridge.sink.invokeVoid(it) } }) { Text("", fontSize = 20.sp) }
449-
TextButton(onClick = { onIncrement?.let { SwiftBridge.sink.invokeVoid(it) } }) { Text("+", fontSize = 20.sp) }
451+
TextButton(onClick = { onDecrement?.let { SwiftBridge.sink.invokeVoid(it) } }, enabled = enabled) { Text("", fontSize = 20.sp) }
452+
TextButton(onClick = { onIncrement?.let { SwiftBridge.sink.invokeVoid(it) } }, enabled = enabled) { Text("+", fontSize = 20.sp) }
450453
}
451454
}
452455

@@ -507,7 +510,7 @@ private fun RenderDatePicker(node: ViewNode) {
507510
Row(verticalAlignment = Alignment.CenterVertically, modifier = node.composeModifiers().fillMaxWidth()) {
508511
RenderChildren(node)
509512
Spacer(modifier = Modifier.weight(1f))
510-
TextButton(onClick = { showDialog = true }) { Text(formatDateMillis(millis)) }
513+
TextButton(onClick = { showDialog = true }, enabled = node.isEnabled()) { Text(formatDateMillis(millis)) }
511514
}
512515
if (showDialog) {
513516
val state = rememberDatePickerState(initialSelectedDateMillis = millis)
@@ -543,7 +546,7 @@ private fun formatDateMillis(millis: Long): String {
543546
private fun RenderMenu(node: ViewNode) {
544547
var expanded by remember { mutableStateOf(false) }
545548
Box {
546-
TextButton(onClick = { expanded = true }) { Text(node.string("label") ?: "") }
549+
TextButton(onClick = { expanded = true }, enabled = node.isEnabled()) { Text(node.string("label") ?: "") }
547550
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
548551
for (child in node.children) {
549552
if (child.isPresentation()) continue
@@ -575,7 +578,7 @@ private fun RenderPicker(node: ViewNode) {
575578
Row(modifier = node.composeModifiers().fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
576579
Text(node.string("title") ?: "")
577580
Spacer(modifier = Modifier.weight(1f))
578-
TextButton(onClick = { expanded = true }) { Text(currentLabel) }
581+
TextButton(onClick = { expanded = true }, enabled = node.isEnabled()) { Text(currentLabel) }
579582
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
580583
for ((value, label) in options) {
581584
DropdownMenuItem(text = { Text(label) }, onClick = {
@@ -674,6 +677,11 @@ private fun RenderEffects(node: ViewNode) {
674677
private fun ViewNode.isDisabled(): Boolean =
675678
modifiers.any { it.kind == "disabled" && (it.args["value"] as? kotlinx.serialization.json.JsonPrimitive)?.content == "true" }
676679

680+
// A control is enabled unless its own `.disabled(true)` or an inherited one
681+
// (from an ancestor container) is in scope.
682+
@Composable
683+
private fun ViewNode.isEnabled(): Boolean = !isDisabled() && !LocalInheritedDisabled.current
684+
677685
// A shape fills its (frame-derived) size with its fill color; without a frame
678686
// it collapses to zero, matching this backend's no-layout-engine limitation.
679687
@Composable

0 commit comments

Comments
 (0)