使用 Compose 构建现代 IDEA 插件 UI

1,084 阅读5分钟

Compose Multiplatform simplifies and accelerates UI development for Desktop and Web applications, and allows extensive UI code sharing between Android, Desktop and Web.

2021 年 12 月,JetBrains 发布了 Compose Multiplatform 1.0。本文中,我们将用到其中 Compose Desktop 来构建现代 IDEA 插件 UI。

创建 IDEA 插件项目

开发环境:IntelliJ IDEA 2022.2.2 根据 JetBrains 的开发文档里 Creating A Plugin 一章提及,有以下三种创建插件工程的方式

  • Github 模板 直接从 Github clone 插件工程模板作为您的项目起点
  • Gradle 使用 IDEA 提供的新建项目向导功能,创建和配置基于 Gradle 的 IntelliJ Platform 插件工程
  • DevKit JetBrains 已不再推荐该方式,略

这里我们选择第二种,也是开发者最习惯的创建项目方式

3D100BF2-F1F3-4B7D-A708-558768F42319.png File -> New -> Project, 在创建项目页,左侧 Generators 下选择 IDE Plugin 类型,然后按照您自己的需求填写基础的项目配置。注意,这里 Type 是 Plugin,Language 是 Kotlin,JDK 最好是 11,因为从 IDEA 2020.3(也就是北极狐的 base 版本) 开始,JDK 版本限制最低为 11。

IDEA 插件开发配置说明

上一步点击 create 之后,我们进入到项目中,在等待 sync 完成期间,我们先了解下关于 IDEA 插件开发常见的配置

compose_sample
├── .run
│   └── Run IDE with Plugin.run.xml
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── src
│   ├── main
│   │   ├── java
│   │   └── resources
│   │       └── META-INF
│   │           └── plugin.xml
│   └── test
│       ├── java
│       └── resources
├── .gitignore
├── build.gradle.kts
├── gradlew
├── gradlew.bat
└── settings.gradle.kts

上面是一个最精简的 IDEA 插件项目的目录结构,其中大部分跟其他基于 Gradle 的项目类型是一致的,例如 Android 开发者熟悉的 build.gradle.kts(注意,这里可以看到 Kotlin Script 已成为 JetBrains 默认的 Gradle 脚本语言)

我们重点介绍两个文件:

./build.gradle.kts

这个 gradle 脚本基本等同于 Android 项目中的 app/build.gradle.kts。同样的,它主要提供了以下作用:

  • 导入我们需要的 Gradle plugins,项目默认引入的有 Java、Kotlin、intelliJ 等
  • 对导入的 Gradle plugins 进行二次配置
  • 指定 IDEA 插件的包名和版本号等信息
  • 声明项目依赖及依赖仓库
  • … 下面我们逐一认识这个文件里的各项配置
plugins {
    id("java") // Java Gradle plugin
    id("org.jetbrains.kotlin.jvm") version "1.7.10" // Kotlin Gradle plugin
    id("org.jetbrains.intellij") version "1.8.0" // IntelliJ Gradle plugin,主要为 IDEA 插件开发流程提供各方面的支持,例如运行、测试、部署等
}

// 指定我们正在开发的这个 IDEA 插件的包名和版本号
group = "com.example"
version = "1.0-SNAPSHOT"

// 指定依赖仓库。可以看到下面并没有 dependencies 配置,因为默认没有依赖其他库,之后我们会逐一添加
repositories {
    mavenCentral()
}

// 这里是对文件头 org.jetbrains.intellij Gradle 插件的二次配置,默认生成的以下三项,您可以理解为是配置了一个简易版本的 JetBrains IDE 环境,用来运行和调试我们正在开发的这个 IDEA 插件
intellij {
    version.set("2021.3.3") // IDEA 插件运行调试版本
    type.set("IC") // 指定运行在哪个 JetBrains 的 IDE 类型上,这里 IC 是 IntelliJ IDEA Community Edition 的缩写,您也可以指定 JetBrains 的其他 IDE 产品例如 CLion, PyCharm...

    plugins.set(listOf(/* Plugin Dependencies */)) // 指定上面这个 IDE 环境需要自带哪些插件,默认不需要
}

tasks {
    // 设置 Java/Kotlin 相关的编译选项,一般我们需要指定为 11
    withType<JavaCompile> {
        sourceCompatibility = "11"
        targetCompatibility = "11"
    }
    withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
        kotlinOptions.jvmTarget = "11"
    }

    // 声明 IDEA 插件的版本支持范围
    patchPluginXml {
        sinceBuild.set("213") // 213 代表 2021.3
        untilBuild.set("223.*") // 223.* 代表 2022.3 及任意子版本
    }

    signPlugin {
        certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
        privateKey.set(System.getenv("PRIVATE_KEY"))
        password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
    }

    publishPlugin {
        token.set(System.getenv("PUBLISH_TOKEN"))
    }
}

./src/main/resources/META-INF/plugin.xml

如果您是一个 Android 开发者,那此文件基本可以理解为是 AndroidManifest.xml 。plugin 主要用来声明关于插件的配置信息,包含且不限于:

  1. 支持的 IntelliJ Platform 版本范围
  2. 插件 id, 名称, 描述信息
  3. 更重要的,声明插件里的 Actions。不同的 action 会触发不同的事件,比如前往哪个界面,比如触发某个功能,等等 我们会在后面进一步配置 plugin.xml

OK,至此,我们初步地认识了关于 IDEA 插件开发的一些相关配置。

ps. 如果您是第一次创建 IDEA 插件项目,在 sync 过程中,除了常规的依赖之外,还会下载我们之后会在运行调试中用到的 IDE 环境,即上面 build.gradle.kts 说明里的 简易版本的 JetBrains IDE,size 大约为600mb,请耐心等候直到 sync 成功。

添加 Compose Desktop 依赖

在 build.gradle.kts 中,添加如下代码与依赖项

plugins {
    ...
	  // JetBrains Compose Gradle 插件,主要用于提供 Compose multiplatfrom 需要的依赖项配置
    id("org.jetbrains.compose") version "1.2.0-beta01"
}

...
dependencies {
	  // 添加 Compose Desktop 需要的依赖项,compose.desktop.currentOs 这个 value 便来自于上面添加的 org.jetbrains.compose 插件
    implementation(compose.desktop.currentOs)
}

sync 成功后,您会发现项目里多了许多 Compose 与 Skia 相关的依赖库

438F0A4C-05A1-4373-BA37-D3599062AD44.png

使用 Compose 编写一个简单的 hello world

这里我们简单地实现一个计数界面:即有一个文本和一个按钮,前者会显示后者的点击次数

package com.example.composesample

import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun App() {

    Surface {

        Box(contentAlignment = Alignment.Center) {

            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                // 声明 count state,接收读写
                var count by remember { mutableStateOf(0) }

                // Text 的内容跟随 count 变化
                Text("Count: $count")

                Spacer(modifier = Modifier.height(8.dp))

                OutlinedButton(
                    onClick = {
                        // 每点击一次,count 加一
                        count += 1
                    }
                ) {

                    Icon(
                        imageVector = Icons.Default.FavoriteBorder,
                        contentDescription = null,
                        modifier = Modifier.padding(4.dp)
                    )

                    Text("Hello JetBrains!")
                }
            }

        }
    }
}

运行您的第一段 Compose 代码

在运行您的第一段 Compose 代码前,先为它添加触发显示的逻辑。

定义 Action

Action是连接各种事件(例如菜单项的点击)与功能的桥梁,类比 Android 里的 Intent

package com.example.composesample

import androidx.compose.material.MaterialTheme
import androidx.compose.ui.awt.ComposePanel
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import javax.swing.JComponent

class SampleAction : DumbAwareAction() {

    override fun actionPerformed(e: AnActionEvent) {
        SampleDialog(e.project).show()
    }

    class SampleDialog(project: Project?) : DialogWrapper(project) {

        init {
            title = "Compose Sample"
            init()
        }

        override fun createCenterPanel(): JComponent? {
            return ComposePanel().apply {
                setBounds(0, 0, 800, 600)
                setContent {
                    // 这里嵌入我们之前写好的计数器界面
                    MaterialTheme {
                        App()
                    }
                }
            }
        }
    }
}

在 plugin.xml 里注册 Action

<idea-plugin>
    ...
    // 将下面的 action tag 内容添加到 plugin.xml 里
    <actions>
        <action id="ComposeSampleAction" class="com.example.composesample.SampleAction"
                text="Show Compose Sample">
	          // 这里的作用是将此 action 添加到 IDEA tools 下拉菜单里的最后一个位置
            <add-to-group group-id="ToolsMenu" anchor="last"/>
        </action>
    </actions>
    
</idea-plugin>

Run Plugin!

至此,我们的编码工作已全部完成。在运行插件之前,检查 IDEA 右上角当前的 Gradle 任务是否为 Run Plugin,然后点击运行 icon,等候构建运行成功

第一次运行成功后,会弹出如下窗口

09EF8FAD-41D7-4E9D-982C-BC970AE41AF6.png 我们需要先选择或创建一个项目才能进入平时的开发视图

B82C1E05-BBCE-4E1B-BD64-C8FB08B9D505.png 进入项目后,从菜单栏 Tools -> Show Compose Sample 进入 Compose 界面

54499CBD-4717-46BB-9942-55A6696F15BF.png 至此,我们成功在 IDEA 上运行了第一个使用 Compose 编写的插件

参考资料

[1] plugins.jetbrains.com/docs/intell…

[2] github.com/JetBrains/c…