[MOVED TO IJ PLATFORM] An implementation of the IntelliJ look and feels in Compose for Desktop
| Files | Last commit | Last update |
|---|---|---|
| 1 year ago | ||
| 1 year ago | ||
| 2 years ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 3 years ago | ||
| 2 years ago | ||
| 2 years ago | ||
| 1 year ago | ||
| 2 years ago | ||
| 3 years ago | ||
| 1 year ago | ||
| 2 years ago | ||
| 1 year ago | ||
| 1 year ago | ||
| 4 years ago | ||
| 4 years ago | ||
| 1 year ago |
Jewel:桌面端 Compose 主题
Jewel:一款 Compose for Desktop 主题
Jewel 旨在在 Compose for Desktop 中重现 IntelliJ 平台的新 UI Swing 外观和感觉,提供一个优化后的桌面主题和组件集。
[!警告] Jewel 正在迁往 IntelliJ 平台!所有活跃开发将迁至 https://github.com/JetBrains/intellij-community,而这个仓库将仅作为镜像。 更多信息即将跟进 —— 但请将此仓库的代码视为只读。
[!注意]
此项目正在积极开发中,考虑用于生产环境时请谨慎。您可以使用它,但应该预期 API 会频繁变化,事物可能会移动和/或中断,以及所有这些繁琐之事。不同版本间的二进制兼容性无法保证,API 仍处于变动中,可能会有所更改。
目前,在 Compose for Desktop 中编写第三方的 IntelliJ 插件尚未得到 IntelliJ 平台官方支持。它应该可以工作,但您的体验可能会有所不同,如果出现故障,您将需要自行解决。
请自行承担风险!
Jewel 提供了 IntelliJ 平台主题的实现,可用于任何 Compose for Desktop 应用程序。此外,它还拥有一个 Swing LaF 桥接,仅在 IntelliJ 平台中工作(即用于创建 IDE 插件),但会自动将当前的 Swing LaF 镜像到 Compose 中,以实现原生外观和一致的 UI。
如果您想了解更多关于 Jewel 和 Compose for Desktop 的信息,以及为什么它们是满足您桌面 UI 需求的出色现代解决方案,请查看 这场演讲,由 Jewel 贡献者 Sebastiano 和 Chris 进行分享。
它涵盖了为什么 Compose 是一个可行的选择,以及对 Jewel 项目的概述,以及一些实际应用案例。
快速入门
首先需要添加必要的 Gradle 插件,包括 Compose 多平台插件。您需要在 settings.gradle.kts 中为其添加一个自定义仓库:
pluginManagement {
repositories {
google()
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
mavenCentral()
}
}
然后在您的应用程序的 build.gradle.kts 文件中:
plugins {
// MUST align with the Kotlin and Compose dependencies in Jewel
kotlin("jvm") version "..."
id("org.jetbrains.compose") version "..."
}
repositories {
maven("https://packages.jetbrains.team/maven/p/kpm/public/")
// Any other repositories you need (e.g., mavenCentral())
}
[!警告] 如果您使用约定插件来配置项目,可能会遇到此类问题链接。要解决此问题,请确保插件仅初始化一次 —— 例如,在根目录的
build.gradle.kts文件中声明插件并使用apply false,然后在需要它们的各个子模块中应用。
在您的应用中使用 Jewel,您只需添加相关的依赖项。有两种场景:独立运行的 Compose for Desktop 应用,以及 IntelliJ 平台插件。
如果您正在编写一个独立应用,那么您应该依赖最新的 int-ui-standalone-* 构件:
dependencies {
// See https://github.com/JetBrains/Jewel/releases for the release notes
implementation("org.jetbrains.jewel:jewel-int-ui-standalone-[latest platform version]:[jewel version]")
// Optional, for custom decorated windows:
implementation("org.jetbrains.jewel:jewel-int-ui-decorated-window-[latest platform version]:[jewel version]")
// Do not bring in Material (we use Jewel)
implementation(compose.desktop.currentOs) {
exclude(group = "org.jetbrains.compose.material")
}
}
对于IntelliJ 平台插件,您应当依赖于相应的 ide-laf-bridge-* 艺术品:
dependencies {
// See https://github.com/JetBrains/Jewel/releases for the release notes
// The platform version is a supported major IJP version (e.g., 232 or 233 for 2023.2 and 2023.3 respectively)
implementation("org.jetbrains.jewel:jewel-ide-laf-bridge-[platform version]:[jewel version]")
// Do not bring in Material (we use Jewel) and Coroutines (the IDE has its own)
api(compose.desktop.currentOs) {
exclude(group = "org.jetbrains.compose.material")
exclude(group = "org.jetbrains.kotlinx")
}
}
Tip
使用版本目录更为方便 —— 你可以参考 Jewel 的 版本目录。
使用 ProGuard/代码混淆/压缩
Jewel 官方不支持使用 ProGuard 来最小化和/或混淆你的代码,目前也没有这样的计划。 尽管如此,有人报告称使用它是成功的。请注意,不能保证它将一直有效,而且你绝对需要有一些规则。我们不提供任何官方规则集,但以下已知对某些人有效:https://github.com/romainguy/kotlin-explorer/blob/main/compose-desktop.pro
Important
我们不会接受由使用 ProGuard 或类似工具引起的问题的错误报告。
依赖关系矩阵
Jewel 处于持续开发中,我们只专注于支持我们内部使用的 Compose 版本。你可以在 libs.versions.toml 中看到最新支持的版本。
不同的 Compose 版本之间不保证与不同版本的 Jewel 兼容。
所使用的 Compose 编译器版本是与给定 Kotlin 版本兼容的最新版本。请查看 这里 的 Compose 编译器发行说明,了解兼容性。
支持的最低 Kotlin 版本由支持的最低 IntelliJ IDEA 平台决定。
项目结构
项目分为以下模块:
buildSrc包含构建逻辑,包括:jewel和jewel-publish配置插件jewel-check-public-api和jewel-linting配置插件- 主题调色板生成器插件
- Studio 发行版生成器插件
foundation包含基础 Jewel 功能:- 没有强烈样式的基组件(例如,
SelectableLazyColumn,BasicLazyTree) JewelTheme接口和一些基础组合本地值- 状态管理原语
- Jewel 注解
- 其他几个原语
- 没有强烈样式的基组件(例如,
ui包含所有样式组件和自定义画师逻辑decorated-window包含在 JetBrains 运行时上实现自定义窗口装饰的基本、未样式化功能int-ui包含两个模块:int-ui-standalone具有独立版本的 Int UI 样式值,可用于任何 Compose for Desktop 应用程序int-ui-decorated-window具有独立版本的 Int UI 样式值,用于任何 Compose for Desktop 应用的自定义窗口装饰
ide-laf-bridge包含用于 IntelliJ 平台插件的 Swing LaF 桥(如下所述)markdown包含几个模块:core使用类似 GitHub 的样式解析和渲染 Markdown 文档的核心逻辑extension包含几个用于添加更多功能的基于 CommonMark 规范的扩展ide-laf-bridge-styling包含用于 Markdown 渲染器的 IntelliJ 平台桥接主题样式int-ui-standalone-styling包含用于 Markdown 渲染器的独立 Int UI 主题样式
samples包含示例应用,展示了可用的组件:standalone是一个常规的 CfD 应用程序,使用独立主题定义和自定义窗口装饰ide-plugin是一个展示 Swing 桥使用的 IntelliJ 插件
分支策略和 IJ 平台
主分支上的代码针对当前最新的 IntelliJ 平台版本进行开发和测试。
当新主版本的 EAP 开始时,我们创建一个 releases/xxx 发布分支,其中 xxx 是跟踪的主 IJP 版本。此时,主分支开始跟踪最新可用的主 IJP 版本,并根据需要将更改 cherry-pick 到每个发布分支。所有活跃的发布分支具有相同的功能(在相应的 IJP 版本支持的情况下),但可能在平台版本特定的修复和内部结构方面有所不同。
独立 Int UI 主题将始终与最新的主 IJP 版本保持相同的工作方式;发布分支将不包括 int-ui 模块,该模块始终从主分支发布。
Jewel 的发布始终从主分支上的标签切出;每个 releases/xxx 分支的 HEAD 随后标记为 [mainTag]-xxx,并用于发布该主 IJP 版本的艺术品。
Important
我们只支持每个主要 IJP 版本的最新构建。例如,如果最新的 233 版本是 2023.3.3,我们将只保证 Jewel 在该版本上工作。版本 2023.3.0–2023.3.2 可能工作也可能不工作。
Caution
当你针对 Android Studio 时,可能会遇到问题,因为 Studio 附带了自己的(较旧)版本的 Jewel 和 Compose for Desktop。如果你要针对 Android Studio,你需要将 CfD 和 Jewel 依赖项设置为阴影,直到 Studio 不再在类路径上泄漏该依赖项。你可以查看 Package Search 插件是如何实现阴影的。
Int UI 独立主题
独立主题可以用于任何 Compose for Desktop 应用程序。你像使用正常主题一样使用它,可以根据你的喜好对其进行自定义。默认情况下,它与官方 Int UI 规范匹配。
关于如何设置独立应用的示例,你可以参考 独立示例。
Warning
请注意,Jewel 需要 JetBrains 运行时才能正确工作。某些功能(如字体加载)依赖于它,因为它具有对 UI 功能的额外功能和修补程序,这些在其他的 JDK 中不可用。 我们不支持在其他任何 JDK 上运行 Jewel。
要在非 IntelliJ 平台环境中使用 Jewel 组件,你需要将你的 UI 层次结构包装在 IntUiTheme 可组合函数中:
IntUiTheme(isDark = false) {
// ...
}
如果您希望对主题有更多的控制,您可以使用其他 IntUiTheme 的重载版本,就像独立示例中所做的那样。
自定义窗口装饰
JetBrains 运行时允许窗口使用自定义装饰,而不是标准的标题栏。

独立示例应用程序展示了如何轻松实现类似 JetBrains IDE 的外观;如果您想要进行高度自定义,只需依赖 decorated-window 模块即可,该模块包含所有必要的基元,但不包含 Int UI 样式。
要获得类似 IntelliJ 的自定义标题栏,您需要将窗口装饰样式传递给主题调用,并在主题的顶层添加 DecoratedWindow 可组合项:
IntUiTheme(
theme = themeDefinition,
styling = ComponentStyling.default().decoratedWindow(
titleBarStyle = TitleBarStyle.light()
),
) {
DecoratedWindow(
onCloseRequest = { exitApplication() },
) {
// ...
}
}
在 IntelliJ 平台上运行:Swing 桥梁
Jewel 包含了一个与 IDE 正确集成的关键元素:Swing 组件(主题和 LaF)与 Compose 世界之间的桥梁。
这座桥梁确保我们能获取到当前 IntelliJ 主题中定义的色彩、字体、度量单位和图像,并将它们应用于 Compose 组件。这意味着 Jewel 将自动适应使用 标准主题机制 的 IntelliJ 平台主题。
[!注意] 使用非标准机制(例如为 Swing 组件提供自定义 UI 实现)的 IntelliJ 主题目前无法,也永远不会得到支持。
如果您正在编写一个 IntelliJ 平台插件,您应该使用 SwingBridgeTheme 而不是独立主题:
SwingBridgeTheme {
// ...
}
支持的 IntelliJ 平台版本
要在 IntelliJ 平台中使用 Jewel,您应该依赖相应的 jewel-ide-laf-bridge-* 艺术品,这将引入必要的传递依赖项。以下是目前支持的 IntelliJ 平台版本以及相应桥接代码所在的分支如下所示:
| IntelliJ 平台版本(s) | 使用分支 |
|---|---|
| 2024.3 (EAP 6+) | main |
| 2024.2 (beta 1+) | releases/242 |
| 2024.1 (EAP 3+) | releases/241 |
| 2023.3 (存档) | archived-releases/233 |
| 2023.2 (存档) | archived-releases/232 |
| 2023.1 或更早版本 | 不支持 |
关于如何设置 IntelliJ 插件的示例,您可以参考 ide-plugin 示例。
图标
加载图标最好使用 Icon 可组合项,它提供了一个基于键的 API,该 API 在桥接和独立模块之间具有可移植性。图标键实现了 IconKey 接口,然后内部使用该接口获取资源路径以从其中加载图标。
Icon(key = MyIconKeys.myIcon, contentDescription = "My icon")
从 IntelliJ 平台加载图标
如果您想加载 IntelliJ 平台的图标,可以使用 AllIconsKeys,它是从 AllIcons 平台文件生成的。在 IntelliJ 插件中使用时,请确保您使用的 Jewel 库版本与平台版本相匹配,因为已知图标在不同的大版本间会发生变动——有时,小版本也会有所变动。
在 IntelliJ 插件中使用 AllIconsKeys 中的图标时,您无需进行任何操作,因为这些图标默认已包含在类路径中。如果您想在独立应用程序中使用图标,您需要确保所需的图标在类路径上。您可以将必要的图标复制到您的资源中,路径要与 IDE 中的路径完全一致,或者您可以添加对 com.jetbrains.intellij.platform:icons 资源的依赖,该资源包含了所有最终会出现在 AllIconsKeys 中的图标。后一种方法是推荐的,因为它简单且图标不会占用太多磁盘空间。
将以下内容添加到您的 独立应用程序 构建脚本中:
dependencies {
implementation("com.jetbrains.intellij.platform:icons:[ijpVersion]")
// ...
}
repositories {
// Choose either of these two, depending on whether you're using a stable IJP or not
maven("https://www.jetbrains.com/intellij-repository/releases")
maven("https://www.jetbrains.com/intellij-repository/snapshots")
}
[!注意] 如果您的目标是 IntelliJ 插件,则无需此额外设置,因为图标由平台本身提供。
加载您的图标
要访问您自己的图标,您需要创建并维护相应的 IconKey。我们发现,当您有数十个图标时,最简单的方法是手动创建一个图标键的容器,就像我们在示例中展示的那样。如果您有更多图标,您应考虑生成这些容器。
在您的容器中,您可以选择使用以下 IconKey 实现:
- 如果您的图标在旧界面和新界面之间无需更改,可以使用更简单的
PathIconKey - 如果您的图标在旧界面和新界面中有所不同,应使用
IntelliJIconKey,它接受两个路径,分别对应不同版本 - 如果您有其他需求,也可以实现您自己的
IconKey版本
绘制提示
Jewel 提供了一个影响图标加载和绘制的 API,称为 PainterHint。Icon 可组合项具有重载版本,可以接受零个、一个或多个 PainterHint,用于计算屏幕上显示的最终结果。
PainterHint 可以更改图标路径(通过添加前缀/后缀,或完全更改它)、调整图像内容(SVG 修补、XML 修补、位图修补)、添加装饰(例如,徽章),或者不执行任何操作(None)。我们内置了多种类型的 PainterHint,应涵盖所有需求;如果您发现某些用法尚未处理,请提交功能请求,我们将进行评估。
独立和桥接主题都提供了一套默认隐式 PainterHint,例如用于实现运行时修补,就像 IDE 那样。您还可以使用 PainterHint 来影响图标的绘制方式,或基于某些条件(例如,Size)选择特定的图标文件。
如果您有带状态的图标,即需要根据某些状态显示不同图标,您可以使用 Icon(..., hint) 和 Icon(..., hints) 的重载版本。然后,您可以使用状态映射 PainterHint 让 Jewel 自动加载适当的图标:
// myState implements SelectableComponentState and has a ToggleableState property
val indeterminateHint =
if (myState.toggleableState == ToggleableState.Indeterminate) {
IndeterminateHint
} else {
PainterHint.None
}
Icon(
key = myKey,
contentDescription = "My icon",
indeterminateHint,
Selected(myState),
Stateful(myState),
)
在 IndeterminateHint 看起来是这样的:
private object IndeterminateHint : PainterSuffixHint() {
override fun suffix(): String = "Indeterminate"
}
假设 PainterProvider 的基路径是 components/myIcon.svg,Jewel 会根据状态自动将其转换到正确的路径。如果您想了解更多关于这个系统的信息,请查看 PainterHint 接口及其实现。
请进一步查阅 PainterHint 的实现和我们的示例。
默认图标运行时修补
Jewel 模拟了在 IntelliJ 平台上加载图标时底层的复杂操作。具体来说,资源在加载前会进行一些转换。这是基于我们上面描述的 PainterHint API 构建的。
例如,在 IDE 中,如果新 UI 处于活动状态,图标路径可能会被替换为另一个路径。SVG 图标中的某些关键颜色也会根据当前主题进行替换。详情请见 官方文档。
除此之外,即使在独立应用中,Jewel 也会根据当前主题选择适当的深色/浅色图标变体,对于位图图标,它会根据 LocalDensity 尝试选择 2x 变体。
字体
要加载系统字体,可以通过其字体家族名称来获取:
val myFamily = FontFamily("My Family")
如果你希望使用内嵌在 JetBrains 运行时中的字体,你可以使用 EmbeddedFontFamily API 代替:
import javax.swing.text.StyledEditorKit.FontFamilyAction
// Will return null if no matching font family exists in the JBR
val myEmbeddedFamily = EmbeddedFontFamily("Embedded family")
// It's recommended to load a fallback family when dealing with embedded familes
val myFamily = myEmbeddedFamily ?: FontFamily("Fallback family")
您可以通过使用 asComposeFontFamily() API,从任意的 java.awt.Font —— 包括 JBFonts —— 中获取一个 FontFamily。
val myAwtFamily = myFont.asComposeFontFamily()
// This will attempt to resolve the logical AWT font
val myLogicalFamily = Font("Dialog").asComposeFontFamily()
// This only works in the IntelliJ Platform,
// since JBFont is only available there
val myLabelFamily = JBFont.label().asComposeFontFamily()
Swing 互操作性
既然是 Compose for Desktop,您将获得与 Swing 的高度互操作性。为了避免出现故障和层叠顺序(z-order)问题,您应在初始化 Compose 内容之前启用 实验性 Swing 渲染管道。
ide-laf-bridge 模块提供的 ToolWindow.addComposeTab() 扩展函数会为您处理这一过程。然而,如果您还想在其他场景和独立应用程序中启用此功能,可以在您的 Compose 入口点调用 enableNewSwingCompositing() 函数(即在创建 ComposePanel 之前)。
Note
新的 Swing 渲染管道是实验性的,并且在使用无限重复动画时可能会带来性能影响。这是 Compose Multiplatform 团队已知的 issue,需要修改 Java 运行时才能解决。一旦在 JetBrains Runtime 中完成所需的更改,我们将移除此通知。
使用 Jewel 编写
以下是一些使用 Compose for Desktop 和 Jewel 的项目精选:
- Package Search(IntelliJ 平台插件)
- Kotlin Explorer(独立应用)
- Android Studio Koala 中基于任务的性能分析器 UI 新功能
- ...更多内容即将到来!
故障排除
Git push 钩子不起作用?
error: cannot spawn .git/hooks/pre-push: No such file or directory
error: waitpid for (NULL) failed: No child processes
试着运行一下命令 git lfs update --force。
需要帮助?
您可以在 Kotlin Slack 的 #jewel 频道中寻求帮助。
如果您还没有 Kotlin Slack 的访问权限,可以在这里申请加入 这里。
许可
Jewel 根据 Apache 2.0 许可 授权。
Copyright 2022–4 JetBrains s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
请提供需要翻译的原始文本和Markdown格式,我将根据您的要求进行翻译。
Introduction
"jewel" —— 由JetBrain开发的Compose库,其宗旨是在桌面应用中重现IntelliJ平台的新UI Swing外观与体验,专为使用Compose for Desktop进行用户界面开发的程序员设计。【此简介由AI生成】
Customize your domain