Skip to content

Commit 961ab95

Browse files
committed
feat(segmentedcontrol): make API modular with SegmentedControlDefaults
- Add SegmentedControlColors data class for public color overrides - Add SegmentedControlDefaults with colors() factory and shape() - Replace variant parameter with colors + shape parameters - colors() accepts individual color overrides with Color.Unspecified defaults - Variant preset can be passed to colors() to select theme baseline - Sample page: add 5 examples showing custom colors, shapes, and combinations
1 parent b595811 commit 961ab95

3 files changed

Lines changed: 247 additions & 32 deletions

File tree

macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/components/SegmentedControl.kt

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier
3131
import androidx.compose.ui.draw.alpha
3232
import androidx.compose.ui.draw.clip
3333
import androidx.compose.ui.graphics.Color
34+
import androidx.compose.ui.graphics.Shape
3435
import androidx.compose.ui.layout.onGloballyPositioned
3536
import androidx.compose.ui.layout.positionInParent
3637
import androidx.compose.ui.platform.LocalDensity
@@ -43,18 +44,62 @@ import androidx.compose.ui.unit.IntOffset
4344
import androidx.compose.ui.unit.dp
4445
import androidx.compose.ui.unit.sp
4546
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.ControlSize
46-
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.SpringPreset
47-
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.MacosTheme
48-
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalControlSize
4947
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalContentColor
48+
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalControlSize
5049
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalTextStyle
5150
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalWindowActive
52-
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.SegmentedControlStyle
51+
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.MacosTheme
52+
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.SegmentedControlColors
5353
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.SegmentedControlVariant
54+
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.SpringPreset
5455
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.macosSpring
5556
import kotlinx.coroutines.launch
5657
import kotlin.math.roundToInt
5758

59+
// =============================================================================
60+
// SegmentedControlDefaults
61+
// =============================================================================
62+
63+
object SegmentedControlDefaults {
64+
65+
@Composable
66+
fun colors(
67+
track: Color = Color.Unspecified,
68+
selectedSegment: Color = Color.Unspecified,
69+
selectedContent: Color = Color.Unspecified,
70+
unselectedContent: Color = Color.Unspecified,
71+
pressedOverlay: Color = Color.Unspecified,
72+
disabledContent: Color = Color.Unspecified,
73+
separatorColor: Color = Color.Unspecified,
74+
inactiveSelectedSegment: Color = Color.Unspecified,
75+
inactiveSelectedContent: Color = Color.Unspecified,
76+
variant: SegmentedControlVariant = SegmentedControlVariant.ContentArea,
77+
): SegmentedControlColors {
78+
val style = MacosTheme.componentStyling.segmentedControl.colorsFor(variant)
79+
return SegmentedControlColors(
80+
track = track.takeOrElse(style.track),
81+
selectedSegment = selectedSegment.takeOrElse(style.selectedSegment),
82+
selectedContent = selectedContent.takeOrElse(style.selectedContent),
83+
unselectedContent = unselectedContent.takeOrElse(style.unselectedContent),
84+
pressedOverlay = pressedOverlay.takeOrElse(style.pressedOverlay),
85+
disabledContent = disabledContent.takeOrElse(style.disabledContent),
86+
separatorColor = separatorColor.takeOrElse(style.separatorColor),
87+
inactiveSelectedSegment = inactiveSelectedSegment.takeOrElse(style.inactiveSelectedSegment),
88+
inactiveSelectedContent = inactiveSelectedContent.takeOrElse(style.inactiveSelectedContent),
89+
)
90+
}
91+
92+
@Composable
93+
fun shape(): Shape {
94+
val controlSize = LocalControlSize.current
95+
val metrics = MacosTheme.componentStyling.segmentedControl.metrics
96+
return RoundedCornerShape(metrics.cornerRadiusFor(controlSize))
97+
}
98+
}
99+
100+
private fun Color.takeOrElse(other: Color): Color =
101+
if (this != Color.Unspecified) this else other
102+
58103
// =============================================================================
59104
// SegmentedControl — slot-based API
60105
// =============================================================================
@@ -66,22 +111,18 @@ fun SegmentedControl(
66111
onSelectedIndexChange: (Int) -> Unit,
67112
modifier: Modifier = Modifier,
68113
enabled: Boolean = true,
69-
variant: SegmentedControlVariant = SegmentedControlVariant.ContentArea,
114+
colors: SegmentedControlColors = SegmentedControlDefaults.colors(),
115+
shape: Shape = SegmentedControlDefaults.shape(),
70116
segment: @Composable (index: Int) -> Unit,
71117
) {
72118
require(segmentCount > 0) { "segmentCount must be > 0" }
73119

74120
val controlSize = LocalControlSize.current
75121
val isWindowActive = LocalWindowActive.current
76-
val style = MacosTheme.componentStyling.segmentedControl
77-
val metrics = style.metrics
78-
val colors = style.colorsFor(variant)
122+
val metrics = MacosTheme.componentStyling.segmentedControl.metrics
79123
val density = LocalDensity.current
80124

81125
val trackHeight = metrics.containerHeightFor(controlSize)
82-
val cornerRadius = metrics.cornerRadiusFor(controlSize)
83-
val trackShape = RoundedCornerShape(cornerRadius)
84-
val pillShape = RoundedCornerShape(cornerRadius)
85126

86127
// Resolve pill color based on active/inactive window state
87128
val pillColor by animateColorAsState(
@@ -123,8 +164,8 @@ fun SegmentedControl(
123164
Box(
124165
modifier = modifier
125166
.height(trackHeight)
126-
.clip(trackShape)
127-
.background(colors.track, trackShape),
167+
.clip(shape)
168+
.background(colors.track, shape),
128169
contentAlignment = Alignment.CenterStart,
129170
) {
130171
// Sliding pill indicator
@@ -141,7 +182,7 @@ fun SegmentedControl(
141182
width = with(density) { indicatorWidthAnim.value.toDp() },
142183
height = trackHeight,
143184
)
144-
.background(pillColor, pillShape),
185+
.background(pillColor, shape),
145186
)
146187
}
147188

@@ -164,7 +205,7 @@ fun SegmentedControl(
164205
isSelected = index == selectedIndex,
165206
enabled = enabled,
166207
controlSize = controlSize,
167-
cornerRadius = cornerRadius,
208+
shape = shape,
168209
selectedTextColor = selectedContentColor,
169210
unselectedTextColor = if (enabled) colors.unselectedContent else colors.disabledContent,
170211
pressedOverlay = colors.pressedOverlay,
@@ -184,7 +225,7 @@ fun SegmentedControl(
184225
}
185226

186227
private fun resolveSelectedSegmentColor(
187-
colors: SegmentedControlStyle.Colors,
228+
colors: SegmentedControlColors,
188229
enabled: Boolean,
189230
isWindowActive: Boolean,
190231
): Color = when {
@@ -204,15 +245,17 @@ fun SegmentedControl(
204245
onSelectedIndexChange: (Int) -> Unit,
205246
modifier: Modifier = Modifier,
206247
enabled: Boolean = true,
207-
variant: SegmentedControlVariant = SegmentedControlVariant.ContentArea,
248+
colors: SegmentedControlColors = SegmentedControlDefaults.colors(),
249+
shape: Shape = SegmentedControlDefaults.shape(),
208250
) {
209251
SegmentedControl(
210252
segmentCount = options.size,
211253
selectedIndex = selectedIndex,
212254
onSelectedIndexChange = onSelectedIndexChange,
213255
modifier = modifier,
214256
enabled = enabled,
215-
variant = variant,
257+
colors = colors,
258+
shape = shape,
216259
) { index ->
217260
Text(options[index])
218261
}
@@ -236,7 +279,7 @@ private fun Segment(
236279
isSelected: Boolean,
237280
enabled: Boolean,
238281
controlSize: ControlSize,
239-
cornerRadius: Dp,
282+
shape: Shape,
240283
selectedTextColor: Color,
241284
unselectedTextColor: Color,
242285
pressedOverlay: Color,
@@ -270,8 +313,6 @@ private fun Segment(
270313
fontSize = fontSizeFor(controlSize),
271314
)
272315

273-
val segmentShape = RoundedCornerShape(cornerRadius)
274-
275316
val metrics = MacosTheme.componentStyling.segmentedControl.metrics
276317
val minWidth = metrics.segmentMinWidthFor(controlSize)
277318
val horizontalPadding = metrics.segmentHorizontalPaddingFor(controlSize)
@@ -287,7 +328,7 @@ private fun Segment(
287328
coordinates.size.width.toFloat(),
288329
)
289330
}
290-
.clip(segmentShape)
331+
.clip(shape)
291332
.then(
292333
if (pressAlpha > 0f) {
293334
Modifier.background(pressedOverlay.copy(alpha = pressedOverlay.alpha * pressAlpha))

macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/theme/ComponentStyles.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,23 @@ data class SegmentedControlStyle(
653653
}
654654
}
655655

656+
// ---------------------------------------------------------------------------
657+
// SegmentedControlColors — public immutable color set for call-site overrides
658+
// ---------------------------------------------------------------------------
659+
660+
@Immutable
661+
data class SegmentedControlColors(
662+
val track: Color,
663+
val selectedSegment: Color,
664+
val selectedContent: Color,
665+
val unselectedContent: Color,
666+
val pressedOverlay: Color,
667+
val disabledContent: Color,
668+
val separatorColor: Color,
669+
val inactiveSelectedSegment: Color,
670+
val inactiveSelectedContent: Color,
671+
)
672+
656673

657674
// ---------------------------------------------------------------------------
658675
// ProgressStyle

0 commit comments

Comments
 (0)