Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.intui.standalone.window.Window
import org.jetbrains.jewel.window.styling.DecoratedWindowStyle
import org.jetbrains.jewel.window.utils.DesktopPlatform
import org.jetbrains.jewel.window.utils.ClientRegion

@Suppress("ModifierMissing")
@Composable
Expand Down Expand Up @@ -279,7 +280,12 @@ public value class DecoratedWindowState(public val state: ULong) {
}
}

internal data class TitleBarInfo(val title: String, val icon: Painter?)
internal data class TitleBarInfo(
val title: String,
val icon: Painter?
) {
val clientRegions: HashMap<String, ClientRegion> = hashMapOf()
}

internal val LocalTitleBarInfo: ProvidableCompositionLocal<TitleBarInfo> = compositionLocalOf {
error("LocalTitleBarInfo not provided, TitleBar must be used in DecoratedWindow")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp
import com.jetbrains.JBR
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.window.styling.TitleBarStyle
import org.jetbrains.jewel.window.utils.WindowMouseEventEffect
import org.jetbrains.jewel.window.utils.macos.MacUtil

public fun Modifier.newFullscreenControls(newControls: Boolean = true): Modifier =
Expand Down Expand Up @@ -78,8 +79,9 @@ internal fun DecoratedWindowScope.TitleBarOnMacOs(

val titleBar = remember { JBR.getWindowDecorations().createCustomTitleBar() }

WindowMouseEventEffect(titleBar)

TitleBarImpl(
modifier = modifier.customTitleBarMouseEventHandler(titleBar),
gradientStartColor = gradientStartColor,
style = style,
applyTitleBar = { height, state ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import com.jetbrains.JBR
import com.jetbrains.WindowDecorations.CustomTitleBar
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.util.isDark
import org.jetbrains.jewel.window.styling.TitleBarStyle
import org.jetbrains.jewel.window.utils.WindowMouseEventEffect

@Composable
internal fun DecoratedWindowScope.TitleBarOnWindows(
Expand All @@ -28,6 +23,8 @@ internal fun DecoratedWindowScope.TitleBarOnWindows(
) {
val titleBar = remember { JBR.getWindowDecorations().createCustomTitleBar() }

WindowMouseEventEffect(titleBar)

TitleBarImpl(
modifier = modifier,
gradientStartColor = gradientStartColor,
Expand All @@ -38,31 +35,7 @@ internal fun DecoratedWindowScope.TitleBarOnWindows(
JBR.getWindowDecorations().setCustomTitleBar(window, titleBar)
PaddingValues(start = titleBar.leftInset.dp, end = titleBar.rightInset.dp)
},
backgroundContent = { Spacer(modifier = Modifier.fillMaxSize().customTitleBarMouseEventHandler(titleBar)) },
backgroundContent = { Spacer(modifier = Modifier.fillMaxSize()) },
content = content,
)
}

internal fun Modifier.customTitleBarMouseEventHandler(titleBar: CustomTitleBar): Modifier =
pointerInput(titleBar) {
val currentContext = currentCoroutineContext()
awaitPointerEventScope {
var inUserControl = false
while (currentContext.isActive) {
val event = awaitPointerEvent(PointerEventPass.Main)
event.changes.forEach {
if (!it.isConsumed && !inUserControl) {
titleBar.forceHitTest(false)
} else {
if (event.type == PointerEventType.Press) {
inUserControl = true
}
if (event.type == PointerEventType.Release) {
inUserControl = false
}
titleBar.forceHitTest(true)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.jetbrains.jewel.window.utils

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.InspectorInfo
import com.jetbrains.WindowDecorations
import org.jetbrains.jewel.window.DecoratedWindowScope
import org.jetbrains.jewel.window.LocalTitleBarInfo
import org.jetbrains.jewel.window.TitleBarInfo
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import kotlin.collections.set

internal data class ClientRegion(
val x: Float,
val y: Float,
val width: Int,
val height: Int,
)

public fun Modifier.clientRegion(key: String): Modifier = then(RegisterClientRegionElement(key))

private data class RegisterClientRegionElement(
private val key: String
) : ModifierNodeElement<RegisterClientRegionNode>() {

override fun create() = RegisterClientRegionNode(key)

override fun update(node: RegisterClientRegionNode) = node.updateKey(key)

override fun InspectorInfo.inspectableProperties() {
name = "registerRegion"
properties["key"] = key
}
}

private class RegisterClientRegionNode(
var key: String
) : Modifier.Node(), GlobalPositionAwareModifierNode, CompositionLocalConsumerModifierNode {

private var titleBarInfo: TitleBarInfo? = null

override fun onAttach() {
titleBarInfo = currentValueOf(LocalTitleBarInfo)
}

override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
val info = titleBarInfo ?: return
val position = coordinates.positionInWindow()
val size = coordinates.size

info.clientRegions[key] = ClientRegion(
x = position.x,
y = position.y,
width = size.width,
height = size.height
)
}

override fun onDetach() {
titleBarInfo?.clientRegions?.remove(key)
titleBarInfo = null
}

fun updateKey(newKey: String) {
if (key != newKey) {
titleBarInfo?.clientRegions?.remove(key)
key = newKey
}
}
}

@Composable
internal fun DecoratedWindowScope.WindowMouseEventEffect(titleBar: WindowDecorations.CustomTitleBar) {
val titleBarInfo = LocalTitleBarInfo.current

DisposableEffect(window, window.graphicsConfiguration, titleBar) {
val graphicsConfig = window.graphicsConfiguration
val scaleX = graphicsConfig?.defaultTransform?.scaleX ?: 1.0
val scaleY = graphicsConfig?.defaultTransform?.scaleY ?: 1.0
val listener = object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) = updateHitTest(e)
override fun mouseReleased(e: MouseEvent) = updateHitTest(e)
override fun mouseDragged(e: MouseEvent) = updateHitTest(e)
override fun mouseMoved(e: MouseEvent) = updateHitTest(e)

private fun updateHitTest(e: MouseEvent) {
// Convert from AWT raw pixels to Compose positionInWindow() coordinates
val x = e.x * scaleX
val y = e.y * scaleY

val isClientRegion = titleBarInfo.clientRegions.any { region ->
(x >= region.value.x && x <= region.value.x + region.value.width) &&
(y >= region.value.y && y <= region.value.y + region.value.height)
}

titleBar.forceHitTest(isClientRegion)
}
}
window.addMouseListener(listener)
window.addMouseMotionListener(listener)

onDispose {
window.removeMouseListener(listener)
window.removeMouseMotionListener(listener)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ import org.jetbrains.jewel.ui.painter.hints.Size
import org.jetbrains.jewel.window.DecoratedWindowScope
import org.jetbrains.jewel.window.TitleBar
import org.jetbrains.jewel.window.newFullscreenControls
import org.jetbrains.jewel.window.utils.clientRegion

@ExperimentalLayoutApi
@Composable
internal fun DecoratedWindowScope.TitleBarView() {
TitleBar(Modifier.newFullscreenControls(), gradientStartColor = MainViewModel.projectColor) {
Row(Modifier.align(Alignment.Start)) {
Dropdown(
Modifier.height(30.dp),
Modifier.height(30.dp).clientRegion("dropdown"),
menuContent = {
MainViewModel.views.forEach {
selectableItem(
Expand Down Expand Up @@ -73,7 +74,7 @@ internal fun DecoratedWindowScope.TitleBarView() {
val jewelGithubLink = "https://github.com/JetBrains/intellij-community/tree/master/platform/jewel"
IconButton(
{ Desktop.getDesktop().browse(URI.create(jewelGithubLink)) },
Modifier.size(40.dp).padding(5.dp),
Modifier.size(40.dp).padding(5.dp).clientRegion("github_link"),
) {
Icon(ShowcaseIcons.gitHub, "Github")
}
Expand All @@ -99,7 +100,7 @@ internal fun DecoratedWindowScope.TitleBarView() {
IntUiThemes.System -> IntUiThemes.Light
}
},
Modifier.size(40.dp).padding(5.dp),
Modifier.size(40.dp).padding(5.dp).clientRegion("switch_theme"),
) {
when (MainViewModel.theme) {
IntUiThemes.Light ->
Expand Down