From 3221c0d39f857e4398851b0be4a84890036ed010 Mon Sep 17 00:00:00 2001 From: Andrii Rublov Date: Sun, 6 Oct 2024 22:29:32 +0200 Subject: [PATCH] Templates: handle non-installed case with instructions --- .../seclerp/rider/extensions/ClipboardUtil.kt | 12 ++ .../MonoGameProjectTemplateCustomizer.kt | 2 +- .../MonoGameProjectTemplateGenerator.kt | 146 ++++++++++++++++++ .../templates/MonoGameTemplateMetadata.kt | 13 ++ .../templates/MonoGameTemplateNames.kt | 11 -- .../templates/MonoGameTemplateType.kt | 21 +-- .../messages/MonoGameUiBundle.properties | 4 + 7 files changed, 178 insertions(+), 31 deletions(-) create mode 100644 src/rider/main/kotlin/me/seclerp/rider/extensions/ClipboardUtil.kt create mode 100644 src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateGenerator.kt create mode 100644 src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateMetadata.kt delete mode 100644 src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateNames.kt diff --git a/src/rider/main/kotlin/me/seclerp/rider/extensions/ClipboardUtil.kt b/src/rider/main/kotlin/me/seclerp/rider/extensions/ClipboardUtil.kt new file mode 100644 index 0000000..c1ef708 --- /dev/null +++ b/src/rider/main/kotlin/me/seclerp/rider/extensions/ClipboardUtil.kt @@ -0,0 +1,12 @@ +package me.seclerp.rider.extensions + +import java.awt.Toolkit +import java.awt.datatransfer.StringSelection + +internal object ClipboardUtil { + fun copyToClipboard(text: String) { + val selection = StringSelection(text) + val clipboard = Toolkit.getDefaultToolkit().systemClipboard + clipboard.setContents(selection, null) + } +} \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateCustomizer.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateCustomizer.kt index 537fffb..06b8b26 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateCustomizer.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateCustomizer.kt @@ -2,7 +2,7 @@ package me.seclerp.rider.plugins.monogame.templates import com.jetbrains.rider.projectView.projectTemplates.providers.ProjectTemplateCustomizer -class MonoGameProjectTemplateCustomizer : ProjectTemplateCustomizer { +internal class MonoGameProjectTemplateCustomizer : ProjectTemplateCustomizer { private val monoGameTemplateTypes = setOf(MonoGameTemplateType()) override fun getCustomProjectTemplateTypes() = monoGameTemplateTypes } \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateGenerator.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateGenerator.kt new file mode 100644 index 0000000..95d2f67 --- /dev/null +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateGenerator.kt @@ -0,0 +1,146 @@ +package me.seclerp.rider.plugins.monogame.templates + +import com.intellij.icons.AllIcons +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.colors.EditorFontType +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.AnimatedIcon +import com.intellij.ui.EditorNotificationPanel +import com.intellij.ui.components.fields.ExtendableTextComponent +import com.intellij.ui.components.fields.ExtendableTextField +import com.intellij.ui.components.panels.NonOpaquePanel +import com.intellij.ui.components.panels.VerticalLayout +import com.intellij.ui.dsl.builder.Panel +import com.intellij.ui.dsl.builder.Row +import com.intellij.ui.dsl.builder.TopGap +import com.intellij.ui.dsl.builder.panel +import com.intellij.util.ui.JBUI +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.reactive.IOptProperty +import com.jetbrains.rider.model.RdProjectTemplate +import com.jetbrains.rider.projectView.projectTemplates.NewProjectDialogContext +import com.jetbrains.rider.projectView.projectTemplates.ProjectTemplatesSharedModel +import com.jetbrains.rider.projectView.projectTemplates.generators.TypeListBasedProjectTemplateGenerator +import me.seclerp.rider.extensions.ClipboardUtil +import me.seclerp.rider.plugins.monogame.MonoGameIcons +import me.seclerp.rider.plugins.monogame.MonoGameUiBundle +import me.seclerp.rider.plugins.monogame.templates.MonoGameTemplateMetadata.Names +import java.awt.BorderLayout +import java.awt.Component +import javax.swing.JLabel +import javax.swing.SwingConstants + +internal class MonoGameProjectTemplateGenerator( + lifetime: Lifetime, + context: NewProjectDialogContext, + sharedModel: ProjectTemplatesSharedModel, + projectTemplates: IOptProperty> +) : TypeListBasedProjectTemplateGenerator(lifetime, context, sharedModel, projectTemplates) { + init { + context.isReady.afterChange { + val templates = tryGetAcceptableTemplates() + when { + it && templates.isNullOrEmpty() -> setTemplatesMissingState() + it && !templates.isNullOrEmpty() -> setReadyState() + else -> setLoadingState() + } + } + } + + override val defaultName = "MonoGameProject1" + + override fun getPredefinedTypes() = listOf( + TemplateTypeWithIcon(Names.CROSS_PLATFORM_APP, MonoGameIcons.MgcbFile), + TemplateTypeWithIcon(Names.WINDOWS_DESKTOP_APP, MonoGameIcons.MgcbFile), + TemplateTypeWithIcon(Names.ANDROID_APP, MonoGameIcons.MgcbFile), + TemplateTypeWithIcon(Names.IOS_APP, MonoGameIcons.MgcbFile), + TemplateTypeWithIcon(Names.GAME_LIB, MonoGameIcons.MgcbFile), + TemplateTypeWithIcon(Names.CONTENT_PIPELINE_EXTENSION, MonoGameIcons.MgcbFile), + TemplateTypeWithIcon(Names.SHARED_LIB, MonoGameIcons.MgcbFile), + ) + + override fun getType(template: RdProjectTemplate) = template.name + .removePrefix("MonoGame ") + .replace("Application", "App") + + private var myLoadingRow: Row? = null + private var myTemplatesMissingRow: Row? = null + private var myTemplatesRow: Row? = null + + override fun createTemplateSpecificPanelAfterLanguage(): DialogPanel { + return panel { + myLoadingRow = createLoadingRow().apply { visible(false)} + myTemplatesMissingRow = createMissingTemplatesRow().apply { visible(false)} + myTemplatesRow = createReadyRow().apply { visible(false)} + setLoadingState() + } + } + + private fun setLoadingState() { + myTemplatesRow?.visible(false) + myTemplatesMissingRow?.visible(false) + myLoadingRow?.visible(true) + } + + private fun setReadyState() { + myLoadingRow?.visible(false) + myTemplatesMissingRow?.visible(false) + myTemplatesRow?.visible(true) + } + + private fun setTemplatesMissingState() { + myLoadingRow?.visible(false) + myTemplatesRow?.visible(false) + myTemplatesMissingRow?.visible(true) + } + + private fun Panel.createLoadingRow() = row(" ") { + cell(JLabel(MonoGameUiBundle.message("templates.loading.templates"), AnimatedIcon.Default(), SwingConstants.LEFT)) + } + + private fun Panel.createReadyRow() = row { + cell(super.createTemplateSpecificPanelAfterLanguage()) + } + + private fun Panel.createMissingTemplatesRow() = row(" ") { + val installCommand = "dotnet new install MonoGame.Templates.CSharp" + cell(CustomNotificationPanel(EditorNotificationPanel.Status.Warning)) + .applyToComponent { + text(MonoGameUiBundle.message("templates.no.templates.found")) + addBottomItem(JLabel(MonoGameUiBundle.message("templates.no.templates.install.hint")).apply { + border = JBUI.Borders.emptyTop(8) + }) + addBottomItem(CodeSnippetTextField(installCommand, 50)) + } + .resizableColumn() + topGap(TopGap.SMALL) + } + + private class CustomNotificationPanel(status: Status) : EditorNotificationPanel(status) { + private val myBottomPanel = NonOpaquePanel(VerticalLayout(8, VerticalLayout.FILL)) + + init { + add(myBottomPanel, BorderLayout.SOUTH) + } + + fun addBottomItem(comp: Component) { + myBottomPanel.add(comp) + } + } + + private class CodeSnippetTextField(text: String, columns: Int = 20) : ExtendableTextField(text, columns) { + init { + isEditable = false + font = EditorColorsManager.getInstance().globalScheme.getFont(EditorFontType.PLAIN) + addExtension(CopyToClipboardExtension()) + } + + private inner class CopyToClipboardExtension : ExtendableTextComponent.Extension { + override fun getIcon(hovered: Boolean) = AllIcons.General.Copy + + override fun getActionOnClick() = Runnable { + ClipboardUtil.copyToClipboard(this@CodeSnippetTextField.text) + } + } + } +} \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateMetadata.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateMetadata.kt new file mode 100644 index 0000000..de9edb6 --- /dev/null +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateMetadata.kt @@ -0,0 +1,13 @@ +package me.seclerp.rider.plugins.monogame.templates + +internal object MonoGameTemplateMetadata { + internal object Names { + const val CROSS_PLATFORM_APP = "Cross-Platform Desktop App" + const val WINDOWS_DESKTOP_APP = "Windows Desktop App" + const val ANDROID_APP = "Android App" + const val IOS_APP = "iOS App" + const val GAME_LIB = "Game Library" + const val CONTENT_PIPELINE_EXTENSION = "Content Pipeline Extension" + const val SHARED_LIB = "Shared Library Project" + } +} \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateNames.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateNames.kt deleted file mode 100644 index 60aabd3..0000000 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateNames.kt +++ /dev/null @@ -1,11 +0,0 @@ -package me.seclerp.rider.plugins.monogame.templates - -object MonoGameTemplateNames { - const val CROSS_PLATFORM_APP = "Cross-Platform Desktop App" - const val WINDOWS_DESKTOP_APP = "Windows Desktop App" - const val ANDROID_APP = "Android App" - const val IOS_APP = "iOS App" - const val GAME_LIB = "Game Library" - const val CONTENT_PIPELINE_EXTENSION = "Content Pipeline Extension" - const val SHARED_LIB = "Shared Library Project" -} \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateType.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateType.kt index 370888f..76841ff 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateType.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateType.kt @@ -4,12 +4,11 @@ import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rider.model.RdProjectTemplate import com.jetbrains.rider.projectView.projectTemplates.NewProjectDialogContext import com.jetbrains.rider.projectView.projectTemplates.ProjectTemplatesSharedModel -import com.jetbrains.rider.projectView.projectTemplates.generators.TypeListBasedProjectTemplateGenerator import com.jetbrains.rider.projectView.projectTemplates.templateTypes.PredefinedProjectTemplateType import com.jetbrains.rider.projectView.projectTemplates.utils.hasClassification import me.seclerp.rider.plugins.monogame.MonoGameIcons -class MonoGameTemplateType : PredefinedProjectTemplateType() { +internal class MonoGameTemplateType : PredefinedProjectTemplateType() { override val icon = MonoGameIcons.MgcbFile override val name = "MonoGame" override val order = 50 @@ -19,21 +18,5 @@ class MonoGameTemplateType : PredefinedProjectTemplateType() { } override fun createGenerator(lifetime: Lifetime, context: NewProjectDialogContext, sharedModel: ProjectTemplatesSharedModel) = - object : TypeListBasedProjectTemplateGenerator(lifetime, context, sharedModel, projectTemplates) { - override val defaultName = "MonoGameProject1" - - override fun getPredefinedTypes() = listOf( - TemplateTypeWithIcon(MonoGameTemplateNames.CROSS_PLATFORM_APP, MonoGameIcons.MgcbFile), - TemplateTypeWithIcon(MonoGameTemplateNames.WINDOWS_DESKTOP_APP, MonoGameIcons.MgcbFile), - TemplateTypeWithIcon(MonoGameTemplateNames.ANDROID_APP, MonoGameIcons.MgcbFile), - TemplateTypeWithIcon(MonoGameTemplateNames.IOS_APP, MonoGameIcons.MgcbFile), - TemplateTypeWithIcon(MonoGameTemplateNames.GAME_LIB, MonoGameIcons.MgcbFile), - TemplateTypeWithIcon(MonoGameTemplateNames.CONTENT_PIPELINE_EXTENSION, MonoGameIcons.MgcbFile), - TemplateTypeWithIcon(MonoGameTemplateNames.SHARED_LIB, MonoGameIcons.MgcbFile), - ) - - override fun getType(template: RdProjectTemplate) = template.name - .removePrefix("MonoGame ") - .replace("Application", "App") - } + MonoGameProjectTemplateGenerator(lifetime, context, sharedModel, projectTemplates) } \ No newline at end of file diff --git a/src/rider/main/resources/messages/MonoGameUiBundle.properties b/src/rider/main/resources/messages/MonoGameUiBundle.properties index 9e9e13e..3dd0da8 100644 --- a/src/rider/main/resources/messages/MonoGameUiBundle.properties +++ b/src/rider/main/resources/messages/MonoGameUiBundle.properties @@ -17,3 +17,7 @@ command.execution.error.message.code=Status code doesn't indicate success: {0}.\ command.mgcb.open.title=Open in external MGCB editor command.mgcb.open.missing.editor.title=mgcb-editor tool is missing + +templates.loading.templates=Loading templates... +templates.no.templates.found=No MonoGame templates were found. +templates.no.templates.install.hint=Please install them using the following command: