Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,20 @@ public extension View {
modifier(_ClipShapeModifier(kind: shape._shapeKind, cornerRadius: shape._cornerRadius))
}
}

// MARK: - Tint

/// Sets the accent color for controls in the subtree (an environment value,
/// like `.foregroundColor`).
public struct _TintModifier: RenderModifier {
let color: Color
public var _modifierNode: ModifierNode {
ModifierNode(kind: "tint", args: ["color": color.propValue])
}
}

public extension View {
func tint(_ color: Color) -> ModifiedContent<Self, _TintModifier> {
modifier(_TintModifier(color: color))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ struct ModifierTests {
let node = ViewHost(Text("x").shadow(radius: 6)).evaluate()
#expect(node.modifiers.first { $0.kind == "shadow" }?.args["radius"] == .double(6))
}

@Test("tint emits its color")
func tint() {
let node = ViewHost(Text("x").tint(.green)).evaluate()
#expect(node.modifiers.first { $0.kind == "tint" }?.args["color"] == Color.green.propValue)
}
}

// MARK: - Graphics
Expand Down
11 changes: 11 additions & 0 deletions Demo/App.swiftpm/Sources/StylePlaygrounds.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,20 @@ import SwiftUI

struct StylePlayground: View {
@State private var taps = 0
@State private var toggle = true
@State private var slider = 0.5
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
Example("Inherited tint") {
VStack(alignment: .leading, spacing: 10) {
Button("Tinted button") { taps += 1 }
Toggle("Tinted toggle", isOn: $toggle)
Slider(value: $slider, in: 0...1)
ProgressView(value: slider)
}
.tint(.pink)
}
Example("Inherited font") {
VStack(alignment: .leading, spacing: 6) {
Text("All three")
Expand Down
26 changes: 24 additions & 2 deletions Demo/swiftui/src/commonMain/kotlin/com/pureswift/swiftui/Render.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.rememberDatePickerState
Expand Down Expand Up @@ -150,6 +154,7 @@ internal val LocalInheritedFontSize = compositionLocalOf { TextUnit.Unspecified
internal val LocalInheritedFontWeight = compositionLocalOf<FontWeight?> { null }
internal val LocalInheritedColor = compositionLocalOf { Color.Unspecified }
internal val LocalInheritedDisabled = compositionLocalOf { false }
internal val LocalTint = compositionLocalOf<Color?> { null }

/// Interprets a Swift-evaluated node tree into Material 3 composables.
///
Expand Down Expand Up @@ -180,6 +185,7 @@ internal fun RenderChild(node: ViewNode) {
var fontWeight = LocalInheritedFontWeight.current
var color = LocalInheritedColor.current
var disabled = LocalInheritedDisabled.current
var tint = LocalTint.current
for (m in node.modifiers) {
when (m.kind) {
"font" -> {
Expand All @@ -193,6 +199,7 @@ internal fun RenderChild(node: ViewNode) {
"fontWeight" -> m.args.string("weight")?.let { fontWeight = fontWeightFor(it) }
"foregroundColor" -> m.args.long("color")?.let { color = Color(it.toInt()) }
"disabled" -> if ((m.args["value"] as? kotlinx.serialization.json.JsonPrimitive)?.content == "true") disabled = true
"tint" -> m.args.long("color")?.let { tint = Color(it.toInt()) }
}
}

Expand All @@ -202,6 +209,7 @@ internal fun RenderChild(node: ViewNode) {
LocalInheritedFontWeight provides fontWeight,
LocalInheritedColor provides color,
LocalInheritedDisabled provides disabled,
LocalTint provides tint,
) { RenderResolved(node) }
}

Expand All @@ -214,10 +222,12 @@ private fun RenderResolved(node: ViewNode) {

"Button" -> {
val onTap = node.long("onTap")
val tint = LocalTint.current
Button(
// own disabled and any inherited `.disabled` both apply
onClick = { onTap?.let { SwiftBridge.sink.invokeVoid(it) } },
enabled = !node.isDisabled() && !LocalInheritedDisabled.current,
colors = if (tint != null) ButtonDefaults.buttonColors(containerColor = tint) else ButtonDefaults.buttonColors(),
modifier = node.composeModifiers(),
) {
RenderChildren(node)
Expand All @@ -226,11 +236,13 @@ private fun RenderResolved(node: ViewNode) {

"Toggle" -> {
val onChange = node.long("onChange")
val tint = LocalTint.current
Row(verticalAlignment = Alignment.CenterVertically, modifier = node.composeModifiers()) {
RenderChildren(node)
Switch(
checked = node.bool("isOn") ?: false,
onCheckedChange = { onChange?.let { id -> SwiftBridge.sink.invokeBool(id, it) } },
colors = if (tint != null) SwitchDefaults.colors(checkedTrackColor = tint) else SwitchDefaults.colors(),
)
}
}
Expand Down Expand Up @@ -313,21 +325,31 @@ private fun RenderResolved(node: ViewNode) {

"ProgressView" -> {
val value = node.double("value")
val tint = LocalTint.current
if (value != null) {
LinearProgressIndicator(progress = { value.toFloat() }, modifier = node.composeModifiers().fillMaxWidth())
LinearProgressIndicator(
progress = { value.toFloat() },
color = tint ?: ProgressIndicatorDefaults.linearColor,
modifier = node.composeModifiers().fillMaxWidth(),
)
} else {
CircularProgressIndicator(modifier = node.composeModifiers())
CircularProgressIndicator(
color = tint ?: ProgressIndicatorDefaults.circularColor,
modifier = node.composeModifiers(),
)
}
}

"Slider" -> {
val onChange = node.long("onChange")
val min = (node.double("min") ?: 0.0).toFloat()
val max = (node.double("max") ?: 1.0).toFloat()
val tint = LocalTint.current
Slider(
value = (node.double("value") ?: 0.0).toFloat(),
onValueChange = { onChange?.let { id -> SwiftBridge.sink.invokeDouble(id, it.toDouble()) } },
valueRange = min..max,
colors = if (tint != null) SliderDefaults.colors(thumbColor = tint, activeTrackColor = tint) else SliderDefaults.colors(),
modifier = node.composeModifiers().fillMaxWidth(),
)
}
Expand Down
Loading