Skip to content

Commit

Permalink
Fix component showcase setup, prevent deadlocks in IJ
Browse files Browse the repository at this point in the history
* The components showcase dialog setup makes more sense now (started
  from an action instead of a random button in the toolwindow)
* Dialogs don't deadlock the IDE process hard anymore if opened before
  any other Compose UI is shown (workaround for IJPL-166436)
* Jewel actions have been renamed and cleaned up
* The whole components showcase modifier setup now makes sense to hoist
  (and uses the right padding default in standalone)
* Icon buttons now allow passing a modifier to the inner icon
* Toolbar icons in the showcase look (more) like the IDE ones now
  • Loading branch information
rock3r committed Jan 20, 2025
1 parent 1743f72 commit a2288e1
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 81 deletions.
7 changes: 6 additions & 1 deletion samples/ide-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ dependencies {
exclude(group = "org.jetbrains.kotlinx")
}
implementation(project(":samples:showcase")) { exclude(group = "org.jetbrains.kotlinx") }

// TODO remove once https://youtrack.jetbrains.com/issue/IJPL-166436 is fixed
implementation("androidx.lifecycle:lifecycle-common-jvm:2.8.5") { exclude(group = "org.jetbrains.kotlinx") }
implementation("androidx.lifecycle:lifecycle-runtime-desktop:2.8.5") { exclude(group = "org.jetbrains.kotlinx") }
// END TODO
}

intellijPlatform {
Expand All @@ -53,4 +58,4 @@ intellijPlatform {
autoReload = false
}

tasks { runIde { jvmArgs = listOf("-Xmx3g") } }
tasks { runIde { jvmArgs = listOf("-Xmx3g") } }
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.foundation.theme.LocalContentColor
import org.jetbrains.jewel.intui.markdown.bridge.ProvideMarkdownStyling
import org.jetbrains.jewel.markdown.Markdown
import org.jetbrains.jewel.samples.ideplugin.dialog.ComponentShowcaseDialog
import org.jetbrains.jewel.ui.Orientation
import org.jetbrains.jewel.ui.Outline
import org.jetbrains.jewel.ui.component.CheckboxRow
Expand Down Expand Up @@ -106,7 +105,6 @@ private fun RowScope.ColumnOne() {
Modifier.onActivated { activated = it },
style = Typography.h3TextStyle(),
)
DefaultButton(onClick = { ComponentShowcaseDialog().show() }) { Text("Open Component Showcase") }

var selectedItem by remember { mutableIntStateOf(-1) }
Dropdown(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,88 @@
package org.jetbrains.jewel.samples.ideplugin.dialog

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.intellij.ide.ui.UISettings
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.toolWindow.ResizeStripeManager
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.JBUI.CurrentTheme.Toolbar
import java.awt.Dimension
import javax.swing.JComponent
import org.jetbrains.jewel.bridge.JewelComposePanel
import org.jetbrains.jewel.bridge.retrieveArcAsCornerSizeOrDefault
import org.jetbrains.jewel.bridge.theme.default
import org.jetbrains.jewel.bridge.theme.macOs
import org.jetbrains.jewel.bridge.toDpSize
import org.jetbrains.jewel.bridge.toPaddingValues
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.samples.showcase.views.ComponentsView
import org.jetbrains.jewel.samples.showcase.views.ComponentsViewModel
import org.jetbrains.jewel.ui.component.styling.IconButtonMetrics
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
import org.jetbrains.jewel.ui.theme.iconButtonStyle

internal class ComponentShowcaseDialog : DialogWrapper(true) {
internal class ComponentShowcaseDialog(project: Project) : DialogWrapper(project) {
init {
title = "Component Showcase"
ApplicationManager.getApplication()
.invokeLater({ initializeComposeMainDispatcherChecker() }, ModalityState.any())

title = "Jewel Components Showcase"
init()
contentPanel.border = JBUI.Borders.empty()
rootPane.border = JBUI.Borders.empty()
}

override fun createSouthPanel(): JComponent? = null

override fun createCenterPanel(): JComponent {
val dialogPanel = JewelComposePanel {
val viewModel =
ComponentsViewModel(
alwaysVisibleScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.default(),
whenScrollingScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(),
)

val iconButtonMetrics = JewelTheme.iconButtonStyle.metrics
val uiSettings = UISettings.Companion.getInstance()
ComponentsView(
viewModel = viewModel,
railNavigationModifier =
Modifier.size(40.dp).padding(start = 0.dp, end = 4.dp, top = 4.dp, bottom = 4.dp),
toolbarButtonMetrics =
remember(uiSettings.compactMode, ResizeStripeManager.isShowNames()) {
iconButtonMetrics.tweak(
minSize = Toolbar.stripeToolbarButtonSize().toDpSize(),
// See com.intellij.openapi.wm.impl.SquareStripeButtonLook.getButtonArc
cornerSize =
retrieveArcAsCornerSizeOrDefault(
"Button.ToolWindow.arc",
CornerSize(if (uiSettings.compactMode) 4.dp else 6.dp),
),
padding =
Toolbar.stripeToolbarButtonIconPadding(
/* compactMode = */ uiSettings.compactMode,
/* showNames = */ ResizeStripeManager.isShowNames(),
)
.toPaddingValues(),
borderWidth = 0.dp,
)
},
)
}
dialogPanel.preferredSize = Dimension(800, 600)
return dialogPanel
}

private fun IconButtonMetrics.tweak(
cornerSize: CornerSize = this.cornerSize,
borderWidth: Dp = this.borderWidth,
padding: PaddingValues = this.padding,
minSize: DpSize = this.minSize,
) = IconButtonMetrics(cornerSize, borderWidth, padding, minSize)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.jetbrains.jewel.samples.ideplugin.dialog

import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAwareAction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

internal class ComponentShowcaseDialogAction : DumbAwareAction() {
init {
ApplicationManager.getApplication()
.invokeLater({ initializeComposeMainDispatcherChecker() }, ModalityState.any())
}

override fun actionPerformed(event: AnActionEvent) {
val project = checkNotNull(event.project) { "Project not available" }
val scope = project.service<ProjectScopeProviderService>().scope

scope.launch(Dispatchers.EDT) { ComponentShowcaseDialog(project).showAndGet() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.jetbrains.jewel.samples.ideplugin.dialog

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry

/**
* Initializes the MainDispatcherChecker in Compose; this must be called from the UI thread in non-modal state. This is
* a workaround for a freeze in the UI thread that is triggered by using Compose in a dialog before it has been used in
* a non-modal state.
*
* See https://youtrack.jetbrains.com/issue/IJPL-166436
*
* TODO Delete this when the upstream bug is fixed.
*/
internal fun initializeComposeMainDispatcherChecker() {
object : LifecycleOwner {
override val lifecycle = LifecycleRegistry(this)

init {
lifecycle.currentState = Lifecycle.State.STARTED
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.Service
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -29,9 +29,12 @@ import org.jetbrains.jewel.ui.component.CheckboxRow
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.component.Typography

@Service(Service.Level.PROJECT) private class ProjectScopeProviderService(val scope: CoroutineScope)
internal class JewelWizardDialogAction : DumbAwareAction() {
init {
ApplicationManager.getApplication()
.invokeLater({ initializeComposeMainDispatcherChecker() }, ModalityState.any())
}

internal class JewelDemoAction : DumbAwareAction() {
override fun actionPerformed(event: AnActionEvent) {
val project = checkNotNull(event.project) { "Project not available" }
val scope = project.service<ProjectScopeProviderService>().scope
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.jetbrains.jewel.samples.ideplugin.dialog

import com.intellij.openapi.components.Service
import kotlinx.coroutines.CoroutineScope

@Service(Service.Level.PROJECT) internal class ProjectScopeProviderService(val scope: CoroutineScope)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package org.jetbrains.jewel.samples.ideplugin.dialog
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
Expand Down Expand Up @@ -46,6 +48,9 @@ internal class WizardDialogWrapper(

init {
require(pages.isNotEmpty()) { "Wizard must have at least one page" }
ApplicationManager.getApplication()
.invokeLater({ initializeComposeMainDispatcherChecker() }, ModalityState.any())

init()

this.title = title
Expand Down
16 changes: 8 additions & 8 deletions samples/ide-plugin/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ See the <a href="https://github.com/JetBrains/jewel">Jewel repository</a> for mo
<iconMapper mappingFile="JewelIntUiIconMappings.json"/>
</extensions>

<actions>
<action id="Jewel Dialog Demo" class="org.jetbrains.jewel.samples.ideplugin.dialog.JewelDemoAction"
text="Jewel Demo Dialog">
</action>
<action id="Jewel Action System Test" class="org.jetbrains.jewel.samples.ideplugin.ActionSystemTestAction"
text="Jewel Action System Test">
</action>
<actions resource-bundle="messages.JewelBundle">
<action id="JewelWizardDialog" class="org.jetbrains.jewel.samples.ideplugin.dialog.JewelWizardDialogAction"
internal="true"/>
<action id="JewelActionSystemTest" class="org.jetbrains.jewel.samples.ideplugin.ActionSystemTestAction"
internal="true"/>
<action id="JewelComponentShowcaseDialog"
class="org.jetbrains.jewel.samples.ideplugin.dialog.ComponentShowcaseDialogAction" internal="true"/>
</actions>
</idea-plugin>
</idea-plugin>
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
toolwindow.stripe.JewelDemo=Jewel Demo
action.JewelActionSystemTest.text=Jewel Action System Test
action.JewelWizardDialog.text=Jewel Wizard Dialog
action.JewelComponentShowcaseDialog.text=Jewel Components Showcase
toolwindow.stripe.JewelDemo=Jewel
4 changes: 2 additions & 2 deletions samples/showcase/api/showcase.api
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,8 @@ public final class org/jetbrains/jewel/samples/showcase/components/TooltipsKt {
}

public final class org/jetbrains/jewel/samples/showcase/views/ComponentsViewKt {
public static final fun ComponentsToolBar (Lorg/jetbrains/jewel/samples/showcase/views/ComponentsViewModel;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
public static final fun ComponentsView (Lorg/jetbrains/jewel/samples/showcase/views/ComponentsViewModel;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
public static final fun ComponentsToolBar (Lorg/jetbrains/jewel/samples/showcase/views/ComponentsViewModel;Lorg/jetbrains/jewel/ui/component/styling/IconButtonMetrics;Landroidx/compose/runtime/Composer;I)V
public static final fun ComponentsView (Lorg/jetbrains/jewel/samples/showcase/views/ComponentsViewModel;Lorg/jetbrains/jewel/ui/component/styling/IconButtonMetrics;Landroidx/compose/runtime/Composer;I)V
}

public final class org/jetbrains/jewel/samples/showcase/views/ComponentsViewModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
Expand All @@ -23,30 +23,35 @@ import org.jetbrains.jewel.ui.component.Divider
import org.jetbrains.jewel.ui.component.SelectableIconActionButton
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.component.Typography
import org.jetbrains.jewel.ui.component.styling.IconButtonMetrics
import org.jetbrains.jewel.ui.component.styling.IconButtonStyle
import org.jetbrains.jewel.ui.component.styling.TooltipMetrics
import org.jetbrains.jewel.ui.component.styling.TooltipStyle
import org.jetbrains.jewel.ui.painter.hints.Size
import org.jetbrains.jewel.ui.theme.iconButtonStyle
import org.jetbrains.jewel.ui.theme.tooltipStyle

@Composable
public fun ComponentsView(viewModel: ComponentsViewModel, railNavigationModifier: Modifier) {
public fun ComponentsView(viewModel: ComponentsViewModel, toolbarButtonMetrics: IconButtonMetrics) {
Row(Modifier.trackActivation().fillMaxSize().background(JewelTheme.globalColors.panelBackground)) {
ComponentsToolBar(viewModel, railNavigationModifier)
ComponentsToolBar(viewModel, toolbarButtonMetrics)
Divider(Orientation.Vertical, Modifier.fillMaxHeight())
ComponentView(viewModel.getCurrentView())
}
}

@Composable
public fun ComponentsToolBar(viewModel: ComponentsViewModel, railNavigationModifier: Modifier) {
Column(Modifier.fillMaxHeight().width(40.dp).verticalScroll(rememberScrollState())) {
public fun ComponentsToolBar(viewModel: ComponentsViewModel, buttonMetrics: IconButtonMetrics) {
Column(Modifier.fillMaxHeight().verticalScroll(rememberScrollState())) {
val iconButtonStyle = JewelTheme.iconButtonStyle
val style = remember(iconButtonStyle) { IconButtonStyle(iconButtonStyle.colors, buttonMetrics) }
viewModel.getViews().forEach {
SelectableIconActionButton(
key = it.iconKey,
contentDescription = "Show ${it.title}",
selected = viewModel.getCurrentView() == it,
onClick = { viewModel.setCurrentView(it) },
modifier = railNavigationModifier,
style = style,
tooltip = { Text(it.title) },
tooltipStyle =
TooltipStyle(JewelTheme.tooltipStyle.colors, TooltipMetrics.defaults(showDelay = 150.milliseconds)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package org.jetbrains.jewel.samples.standalone.viewmodel

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.intui.standalone.styling.default
import org.jetbrains.jewel.intui.standalone.styling.defaults
import org.jetbrains.jewel.samples.showcase.components.ShowcaseIcons
import org.jetbrains.jewel.samples.showcase.views.ComponentsView
import org.jetbrains.jewel.samples.showcase.views.ComponentsViewModel
Expand All @@ -19,6 +20,7 @@ import org.jetbrains.jewel.samples.standalone.IntUiThemes
import org.jetbrains.jewel.samples.standalone.view.MarkdownDemo
import org.jetbrains.jewel.samples.standalone.view.WelcomeView
import org.jetbrains.jewel.samples.standalone.viewmodel.MainViewModel.componentsViewModel
import org.jetbrains.jewel.ui.component.styling.IconButtonMetrics
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility

object MainViewModel {
Expand Down Expand Up @@ -65,7 +67,18 @@ private val mainMenuItems =
title = "Components",
iconKey = ShowcaseIcons.componentsMenu,
keyboardShortcut = KeyBinding(macOs = setOf("", "C"), windows = setOf("Alt", "C")),
content = { ComponentsView(viewModel = componentsViewModel, Modifier.size(40.dp).padding(4.dp)) },
content = {
ComponentsView(
viewModel = componentsViewModel,
toolbarButtonMetrics =
// See JBUI.CurrentTheme.Toolbar.stripeToolbarButton* defaults
IconButtonMetrics.defaults(
cornerSize = CornerSize(6.dp),
padding = PaddingValues(5.dp),
minSize = DpSize(40.dp, 40.dp),
),
)
},
),
ViewInfo(
title = "Markdown",
Expand Down
Loading

0 comments on commit a2288e1

Please sign in to comment.