Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decorated window with custom titlebar support #173

Merged
merged 29 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e9e9786
Decorated window support
devkanro Oct 11, 2023
b5acf32
TitleBar for different platform
devkanro Oct 11, 2023
d88b2bf
TitleBar on windows
devkanro Oct 11, 2023
4877b2b
Avoid use custom title bar on linux
devkanro Oct 11, 2023
37854ff
Linux custom title bar support
devkanro Oct 11, 2023
08ec79a
Add control buttons to linux
devkanro Oct 12, 2023
85f3fc1
Support window border on Linux
devkanro Oct 12, 2023
3629c28
Support window border on Linux
devkanro Oct 12, 2023
9fdab10
Fix linux border
devkanro Oct 12, 2023
93a080c
Support double click to maxilize window on linux
devkanro Oct 12, 2023
72a4645
Make detekt happy
devkanro Oct 12, 2023
1b238c4
Make detekt happy
devkanro Oct 12, 2023
a125852
Make detekt happy
devkanro Oct 12, 2023
ba3563e
Make detekt happy
devkanro Oct 12, 2023
a03c6aa
Remove unused local window
devkanro Oct 12, 2023
458650a
Make detekt happy
devkanro Oct 12, 2023
ac17d79
Make ktlint happy
devkanro Oct 12, 2023
2198754
Remove unused JBR component
devkanro Oct 13, 2023
c56ffec
Make tooltip placement same as IJ
devkanro Oct 13, 2023
3ec04c1
Fix comments
devkanro Oct 13, 2023
6650d59
Fix comments
devkanro Oct 13, 2023
4672d60
Switch themes with titlebar icon
devkanro Oct 13, 2023
1901806
Avoid pass lightWithLightHeader to withDecoratedWindow DSL
devkanro Oct 13, 2023
82d3924
Dropdown in titlebar support
devkanro Oct 14, 2023
cb6abdb
Dropdown in titlebar support
devkanro Oct 14, 2023
bdecab9
Fix comment
devkanro Oct 17, 2023
1efb904
Make ktlint happy
devkanro Oct 17, 2023
d1f6c9d
Fix comments
devkanro Oct 17, 2023
628afbe
Fix comments
devkanro Oct 17, 2023
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
145 changes: 70 additions & 75 deletions core/src/main/kotlin/org/jetbrains/jewel/Dropdown.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.platform.LocalDensity
Expand Down Expand Up @@ -60,91 +61,85 @@ fun Dropdown(
menuContent: MenuScope.() -> Unit,
content: @Composable BoxScope.() -> Unit,
) {
Box {
var expanded by remember { mutableStateOf(false) }
var skipNextClick by remember { mutableStateOf(false) }
var expanded by remember { mutableStateOf(false) }
var skipNextClick by remember { mutableStateOf(false) }

var dropdownState by remember(interactionSource) {
mutableStateOf(DropdownState.of(enabled = enabled))
}
var dropdownState by remember(interactionSource) {
mutableStateOf(DropdownState.of(enabled = enabled))
}

remember(enabled) {
dropdownState = dropdownState.copy(enabled = enabled)
}
remember(enabled) {
dropdownState = dropdownState.copy(enabled = enabled)
}

LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> dropdownState = dropdownState.copy(pressed = true)
is PressInteraction.Cancel, is PressInteraction.Release ->
dropdownState =
dropdownState.copy(pressed = false)
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> dropdownState = dropdownState.copy(pressed = true)
is PressInteraction.Cancel, is PressInteraction.Release ->
dropdownState =
dropdownState.copy(pressed = false)

is HoverInteraction.Enter -> dropdownState = dropdownState.copy(hovered = true)
is HoverInteraction.Exit -> dropdownState = dropdownState.copy(hovered = false)
is FocusInteraction.Focus -> dropdownState = dropdownState.copy(focused = true)
is FocusInteraction.Unfocus -> dropdownState = dropdownState.copy(focused = false)
}
is HoverInteraction.Enter -> dropdownState = dropdownState.copy(hovered = true)
is HoverInteraction.Exit -> dropdownState = dropdownState.copy(hovered = false)
is FocusInteraction.Focus -> dropdownState = dropdownState.copy(focused = true)
is FocusInteraction.Unfocus -> dropdownState = dropdownState.copy(focused = false)
}
}
}

val colors = style.colors
val metrics = style.metrics
val shape = RoundedCornerShape(style.metrics.cornerSize)
val minSize = metrics.minSize
val arrowMinSize = style.metrics.arrowMinSize
val borderColor by colors.borderFor(dropdownState)

val outlineState = remember(dropdownState, expanded) {
dropdownState.copy(focused = dropdownState.isFocused || expanded)
}
val colors = style.colors
val metrics = style.metrics
val shape = RoundedCornerShape(style.metrics.cornerSize)
val minSize = metrics.minSize
val arrowMinSize = style.metrics.arrowMinSize
val borderColor by colors.borderFor(dropdownState)

Box(
modifier.clickable(
onClick = {
// TODO: Trick to skip click event when close menu by click dropdown
if (!skipNextClick) {
expanded = !expanded
}
skipNextClick = false
},
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = null,
)
.background(colors.backgroundFor(dropdownState).value, shape)
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
.appendIf(outline == Outline.None) { focusOutline(outlineState, shape) }
.outline(outlineState, outline, shape)
.width(IntrinsicSize.Max)
.defaultMinSize(minSize.width, minSize.height.coerceAtLeast(arrowMinSize.height)),
contentAlignment = Alignment.CenterStart,
Box(
modifier.clickable(
onClick = {
// TODO: Trick to skip click event when close menu by click dropdown
if (!skipNextClick) {
expanded = !expanded
}
skipNextClick = false
},
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = null,
)
.background(colors.backgroundFor(dropdownState).value, shape)
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
.appendIf(outline == Outline.None) { focusOutline(dropdownState, shape) }
.outline(dropdownState, outline, shape)
.width(IntrinsicSize.Max)
.defaultMinSize(minSize.width, minSize.height.coerceAtLeast(arrowMinSize.height)),
contentAlignment = Alignment.CenterStart,
) {
CompositionLocalProvider(
LocalContentColor provides colors.contentFor(dropdownState).value,
) {
CompositionLocalProvider(
LocalContentColor provides colors.contentFor(dropdownState).value,
Box(
modifier = Modifier
.fillMaxWidth()
.padding(style.metrics.contentPadding)
.padding(end = minSize.height),
contentAlignment = Alignment.CenterStart,
content = content,
)

Box(
modifier = Modifier.size(arrowMinSize)
.align(Alignment.CenterEnd),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(style.metrics.contentPadding)
.padding(end = minSize.height),
contentAlignment = Alignment.CenterStart,
content = content,
val chevronIcon by style.icons.chevronDown.getPainter(resourceLoader, dropdownState)
Icon(
painter = chevronIcon,
contentDescription = null,
tint = colors.iconTintFor(dropdownState).value,
)

Box(
modifier = Modifier.size(arrowMinSize)
.align(Alignment.CenterEnd),
contentAlignment = Alignment.Center,
) {
val chevronIcon by style.icons.chevronDown.getPainter(resourceLoader, dropdownState)
Icon(
painter = chevronIcon,
contentDescription = null,
tint = colors.iconTintFor(dropdownState).value,
)
}
}
}

Expand All @@ -157,7 +152,7 @@ fun Dropdown(
}
true
},
modifier = menuModifier,
modifier = menuModifier.focusProperties { canFocus = true },
style = style.menuStyle,
horizontalAlignment = Alignment.Start,
content = menuContent,
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/kotlin/org/jetbrains/jewel/IconButton.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ fun IconButton(
.border(style.metrics.borderWidth, border, shape),
contentAlignment = Alignment.Center,
content = {
content(buttonState)
onBackground(background) {
devkanro marked this conversation as resolved.
Show resolved Hide resolved
content(buttonState)
}
},
)
}
26 changes: 26 additions & 0 deletions core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.text.TextStyle
import org.jetbrains.jewel.styling.ButtonStyle
import org.jetbrains.jewel.styling.CheckboxStyle
Expand Down Expand Up @@ -46,6 +47,7 @@ import org.jetbrains.jewel.styling.TabStyle
import org.jetbrains.jewel.styling.TextAreaStyle
import org.jetbrains.jewel.styling.TextFieldStyle
import org.jetbrains.jewel.styling.TooltipStyle
import org.jetbrains.jewel.util.isDark

interface IntelliJTheme {

Expand Down Expand Up @@ -220,10 +222,12 @@ fun IntelliJTheme(
fun IntelliJTheme(theme: IntelliJThemeDefinition, content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalIsDarkTheme provides theme.isDark,
LocalOnDarkBackground provides theme.isDark,
LocalContentColor provides theme.contentColor,
LocalTextStyle provides theme.defaultTextStyle,
LocalGlobalColors provides theme.globalColors,
LocalGlobalMetrics provides theme.globalMetrics,
*theme.extensionStyles,
content = content,
)
}
Expand All @@ -232,6 +236,10 @@ internal val LocalIsDarkTheme = staticCompositionLocalOf<Boolean> {
error("No InDarkTheme provided")
}

internal val LocalOnDarkBackground = staticCompositionLocalOf<Boolean> {
devkanro marked this conversation as resolved.
Show resolved Hide resolved
error("No OnDarkBackground provided")
}

internal val LocalSwingCompatMode = staticCompositionLocalOf {
// By default, Swing compat is not enabled
false
Expand All @@ -244,3 +252,21 @@ val LocalColorPalette = staticCompositionLocalOf<IntelliJThemeColorPalette> {
val LocalIconData = staticCompositionLocalOf<IntelliJThemeIconData> {
EmptyThemeIconData
}

/**
* Sets the background color of the current area,
* which affects the style(light or dark) of the icon rendered above it,
* by calculating the luminance.
* If the color is not specified, the style will follow the current theme style.
* Transparent color will be ignored.
*/
@Composable
fun onBackground(color: Color, content: @Composable () -> Unit) {
devkanro marked this conversation as resolved.
Show resolved Hide resolved
val locals = if (color.isSpecified && color.alpha > 0) {
arrayOf(LocalOnDarkBackground provides color.isDark())
} else {
emptyArray()
}

CompositionLocalProvider(values = locals, content)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jetbrains.jewel

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ProvidedValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle

Expand All @@ -15,4 +16,8 @@ interface IntelliJThemeDefinition {

val colorPalette: IntelliJThemeColorPalette
val iconData: IntelliJThemeIconData

val extensionStyles: Array<ProvidedValue<*>>
devkanro marked this conversation as resolved.
Show resolved Hide resolved

fun withExtensions(vararg extensions: ProvidedValue<*>): IntelliJThemeDefinition
}
4 changes: 2 additions & 2 deletions core/src/main/kotlin/org/jetbrains/jewel/Menu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Loading