Skip to content

Commit f7e4890

Browse files
authored
Merge pull request #182 from kdroidFilter/feat/control-button-icon-colors
feat(decorated-window): control button icon color customization
2 parents b09b9c7 + 0596038 commit f7e4890

13 files changed

Lines changed: 151 additions & 25 deletions

File tree

decorated-window-core/src/main/kotlin/io/github/kdroidfilter/nucleus/window/WindowControlArea.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import androidx.compose.runtime.setValue
1616
import androidx.compose.ui.Alignment
1717
import androidx.compose.ui.ExperimentalComposeUiApi
1818
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.graphics.Color
20+
import androidx.compose.ui.graphics.ColorFilter
1921
import androidx.compose.ui.graphics.painter.Painter
2022
import androidx.compose.ui.input.pointer.PointerEventType
2123
import androidx.compose.ui.input.pointer.onPointerEvent
@@ -53,6 +55,7 @@ fun TitleBarScope.WindowControlArea(
5355
iconPressed = closePressed,
5456
contentDescription = "Close",
5557
style = style,
58+
isCloseButton = true,
5659
)
5760

5861
// In fullscreen: show maximize icon but click exits fullscreen
@@ -136,6 +139,7 @@ fun TitleBarScope.DialogCloseButton(
136139
iconPressed = closePressed,
137140
contentDescription = "Close",
138141
style = style,
142+
isCloseButton = true,
139143
)
140144
}
141145
}
@@ -151,6 +155,7 @@ private fun TitleBarScope.ControlButton(
151155
iconPressed: Painter,
152156
contentDescription: String,
153157
style: TitleBarStyle,
158+
isCloseButton: Boolean = false,
154159
) {
155160
val interactionSource = remember { MutableInteractionSource() }
156161

@@ -171,16 +176,30 @@ private fun TitleBarScope.ControlButton(
171176
var hovered by remember { mutableStateOf(false) }
172177
var pressed by remember { mutableStateOf(false) }
173178

179+
val isCloseInteracted = isCloseButton && (hovered || pressed)
174180
val currentIcon =
175181
when {
176182
pressed && (state.isActive || isKde) -> iconPressed
177183
hovered && (state.isActive || isKde) -> iconHover
178184
else -> icon
179185
}
180186

187+
// Apply icon tint when controlButtonIconColor is set,
188+
// but skip tinting for close button hover/pressed (icons have baked-in colors).
189+
val iconTint = style.colors.controlButtonIconColor
190+
val iconHoverTint = style.colors.controlButtonIconHoverColor
191+
val colorFilter =
192+
when {
193+
isCloseInteracted -> null
194+
(hovered || pressed) && iconHoverTint != Color.Unspecified -> ColorFilter.tint(iconHoverTint)
195+
iconTint != Color.Unspecified -> ColorFilter.tint(iconTint)
196+
else -> null
197+
}
198+
181199
Image(
182200
painter = currentIcon,
183201
contentDescription = contentDescription,
202+
colorFilter = colorFilter,
184203
modifier =
185204
Modifier
186205
.onPointerEvent(PointerEventType.Enter) { hovered = true }

decorated-window-core/src/main/kotlin/io/github/kdroidfilter/nucleus/window/WindowsWindowControlArea.kt

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
1818
import androidx.compose.ui.ExperimentalComposeUiApi
1919
import androidx.compose.ui.Modifier
2020
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.graphics.ColorFilter
2122
import androidx.compose.ui.graphics.painter.Painter
2223
import androidx.compose.ui.input.pointer.PointerEventType
2324
import androidx.compose.ui.input.pointer.onPointerEvent
@@ -148,7 +149,7 @@ fun TitleBarScope.WindowsDialogCloseButton(
148149
}
149150

150151
@OptIn(ExperimentalComposeUiApi::class)
151-
@Suppress("FunctionNaming", "LongParameterList", "UnusedParameter")
152+
@Suppress("FunctionNaming", "LongParameterList", "UnusedParameter", "CyclomaticComplexMethod")
152153
@Composable
153154
private fun TitleBarScope.WindowsCaptionButton(
154155
onClick: () -> Unit,
@@ -164,20 +165,29 @@ private fun TitleBarScope.WindowsCaptionButton(
164165

165166
val isDark = LocalIsDarkTheme.current
166167
val backgroundColor =
167-
when {
168-
pressed && isCloseButton -> WindowsCloseButtonPressed
169-
pressed -> if (isDark) WindowsButtonPressedDark else WindowsButtonPressedLight
170-
hovered && isCloseButton -> WindowsCloseButtonHovered
171-
hovered -> if (isDark) WindowsButtonHoveredDark else WindowsButtonHoveredLight
172-
else -> Color.Transparent
173-
}
168+
captionButtonBackground(
169+
hovered = hovered,
170+
pressed = pressed,
171+
isCloseButton = isCloseButton,
172+
isDark = isDark,
173+
style = style,
174+
)
174175

176+
val isCloseHovered = (hovered || pressed) && isCloseButton
175177
val currentIcon =
176178
when {
177-
(hovered || pressed) && isCloseButton && iconHover != null -> iconHover
179+
isCloseHovered && iconHover != null -> iconHover
178180
else -> icon
179181
}
180182

183+
val colorFilter =
184+
captionButtonColorFilter(
185+
hovered = hovered,
186+
pressed = pressed,
187+
isCloseHovered = isCloseHovered,
188+
style = style,
189+
)
190+
181191
Box(
182192
modifier =
183193
Modifier
@@ -202,6 +212,48 @@ private fun TitleBarScope.WindowsCaptionButton(
202212
Image(
203213
painter = currentIcon,
204214
contentDescription = contentDescription,
215+
colorFilter = colorFilter,
205216
)
206217
}
207218
}
219+
220+
private fun captionButtonBackground(
221+
hovered: Boolean,
222+
pressed: Boolean,
223+
isCloseButton: Boolean,
224+
isDark: Boolean,
225+
style: TitleBarStyle,
226+
): Color {
227+
val customHover = style.colors.iconButtonHoveredBackground
228+
val customPressed = style.colors.iconButtonPressedBackground
229+
val pressedColor =
230+
customPressed.takeUnless { it == Color.Transparent }
231+
?: if (isDark) WindowsButtonPressedDark else WindowsButtonPressedLight
232+
val hoveredColor =
233+
customHover.takeUnless { it == Color.Transparent }
234+
?: if (isDark) WindowsButtonHoveredDark else WindowsButtonHoveredLight
235+
return when {
236+
pressed && isCloseButton -> WindowsCloseButtonPressed
237+
pressed -> pressedColor
238+
hovered && isCloseButton -> WindowsCloseButtonHovered
239+
hovered -> hoveredColor
240+
else -> Color.Transparent
241+
}
242+
}
243+
244+
private fun captionButtonColorFilter(
245+
hovered: Boolean,
246+
pressed: Boolean,
247+
isCloseHovered: Boolean,
248+
style: TitleBarStyle,
249+
): ColorFilter? {
250+
val iconTint = style.colors.controlButtonIconColor
251+
val iconHoverTint = style.colors.controlButtonIconHoverColor
252+
return when {
253+
isCloseHovered -> null
254+
(hovered || pressed) && iconHoverTint != Color.Unspecified ->
255+
ColorFilter.tint(iconHoverTint)
256+
iconTint != Color.Unspecified -> ColorFilter.tint(iconTint)
257+
else -> null
258+
}
259+
}

decorated-window-core/src/main/kotlin/io/github/kdroidfilter/nucleus/window/styling/TitleBarStyling.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ data class TitleBarColors(
2323
val fullscreenControlButtonsBackground: Color = Color.Unspecified,
2424
val iconButtonHoveredBackground: Color = Color.Transparent,
2525
val iconButtonPressedBackground: Color = Color.Transparent,
26+
val controlButtonIconColor: Color = Color.Unspecified,
27+
val controlButtonIconHoverColor: Color = Color.Unspecified,
2628
) {
2729
@Composable
2830
fun backgroundFor(state: DecoratedWindowState): State<Color> =

decorated-window-jewel/src/main/kotlin/io/github/kdroidfilter/nucleus/window/jewel/JewelDecoratedWindow.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.compose.ui.window.rememberWindowState
88
import io.github.kdroidfilter.nucleus.window.DecoratedWindow
99
import io.github.kdroidfilter.nucleus.window.DecoratedWindowScope
1010
import io.github.kdroidfilter.nucleus.window.NucleusDecoratedWindowTheme
11+
import io.github.kdroidfilter.nucleus.window.styling.TitleBarStyle
1112
import org.jetbrains.jewel.foundation.theme.JewelTheme
1213

1314
@Suppress("FunctionNaming", "LongParameterList")
@@ -24,15 +25,16 @@ fun JewelDecoratedWindow(
2425
alwaysOnTop: Boolean = false,
2526
onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
2627
onKeyEvent: (KeyEvent) -> Boolean = { false },
28+
titleBarStyle: TitleBarStyle? = null,
2729
content: @Composable DecoratedWindowScope.() -> Unit,
2830
) {
2931
val windowStyle = rememberJewelWindowStyle()
30-
val titleBarStyle = rememberJewelTitleBarStyle()
32+
val jewelTitleBarStyle = rememberJewelTitleBarStyle()
3133

3234
NucleusDecoratedWindowTheme(
3335
isDark = JewelTheme.isDark,
3436
windowStyle = windowStyle,
35-
titleBarStyle = titleBarStyle,
37+
titleBarStyle = titleBarStyle ?: jewelTitleBarStyle,
3638
) {
3739
DecoratedWindow(
3840
onCloseRequest = onCloseRequest,

decorated-window-jewel/src/main/kotlin/io/github/kdroidfilter/nucleus/window/jewel/JewelDialogTitleBar.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import io.github.kdroidfilter.nucleus.window.DecoratedDialogScope
88
import io.github.kdroidfilter.nucleus.window.DecoratedDialogState
99
import io.github.kdroidfilter.nucleus.window.DialogTitleBar
1010
import io.github.kdroidfilter.nucleus.window.TitleBarScope
11+
import io.github.kdroidfilter.nucleus.window.styling.LocalTitleBarStyle
12+
import io.github.kdroidfilter.nucleus.window.styling.TitleBarStyle
1113

1214
@Suppress("FunctionNaming")
1315
@Composable
1416
fun DecoratedDialogScope.JewelDialogTitleBar(
1517
modifier: Modifier = Modifier,
1618
gradientStartColor: Color = Color.Unspecified,
19+
style: TitleBarStyle = LocalTitleBarStyle.current,
1720
controlButtonsDirection: ControlButtonsDirection = ControlButtonsDirection.Auto,
1821
content: @Composable TitleBarScope.(DecoratedDialogState) -> Unit = {},
1922
) {
20-
val style = rememberJewelTitleBarStyle()
2123
DialogTitleBar(
2224
modifier = modifier,
2325
gradientStartColor = gradientStartColor,

decorated-window-jewel/src/main/kotlin/io/github/kdroidfilter/nucleus/window/jewel/JewelTitleBar.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ import io.github.kdroidfilter.nucleus.window.DecoratedWindowScope
88
import io.github.kdroidfilter.nucleus.window.DecoratedWindowState
99
import io.github.kdroidfilter.nucleus.window.TitleBar
1010
import io.github.kdroidfilter.nucleus.window.TitleBarScope
11+
import io.github.kdroidfilter.nucleus.window.styling.LocalTitleBarStyle
12+
import io.github.kdroidfilter.nucleus.window.styling.TitleBarStyle
1113

1214
@Suppress("FunctionNaming")
1315
@Composable
1416
fun DecoratedWindowScope.JewelTitleBar(
1517
modifier: Modifier = Modifier,
1618
gradientStartColor: Color = Color.Unspecified,
19+
style: TitleBarStyle = LocalTitleBarStyle.current,
1720
controlButtonsDirection: ControlButtonsDirection = ControlButtonsDirection.Auto,
1821
backgroundContent: @Composable () -> Unit = {},
1922
content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit = {},
2023
) {
21-
val style = rememberJewelTitleBarStyle()
2224
TitleBar(
2325
modifier = modifier,
2426
gradientStartColor = gradientStartColor,

decorated-window-material2/src/main/kotlin/io/github/kdroidfilter/nucleus/window/material2/MaterialDecoratedWindow.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.ui.window.rememberWindowState
99
import io.github.kdroidfilter.nucleus.window.DecoratedWindow
1010
import io.github.kdroidfilter.nucleus.window.DecoratedWindowScope
1111
import io.github.kdroidfilter.nucleus.window.NucleusDecoratedWindowTheme
12+
import io.github.kdroidfilter.nucleus.window.styling.TitleBarStyle
1213

1314
@Suppress("FunctionNaming", "LongParameterList")
1415
@Composable
@@ -24,16 +25,17 @@ fun MaterialDecoratedWindow(
2425
alwaysOnTop: Boolean = false,
2526
onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
2627
onKeyEvent: (KeyEvent) -> Boolean = { false },
28+
titleBarStyle: TitleBarStyle? = null,
2729
content: @Composable DecoratedWindowScope.() -> Unit,
2830
) {
2931
val colors = MaterialTheme.colors
3032
val windowStyle = rememberMaterialWindowStyle(colors)
31-
val titleBarStyle = rememberMaterialTitleBarStyle(colors)
33+
val materialTitleBarStyle = rememberMaterialTitleBarStyle(colors)
3234

3335
NucleusDecoratedWindowTheme(
3436
isDark = !colors.isLight,
3537
windowStyle = windowStyle,
36-
titleBarStyle = titleBarStyle,
38+
titleBarStyle = titleBarStyle ?: materialTitleBarStyle,
3739
) {
3840
DecoratedWindow(
3941
onCloseRequest = onCloseRequest,

decorated-window-material2/src/main/kotlin/io/github/kdroidfilter/nucleus/window/material2/MaterialDialogTitleBar.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.kdroidfilter.nucleus.window.material2
22

3-
import androidx.compose.material.MaterialTheme
43
import androidx.compose.runtime.Composable
54
import androidx.compose.ui.Modifier
65
import androidx.compose.ui.graphics.Color
@@ -9,16 +8,18 @@ import io.github.kdroidfilter.nucleus.window.DecoratedDialogScope
98
import io.github.kdroidfilter.nucleus.window.DecoratedDialogState
109
import io.github.kdroidfilter.nucleus.window.DialogTitleBar
1110
import io.github.kdroidfilter.nucleus.window.TitleBarScope
11+
import io.github.kdroidfilter.nucleus.window.styling.LocalTitleBarStyle
12+
import io.github.kdroidfilter.nucleus.window.styling.TitleBarStyle
1213

1314
@Suppress("FunctionNaming")
1415
@Composable
1516
fun DecoratedDialogScope.MaterialDialogTitleBar(
1617
modifier: Modifier = Modifier,
1718
gradientStartColor: Color = Color.Unspecified,
19+
style: TitleBarStyle = LocalTitleBarStyle.current,
1820
controlButtonsDirection: ControlButtonsDirection = ControlButtonsDirection.Auto,
1921
content: @Composable TitleBarScope.(DecoratedDialogState) -> Unit = {},
2022
) {
21-
val style = rememberMaterialTitleBarStyle(MaterialTheme.colors)
2223
DialogTitleBar(
2324
modifier = modifier,
2425
gradientStartColor = gradientStartColor,

decorated-window-material2/src/main/kotlin/io/github/kdroidfilter/nucleus/window/material2/MaterialTitleBar.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.kdroidfilter.nucleus.window.material2
22

3-
import androidx.compose.material.MaterialTheme
43
import androidx.compose.runtime.Composable
54
import androidx.compose.ui.Modifier
65
import androidx.compose.ui.graphics.Color
@@ -9,6 +8,8 @@ import io.github.kdroidfilter.nucleus.window.DecoratedWindowScope
98
import io.github.kdroidfilter.nucleus.window.DecoratedWindowState
109
import io.github.kdroidfilter.nucleus.window.TitleBar
1110
import io.github.kdroidfilter.nucleus.window.TitleBarScope
11+
import io.github.kdroidfilter.nucleus.window.styling.LocalTitleBarStyle
12+
import io.github.kdroidfilter.nucleus.window.styling.TitleBarStyle
1213

1314
/**
1415
* Material 2 themed title bar.
@@ -23,11 +24,11 @@ import io.github.kdroidfilter.nucleus.window.TitleBarScope
2324
fun DecoratedWindowScope.MaterialTitleBar(
2425
modifier: Modifier = Modifier,
2526
gradientStartColor: Color = Color.Unspecified,
27+
style: TitleBarStyle = LocalTitleBarStyle.current,
2628
controlButtonsDirection: ControlButtonsDirection = ControlButtonsDirection.Auto,
2729
backgroundContent: @Composable () -> Unit = {},
2830
content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit = {},
2931
) {
30-
val style = rememberMaterialTitleBarStyle(MaterialTheme.colors)
3132
TitleBar(
3233
modifier = modifier,
3334
gradientStartColor = gradientStartColor,

decorated-window-material3/src/main/kotlin/io/github/kdroidfilter/nucleus/window/material/MaterialDecoratedWindow.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.ui.window.rememberWindowState
99
import io.github.kdroidfilter.nucleus.window.DecoratedWindow
1010
import io.github.kdroidfilter.nucleus.window.DecoratedWindowScope
1111
import io.github.kdroidfilter.nucleus.window.NucleusDecoratedWindowTheme
12+
import io.github.kdroidfilter.nucleus.window.styling.TitleBarStyle
1213

1314
@Suppress("FunctionNaming", "LongParameterList")
1415
@Composable
@@ -24,16 +25,17 @@ fun MaterialDecoratedWindow(
2425
alwaysOnTop: Boolean = false,
2526
onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
2627
onKeyEvent: (KeyEvent) -> Boolean = { false },
28+
titleBarStyle: TitleBarStyle? = null,
2729
content: @Composable DecoratedWindowScope.() -> Unit,
2830
) {
2931
val colorScheme = MaterialTheme.colorScheme
3032
val windowStyle = rememberMaterialWindowStyle(colorScheme)
31-
val titleBarStyle = rememberMaterialTitleBarStyle(colorScheme)
33+
val materialTitleBarStyle = rememberMaterialTitleBarStyle(colorScheme)
3234

3335
NucleusDecoratedWindowTheme(
3436
isDark = colorScheme.isDark(),
3537
windowStyle = windowStyle,
36-
titleBarStyle = titleBarStyle,
38+
titleBarStyle = titleBarStyle ?: materialTitleBarStyle,
3739
) {
3840
DecoratedWindow(
3941
onCloseRequest = onCloseRequest,

0 commit comments

Comments
 (0)