Skip to content

Commit 971ccee

Browse files
feat: WIP add custom proc to handle titlebar content
IN PROGRESS. Not working yet.
1 parent b362e0d commit 971ccee

File tree

10 files changed

+247
-20
lines changed

10 files changed

+247
-20
lines changed

settings.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ pluginManagement {
88
}
99

1010
rootProject.name = "ComposeWindowStyler"
11-
include("window-styler", "window-styler-demo")
11+
include(
12+
"window-styler",
13+
"window-styler-demo",
14+
"window-styler-demo-transparent",
15+
)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2+
3+
// Suppress annotation is a workaround for a bug.
4+
@Suppress("DSL_SCOPE_VIOLATION")
5+
plugins {
6+
alias(libs.plugins.kotlin.multiplatform)
7+
alias(libs.plugins.compose)
8+
}
9+
10+
kotlin {
11+
jvmToolchain(17)
12+
13+
jvm {
14+
withJava()
15+
}
16+
17+
sourceSets {
18+
named("jvmMain") {
19+
dependencies {
20+
implementation(compose.desktop.currentOs)
21+
22+
implementation(project(":window-styler"))
23+
}
24+
}
25+
}
26+
}
27+
28+
compose.desktop {
29+
application {
30+
mainClass = "MainKt"
31+
32+
nativeDistributions {
33+
targetFormats(TargetFormat.Msi)
34+
35+
packageName = "window-styler-demo-transparent"
36+
packageVersion = "1.0.0"
37+
}
38+
}
39+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import androidx.compose.desktop.ui.tooling.preview.Preview
2+
import androidx.compose.foundation.isSystemInDarkTheme
3+
import androidx.compose.material.Button
4+
import androidx.compose.material.MaterialTheme
5+
import androidx.compose.material.Text
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.window.Window
8+
import androidx.compose.ui.window.application
9+
import com.mayakapps.compose.windowstyler.WindowBackdrop
10+
import com.mayakapps.compose.windowstyler.WindowStyle
11+
12+
@Composable
13+
@Preview
14+
fun App() {
15+
Button(onClick = {}) {
16+
Text("Button")
17+
}
18+
}
19+
20+
fun main() = application {
21+
Window(
22+
onCloseRequest = ::exitApplication,
23+
title = "Compose Window Styler Demo",
24+
) {
25+
WindowStyle(
26+
isDarkTheme = isSystemInDarkTheme(),
27+
backdropType = WindowBackdrop.Mica,
28+
manageTitlebar = true
29+
)
30+
31+
MaterialTheme {
32+
App()
33+
}
34+
}
35+
}

window-styler/src/jvmMain/kotlin/com/mayakapps/compose/windowstyler/WindowStyle.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ fun WindowScope.WindowStyle(
1515
isDarkTheme: Boolean = false,
1616
backdropType: WindowBackdrop = WindowBackdrop.Default,
1717
frameStyle: WindowFrameStyle = WindowFrameStyle(),
18+
manageTitlebar: Boolean = false,
1819
) {
19-
val manager = remember { WindowStyleManager(window, isDarkTheme, backdropType, frameStyle) }
20+
val manager = remember { WindowStyleManager(window, isDarkTheme, backdropType, frameStyle, manageTitlebar) }
2021

2122
LaunchedEffect(isDarkTheme) {
2223
manager.isDarkTheme = isDarkTheme

window-styler/src/jvmMain/kotlin/com/mayakapps/compose/windowstyler/WindowStyleManager.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import java.awt.Window
1313
*/
1414
fun WindowStyleManager(
1515
window: Window,
16-
isDarkTheme: Boolean = false,
17-
backdropType: WindowBackdrop = WindowBackdrop.Default,
18-
frameStyle: WindowFrameStyle = WindowFrameStyle(),
16+
isDarkTheme: Boolean,
17+
backdropType: WindowBackdrop,
18+
frameStyle: WindowFrameStyle,
19+
manageTitlebar: Boolean,
1920
) = when (hostOs) {
20-
OS.Windows -> WindowsWindowStyleManager(window, isDarkTheme, backdropType, frameStyle)
21-
else -> StubWindowStyleManager(isDarkTheme, backdropType, frameStyle)
21+
OS.Windows -> WindowsWindowStyleManager(window, isDarkTheme, backdropType, frameStyle, manageTitlebar)
22+
else -> StubWindowStyleManager(isDarkTheme, backdropType, frameStyle, manageTitlebar)
2223
}
2324

2425
/**
@@ -46,10 +47,17 @@ interface WindowStyleManager {
4647
* The style of the window frame which includes the title bar and window border. See [WindowFrameStyle].
4748
*/
4849
var frameStyle: WindowFrameStyle
50+
51+
/**
52+
* Whether to manage the title bar of the window. If set to `true`, the title bar will be hidden and the window
53+
* will need to manage its own title.
54+
*/
55+
var manageTitlebar: Boolean
4956
}
5057

5158
internal class StubWindowStyleManager(
5259
override var isDarkTheme: Boolean,
5360
override var backdropType: WindowBackdrop,
5461
override var frameStyle: WindowFrameStyle,
62+
override var manageTitlebar: Boolean,
5563
) : WindowStyleManager
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.mayakapps.compose.windowstyler.windows
2+
3+
import com.mayakapps.compose.windowstyler.windows.jna.Dwm
4+
import com.mayakapps.compose.windowstyler.windows.jna.User32
5+
import com.sun.jna.platform.win32.WinDef
6+
import com.sun.jna.platform.win32.WinUser
7+
8+
private const val WM_NCCALCSIZE = 0x0083
9+
private const val WM_NCHITTEST = 0x0084
10+
11+
class CustomDecorationWindowProc private constructor(private val hwnd: WinDef.HWND) : WinUser.WindowProc {
12+
private val defWndProc = User32.setWindowProc(hwnd, this)
13+
14+
override fun callback(hWnd: WinDef.HWND, uMsg: Int, wParam: WinDef.WPARAM, lParam: WinDef.LPARAM): WinDef.LRESULT {
15+
if (Dwm.callDefaultWindowHitProc(hwnd, uMsg, wParam, lParam)) {
16+
return WinDef.LRESULT(0)
17+
}
18+
19+
return when (uMsg) {
20+
WM_NCCALCSIZE -> {
21+
WinDef.LRESULT(0)
22+
}
23+
24+
WM_NCHITTEST -> {
25+
User32.callWindowProc(defWndProc, hWnd, uMsg, wParam, lParam)
26+
}
27+
28+
WinUser.WM_DESTROY -> {
29+
User32.setWindowProc(hWnd, defWndProc)
30+
WinDef.LRESULT(-1)
31+
}
32+
33+
else -> {
34+
User32.callWindowProc(defWndProc, hWnd, uMsg, wParam, lParam)
35+
}
36+
}
37+
}
38+
39+
companion object {
40+
/**
41+
* Installs a [CustomDecorationWindowProc] for the given window.
42+
*
43+
* @param hwnd The window handle.
44+
*/
45+
fun install(hwnd: WinDef.HWND) = CustomDecorationWindowProc(hwnd)
46+
}
47+
}

window-styler/src/jvmMain/kotlin/com/mayakapps/compose/windowstyler/windows/WindowsBackdropApis.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmSystemBackdrop
88
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmWindowAttribute
99
import com.sun.jna.platform.win32.WinDef
1010

11-
internal class WindowsBackdropApis(private val hwnd: WinDef.HWND) {
11+
internal class WindowsBackdropApis private constructor(private val hwnd: WinDef.HWND) {
1212
private var isSystemBackdropSet = false
1313
private var isMicaEnabled = false
1414
private var isAccentPolicySet = false
1515
private var isSheetOfGlassApplied = false
1616

17+
companion object {
18+
/**
19+
* Instantiate [WindowsBackdropApis] for the given window and install it.
20+
*/
21+
fun install(hwnd: WinDef.HWND) = WindowsBackdropApis(hwnd)
22+
}
23+
1724
fun setSystemBackdrop(systemBackdrop: DwmSystemBackdrop) {
1825
createSheetOfGlassEffect()
1926
if (Dwm.setSystemBackdrop(hwnd, systemBackdrop)) {

window-styler/src/jvmMain/kotlin/com/mayakapps/compose/windowstyler/windows/WindowsWindowStyleManager.kt

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ package com.mayakapps.compose.windowstyler.windows
33
import androidx.compose.ui.awt.ComposeWindow
44
import androidx.compose.ui.graphics.Color
55
import androidx.compose.ui.graphics.isSpecified
6-
import com.mayakapps.compose.windowstyler.*
6+
import com.mayakapps.compose.windowstyler.ColorableWindowBackdrop
7+
import com.mayakapps.compose.windowstyler.WindowBackdrop
8+
import com.mayakapps.compose.windowstyler.WindowCornerPreference
9+
import com.mayakapps.compose.windowstyler.WindowFrameStyle
10+
import com.mayakapps.compose.windowstyler.WindowStyleManager
11+
import com.mayakapps.compose.windowstyler.hackContentPane
12+
import com.mayakapps.compose.windowstyler.isTransparent
13+
import com.mayakapps.compose.windowstyler.isUndecorated
14+
import com.mayakapps.compose.windowstyler.setComposeLayerTransparency
715
import com.mayakapps.compose.windowstyler.windows.jna.Dwm
816
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentFlag
917
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmWindowAttribute
@@ -20,16 +28,18 @@ import javax.swing.SwingUtilities
2028
*/
2129
class WindowsWindowStyleManager(
2230
window: Window,
23-
isDarkTheme: Boolean = false,
24-
backdropType: WindowBackdrop = WindowBackdrop.Default,
25-
frameStyle: WindowFrameStyle = WindowFrameStyle(),
31+
isDarkTheme: Boolean,
32+
backdropType: WindowBackdrop,
33+
frameStyle: WindowFrameStyle,
34+
manageTitlebar: Boolean,
2635
) : WindowStyleManager {
2736

2837
private val hwnd: HWND = window.hwnd
2938
private val isUndecorated = window.isUndecorated
3039
private var wasAero = false
3140

32-
private val backdropApis = WindowsBackdropApis(hwnd)
41+
private val backdropApis = WindowsBackdropApis.install(hwnd)
42+
private val customDecorationWindowProc = if (manageTitlebar) CustomDecorationWindowProc.install(hwnd) else null
3343

3444
override var isDarkTheme: Boolean = isDarkTheme
3545
set(value) {
@@ -59,6 +69,17 @@ class WindowsWindowStyleManager(
5969
}
6070
}
6171

72+
override var manageTitlebar: Boolean = manageTitlebar
73+
get() = field
74+
set(value) {
75+
field = if (value) {
76+
true
77+
} else {
78+
// TODO: reset the window proc to the default one
79+
false
80+
}
81+
}
82+
6283
init {
6384
// invokeLater is called to make sure that ComposeLayer was initialized first
6485
SwingUtilities.invokeLater {
@@ -193,6 +214,7 @@ class WindowsWindowStyleManager(
193214
if (this is ThemedAcrylic) themedTransparent
194215
else WindowBackdrop.Transparent(color)
195216
}
217+
196218
else -> WindowBackdrop.Default
197219
}.fallbackIfUnsupported()
198220
}

window-styler/src/jvmMain/kotlin/com/mayakapps/compose/windowstyler/windows/jna/Dwm.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ import com.sun.jna.PointerType
99
import com.sun.jna.platform.win32.W32Errors
1010
import com.sun.jna.platform.win32.WinDef
1111
import com.sun.jna.platform.win32.WinDef.HWND
12+
import com.sun.jna.platform.win32.WinDef.LPARAM
13+
import com.sun.jna.platform.win32.WinDef.LRESULT
14+
import com.sun.jna.platform.win32.WinDef.WPARAM
1215
import com.sun.jna.platform.win32.WinNT.HRESULT
1316
import com.sun.jna.ptr.IntByReference
1417
import com.sun.jna.win32.StdCallLibrary
1518
import com.sun.jna.win32.W32APIOptions
1619

1720
internal object Dwm {
18-
fun extendFrameIntoClientArea(hwnd: HWND, margin: Int = 0) =
19-
extendFrameIntoClientArea(hwnd, margin, margin, margin, margin)
21+
fun extendFrameIntoClientArea(hwnd: HWND, allMargins: Int = 0) =
22+
extendFrameIntoClientArea(hwnd, allMargins, allMargins, allMargins, allMargins)
2023

2124
fun extendFrameIntoClientArea(
2225
hwnd: HWND,
@@ -47,6 +50,11 @@ internal object Dwm {
4750
fun setWindowAttribute(hwnd: HWND, attribute: DwmWindowAttribute, value: Int) =
4851
setWindowAttribute(hwnd, attribute, IntByReference(value), INT_SIZE)
4952

53+
fun callDefaultWindowHitProc(hwnd: HWND, msg: Int, wParam: WPARAM, lParam: LPARAM): Boolean {
54+
val dwmDefWindowProc = DwmImpl.DwmDefWindowProc(hwnd, msg, wParam, lParam)
55+
return dwmDefWindowProc != LRESULT(0)
56+
}
57+
5058
private fun setWindowAttribute(
5159
hwnd: HWND,
5260
attribute: DwmWindowAttribute,
@@ -67,4 +75,5 @@ private object DwmImpl : DwmApi by Native.load("dwmapi", DwmApi::class.java, W32
6775
private interface DwmApi : StdCallLibrary {
6876
fun DwmExtendFrameIntoClientArea(hwnd: HWND, margins: Margins): HRESULT
6977
fun DwmSetWindowAttribute(hwnd: HWND, attribute: Int, value: PointerType?, valueSize: Int): HRESULT
78+
fun DwmDefWindowProc(hwnd: HWND, msg: Int, wParam: WPARAM, lParam: LPARAM): LRESULT
7079
}

0 commit comments

Comments
 (0)