forked from xueque/eimusic-app
init
This commit is contained in:
32
window-styler/build.gradle.kts
Normal file
32
window-styler/build.gradle.kts
Normal file
@ -0,0 +1,32 @@
|
||||
// Suppress annotation is a workaround for a bug.
|
||||
@Suppress("DSL_SCOPE_VIOLATION")
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("org.jetbrains.compose")
|
||||
}
|
||||
|
||||
|
||||
group = extra["GROUP"] as String
|
||||
version = extra["VERSION_NAME"] as String
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = "11"
|
||||
}
|
||||
|
||||
withJava()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
named("jvmMain") {
|
||||
dependencies {
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.ui)
|
||||
|
||||
api("net.java.dev.jna:jna-platform:latest.release")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
window-styler/gradle.properties
Normal file
27
window-styler/gradle.properties
Normal file
@ -0,0 +1,27 @@
|
||||
#
|
||||
# Publishing Configuration
|
||||
#
|
||||
|
||||
SONATYPE_HOST=S01
|
||||
RELEASE_SIGNING_ENABLED=true
|
||||
|
||||
GROUP=com.mayakapps.compose
|
||||
POM_ARTIFACT_ID=window-styler
|
||||
VERSION_NAME=0.3.3-SNAPSHOT
|
||||
|
||||
POM_NAME=Compose Window Styler
|
||||
POM_DESCRIPTION=A library that lets you style your Compose Desktop application window
|
||||
POM_INCEPTION_YEAR=2022
|
||||
|
||||
POM_URL=https://github.com/MayakaApps/ComposeWindowStyler
|
||||
POM_SCM_URL=https://github.com/MayakaApps/ComposeWindowStyler
|
||||
POM_SCM_CONNECTION=scm:git:git://github.com/MayakaApps/ComposeWindowStyler.git
|
||||
POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/MayakaApps/ComposeWindowStyler.git
|
||||
|
||||
POM_LICENSE_NAME=MIT License
|
||||
POM_LICENSE_URL=https://raw.githubusercontent.com/MayakaApps/ComposeWindowStyler/main/LICENSE
|
||||
POM_LICENSE_DIST=repo
|
||||
|
||||
POM_DEVELOPER_ID=MayakaApps
|
||||
POM_DEVELOPER_NAME=MayakaApps
|
||||
POM_DEVELOPER_URL=https://github.com/MayakaApps/
|
||||
@ -0,0 +1,26 @@
|
||||
package com.mayakapps.compose.windowstyler
|
||||
|
||||
import java.awt.AlphaComposite
|
||||
import java.awt.Graphics
|
||||
import java.awt.Graphics2D
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal class HackedContentPane : JPanel() {
|
||||
|
||||
override fun paint(g: Graphics) {
|
||||
if (background.alpha != 255) {
|
||||
val gg = g.create()
|
||||
try {
|
||||
if (gg is Graphics2D) {
|
||||
gg.setColor(background)
|
||||
gg.composite = AlphaComposite.getInstance(AlphaComposite.SRC)
|
||||
gg.fillRect(0, 0, width, height)
|
||||
}
|
||||
} finally {
|
||||
gg.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
super.paint(g)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
package com.mayakapps.compose.windowstyler
|
||||
|
||||
import androidx.compose.ui.awt.ComposeWindow
|
||||
import org.jetbrains.skiko.SkiaLayer
|
||||
import java.awt.*
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JWindow
|
||||
|
||||
internal fun ComposeWindow.setComposeLayerTransparency(isTransparent: Boolean) {
|
||||
skiaLayer.transparency = isTransparent
|
||||
}
|
||||
|
||||
internal fun Window.hackContentPane() {
|
||||
val oldContentPane = contentPane ?: return
|
||||
|
||||
// Create hacked content pane the same way of AWT
|
||||
val newContentPane: JComponent = HackedContentPane()
|
||||
newContentPane.name = "$name.contentPane"
|
||||
newContentPane.layout = object : BorderLayout() {
|
||||
override fun addLayoutComponent(comp: Component, constraints: Any?) {
|
||||
super.addLayoutComponent(comp, constraints ?: CENTER)
|
||||
}
|
||||
}
|
||||
|
||||
newContentPane.background = Color(0, 0, 0, 0)
|
||||
|
||||
oldContentPane.components.forEach { newContentPane.add(it) }
|
||||
|
||||
contentPane = newContentPane
|
||||
}
|
||||
|
||||
|
||||
internal val ComposeWindow.skiaLayer: SkiaLayer
|
||||
get() {
|
||||
val delegate = delegateField.get(this)
|
||||
val layer = getLayerMethod.invoke(delegate)
|
||||
return getComponentMethod.invoke(layer) as SkiaLayer
|
||||
}
|
||||
|
||||
internal val Window.isTransparent
|
||||
get() = when (this) {
|
||||
is ComposeWindow -> skiaLayer.transparency
|
||||
else -> background.alpha != 255
|
||||
}
|
||||
|
||||
internal val Window.isUndecorated
|
||||
get() = when (this) {
|
||||
is Frame -> isUndecorated
|
||||
is JDialog -> isUndecorated
|
||||
is JWindow -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
|
||||
private val delegateField by lazy {
|
||||
ComposeWindow::class.java.getDeclaredField("delegate").apply { isAccessible = true }
|
||||
}
|
||||
|
||||
private val getLayerMethod by lazy {
|
||||
delegateField.type.getDeclaredMethod("getLayer").apply { isAccessible = true }
|
||||
}
|
||||
|
||||
private val getComponentMethod by lazy {
|
||||
getLayerMethod.returnType.getDeclaredMethod("getComponent")
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.mayakapps.compose.windowstyler
|
||||
|
||||
import java.awt.Window
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JWindow
|
||||
|
||||
// Try hard to get the contentPane
|
||||
internal var Window.contentPane
|
||||
get() = when (this) {
|
||||
is JFrame -> contentPane
|
||||
is JDialog -> contentPane
|
||||
is JWindow -> contentPane
|
||||
else -> null
|
||||
}
|
||||
set(value) = when (this) {
|
||||
is JFrame -> contentPane = value
|
||||
is JDialog -> contentPane = value
|
||||
is JWindow -> contentPane = value
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
package com.mayakapps.compose.windowstyler
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
* The type of the window backdrop/background.
|
||||
*
|
||||
* **Fallback Strategy**
|
||||
*
|
||||
* In case of unsupported effect the library tries to fall back to the nearest supported effect as follows:
|
||||
*
|
||||
* [Tabbed] -> [Mica] -> [Acrylic] -> [Transparent]
|
||||
*
|
||||
* [Aero] is dropped from the fallback as it is much more transparent than [Tabbed] or [Mica] and not customizable as
|
||||
* [Acrylic]. If [Tabbed] or [Mica] falls back to [Acrylic] or [Transparent], high alpha is used with white or black
|
||||
* color according to `isDarkTheme` to emulate these effects.
|
||||
*/
|
||||
sealed interface WindowBackdrop {
|
||||
|
||||
/**
|
||||
* This effect provides a simple solid backdrop colored as white or black according to isDarkTheme. This allows the
|
||||
* backdrop to blend with the title bar as well. Though its name may imply that the window will be left unchanged,
|
||||
* this is not the case as once the transparency is hacked into the window, it can't be reverted.
|
||||
*/
|
||||
object Default : WindowBackdrop
|
||||
|
||||
/**
|
||||
* This applies [color] as a solid background which means that any alpha component is ignored and the color is
|
||||
* rendered as opaque.
|
||||
*/
|
||||
open class Solid(override val color: Color) : WindowBackdrop, ColorableWindowBackdrop {
|
||||
override fun equals(other: Any?): Boolean = equalsImpl(other)
|
||||
override fun hashCode(): Int = hashCodeImpl()
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as [Solid] but allows transparency taking into account the alpha value. If the passed [color] is fully
|
||||
* opaque, the alpha is set to 0.5F.
|
||||
*/
|
||||
open class Transparent(color: Color) : WindowBackdrop, ColorableWindowBackdrop {
|
||||
// If you really want the color to be fully opaque, just use Solid which is simpler and more stable
|
||||
override val color: Color =
|
||||
if (color.alpha != 1F) color else color.copy(alpha = 0.5F)
|
||||
|
||||
override fun equals(other: Any?): Boolean = equalsImpl(other)
|
||||
override fun hashCode(): Int = hashCodeImpl()
|
||||
|
||||
/**
|
||||
* This makes the window fully transparent.
|
||||
*/
|
||||
companion object : Transparent(Color.Transparent)
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies [Aero](https://en.wikipedia.org/wiki/Windows_Aero) backdrop which is Windows Vista and Windows 7
|
||||
* version of blur.
|
||||
*
|
||||
* This effect doesn't allow any customization.
|
||||
*/
|
||||
object Aero : WindowBackdrop
|
||||
|
||||
/**
|
||||
* This applies [Acrylic](https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic) backdrop blended with
|
||||
* the supplied [color]. If the backdrop is rendered opaque, double check that [color] has reasonable alpha value.
|
||||
*
|
||||
* **Supported on Windows 10 version 1803 or greater.**
|
||||
*/
|
||||
open class Acrylic(override val color: Color) : WindowBackdrop, ColorableWindowBackdrop {
|
||||
override fun equals(other: Any?): Boolean = equalsImpl(other)
|
||||
override fun hashCode(): Int = hashCodeImpl()
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies [Mica](https://docs.microsoft.com/en-us/windows/apps/design/style/mica) backdrop themed according
|
||||
* to `isDarkTheme` value.
|
||||
*
|
||||
* **Supported on Windows 11 21H2 or greater.**
|
||||
*/
|
||||
object Mica : WindowBackdrop
|
||||
|
||||
/**
|
||||
* This applies Tabbed backdrop themed according to `isDarkTheme` value. This is a backdrop that is similar to
|
||||
* [Mica] but targeted at tabbed windows.
|
||||
*
|
||||
* **Supported on Windows 11 22H2 or greater.**
|
||||
*/
|
||||
object Tabbed : WindowBackdrop
|
||||
}
|
||||
|
||||
internal sealed interface ColorableWindowBackdrop {
|
||||
val color: Color
|
||||
|
||||
fun equalsImpl(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ColorableWindowBackdrop
|
||||
|
||||
return color == other.color
|
||||
}
|
||||
|
||||
fun hashCodeImpl(): Int = color.hashCode()
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.mayakapps.compose.windowstyler
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
* Styles for the window frame which includes the title bar and window border.
|
||||
*
|
||||
* All these styles are only supported on Windows 11 or greater and has no effect on other OSes.
|
||||
*
|
||||
* @property borderColor Specifies the color of the window border that is running around the window if the window is
|
||||
* decorated. This property doesn't support transparency.
|
||||
* @property titleBarColor Specifies the color of the window title bar (caption bar) if the window is decorated. This
|
||||
* property doesn't support transparency.
|
||||
* @property captionColor Specifies the color of the window caption (title) text if the window is decorated. This
|
||||
* property doesn't support transparency.
|
||||
* @property cornerPreference Specifies the shape of the corners you want. For example, you can use this property to
|
||||
* avoid rounded corners in a decorated window or get the corners rounded in an undecorated window.
|
||||
*/
|
||||
data class WindowFrameStyle(
|
||||
val borderColor: Color = Color.Unspecified,
|
||||
val titleBarColor: Color = Color.Unspecified,
|
||||
val captionColor: Color = Color.Unspecified,
|
||||
val cornerPreference: WindowCornerPreference = WindowCornerPreference.DEFAULT
|
||||
)
|
||||
|
||||
/**
|
||||
* The preferred corner shape of the window.
|
||||
*/
|
||||
enum class WindowCornerPreference {
|
||||
DEFAULT,
|
||||
NOT_ROUNDED,
|
||||
ROUNDED,
|
||||
SMALL_ROUNDED,
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.mayakapps.compose.windowstyler
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.window.WindowScope
|
||||
|
||||
/**
|
||||
* Applies the provided styles to the current window.
|
||||
*
|
||||
* See [WindowStyleManager.isDarkTheme], [WindowBackdrop], [WindowFrameStyle].
|
||||
*/
|
||||
@Composable
|
||||
fun WindowScope.WindowBackdropStyle(
|
||||
isDarkTheme: Boolean = false,
|
||||
backdropType: WindowBackdrop = WindowBackdrop.Default,
|
||||
frameStyle: WindowFrameStyle = WindowFrameStyle(),
|
||||
) {
|
||||
val manager = remember { WindowStyleManager(window, isDarkTheme, backdropType, frameStyle) }
|
||||
|
||||
LaunchedEffect(isDarkTheme) {
|
||||
manager.isDarkTheme = isDarkTheme
|
||||
}
|
||||
|
||||
LaunchedEffect(backdropType) {
|
||||
manager.backdropType = backdropType
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package com.mayakapps.compose.windowstyler
|
||||
|
||||
import com.mayakapps.compose.windowstyler.windows.WindowsWindowStyleManager
|
||||
import org.jetbrains.skiko.OS
|
||||
import org.jetbrains.skiko.hostOs
|
||||
import java.awt.Window
|
||||
|
||||
/**
|
||||
* Creates a suitable [WindowStyleManager] for [window] or a stub manager if the OS is not supported.
|
||||
*
|
||||
* The created manager is initialized by the supplied parameters.
|
||||
* See [WindowStyleManager.isDarkTheme], [WindowBackdrop], [WindowFrameStyle].
|
||||
*/
|
||||
fun WindowStyleManager(
|
||||
window: Window,
|
||||
isDarkTheme: Boolean = false,
|
||||
backdropType: WindowBackdrop = WindowBackdrop.Default,
|
||||
frameStyle: WindowFrameStyle = WindowFrameStyle(),
|
||||
) = when (hostOs) {
|
||||
OS.Windows -> WindowsWindowStyleManager(window, isDarkTheme, backdropType, frameStyle)
|
||||
else -> StubWindowStyleManager(isDarkTheme, backdropType, frameStyle)
|
||||
}
|
||||
|
||||
/**
|
||||
* Style manager which lets you update the style of the provided window using the exposed properties.
|
||||
*
|
||||
* Only use this manager if you can't use the `@Composable` method [WindowStyle]
|
||||
*/
|
||||
interface WindowStyleManager {
|
||||
|
||||
/**
|
||||
* This property should match the theming system used in your application. It's effect depends on the used backdrop
|
||||
* as follows:
|
||||
* * If the [backdropType] is [WindowBackdrop.Default], [WindowBackdrop.Mica] or [WindowBackdrop.Tabbed], it is
|
||||
* used to manage the color of the background whether it is light or dark.
|
||||
* * Otherwise, it is used to control the color of the title bar of the window white/black.
|
||||
*/
|
||||
var isDarkTheme: Boolean
|
||||
|
||||
/**
|
||||
* The type of the window backdrop/background. See [WindowBackdrop] and its implementations.
|
||||
*/
|
||||
var backdropType: WindowBackdrop
|
||||
|
||||
/**
|
||||
* The style of the window frame which includes the title bar and window border. See [WindowFrameStyle].
|
||||
*/
|
||||
var frameStyle: WindowFrameStyle
|
||||
}
|
||||
|
||||
internal class StubWindowStyleManager(
|
||||
override var isDarkTheme: Boolean,
|
||||
override var backdropType: WindowBackdrop,
|
||||
override var frameStyle: WindowFrameStyle,
|
||||
) : WindowStyleManager
|
||||
@ -0,0 +1,35 @@
|
||||
package com.mayakapps.compose.windowstyler.windows
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.colorspace.connect
|
||||
|
||||
internal fun Color.toBgr(): Int {
|
||||
val colorSpace = colorSpace
|
||||
val color = floatArrayOf(red, green, blue)
|
||||
|
||||
// The transformation saturates the output
|
||||
colorSpace.connect().transform(color)
|
||||
|
||||
return ((color[2] * 255.0f + 0.5f).toInt() shl 16) or
|
||||
((color[1] * 255.0f + 0.5f).toInt() shl 8) or
|
||||
(color[0] * 255.0f + 0.5f).toInt()
|
||||
}
|
||||
|
||||
// Modified version of toArgb
|
||||
internal fun Color.toAbgr(): Int {
|
||||
val colorSpace = colorSpace
|
||||
val color = floatArrayOf(red, green, blue, alpha)
|
||||
|
||||
// The transformation saturates the output
|
||||
colorSpace.connect().transform(color)
|
||||
|
||||
return (color[3] * 255.0f + 0.5f).toInt() shl 24 or
|
||||
((color[2] * 255.0f + 0.5f).toInt() shl 16) or
|
||||
((color[1] * 255.0f + 0.5f).toInt() shl 8) or
|
||||
(color[0] * 255.0f + 0.5f).toInt()
|
||||
}
|
||||
|
||||
// For some reason, passing 0 (fully transparent black) to the setAccentPolicy with
|
||||
// transparent accent policy results in solid red color. As a workaround, we pass
|
||||
// fully transparent white which has the same visual effect.
|
||||
internal fun Color.toAbgrForTransparent() = if (alpha == 0F) 0x00FFFFFF else toAbgr()
|
||||
@ -0,0 +1,50 @@
|
||||
package com.mayakapps.compose.windowstyler.windows
|
||||
|
||||
import androidx.compose.ui.awt.ComposeWindow
|
||||
import com.mayakapps.compose.windowstyler.WindowBackdrop
|
||||
import com.mayakapps.compose.windowstyler.WindowCornerPreference
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.Nt
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentState
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmSystemBackdrop
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmWindowCornerPreference
|
||||
import com.sun.jna.Native
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.platform.win32.WinDef
|
||||
import java.awt.Window
|
||||
|
||||
val Window.hwnd
|
||||
get() =
|
||||
if (this is ComposeWindow) WinDef.HWND(Pointer(windowHandle))
|
||||
else WinDef.HWND(Native.getWindowPointer(this))
|
||||
|
||||
internal val windowsBuild by lazy {
|
||||
val osVersionInfo = Nt.getVersion()
|
||||
val buildNumber = osVersionInfo.buildNumber
|
||||
osVersionInfo.dispose()
|
||||
buildNumber
|
||||
}
|
||||
|
||||
internal fun WindowBackdrop.toDwmSystemBackdrop(): DwmSystemBackdrop =
|
||||
when (this) {
|
||||
is WindowBackdrop.Mica -> DwmSystemBackdrop.DWMSBT_MAINWINDOW
|
||||
is WindowBackdrop.Acrylic -> DwmSystemBackdrop.DWMSBT_TRANSIENTWINDOW
|
||||
is WindowBackdrop.Tabbed -> DwmSystemBackdrop.DWMSBT_TABBEDWINDOW
|
||||
else -> DwmSystemBackdrop.DWMSBT_DISABLE
|
||||
}
|
||||
|
||||
internal fun WindowBackdrop.toAccentState(): AccentState =
|
||||
when (this) {
|
||||
is WindowBackdrop.Default, is WindowBackdrop.Solid -> AccentState.ACCENT_ENABLE_GRADIENT
|
||||
is WindowBackdrop.Transparent -> AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT
|
||||
is WindowBackdrop.Aero -> AccentState.ACCENT_ENABLE_BLURBEHIND
|
||||
is WindowBackdrop.Acrylic -> AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND
|
||||
else -> AccentState.ACCENT_DISABLED
|
||||
}
|
||||
|
||||
internal fun WindowCornerPreference.toDwmWindowCornerPreference(): DwmWindowCornerPreference =
|
||||
when (this) {
|
||||
WindowCornerPreference.DEFAULT -> DwmWindowCornerPreference.DWMWCP_DEFAULT
|
||||
WindowCornerPreference.NOT_ROUNDED -> DwmWindowCornerPreference.DWMWCP_DONOTROUND
|
||||
WindowCornerPreference.ROUNDED -> DwmWindowCornerPreference.DWMWCP_ROUND
|
||||
WindowCornerPreference.SMALL_ROUNDED -> DwmWindowCornerPreference.DWMWCP_ROUNDSMALL
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package com.mayakapps.compose.windowstyler.windows
|
||||
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.Dwm
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.User32
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentFlag
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentState
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmSystemBackdrop
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmWindowAttribute
|
||||
import com.sun.jna.platform.win32.WinDef
|
||||
|
||||
internal class WindowsBackdropApis(private val hwnd: WinDef.HWND) {
|
||||
private var isSystemBackdropSet = false
|
||||
private var isMicaEnabled = false
|
||||
private var isAccentPolicySet = false
|
||||
private var isSheetOfGlassApplied = false
|
||||
|
||||
fun setSystemBackdrop(systemBackdrop: DwmSystemBackdrop) {
|
||||
createSheetOfGlassEffect()
|
||||
if (Dwm.setSystemBackdrop(hwnd, systemBackdrop)) {
|
||||
isSystemBackdropSet = systemBackdrop == DwmSystemBackdrop.DWMSBT_DISABLE
|
||||
if (isSystemBackdropSet) resetAccentPolicy()
|
||||
}
|
||||
}
|
||||
|
||||
fun setMicaEffectEnabled(enabled: Boolean) {
|
||||
createSheetOfGlassEffect()
|
||||
if (Dwm.setWindowAttribute(hwnd, DwmWindowAttribute.DWMWA_MICA_EFFECT, enabled)) {
|
||||
isMicaEnabled = enabled
|
||||
if (isMicaEnabled) resetAccentPolicy()
|
||||
}
|
||||
}
|
||||
|
||||
fun setAccentPolicy(
|
||||
accentState: AccentState = AccentState.ACCENT_DISABLED,
|
||||
accentFlags: Set<AccentFlag> = emptySet(),
|
||||
color: Int = 0,
|
||||
animationId: Int = 0,
|
||||
) {
|
||||
if (User32.setAccentPolicy(hwnd, accentState, accentFlags, color, animationId)) {
|
||||
isAccentPolicySet = accentState != AccentState.ACCENT_DISABLED
|
||||
if (isAccentPolicySet) {
|
||||
resetSystemBackdrop()
|
||||
resetMicaEffectEnabled()
|
||||
resetWindowFrame()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createSheetOfGlassEffect() {
|
||||
if (!isSheetOfGlassApplied && Dwm.extendFrameIntoClientArea(hwnd, -1)) isSheetOfGlassApplied = true
|
||||
}
|
||||
|
||||
|
||||
fun resetSystemBackdrop() {
|
||||
if (isSystemBackdropSet) setSystemBackdrop(DwmSystemBackdrop.DWMSBT_DISABLE)
|
||||
}
|
||||
|
||||
fun resetMicaEffectEnabled() {
|
||||
if (isMicaEnabled) setMicaEffectEnabled(false)
|
||||
}
|
||||
|
||||
fun resetAccentPolicy() {
|
||||
if (isAccentPolicySet) setAccentPolicy(AccentState.ACCENT_DISABLED)
|
||||
}
|
||||
|
||||
fun resetWindowFrame() {
|
||||
// At least one margin should be non-negative in order to show the DWM
|
||||
// window shadow created by handling [WM_NCCALCSIZE].
|
||||
//
|
||||
// Matching value with bitsdojo_window.
|
||||
// https://github.com/bitsdojo/bitsdojo_window/blob/adad0cd40be3d3e12df11d864f18a96a2d0fb4fb/bitsdojo_window_windows/windows/bitsdojo_window.cpp#L149
|
||||
if (isSheetOfGlassApplied && Dwm.extendFrameIntoClientArea(hwnd, 0, 0, 1, 0)) {
|
||||
isSheetOfGlassApplied = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,243 @@
|
||||
package com.mayakapps.compose.windowstyler.windows
|
||||
|
||||
import androidx.compose.ui.awt.ComposeWindow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import com.mayakapps.compose.windowstyler.*
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.Dwm
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentFlag
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmWindowAttribute
|
||||
import com.sun.jna.platform.win32.WinDef.HWND
|
||||
import java.awt.Window
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
/**
|
||||
* Windows implementation of [WindowStyleManager]. It is not recommended to use this class directly.
|
||||
*
|
||||
* If used on an OS other than Windows, it'll crash.
|
||||
*/
|
||||
class WindowsWindowStyleManager(
|
||||
window: Window,
|
||||
isDarkTheme: Boolean = false,
|
||||
backdropType: WindowBackdrop = WindowBackdrop.Default,
|
||||
frameStyle: WindowFrameStyle = WindowFrameStyle(),
|
||||
) : WindowStyleManager {
|
||||
|
||||
private val hwnd: HWND = window.hwnd
|
||||
private val isUndecorated = window.isUndecorated
|
||||
private var wasAero = false
|
||||
|
||||
private val backdropApis = WindowsBackdropApis(hwnd)
|
||||
|
||||
override var isDarkTheme: Boolean = isDarkTheme
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
updateTheme()
|
||||
}
|
||||
}
|
||||
|
||||
override var backdropType: WindowBackdrop = backdropType
|
||||
set(value) {
|
||||
val finalValue = value.fallbackIfUnsupported()
|
||||
|
||||
if (field != finalValue) {
|
||||
wasAero = field is WindowBackdrop.Aero
|
||||
field = finalValue
|
||||
updateBackdrop()
|
||||
}
|
||||
}
|
||||
|
||||
override var frameStyle: WindowFrameStyle = frameStyle
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
val oldValue = field
|
||||
field = value
|
||||
updateFrameStyle(oldValue)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// invokeLater is called to make sure that ComposeLayer was initialized first
|
||||
SwingUtilities.invokeLater {
|
||||
// If the window is not already transparent, hack it to be transparent
|
||||
if (!window.isTransparent) {
|
||||
// For some reason, reversing the order of these two calls doesn't work.
|
||||
if (window is ComposeWindow) window.setComposeLayerTransparency(true)
|
||||
window.hackContentPane()
|
||||
}
|
||||
|
||||
updateTheme()
|
||||
updateBackdrop()
|
||||
updateFrameStyle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTheme() {
|
||||
val attribute =
|
||||
when {
|
||||
windowsBuild < 17763 -> return
|
||||
windowsBuild >= 18985 -> DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
else -> DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1
|
||||
}
|
||||
|
||||
if (windowsBuild >= 17763 && Dwm.setWindowAttribute(hwnd, attribute, isDarkTheme)) {
|
||||
// Default: This is done to update the background color between white or black
|
||||
// ThemedAcrylic: Update the acrylic effect if it is themed
|
||||
// Transparent:
|
||||
// For some reason, using setImmersiveDarkModeEnabled after setting accent policy to transparent
|
||||
// results in solid red backdrop. So, we have to reset the transparent backdrop after using it.
|
||||
// This is also required for updating emulated transparent effect
|
||||
if (backdropType is WindowBackdrop.Default || backdropType is ThemedAcrylic ||
|
||||
backdropType is WindowBackdrop.Transparent
|
||||
) updateBackdrop()
|
||||
// This is necessary for window buttons to change color correctly
|
||||
else if (backdropType is WindowBackdrop.Mica && !isUndecorated) {
|
||||
backdropApis.resetWindowFrame()
|
||||
backdropApis.createSheetOfGlassEffect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBackdrop() {
|
||||
// This is done to make sure that the window has become visible
|
||||
// If the window isn't shown yet, and we try to apply Default, Solid, Aero,
|
||||
// or Acrylic, the effect will be applied to the title bar background
|
||||
// leaving the caption with awkward background box.
|
||||
// Unfortunately, even with this method, mica has this background box.
|
||||
SwingUtilities.invokeLater {
|
||||
// Only on later Windows 11 versions and if effect is WindowEffect.mica,
|
||||
// WindowEffect.acrylic or WindowEffect.tabbed, otherwise fallback to old
|
||||
// approach.
|
||||
if (
|
||||
windowsBuild >= 22523 &&
|
||||
(backdropType is WindowBackdrop.Acrylic || backdropType is WindowBackdrop.Mica || backdropType is WindowBackdrop.Tabbed)
|
||||
) {
|
||||
backdropApis.setSystemBackdrop(backdropType.toDwmSystemBackdrop())
|
||||
} else {
|
||||
if (backdropType is WindowBackdrop.Mica) {
|
||||
// Check for Windows 11.
|
||||
if (windowsBuild >= 22000) {
|
||||
backdropApis.setMicaEffectEnabled(true)
|
||||
}
|
||||
} else {
|
||||
val color = when (val backdropType = backdropType) {
|
||||
// As the transparency hack is irreversible, the default effect is applied by solid backdrop.
|
||||
// The default color is white or black depending on the theme
|
||||
is WindowBackdrop.Default -> (if (isDarkTheme) Color.Black else Color.White).toAbgr()
|
||||
is WindowBackdrop.Transparent -> backdropType.color.toAbgrForTransparent()
|
||||
is ColorableWindowBackdrop -> backdropType.color.toAbgr()
|
||||
else -> 0x7FFFFFFF
|
||||
}
|
||||
|
||||
// wasAero: This is required as sometimes the window gets stuck at aero
|
||||
// Transparent: In many cases, if this is not done, red opaque background is shown
|
||||
if (wasAero || backdropType is WindowBackdrop.Transparent) backdropApis.resetAccentPolicy()
|
||||
|
||||
// Another red opaque background case :'(
|
||||
// Resetting these values needs to be done before applying transparency
|
||||
if (backdropType is WindowBackdrop.Transparent) {
|
||||
backdropApis.resetMicaEffectEnabled()
|
||||
backdropApis.resetSystemBackdrop()
|
||||
}
|
||||
|
||||
backdropApis.setAccentPolicy(
|
||||
accentState = backdropType.toAccentState(),
|
||||
accentFlags = setOf(AccentFlag.DRAW_ALL_BORDERS),
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Frame Style
|
||||
*/
|
||||
|
||||
private fun updateFrameStyle(oldStyle: WindowFrameStyle? = null) {
|
||||
if (windowsBuild >= 22000) {
|
||||
if ((oldStyle?.cornerPreference ?: WindowCornerPreference.DEFAULT) != frameStyle.cornerPreference) {
|
||||
Dwm.setWindowCornerPreference(hwnd, frameStyle.cornerPreference.toDwmWindowCornerPreference())
|
||||
}
|
||||
|
||||
if (frameStyle.borderColor.isSpecified && oldStyle?.borderColor != frameStyle.borderColor) {
|
||||
Dwm.setWindowAttribute(hwnd, DwmWindowAttribute.DWMWA_BORDER_COLOR, frameStyle.borderColor.toBgr())
|
||||
}
|
||||
|
||||
if (frameStyle.titleBarColor.isSpecified && oldStyle?.titleBarColor != frameStyle.titleBarColor) {
|
||||
Dwm.setWindowAttribute(hwnd, DwmWindowAttribute.DWMWA_CAPTION_COLOR, frameStyle.titleBarColor.toBgr())
|
||||
}
|
||||
|
||||
if (frameStyle.captionColor.isSpecified && oldStyle?.captionColor != frameStyle.captionColor) {
|
||||
Dwm.setWindowAttribute(hwnd, DwmWindowAttribute.DWMWA_TEXT_COLOR, frameStyle.captionColor.toBgr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback Strategy
|
||||
*/
|
||||
|
||||
private fun WindowBackdrop.fallbackIfUnsupported(): WindowBackdrop {
|
||||
if (windowsBuild >= supportedSince) return this
|
||||
|
||||
return when (this) {
|
||||
is WindowBackdrop.Tabbed -> WindowBackdrop.Mica
|
||||
is WindowBackdrop.Mica -> themedAcrylic
|
||||
is WindowBackdrop.Acrylic -> {
|
||||
// Aero isn't customizable and too transparent for background
|
||||
// Manual mapping of themedAcrylic is to keep the theming working as expected
|
||||
if (this is ThemedAcrylic) themedTransparent
|
||||
else WindowBackdrop.Transparent(color)
|
||||
}
|
||||
else -> WindowBackdrop.Default
|
||||
}.fallbackIfUnsupported()
|
||||
}
|
||||
|
||||
private val themedTransparent = ThemedTransparent()
|
||||
private val themedAcrylic = ThemedAcrylic()
|
||||
|
||||
private val themedFallbackColor
|
||||
get() = if (isDarkTheme) Color(0xEF000000L) else Color(0xEFFFFFFFL)
|
||||
|
||||
private inner class ThemedAcrylic : WindowBackdrop.Acrylic(Color.Unspecified) {
|
||||
override val color: Color
|
||||
get() = themedFallbackColor
|
||||
}
|
||||
|
||||
private inner class ThemedTransparent : WindowBackdrop.Transparent(Color.Unspecified) {
|
||||
override val color: Color
|
||||
get() = themedFallbackColor
|
||||
}
|
||||
|
||||
private val WindowBackdrop.supportedSince
|
||||
get() = when (this) {
|
||||
is WindowBackdrop.Acrylic -> 17063
|
||||
is WindowBackdrop.Mica -> 22000
|
||||
is WindowBackdrop.Tabbed -> 22523
|
||||
else -> 0
|
||||
}
|
||||
|
||||
/*
|
||||
* Focus Listener for transparency workaround
|
||||
*/
|
||||
|
||||
// This is a workaround for transparency getting replaced by red opaque color for decorated windows on focus
|
||||
// changes. This workaround doesn't appear to be efficient, and there may be red flashes on losing/gaining focus.
|
||||
// Yet, it seems to be enough for the limited use cases of transparent decorated
|
||||
private val windowAdapter = object : WindowAdapter() {
|
||||
override fun windowGainedFocus(e: WindowEvent?) = resetTransparent()
|
||||
override fun windowLostFocus(e: WindowEvent?) = resetTransparent()
|
||||
|
||||
private fun resetTransparent() {
|
||||
if (!isUndecorated && this@WindowsWindowStyleManager.backdropType is WindowBackdrop.Transparent) updateBackdrop()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
window.addWindowFocusListener(windowAdapter)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna
|
||||
|
||||
import androidx.compose.ui.window.WindowScope
|
||||
import com.mayakapps.compose.windowstyler.windows.hwnd
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmSystemBackdrop
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmWindowAttribute
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.DwmWindowCornerPreference
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.structs.Margins
|
||||
import com.sun.jna.Native
|
||||
import com.sun.jna.PointerType
|
||||
import com.sun.jna.platform.win32.W32Errors
|
||||
import com.sun.jna.platform.win32.WinDef
|
||||
import com.sun.jna.platform.win32.WinDef.HWND
|
||||
import com.sun.jna.platform.win32.WinNT.HRESULT
|
||||
import com.sun.jna.ptr.IntByReference
|
||||
import com.sun.jna.win32.StdCallLibrary
|
||||
import com.sun.jna.win32.W32APIOptions
|
||||
|
||||
object Dwm {
|
||||
fun extendFrameIntoClientArea(hwnd: HWND, margin: Int = 0) =
|
||||
extendFrameIntoClientArea(hwnd, margin, margin, margin, margin)
|
||||
|
||||
fun extendFrameIntoClientArea(
|
||||
hwnd: HWND,
|
||||
leftWidth: Int = 0,
|
||||
rightWidth: Int = 0,
|
||||
topHeight: Int = 0,
|
||||
bottomHeight: Int = 0,
|
||||
): Boolean {
|
||||
val margins = Margins(leftWidth, rightWidth, topHeight, bottomHeight)
|
||||
|
||||
val result = DwmImpl.DwmExtendFrameIntoClientArea(hwnd, margins)
|
||||
if (result != W32Errors.S_OK) println("DwmExtendFrameIntoClientArea failed with result $result")
|
||||
|
||||
margins.dispose()
|
||||
return result == W32Errors.S_OK
|
||||
}
|
||||
|
||||
|
||||
fun setSystemBackdrop(hwnd: HWND, systemBackdrop: DwmSystemBackdrop): Boolean =
|
||||
setWindowAttribute(hwnd, DwmWindowAttribute.DWMWA_SYSTEMBACKDROP_TYPE, systemBackdrop.value)
|
||||
|
||||
fun setWindowCornerPreference(hwnd: HWND, cornerPreference: DwmWindowCornerPreference): Boolean =
|
||||
setWindowAttribute(hwnd, DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, cornerPreference.value)
|
||||
|
||||
fun setWindowAttribute(hwnd: HWND, attribute: DwmWindowAttribute, value: Boolean) =
|
||||
setWindowAttribute(hwnd, attribute, WinDef.BOOLByReference(WinDef.BOOL(value)), WinDef.BOOL.SIZE)
|
||||
|
||||
fun setWindowAttribute(hwnd: HWND, attribute: DwmWindowAttribute, value: Int) =
|
||||
setWindowAttribute(hwnd, attribute, IntByReference(value), INT_SIZE)
|
||||
|
||||
fun WindowScope.setWindowAttribute(attribute: DwmWindowAttribute, value: Int) =
|
||||
setWindowAttribute(this.window.hwnd, attribute, IntByReference(value), INT_SIZE)
|
||||
|
||||
private fun setWindowAttribute(
|
||||
hwnd: HWND,
|
||||
attribute: DwmWindowAttribute,
|
||||
value: PointerType?,
|
||||
valueSize: Int,
|
||||
): Boolean {
|
||||
val result = DwmImpl.DwmSetWindowAttribute(hwnd, attribute.value, value, valueSize)
|
||||
|
||||
if (result != W32Errors.S_OK) println("DwmSetWindowAttribute(${attribute.name}) failed with result $result")
|
||||
return result == W32Errors.S_OK
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private object DwmImpl : DwmApi by Native.load("dwmapi", DwmApi::class.java, W32APIOptions.DEFAULT_OPTIONS)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private interface DwmApi : StdCallLibrary {
|
||||
fun DwmExtendFrameIntoClientArea(hwnd: HWND, margins: Margins): HRESULT
|
||||
fun DwmSetWindowAttribute(hwnd: HWND, attribute: Int, value: PointerType?, valueSize: Int): HRESULT
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna
|
||||
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.structs.OsVersionInfo
|
||||
import com.sun.jna.Native
|
||||
import com.sun.jna.win32.StdCallLibrary
|
||||
import com.sun.jna.win32.W32APIOptions
|
||||
|
||||
internal object Nt {
|
||||
fun getVersion() = OsVersionInfo().also { NtImpl.RtlGetVersion(it) }
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private object NtImpl : NtApi by Native.load("Ntdll", NtApi::class.java, W32APIOptions.DEFAULT_OPTIONS)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private interface NtApi : StdCallLibrary {
|
||||
fun RtlGetVersion(osVersionInfo: OsVersionInfo): Int
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna
|
||||
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentFlag
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentState
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.WindowCompositionAttribute
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.structs.AccentPolicy
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.structs.WindowCompositionAttributeData
|
||||
import com.sun.jna.Native
|
||||
import com.sun.jna.platform.win32.WinDef
|
||||
import com.sun.jna.win32.StdCallLibrary
|
||||
import com.sun.jna.win32.W32APIOptions
|
||||
|
||||
internal object User32 {
|
||||
fun setAccentPolicy(
|
||||
hwnd: WinDef.HWND,
|
||||
accentState: AccentState = AccentState.ACCENT_DISABLED,
|
||||
accentFlags: Set<AccentFlag> = emptySet(),
|
||||
color: Int = 0,
|
||||
animationId: Int = 0,
|
||||
): Boolean {
|
||||
val data = WindowCompositionAttributeData(
|
||||
WindowCompositionAttribute.WCA_ACCENT_POLICY,
|
||||
AccentPolicy(accentState, accentFlags, color, animationId),
|
||||
)
|
||||
|
||||
val isSuccess = setWindowCompositionAttribute(hwnd, data)
|
||||
|
||||
data.dispose()
|
||||
return isSuccess
|
||||
}
|
||||
|
||||
private fun setWindowCompositionAttribute(
|
||||
hwnd: WinDef.HWND,
|
||||
attributeData: WindowCompositionAttributeData
|
||||
): Boolean {
|
||||
Native.setLastError(0)
|
||||
|
||||
val isSuccess = User32Impl.SetWindowCompositionAttribute(hwnd, attributeData)
|
||||
|
||||
if (!isSuccess) println("SetWindowCompositionAttribute(${attributeData.attribute}) failed with last error ${Native.getLastError()}")
|
||||
return isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
private object User32Impl : User32Api by Native.load("user32", User32Api::class.java, W32APIOptions.DEFAULT_OPTIONS)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private interface User32Api : StdCallLibrary {
|
||||
fun SetWindowCompositionAttribute(hwnd: WinDef.HWND, attributeData: WindowCompositionAttributeData): Boolean
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna
|
||||
|
||||
internal inline fun <T> Iterable<T>.orOf(selector: (T) -> Int): Int {
|
||||
var result = 0
|
||||
forEach { result = result or selector(it) }
|
||||
return result
|
||||
}
|
||||
|
||||
internal const val INT_SIZE = 4
|
||||
@ -0,0 +1,11 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.enums
|
||||
|
||||
@Suppress("SpellCheckingInspection", "unused")
|
||||
internal enum class AccentFlag(val value: Int) {
|
||||
NONE(0),
|
||||
DRAW_LEFT_BORDER(0x20),
|
||||
DRAW_TOP_BORDER(0x40),
|
||||
DRAW_RIGHT_BORDER(0x80),
|
||||
DRAW_BOTTOM_BORDER(0x100),
|
||||
DRAW_ALL_BORDERS(0x1E0), // OR result of all borders
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.enums
|
||||
|
||||
@Suppress("SpellCheckingInspection", "unused")
|
||||
internal enum class AccentState(val value: Int) {
|
||||
ACCENT_DISABLED(0),
|
||||
ACCENT_ENABLE_GRADIENT(1),
|
||||
ACCENT_ENABLE_TRANSPARENTGRADIENT(2),
|
||||
ACCENT_ENABLE_BLURBEHIND(3),
|
||||
ACCENT_ENABLE_ACRYLICBLURBEHIND(4),
|
||||
ACCENT_ENABLE_HOSTBACKDROP(5),
|
||||
ACCENT_INVALID_STATE(6),
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.enums
|
||||
|
||||
@Suppress("SpellCheckingInspection", "unused")
|
||||
enum class DwmSystemBackdrop(val value: Int) {
|
||||
DWMSBT_AUTO(0),
|
||||
DWMSBT_DISABLE(1), // None
|
||||
DWMSBT_MAINWINDOW(2), // Mica
|
||||
DWMSBT_TRANSIENTWINDOW(3), // Acrylic
|
||||
DWMSBT_TABBEDWINDOW(4), // Tabbed
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.enums
|
||||
|
||||
@Suppress("SpellCheckingInspection", "unused")
|
||||
enum class DwmWindowAttribute(val value: Int) {
|
||||
DWMWA_NCRENDERING_ENABLED(0),
|
||||
DWMWA_NCRENDERING_POLICY(1),
|
||||
DWMWA_TRANSITIONS_FORCEDISABLED(2),
|
||||
DWMWA_ALLOW_NCPAINT(3),
|
||||
DWMWA_CAPTION_BUTTON_BOUNDS(4),
|
||||
DWMWA_NONCLIENT_RTL_LAYOUT(5),
|
||||
DWMWA_FORCE_ICONIC_REPRESENTATION(6),
|
||||
DWMWA_FLIP3D_POLICY(7),
|
||||
DWMWA_EXTENDED_FRAME_BOUNDS(8),
|
||||
DWMWA_HAS_ICONIC_BITMAP(9),
|
||||
DWMWA_DISALLOW_PEEK(10),
|
||||
DWMWA_EXCLUDED_FROM_PEEK(11),
|
||||
DWMWA_CLOAK(12),
|
||||
DWMWA_CLOAKED(13),
|
||||
DWMWA_FREEZE_REPRESENTATION(14),
|
||||
DWMWA_PASSIVE_UPDATE_MODE(15),
|
||||
DWMWA_USE_HOSTBACKDROPBRUSH(16),
|
||||
DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1(19),
|
||||
DWMWA_USE_IMMERSIVE_DARK_MODE(20),
|
||||
DWMWA_WINDOW_CORNER_PREFERENCE(33),
|
||||
DWMWA_BORDER_COLOR(34),
|
||||
DWMWA_CAPTION_COLOR(35),
|
||||
DWMWA_TEXT_COLOR(36),
|
||||
DWMWA_VISIBLE_FRAME_BORDER_THICKNESS(37),
|
||||
DWMWA_SYSTEMBACKDROP_TYPE(38),
|
||||
DWMWA_LAST(39),
|
||||
DWMWA_MICA_EFFECT(1029),
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.enums
|
||||
|
||||
@Suppress("SpellCheckingInspection", "unused")
|
||||
enum class DwmWindowCornerPreference(val value: Int) {
|
||||
DWMWCP_DEFAULT(0),
|
||||
DWMWCP_DONOTROUND(1),
|
||||
DWMWCP_ROUND(2),
|
||||
DWMWCP_ROUNDSMALL(3),
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.enums
|
||||
|
||||
@Suppress("SpellCheckingInspection", "unused")
|
||||
internal enum class WindowCompositionAttribute(val value: Int) {
|
||||
WCA_UNDEFINED(0),
|
||||
WCA_NCRENDERING_ENABLED(1),
|
||||
WCA_NCRENDERING_POLICY(2),
|
||||
WCA_TRANSITIONS_FORCEDISABLED(3),
|
||||
WCA_ALLOW_NCPAINT(4),
|
||||
WCA_CAPTION_BUTTON_BOUNDS(5),
|
||||
WCA_NONCLIENT_RTL_LAYOUT(6),
|
||||
WCA_FORCE_ICONIC_REPRESENTATION(7),
|
||||
WCA_EXTENDED_FRAME_BOUNDS(8),
|
||||
WCA_HAS_ICONIC_BITMAP(9),
|
||||
WCA_THEME_ATTRIBUTES(10),
|
||||
WCA_NCRENDERING_EXILED(11),
|
||||
WCA_NCADORNMENTINFO(12),
|
||||
WCA_EXCLUDED_FROM_LIVEPREVIEW(13),
|
||||
WCA_VIDEO_OVERLAY_ACTIVE(14),
|
||||
WCA_FORCE_ACTIVEWINDOW_APPEARANCE(15),
|
||||
WCA_DISALLOW_PEEK(16),
|
||||
WCA_CLOAK(17),
|
||||
WCA_CLOAKED(18),
|
||||
WCA_ACCENT_POLICY(19),
|
||||
WCA_FREEZE_REPRESENTATION(20),
|
||||
WCA_EVER_UNCLOAKED(21),
|
||||
WCA_VISUAL_OWNER(22),
|
||||
WCA_HOLOGRAPHIC(23),
|
||||
WCA_EXCLUDED_FROM_DDA(24),
|
||||
WCA_PASSIVEUPDATEMODE(25),
|
||||
WCA_USEDARKMODECOLORS(26),
|
||||
WCA_LAST(27),
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.structs
|
||||
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentFlag
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.AccentState
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.orOf
|
||||
import com.sun.jna.Structure.FieldOrder
|
||||
|
||||
@Suppress("unused")
|
||||
@FieldOrder(
|
||||
"accentState",
|
||||
"accentFlags",
|
||||
"color",
|
||||
"animationId",
|
||||
)
|
||||
internal class AccentPolicy(
|
||||
accentState: AccentState = AccentState.ACCENT_DISABLED,
|
||||
accentFlags: Set<AccentFlag> = emptySet(),
|
||||
@JvmField var color: Int = 0,
|
||||
@JvmField var animationId: Int = 0,
|
||||
) : BaseStructure() {
|
||||
|
||||
@JvmField
|
||||
var accentState: Int = accentState.value
|
||||
|
||||
@JvmField
|
||||
var accentFlags: Int = accentFlags.orOf { it.value }
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.structs
|
||||
|
||||
import com.sun.jna.Structure
|
||||
|
||||
internal open class BaseStructure : Structure(), Structure.ByReference {
|
||||
open fun dispose() = clear()
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.structs
|
||||
|
||||
import com.sun.jna.Structure
|
||||
|
||||
@Structure.FieldOrder(
|
||||
"leftWidth",
|
||||
"rightWidth",
|
||||
"topHeight",
|
||||
"bottomHeight",
|
||||
)
|
||||
internal data class Margins(
|
||||
@JvmField var leftWidth: Int = 0,
|
||||
@JvmField var rightWidth: Int = 0,
|
||||
@JvmField var topHeight: Int = 0,
|
||||
@JvmField var bottomHeight: Int = 0,
|
||||
) : BaseStructure()
|
||||
@ -0,0 +1,28 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.structs
|
||||
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.Structure.FieldOrder
|
||||
import com.sun.jna.platform.win32.WinDef.ULONG
|
||||
|
||||
@Suppress("unused")
|
||||
@FieldOrder(
|
||||
"osVersionInfoSize",
|
||||
"majorVersion",
|
||||
"minorVersion",
|
||||
"buildNumber",
|
||||
"platformId",
|
||||
"csdVersion",
|
||||
)
|
||||
internal class OsVersionInfo(
|
||||
@JvmField var majorVersion: Int = 0,
|
||||
@JvmField var minorVersion: Int = 0,
|
||||
@JvmField var buildNumber: Int = 0,
|
||||
@JvmField var platformId: Int = 0,
|
||||
) : BaseStructure() {
|
||||
|
||||
@JvmField
|
||||
var osVersionInfoSize: Int = (ULONG.SIZE * 5) + 4
|
||||
|
||||
@JvmField
|
||||
var csdVersion: Pointer? = null
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.mayakapps.compose.windowstyler.windows.jna.structs
|
||||
|
||||
import com.mayakapps.compose.windowstyler.windows.jna.enums.WindowCompositionAttribute
|
||||
import com.sun.jna.Structure.FieldOrder
|
||||
|
||||
@Suppress("unused")
|
||||
@FieldOrder(
|
||||
"attribute",
|
||||
"data",
|
||||
"sizeOfData",
|
||||
)
|
||||
internal class WindowCompositionAttributeData(
|
||||
attribute: WindowCompositionAttribute = WindowCompositionAttribute.WCA_UNDEFINED,
|
||||
@JvmField var data: AccentPolicy = AccentPolicy(),
|
||||
) : BaseStructure() {
|
||||
|
||||
@JvmField
|
||||
var attribute: Int = attribute.value
|
||||
|
||||
@JvmField
|
||||
var sizeOfData: Int = data.size()
|
||||
|
||||
override fun dispose() {
|
||||
data.dispose()
|
||||
super.dispose()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user