-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add dialog sample to ide-plugin (#342)
The dialog shows how to implement a simple wizard, with pages that can prevent the wizard from going forward or backwards depending on their state.
- Loading branch information
Showing
5 changed files
with
262 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
...de-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/dialog/JewelDemoAction.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package org.jetbrains.jewel.samples.ideplugin.dialog | ||
|
||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.collectAsState | ||
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.unit.dp | ||
import com.intellij.openapi.actionSystem.AnActionEvent | ||
import com.intellij.openapi.application.EDT | ||
import com.intellij.openapi.components.Service | ||
import com.intellij.openapi.components.service | ||
import com.intellij.openapi.project.DumbAwareAction | ||
import com.intellij.openapi.project.Project | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.delay | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.launch | ||
import org.jetbrains.jewel.ui.component.CheckboxRow | ||
import org.jetbrains.jewel.ui.component.Text | ||
import org.jetbrains.jewel.ui.component.Typography | ||
import kotlin.time.Duration.Companion.seconds | ||
|
||
@Service(Service.Level.PROJECT) | ||
private class ProjectScopeProviderService(val scope: CoroutineScope) | ||
|
||
internal class JewelDemoAction : DumbAwareAction() { | ||
|
||
override fun actionPerformed(event: AnActionEvent) { | ||
val project = checkNotNull(event.project) { "Project not available" } | ||
val scope = project.service<ProjectScopeProviderService>().scope | ||
|
||
scope.launch(Dispatchers.EDT) { | ||
WizardDialogWrapper( | ||
project = project, | ||
title = "Jewel Demo wizard", | ||
pages = listOf(FirstPage(project), SecondPage()), | ||
).showAndGet() | ||
} | ||
} | ||
} | ||
|
||
private class FirstPage(private val project: Project) : WizardPage { | ||
|
||
override val canGoBackwards: StateFlow<Boolean> = MutableStateFlow(true) | ||
|
||
private val checkboxChecked = MutableStateFlow(false) | ||
override val canGoForward: StateFlow<Boolean> = checkboxChecked | ||
|
||
@Composable | ||
override fun PageContent() { | ||
Column { | ||
Text("This is the first page!", style = Typography.h1TextStyle()) | ||
|
||
Spacer(Modifier.height(16.dp)) | ||
|
||
Text("Project name: ${project.name}") | ||
|
||
Spacer(Modifier.height(16.dp)) | ||
|
||
val checked by checkboxChecked.collectAsState() | ||
CheckboxRow("Allow going to next step", checked, { | ||
checkboxChecked.value = it | ||
println("Checkbox value: ${checkboxChecked.value}") | ||
}) | ||
} | ||
} | ||
} | ||
|
||
private class SecondPage : WizardPage { | ||
|
||
override val canGoBackwards: StateFlow<Boolean> = MutableStateFlow(true) | ||
override val canGoForward: StateFlow<Boolean> = MutableStateFlow(true) | ||
|
||
@Composable | ||
override fun PageContent() { | ||
Column { | ||
Text("This is the second page!", style = Typography.h1TextStyle()) | ||
|
||
Spacer(Modifier.height(16.dp)) | ||
|
||
var count by remember { mutableStateOf(0) } | ||
LaunchedEffect(Unit) { | ||
launch { | ||
while (true) { | ||
delay(1.seconds.inWholeMilliseconds) | ||
count++ | ||
} | ||
} | ||
} | ||
|
||
Text("You've been staring at this for $count second(s)") | ||
} | ||
} | ||
} |
150 changes: 150 additions & 0 deletions
150
...lugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/dialog/WizardDialogWrapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
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.EDT | ||
import com.intellij.openapi.diagnostic.thisLogger | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.ui.DialogWrapper | ||
import com.intellij.util.ui.JBDimension | ||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.CoroutineName | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.SupervisorJob | ||
import kotlinx.coroutines.cancel | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.launch | ||
import org.jetbrains.annotations.Nls | ||
import org.jetbrains.jewel.bridge.JewelComposePanel | ||
import org.jetbrains.jewel.foundation.ExperimentalJewelApi | ||
import org.jetbrains.jewel.foundation.enableNewSwingCompositing | ||
import java.awt.event.ActionEvent | ||
import javax.swing.Action | ||
import javax.swing.JComponent | ||
import kotlin.coroutines.CoroutineContext | ||
|
||
internal class WizardDialogWrapper( | ||
project: Project, | ||
@Nls title: String, | ||
private val pages: List<WizardPage>, | ||
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, | ||
) : DialogWrapper(project), CoroutineScope { | ||
|
||
private val logger = thisLogger() | ||
|
||
override val coroutineContext: CoroutineContext = | ||
SupervisorJob() + Dispatchers.EDT + CoroutineName("ComposeWizard") | ||
|
||
private val currentPageIndex = mutableIntStateOf(0) | ||
|
||
private val cancelAction = CancelAction() | ||
private val backAction = WizardAction("Back") { onBackClick() } | ||
private val nextAction = WizardAction("Next") { onNextClick() } | ||
private val finishAction = WizardAction("Finish") { onFinishClick() } | ||
|
||
private var pageScope: CoroutineScope? = null | ||
|
||
init { | ||
require(pages.isNotEmpty()) { "Wizard must have at least one page" } | ||
init() | ||
|
||
this.title = title | ||
|
||
updateActions() | ||
} | ||
|
||
private fun updateActions() { | ||
pageScope?.cancel("Page changing") | ||
val newScope = CoroutineScope(coroutineContext) | ||
pageScope = newScope | ||
|
||
val pageIndex = currentPageIndex.value | ||
val page = pages[pageIndex] | ||
|
||
backAction.isEnabled = pageIndex > 0 && page.canGoBackwards.value | ||
nextAction.isEnabled = pageIndex < pages.lastIndex && page.canGoForward.value | ||
finishAction.isEnabled = pageIndex == pages.lastIndex && page.canGoForward.value | ||
|
||
newScope.launch(defaultDispatcher) { | ||
page.canGoBackwards.collect { canGoBackwards -> | ||
logger.info("CanGoBackwards: $canGoBackwards") | ||
backAction.isEnabled = pageIndex > 0 && canGoBackwards | ||
} | ||
} | ||
newScope.launch(defaultDispatcher) { | ||
page.canGoForward.collect { canGoForward -> | ||
logger.info("CanGoForward: $canGoForward") | ||
nextAction.isEnabled = | ||
pageIndex < pages.lastIndex && canGoForward | ||
finishAction.isEnabled = pageIndex == pages.lastIndex && canGoForward | ||
} | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalJewelApi::class) | ||
override fun createCenterPanel(): JComponent { | ||
enableNewSwingCompositing() | ||
|
||
return JewelComposePanel { | ||
val index by currentPageIndex | ||
pages[index].PageContent() | ||
}.apply { | ||
minimumSize = JBDimension(400, 400) | ||
} | ||
} | ||
|
||
override fun createActions(): Array<Action> = | ||
arrayOf(cancelAction, backAction, nextAction, finishAction) | ||
|
||
private fun onBackClick() { | ||
if (currentPageIndex.value <= 0) { | ||
logger.warn("Trying to go back beyond the first page") | ||
return | ||
} | ||
currentPageIndex.value -= 1 | ||
updateActions() | ||
} | ||
|
||
private fun onNextClick() { | ||
if (currentPageIndex.value >= pages.lastIndex) { | ||
logger.warn("Trying to go next on or beyond the last page") | ||
return | ||
} | ||
currentPageIndex.value += 1 | ||
updateActions() | ||
} | ||
|
||
private fun onFinishClick() { | ||
logger.info("Finish clicked") | ||
close(OK_EXIT_CODE) | ||
} | ||
|
||
private inner class CancelAction : DialogWrapperAction("Cancel") { | ||
|
||
override fun doAction(e: ActionEvent?) { | ||
logger.debug("Cancel clicked") | ||
doCancelAction() | ||
} | ||
} | ||
|
||
private inner class WizardAction( | ||
@Nls name: String, | ||
private val onAction: () -> Unit, | ||
) : DialogWrapperAction(name) { | ||
|
||
override fun doAction(e: ActionEvent?) { | ||
onAction() | ||
} | ||
} | ||
} | ||
|
||
interface WizardPage { | ||
|
||
@Composable | ||
fun PageContent() | ||
|
||
val canGoForward: StateFlow<Boolean> | ||
val canGoBackwards: StateFlow<Boolean> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters