Skip to content

Commit f02ef00

Browse files
authored
Merge pull request #377 from NucleusFramework/fix/tao-titlebar-caption-button-colors
fix(tao): apply TitleBarStyle color overrides to caption buttons
2 parents ea0a411 + 5ba43cd commit f02ef00

3 files changed

Lines changed: 98 additions & 9 deletions

File tree

decorated-window-tao/src/main/kotlin/dev/nucleusframework/window/TitleBar.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ public fun DecoratedWindowScope.BasicTitleBar(
371371
win = taoWindow,
372372
state = titleBarState,
373373
isResizable = taoWindow.isResizable,
374+
style = style,
374375
layout = linuxLayout,
375376
isFullscreen = titleBarState.isFullscreen,
376377
onExitFullscreen = { taoWindow.setFullscreen(false) },
@@ -380,6 +381,7 @@ public fun DecoratedWindowScope.BasicTitleBar(
380381
WindowControlsWindows(
381382
win = taoWindow,
382383
state = titleBarState,
384+
style = style,
383385
modifier = Modifier.align(Alignment.End),
384386
isFullscreen = titleBarState.isFullscreen,
385387
onExitFullscreen = { taoWindow.setFullscreen(false) },

decorated-window-tao/src/main/kotlin/dev/nucleusframework/window/tao/deco/WindowControlsLinux.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import androidx.compose.runtime.setValue
1515
import androidx.compose.ui.Alignment
1616
import androidx.compose.ui.ExperimentalComposeUiApi
1717
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.graphics.ColorFilter
1820
import androidx.compose.ui.graphics.painter.Painter
1921
import androidx.compose.ui.input.pointer.PointerEventType
2022
import androidx.compose.ui.input.pointer.onPointerEvent
@@ -23,6 +25,7 @@ import androidx.compose.ui.unit.dp
2325
import dev.nucleusframework.core.runtime.LinuxDesktopEnvironment
2426
import dev.nucleusframework.window.DecoratedWindowState
2527
import dev.nucleusframework.window.TitleBarScope
28+
import dev.nucleusframework.window.styling.TitleBarStyle
2629
import dev.nucleusframework.window.tao.TaoWindow
2730
import dev.nucleusframework.window.utils.linux.LinuxButtonLayout
2831
import dev.nucleusframework.window.utils.linux.LinuxTitleBarButton
@@ -52,6 +55,7 @@ internal fun TitleBarScope.WindowControlsLinux(
5255
win: TaoWindow,
5356
state: DecoratedWindowState,
5457
isResizable: Boolean,
58+
style: TitleBarStyle,
5559
layout: LinuxButtonLayout = rememberLinuxButtonLayout(),
5660
isFullscreen: Boolean = false,
5761
onExitFullscreen: (() -> Unit)? = null,
@@ -75,6 +79,7 @@ internal fun TitleBarScope.WindowControlsLinux(
7579
iconHover = closeHover,
7680
iconPressed = closePressed,
7781
contentDescription = "Close",
82+
style = style,
7883
alignment = buttonAlignment,
7984
isCloseButton = true,
8085
)
@@ -87,6 +92,7 @@ internal fun TitleBarScope.WindowControlsLinux(
8792
iconHover = icons.maximizeHover,
8893
iconPressed = icons.maximizePressed,
8994
contentDescription = "Exit fullscreen",
95+
style = style,
9096
alignment = buttonAlignment,
9197
)
9298
continue
@@ -99,6 +105,7 @@ internal fun TitleBarScope.WindowControlsLinux(
99105
iconHover = icons.restoreHover,
100106
iconPressed = icons.restorePressed,
101107
contentDescription = "Restore",
108+
style = style,
102109
alignment = buttonAlignment,
103110
)
104111
} else {
@@ -108,6 +115,7 @@ internal fun TitleBarScope.WindowControlsLinux(
108115
iconHover = icons.maximizeHover,
109116
iconPressed = icons.maximizePressed,
110117
contentDescription = "Maximize",
118+
style = style,
111119
alignment = buttonAlignment,
112120
)
113121
}
@@ -119,6 +127,7 @@ internal fun TitleBarScope.WindowControlsLinux(
119127
iconHover = icons.minimizeHover,
120128
iconPressed = icons.minimizePressed,
121129
contentDescription = "Minimize",
130+
style = style,
122131
alignment = buttonAlignment,
123132
)
124133
}
@@ -135,8 +144,9 @@ private fun TitleBarScope.LinuxControlButton(
135144
iconHover: Painter,
136145
iconPressed: Painter,
137146
contentDescription: String,
147+
style: TitleBarStyle,
138148
alignment: Alignment.Horizontal,
139-
@Suppress("UnusedParameter") isCloseButton: Boolean = false,
149+
isCloseButton: Boolean = false,
140150
) {
141151
val interactionSource = remember { MutableInteractionSource() }
142152

@@ -169,9 +179,24 @@ private fun TitleBarScope.LinuxControlButton(
169179
else -> icon
170180
}
171181

182+
// Apply the custom icon tint when set, but skip the close button while
183+
// hovered/pressed — its artwork has baked-in colors. Mirrors
184+
// `decorated-window-core/WindowControlArea.kt`.
185+
val isCloseInteracted = isCloseButton && (hovered || pressed)
186+
val iconTint = style.colors.controlButtonIconColor
187+
val iconHoverTint = style.colors.controlButtonIconHoverColor
188+
val colorFilter =
189+
when {
190+
isCloseInteracted -> null
191+
(hovered || pressed) && iconHoverTint != Color.Unspecified -> ColorFilter.tint(iconHoverTint)
192+
iconTint != Color.Unspecified -> ColorFilter.tint(iconTint)
193+
else -> null
194+
}
195+
172196
Image(
173197
painter = currentIcon,
174198
contentDescription = contentDescription,
199+
colorFilter = colorFilter,
175200
modifier =
176201
Modifier
177202
.onPointerEvent(PointerEventType.Enter) { hovered = true }

decorated-window-tao/src/main/kotlin/dev/nucleusframework/window/tao/deco/WindowControlsWindows.kt

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import androidx.compose.ui.Alignment
1919
import androidx.compose.ui.ExperimentalComposeUiApi
2020
import androidx.compose.ui.Modifier
2121
import androidx.compose.ui.graphics.Color
22+
import androidx.compose.ui.graphics.ColorFilter
2223
import androidx.compose.ui.graphics.painter.Painter
2324
import androidx.compose.ui.graphics.vector.rememberVectorPainter
2425
import androidx.compose.ui.input.pointer.PointerEventType
@@ -50,6 +51,7 @@ import dev.nucleusframework.window.icons.windows.RestoreDark
5051
import dev.nucleusframework.window.icons.windows.RestoreInactive
5152
import dev.nucleusframework.window.icons.windows.RestoreInactiveDark
5253
import dev.nucleusframework.window.icons.windows.WindowsControlButtonIcons
54+
import dev.nucleusframework.window.styling.TitleBarStyle
5355
import dev.nucleusframework.window.tao.TaoWindow
5456

5557
// Mirrors `decorated-window-core/WindowsWindowControlArea.kt` so the visual
@@ -93,6 +95,7 @@ private val WindowsCloseButtonPressed = Color(0xFFF1707A)
9395
internal fun WindowControlsWindows(
9496
win: TaoWindow,
9597
state: DecoratedWindowState,
98+
style: TitleBarStyle,
9699
modifier: Modifier = Modifier,
97100
isFullscreen: Boolean = false,
98101
onExitFullscreen: (() -> Unit)? = null,
@@ -106,6 +109,7 @@ internal fun WindowControlsWindows(
106109
WindowsCaptionButton(
107110
onClick = { win.minimize() },
108111
isDark = isDark,
112+
style = style,
109113
icon =
110114
if (state.isActive) {
111115
if (isDark) WindowsControlButtonIcons.MinimizeDark else WindowsControlButtonIcons.Minimize
@@ -125,6 +129,7 @@ internal fun WindowControlsWindows(
125129
WindowsCaptionButton(
126130
onClick = onExitFullscreen,
127131
isDark = isDark,
132+
style = style,
128133
icon =
129134
if (state.isActive) {
130135
if (isDark) {
@@ -149,6 +154,7 @@ internal fun WindowControlsWindows(
149154
WindowsCaptionButton(
150155
onClick = { win.setMaximized(false) },
151156
isDark = isDark,
157+
style = style,
152158
icon =
153159
if (state.isActive) {
154160
if (isDark) WindowsControlButtonIcons.RestoreDark else WindowsControlButtonIcons.Restore
@@ -165,6 +171,7 @@ internal fun WindowControlsWindows(
165171
WindowsCaptionButton(
166172
onClick = { win.setMaximized(true) },
167173
isDark = isDark,
174+
style = style,
168175
icon =
169176
if (state.isActive) {
170177
if (isDark) WindowsControlButtonIcons.MaximizeDark else WindowsControlButtonIcons.Maximize
@@ -185,6 +192,7 @@ internal fun WindowControlsWindows(
185192
WindowsCaptionButton(
186193
onClick = { win.requestUserClose() },
187194
isDark = isDark,
195+
style = style,
188196
icon =
189197
if (state.isActive) {
190198
if (isDark) WindowsControlButtonIcons.CloseDark else WindowsControlButtonIcons.Close
@@ -209,6 +217,7 @@ internal fun WindowControlsWindows(
209217
private fun WindowsCaptionButton(
210218
onClick: () -> Unit,
211219
isDark: Boolean,
220+
style: TitleBarStyle,
212221
icon: androidx.compose.ui.graphics.vector.ImageVector,
213222
contentDescription: String,
214223
iconHover: androidx.compose.ui.graphics.vector.ImageVector? = null,
@@ -218,20 +227,28 @@ private fun WindowsCaptionButton(
218227
var pressed by remember { mutableStateOf(false) }
219228

220229
val backgroundColor =
221-
when {
222-
pressed && isCloseButton -> WindowsCloseButtonPressed
223-
pressed -> if (isDark) WindowsButtonPressedDark else WindowsButtonPressedLight
224-
hovered && isCloseButton -> WindowsCloseButtonHovered
225-
hovered -> if (isDark) WindowsButtonHoveredDark else WindowsButtonHoveredLight
226-
else -> Color.Transparent
227-
}
230+
captionButtonBackground(
231+
hovered = hovered,
232+
pressed = pressed,
233+
isCloseButton = isCloseButton,
234+
isDark = isDark,
235+
style = style,
236+
)
228237

229238
val isCloseHovered = (hovered || pressed) && isCloseButton
230239
val currentIcon: Painter =
231240
rememberVectorPainter(
232241
if (isCloseHovered && iconHover != null) iconHover else icon,
233242
)
234243

244+
val colorFilter =
245+
captionButtonColorFilter(
246+
hovered = hovered,
247+
pressed = pressed,
248+
isCloseHovered = isCloseHovered,
249+
style = style,
250+
)
251+
235252
Box(
236253
modifier =
237254
Modifier
@@ -252,6 +269,51 @@ private fun WindowsCaptionButton(
252269
),
253270
contentAlignment = Alignment.Center,
254271
) {
255-
Image(painter = currentIcon, contentDescription = contentDescription)
272+
Image(painter = currentIcon, contentDescription = contentDescription, colorFilter = colorFilter)
273+
}
274+
}
275+
276+
// Mirrors `decorated-window-core/WindowsWindowControlArea.kt` so custom
277+
// [TitleBarStyle] colors apply identically on the Tao backend. Close-button
278+
// hover/pressed always use the fixed Windows red — matching AWT.
279+
private fun captionButtonBackground(
280+
hovered: Boolean,
281+
pressed: Boolean,
282+
isCloseButton: Boolean,
283+
isDark: Boolean,
284+
style: TitleBarStyle,
285+
): Color {
286+
val customHover = style.colors.iconButtonHoveredBackground
287+
val customPressed = style.colors.iconButtonPressedBackground
288+
val pressedColor =
289+
customPressed.takeUnless { it == Color.Transparent }
290+
?: if (isDark) WindowsButtonPressedDark else WindowsButtonPressedLight
291+
val hoveredColor =
292+
customHover.takeUnless { it == Color.Transparent }
293+
?: if (isDark) WindowsButtonHoveredDark else WindowsButtonHoveredLight
294+
return when {
295+
pressed && isCloseButton -> WindowsCloseButtonPressed
296+
pressed -> pressedColor
297+
hovered && isCloseButton -> WindowsCloseButtonHovered
298+
hovered -> hoveredColor
299+
else -> Color.Transparent
300+
}
301+
}
302+
303+
private fun captionButtonColorFilter(
304+
hovered: Boolean,
305+
pressed: Boolean,
306+
isCloseHovered: Boolean,
307+
style: TitleBarStyle,
308+
): ColorFilter? {
309+
val iconTint = style.colors.controlButtonIconColor
310+
val iconHoverTint = style.colors.controlButtonIconHoverColor
311+
return when {
312+
// Close hover swaps to the baked-red close artwork; don't tint it.
313+
isCloseHovered -> null
314+
(hovered || pressed) && iconHoverTint != Color.Unspecified ->
315+
ColorFilter.tint(iconHoverTint)
316+
iconTint != Color.Unspecified -> ColorFilter.tint(iconTint)
317+
else -> null
256318
}
257319
}

0 commit comments

Comments
 (0)