diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt index f6fa824f24..bab2b5f9d5 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt @@ -19,5 +19,5 @@ interface IntelliJThemeDefinition { val extensionStyles: Array> - fun withExtension(extension: ProvidedValue<*>): IntelliJThemeDefinition + fun withExtensions(vararg extension: ProvidedValue<*>): IntelliJThemeDefinition } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt b/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt index 733cb202df..cba4e67e21 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt @@ -93,8 +93,8 @@ fun PopupMenu( density = density, ) - var focusManager: FocusManager? by mutableStateOf(null) - var inputModeManager: InputModeManager? by mutableStateOf(null) + var focusManager: FocusManager? by remember { mutableStateOf(null) } + var inputModeManager: InputModeManager? by remember { mutableStateOf(null) } val menuManager = remember(onDismissRequest) { MenuManager(onDismissRequest = onDismissRequest) } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/DecoratedWindow.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/DecoratedWindow.kt index 6fc4972efd..4367b800a3 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/DecoratedWindow.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/DecoratedWindow.kt @@ -1,10 +1,19 @@ package org.jetbrains.jewel.window +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposeWindow +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.layout.Layout @@ -18,9 +27,16 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.offset import androidx.compose.ui.window.FrameWindowScope import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.rememberWindowState +import org.jetbrains.jewel.IntelliJTheme +import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.foundation.border +import org.jetbrains.jewel.window.styling.DecoratedWindowStyle import org.jetbrains.jewel.window.utils.DesktopPlatform +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent import javax.swing.JFrame @Composable fun DecoratedWindow( @@ -35,6 +51,7 @@ import javax.swing.JFrame alwaysOnTop: Boolean = false, onPreviewKeyEvent: (KeyEvent) -> Boolean = { false }, onKeyEvent: (KeyEvent) -> Boolean = { false }, + style: DecoratedWindowStyle = IntelliJTheme.defaultDecoratedWindowStyle, content: @Composable DecoratedWindowScope.() -> Unit, ) { // Using undecorated window for linux @@ -55,16 +72,62 @@ import javax.swing.JFrame onPreviewKeyEvent, onKeyEvent, ) { + var decoratedWindowState by remember { mutableStateOf(DecoratedWindowState.of(window)) } + + DisposableEffect(window) { + val adapter = object : WindowAdapter() { + override fun windowActivated(e: WindowEvent?) { + decoratedWindowState = DecoratedWindowState.of(window) + } + + override fun windowDeactivated(e: WindowEvent?) { + decoratedWindowState = DecoratedWindowState.of(window) + } + + override fun windowIconified(e: WindowEvent?) { + decoratedWindowState = DecoratedWindowState.of(window) + } + + override fun windowDeiconified(e: WindowEvent?) { + decoratedWindowState = DecoratedWindowState.of(window) + } + + override fun windowStateChanged(e: WindowEvent) { + decoratedWindowState = DecoratedWindowState.of(window) + } + } + window.addWindowListener(adapter) + window.addWindowStateListener(adapter) + onDispose { + window.removeWindowListener(adapter) + window.removeWindowStateListener(adapter) + } + } + + val undecoratedWindowBorder = if (undecorated && decoratedWindowState.isMaximized) { + Modifier.border( + Stroke.Alignment.Inside, + style.metrics.borderWidth, + style.colors.borderFor(decoratedWindowState).value, + RectangleShape + ).padding(style.metrics.borderWidth) + } else { + Modifier + } + CompositionLocalProvider( LocalWindow provides window, LocalTitleBarInfo provides TitleBarInfo(title, icon), ) { Layout({ val scope = object : DecoratedWindowScope { + override val state: DecoratedWindowState + get() = decoratedWindowState + override val window: ComposeWindow get() = this@Window.window } scope.content() - }, measurePolicy = DecoratedWindowMeasurePolicy) + }, modifier = undecoratedWindowBorder, measurePolicy = DecoratedWindowMeasurePolicy) } } } @@ -72,6 +135,8 @@ import javax.swing.JFrame @Stable interface DecoratedWindowScope : FrameWindowScope { override val window: ComposeWindow + + val state: DecoratedWindowState } private object DecoratedWindowMeasurePolicy : MeasurePolicy { @@ -118,6 +183,65 @@ private object DecoratedWindowMeasurePolicy : MeasurePolicy { } } +@Immutable @JvmInline +value class DecoratedWindowState(val state: ULong) { + + @Stable val isActive: Boolean + get() = state and Active != 0UL + + @Stable val isFullscreen: Boolean + get() = state and Fullscreen != 0UL + + @Stable val isMinimized: Boolean + get() = state and Minimize != 0UL + + @Stable val isMaximized: Boolean + get() = state and Maximize != 0UL + + fun copy( + fullscreen: Boolean = isFullscreen, + minimized: Boolean = isMinimized, + maximized: Boolean = isMaximized, + active: Boolean = isActive, + ) = of( + fullscreen = fullscreen, + minimized = minimized, + maximized = maximized, + active = active, + ) + + override fun toString() = "${javaClass.simpleName}(isFullscreen=$isFullscreen, isActive=$isActive)" + + companion object { + + val Active = 1UL shl 0 + val Fullscreen = 1UL shl 1 + val Minimize = 1UL shl 2 + val Maximize = 1UL shl 3 + + fun of( + fullscreen: Boolean = false, + minimized: Boolean = false, + maximized: Boolean = false, + active: Boolean = true, + ) = DecoratedWindowState( + state = (if (fullscreen) Fullscreen else 0UL) or + (if (minimized) Minimize else 0UL) or + (if (maximized) Maximize else 0UL) or + (if (active) Active else 0UL), + ) + + fun of( + window: ComposeWindow, + ) = of( + window.placement == WindowPlacement.Fullscreen, + window.isMinimized, + window.placement == WindowPlacement.Maximized, + window.isActive, + ) + } +} + internal data class TitleBarInfo( val title: String, val icon: Painter?, diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/Theme.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/Theme.kt index 64c928059e..690b7d151a 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/Theme.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/Theme.kt @@ -3,6 +3,8 @@ package org.jetbrains.jewel.window import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import org.jetbrains.jewel.IntelliJTheme +import org.jetbrains.jewel.window.styling.DecoratedWindowStyle +import org.jetbrains.jewel.window.styling.LocalDecoratedWindowStyle import org.jetbrains.jewel.window.styling.LocalTitleBarStyle import org.jetbrains.jewel.window.styling.TitleBarStyle @@ -10,3 +12,8 @@ val IntelliJTheme.Companion.defaultTitleBarStyle: TitleBarStyle @Composable @ReadOnlyComposable get() = LocalTitleBarStyle.current + +val IntelliJTheme.Companion.defaultDecoratedWindowStyle: DecoratedWindowStyle + @Composable + @ReadOnlyComposable + get() = LocalDecoratedWindowStyle.current diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt index ef368f5c54..47b40b1af6 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt @@ -29,7 +29,7 @@ import java.awt.event.WindowEvent modifier: Modifier = Modifier, gradientStartColor: Color = Color.Unspecified, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(TitleBarState) -> Unit, + content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, ) { TitleBarImpl( modifier.onPointerEvent(PointerEventType.Press, PointerEventPass.Main) { @@ -67,7 +67,7 @@ import java.awt.event.WindowEvent @Composable private fun TitleBarScope.CloseButton( onClick: () -> Unit, - state: TitleBarState, + state: DecoratedWindowState, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, ) { ControlButton(onClick, state, style.icons.closeButton, "Close", style, style.paneCloseButtonStyle()) @@ -75,8 +75,8 @@ import java.awt.event.WindowEvent @Composable private fun TitleBarScope.ControlButton( onClick: () -> Unit, - state: TitleBarState, - painterProvider: PainterProvider, + state: DecoratedWindowState, + painterProvider: PainterProvider, description: String, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, iconButtonStyle: IconButtonStyle = style.paneButtonStyle(), diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.MacOS.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.MacOS.kt index b02602c8ff..f1131c4053 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.MacOS.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.MacOS.kt @@ -61,7 +61,7 @@ private class NewFullscreenControlsNode( modifier: Modifier = Modifier, gradientStartColor: Color = Color.Unspecified, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(TitleBarState) -> Unit, + content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, ) { val newFullscreenControls = modifier.foldOut(false) { e, r -> if (e is NewFullscreenControlsElement) { diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Windows.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Windows.kt index cf960618f3..cd8d42eda3 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Windows.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Windows.kt @@ -20,7 +20,7 @@ import org.jetbrains.jewel.window.styling.TitleBarStyle modifier: Modifier = Modifier, gradientStartColor: Color = Color.Unspecified, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(TitleBarState) -> Unit, + content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, ) { val titleBar = remember { JBR.getWindowDecorations().createCustomTitleBar() } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt index 9d8f51db31..73cdac679d 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,7 +15,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.ComposeWindow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor @@ -43,7 +41,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset -import androidx.compose.ui.window.WindowPlacement import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.LocalContentColor import org.jetbrains.jewel.onBackground @@ -66,7 +63,7 @@ internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__" modifier: Modifier = Modifier, gradientStartColor: Color = Color.Unspecified, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, - content: @Composable TitleBarScope.(TitleBarState) -> Unit, + content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, ) { when (DesktopPlatform.Current) { DesktopPlatform.Linux -> TitleBarOnLinux(modifier, gradientStartColor, style, content) @@ -80,14 +77,12 @@ internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__" modifier: Modifier = Modifier, gradientStartColor: Color = Color.Unspecified, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, - applyTitleBar: (Dp, TitleBarState) -> PaddingValues, - content: @Composable TitleBarScope.(TitleBarState) -> Unit, + applyTitleBar: (Dp, DecoratedWindowState) -> PaddingValues, + content: @Composable TitleBarScope.(DecoratedWindowState) -> Unit, ) { val titleBarInfo = LocalTitleBarInfo.current - var titleBarState by remember { mutableStateOf(TitleBarState.of(window)) } - - val background by style.colors.backgroundFor(titleBarState) + val background by style.colors.backgroundFor(state) val density = LocalDensity.current @@ -107,36 +102,6 @@ internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__" } } - DisposableEffect(window) { - val adapter = object : WindowAdapter() { - override fun windowActivated(e: WindowEvent?) { - titleBarState = TitleBarState.of(window) - } - - override fun windowDeactivated(e: WindowEvent?) { - titleBarState = TitleBarState.of(window) - } - - override fun windowIconified(e: WindowEvent?) { - titleBarState = TitleBarState.of(window) - } - - override fun windowDeiconified(e: WindowEvent?) { - titleBarState = TitleBarState.of(window) - } - - override fun windowStateChanged(e: WindowEvent) { - titleBarState = TitleBarState.of(window) - } - } - window.addWindowListener(adapter) - window.addWindowStateListener(adapter) - onDispose { - window.removeWindowListener(adapter) - window.removeWindowStateListener(adapter) - } - } - Layout( { CompositionLocalProvider( @@ -145,18 +110,18 @@ internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__" ) { onBackground(background) { val scope = TitleBarScopeImpl(titleBarInfo.title, titleBarInfo.icon) - scope.content(titleBarState) + scope.content(state) } } }, modifier.background(backgroundBrush).layoutId(TITLE_BAR_LAYOUT_ID).height(style.metrics.height).onSizeChanged { with(density) { - applyTitleBar(it.height.toDp(), titleBarState) + applyTitleBar(it.height.toDp(), state) } }.fillMaxWidth(), measurePolicy = rememberTitleBarMeasurePolicy( LocalWindow.current, - titleBarState, + state, applyTitleBar, ), ) @@ -164,69 +129,10 @@ internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__" Spacer(Modifier.layoutId(TITLE_BAR_BORDER_LAYOUT_ID).height(1.dp).fillMaxWidth().background(style.colors.border)) } -@Immutable @JvmInline -value class TitleBarState(val state: ULong) { - - @Stable val isActive: Boolean - get() = state and Active != 0UL - - @Stable val isFullscreen: Boolean - get() = state and Fullscreen != 0UL - - @Stable val isMinimized: Boolean - get() = state and Minimize != 0UL - - @Stable val isMaximized: Boolean - get() = state and Maximize != 0UL - - fun copy( - fullscreen: Boolean = isFullscreen, - minimized: Boolean = isMinimized, - maximized: Boolean = isMaximized, - active: Boolean = isActive, - ) = of( - fullscreen = fullscreen, - minimized = minimized, - maximized = maximized, - active = active, - ) - - override fun toString() = "${javaClass.simpleName}(isFullscreen=$isFullscreen, isActive=$isActive)" - - companion object { - - val Active = 1UL shl 0 - val Fullscreen = 1UL shl 1 - val Minimize = 1UL shl 2 - val Maximize = 1UL shl 3 - - fun of( - fullscreen: Boolean = false, - minimized: Boolean = false, - maximized: Boolean = false, - active: Boolean = true, - ) = TitleBarState( - state = (if (fullscreen) Fullscreen else 0UL) or - (if (minimized) Minimize else 0UL) or - (if (maximized) Maximize else 0UL) or - (if (active) Active else 0UL), - ) - - fun of( - window: ComposeWindow, - ) = of( - window.placement == WindowPlacement.Fullscreen, - window.isMinimized, - window.placement == WindowPlacement.Maximized, - window.isActive, - ) - } -} - internal class TitleBarMeasurePolicy( private val window: Window, - private val state: TitleBarState, - private val applyTitleBar: (Dp, TitleBarState) -> PaddingValues, + private val state: DecoratedWindowState, + private val applyTitleBar: (Dp, DecoratedWindowState) -> PaddingValues, ) : MeasurePolicy { override fun MeasureScope.measure(measurables: List, constraints: Constraints): MeasureResult { @@ -317,8 +223,8 @@ internal class TitleBarMeasurePolicy( @Composable internal fun rememberTitleBarMeasurePolicy( window: Window, - state: TitleBarState, - applyTitleBar: (Dp, TitleBarState) -> PaddingValues, + state: DecoratedWindowState, + applyTitleBar: (Dp, DecoratedWindowState) -> PaddingValues, ): MeasurePolicy { return remember(window, state, applyTitleBar) { TitleBarMeasurePolicy( diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/DecoratedWindowStyling.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/DecoratedWindowStyling.kt new file mode 100644 index 0000000000..61625b8614 --- /dev/null +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/DecoratedWindowStyling.kt @@ -0,0 +1,40 @@ +package org.jetbrains.jewel.window.styling + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import org.jetbrains.jewel.window.DecoratedWindowState + +interface DecoratedWindowStyle { + + val colors: DecoratedWindowColors + val metrics: DecoratedWindowMetrics +} + +@Stable +interface DecoratedWindowColors { + + val border: Color + val borderInactive: Color + + @Composable + fun borderFor(state: DecoratedWindowState) = rememberUpdatedState( + when { + !state.isActive -> borderInactive + else -> border + }, + ) +} + +@Stable +interface DecoratedWindowMetrics { + + val borderWidth: Dp +} + +val LocalDecoratedWindowStyle = staticCompositionLocalOf { + error("No DecoratedWindowStyle provided") +} \ No newline at end of file diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt index a9935c29f7..e2c2843280 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import org.jetbrains.jewel.styling.IconButtonStyle import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.window.TitleBarState +import org.jetbrains.jewel.window.DecoratedWindowState @Stable interface TitleBarStyle { @@ -50,7 +50,7 @@ interface TitleBarColors { val iconButtonPressBackground: Color @Composable - fun backgroundFor(state: TitleBarState) = rememberUpdatedState( + fun backgroundFor(state: DecoratedWindowState) = rememberUpdatedState( when { !state.isActive -> inactiveBackground else -> background @@ -73,13 +73,13 @@ interface TitleBarMetrics { @Immutable interface TitleBarIcons { - val minimizeButton: PainterProvider + val minimizeButton: PainterProvider - val maximizeButton: PainterProvider + val maximizeButton: PainterProvider - val restoreButton: PainterProvider + val restoreButton: PainterProvider - val closeButton: PainterProvider + val closeButton: PainterProvider } val LocalTitleBarStyle = staticCompositionLocalOf { diff --git a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt index 98e2968707..a01c0e1b35 100644 --- a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt +++ b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt @@ -21,8 +21,8 @@ data class IntUiThemeDefinition( override val extensionStyles: Array> = arrayOf(), ) : IntelliJThemeDefinition { - override fun withExtension(extension: ProvidedValue<*>): IntUiThemeDefinition = - copy(extensionStyles = extensionStyles + extension) + override fun withExtensions(vararg extensions: ProvidedValue<*>): IntUiThemeDefinition = + copy(extensionStyles = extensionStyles + extensions) override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt index 3ed69b8f2b..379532ea2b 100644 --- a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt +++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt @@ -5,7 +5,10 @@ import androidx.compose.runtime.getValue import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.IntUiThemeDefinition import org.jetbrains.jewel.intui.standalone.rememberSvgLoader +import org.jetbrains.jewel.intui.window.styling.IntUiDecoratedWindowStyle import org.jetbrains.jewel.intui.window.styling.IntUiTitleBarStyle +import org.jetbrains.jewel.window.styling.DecoratedWindowStyle +import org.jetbrains.jewel.window.styling.LocalDecoratedWindowStyle import org.jetbrains.jewel.window.styling.LocalTitleBarStyle import org.jetbrains.jewel.window.styling.TitleBarStyle @@ -20,8 +23,19 @@ fun IntUiThemeDefinition.titleBarStyle(svgLoader: SvgLoader, lightHeaderInLight: } @Composable -fun IntUiThemeDefinition.withTitleBar(lightHeaderInLight: Boolean = false): IntUiThemeDefinition { +fun IntUiThemeDefinition.decoratedWindowStyle(): DecoratedWindowStyle = + if (isDark) { + IntUiDecoratedWindowStyle.dark() + } else { + IntUiDecoratedWindowStyle.light() + } + +@Composable +fun IntUiThemeDefinition.withDecoratedWindow(lightHeaderInLight: Boolean = false): IntUiThemeDefinition { val svgLoader by rememberSvgLoader(!lightHeaderInLight) - return withExtension(LocalTitleBarStyle provides titleBarStyle(svgLoader, lightHeaderInLight)) + return withExtensions( + LocalDecoratedWindowStyle provides decoratedWindowStyle(), + LocalTitleBarStyle provides titleBarStyle(svgLoader, lightHeaderInLight) + ) } diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiDecoratedWindowStyling.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiDecoratedWindowStyling.kt new file mode 100644 index 0000000000..1dfb8d542c --- /dev/null +++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiDecoratedWindowStyling.kt @@ -0,0 +1,67 @@ +package org.jetbrains.jewel.intui.window.styling + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.SvgLoader +import org.jetbrains.jewel.window.styling.DecoratedWindowColors +import org.jetbrains.jewel.window.styling.DecoratedWindowMetrics +import org.jetbrains.jewel.window.styling.DecoratedWindowStyle + +@Stable data class IntUiDecoratedWindowStyle( + override val colors: IntUiDecoratedWindowColors, + override val metrics: IntUiDecoratedWindowMetrics, +) : DecoratedWindowStyle { + + companion object { + + @Composable fun light( + intUiDecoratedWindowColors: IntUiDecoratedWindowColors = IntUiDecoratedWindowColors.light(), + intUiDecoratedWindowMetrics: IntUiDecoratedWindowMetrics = IntUiDecoratedWindowMetrics(1.dp), + ): IntUiDecoratedWindowStyle = IntUiDecoratedWindowStyle( + colors = intUiDecoratedWindowColors, + metrics = intUiDecoratedWindowMetrics, + ) + + @Composable fun dark( + intUiDecoratedWindowColors: IntUiDecoratedWindowColors = IntUiDecoratedWindowColors.dark(), + intUiDecoratedWindowMetrics: IntUiDecoratedWindowMetrics = IntUiDecoratedWindowMetrics(1.dp), + ): IntUiDecoratedWindowStyle = IntUiDecoratedWindowStyle( + colors = intUiDecoratedWindowColors, + metrics = intUiDecoratedWindowMetrics, + ) + } +} + +@Stable data class IntUiDecoratedWindowColors( + override val border: Color, + override val borderInactive: Color, +) : DecoratedWindowColors { + + companion object { + + @Composable fun light( + // from Window.undecorated.border + borderColor: Color = Color(0xFF5A5D6B), + inactiveBorderColor: Color = borderColor, + ) = IntUiDecoratedWindowColors( + borderColor, + inactiveBorderColor + ) + + @Composable fun dark( + // from Window.undecorated.border + borderColor: Color = Color(0xFF5A5D63), + inactiveBorderColor: Color = borderColor, + ) = IntUiDecoratedWindowColors( + borderColor, + inactiveBorderColor + ) + } +} + +@Stable data class IntUiDecoratedWindowMetrics( + override val borderWidth: Dp +) : DecoratedWindowMetrics diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt index a185c6347b..aa753ce5fd 100644 --- a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt +++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt @@ -19,7 +19,7 @@ import org.jetbrains.jewel.styling.IconButtonStyle import org.jetbrains.jewel.styling.PainterProvider import org.jetbrains.jewel.styling.ResourcePainterProvider import org.jetbrains.jewel.styling.SimpleResourcePathPatcher -import org.jetbrains.jewel.window.TitleBarState +import org.jetbrains.jewel.window.DecoratedWindowState import org.jetbrains.jewel.window.styling.TitleBarColors import org.jetbrains.jewel.window.styling.TitleBarIcons import org.jetbrains.jewel.window.styling.TitleBarMetrics @@ -218,10 +218,10 @@ import org.jetbrains.jewel.window.styling.TitleBarStyle ) : TitleBarMetrics @Immutable data class IntUiTitleBarIcons( - override val minimizeButton: PainterProvider, - override val maximizeButton: PainterProvider, - override val restoreButton: PainterProvider, - override val closeButton: PainterProvider, + override val minimizeButton: PainterProvider, + override val maximizeButton: PainterProvider, + override val restoreButton: PainterProvider, + override val closeButton: PainterProvider, ) : TitleBarIcons { @OptIn(InternalJewelApi::class) @@ -230,39 +230,39 @@ import org.jetbrains.jewel.window.styling.TitleBarStyle @Composable fun minimize( svgLoader: SvgLoader, basePath: String = "icons/intui/window/minimize.svg", - ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) + ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) @Composable fun maximize( svgLoader: SvgLoader, basePath: String = "icons/intui/window/maximize.svg", - ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) + ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) @Composable fun restore( svgLoader: SvgLoader, basePath: String = "icons/intui/window/restore.svg", - ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) + ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) @Composable fun close( svgLoader: SvgLoader, basePath: String = "icons/intui/window/close.svg", - ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) + ): PainterProvider = ResourcePainterProvider(basePath, svgLoader, TitleBarResourcePathPatcher()) } } @Composable fun intUiTitleBarIcons( svgLoader: SvgLoader, - minimize: PainterProvider = IntUiTitleBarIcons.minimize(svgLoader), - maximize: PainterProvider = IntUiTitleBarIcons.maximize(svgLoader), - restore: PainterProvider = IntUiTitleBarIcons.restore(svgLoader), - close: PainterProvider = IntUiTitleBarIcons.close(svgLoader), + minimize: PainterProvider = IntUiTitleBarIcons.minimize(svgLoader), + maximize: PainterProvider = IntUiTitleBarIcons.maximize(svgLoader), + restore: PainterProvider = IntUiTitleBarIcons.restore(svgLoader), + close: PainterProvider = IntUiTitleBarIcons.close(svgLoader), ) = IntUiTitleBarIcons(minimize, maximize, restore, close) private class TitleBarResourcePathPatcher( - private val prefixTokensProvider: (state: TitleBarState) -> String = { "" }, - private val suffixTokensProvider: (state: TitleBarState) -> String = { "" }, -) : SimpleResourcePathPatcher() { + private val prefixTokensProvider: (state: DecoratedWindowState) -> String = { "" }, + private val suffixTokensProvider: (state: DecoratedWindowState) -> String = { "" }, +) : SimpleResourcePathPatcher() { - @Composable override fun injectAdditionalTokens(extraData: TitleBarState?): String = buildString { + @Composable override fun injectAdditionalTokens(extraData: DecoratedWindowState?): String = buildString { if (extraData == null) return@buildString append(prefixTokensProvider(extraData)) diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt index d0f8b9de8c..8cf77f0b97 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt @@ -41,7 +41,7 @@ import org.jetbrains.jewel.Text import org.jetbrains.jewel.VerticalScrollbar import org.jetbrains.jewel.intui.standalone.IntUiTheme import org.jetbrains.jewel.intui.standalone.rememberSvgLoader -import org.jetbrains.jewel.intui.window.withTitleBar +import org.jetbrains.jewel.intui.window.withDecoratedWindow import org.jetbrains.jewel.samples.standalone.components.Borders import org.jetbrains.jewel.samples.standalone.components.Buttons import org.jetbrains.jewel.samples.standalone.components.Checkboxes @@ -64,31 +64,31 @@ import java.io.InputStream fun main() { val icon = svgResource("icons/jewel-logo.svg") application { - DecoratedWindow( - onCloseRequest = { exitApplication() }, - title = "Jewel component catalog", - icon = icon, - ) { - var isDark by remember { mutableStateOf(false) } - var lightHeaderInLight by remember { mutableStateOf(false) } - var swingCompat by remember { mutableStateOf(false) } - val theme = if (isDark) IntUiTheme.darkThemeDefinition() else IntUiTheme.lightThemeDefinition() - val projectColor by remember { - derivedStateOf { - if (isDark) { - Color(0xFF654B40) - } else if (lightHeaderInLight) { - Color(0xFFF5D4C1) - } else { - Color(0xFF654B40) - } + var isDark by remember { mutableStateOf(false) } + var lightHeaderInLight by remember { mutableStateOf(false) } + var swingCompat by remember { mutableStateOf(false) } + val theme = if (isDark) IntUiTheme.darkThemeDefinition() else IntUiTheme.lightThemeDefinition() + val projectColor by remember { + derivedStateOf { + if (isDark) { + Color(0xFF654B40) + } else if (lightHeaderInLight) { + Color(0xFFF5D4C1) + } else { + Color(0xFF654B40) } } + } - IntUiTheme(theme.withTitleBar(lightHeaderInLight), swingCompat) { - val resourceLoader = LocalResourceLoader.current - val svgLoader by rememberSvgLoader() + IntUiTheme(theme.withDecoratedWindow(lightHeaderInLight), swingCompat) { + val resourceLoader = LocalResourceLoader.current + val svgLoader by rememberSvgLoader() + DecoratedWindow( + onCloseRequest = { exitApplication() }, + title = "Jewel component catalog", + icon = icon, + ) { val windowBackground = if (isDark) { IntUiTheme.colorPalette.grey(1) } else {