Project Icon

jewel

Compose桌面应用主题与组件库

Jewel是一个开源项目,为Compose桌面应用提供主题和组件库。它复刻了IntelliJ平台的新UI外观,支持独立应用和IntelliJ插件开发。主要功能包括桌面优化的主题、组件集和Swing LaF桥接。项目仍在积极开发中,API可能变动,暂不保证二进制兼容性。

JetBrains孵化器 CI检查 基于Apache 2.0许可 最新版本 Compose for Desktop版本

Jewel: Compose for Desktop主题

Jewel标志

Jewel旨在以Compose for Desktop重新创建IntelliJ平台的"新UI"Swing外观和感觉,提供针对桌面优化的主题和组件集。

[!警告]

该项目正在积极开发中,在考虑用于生产用途时建议谨慎。您可以使用它,但应该预期API会经常变更,东西可能会移动和/或损坏,以及所有这些常见情况。不保证跨版本的二进制兼容性,API仍在变化中且可能会更改。

目前,IntelliJ平台不正式支持使用Compose for Desktop编写第三方IntelliJ插件。它应该可以工作,但您的体验可能会有所不同,如果出现问题,您将无法获得支持。

使用时风险自负!

Jewel提供了可在任何Compose for Desktop应用程序中使用的IntelliJ平台主题实现。此外,它还有一个Swing LaF桥接器,只能在IntelliJ平台中使用(即用于创建IDE插件),但会自动将当前的Swing LaF镜像到Compose中,以获得原生外观的一致UI。

[!提示]

如果您想了解更多关于Jewel和Compose for Desktop的信息,以及为什么它们是满足您桌面UI需求的出色现代解决方案,请查看Jewel贡献者Sebastiano和Chris的这个演讲

它涵盖了为什么Compose是一个可行的选择,以及Jewel项目的概述,还有一些真实世界的使用案例。


入门指南

首先要添加必要的Gradle插件,包括Compose Multiplatform插件。你需要在settings.gradle.kts中添加一个自定义仓库:

pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
        mavenCentral()
    }
}

然后,在你的应用的build.gradle.kts中:

plugins {
    // 应与Jewel中的Kotlin和Compose依赖保持一致
    kotlin("jvm") version "1.9.21"
    id("org.jetbrains.compose") version "1.6.0-dev1440"
}

repositories {
    maven("https://packages.jetbrains.team/maven/p/kpm/public/")
    // 你需要的任何其他仓库(例如 mavenCentral())
}

[!警告] 如果你使用约定插件来配置项目,可能会遇到诸如这样的问题。要解决它,请确保插件只初始化一次 — 例如,在根build.gradle.kts中用apply false声明它们,然后在所有需要它们的子模块中应用它们。

要在你的应用中使用Jewel,你只需添加相关的依赖项。有两种情况:独立的Compose for Desktop应用,和IntelliJ Platform插件。

如果你正在编写独立应用,那么你应该依赖最新的int-ui-standalone-*构件:

dependencies {
    // 查看 https://github.com/JetBrains/Jewel/releases 获取发布说明
    implementation("org.jetbrains.jewel:jewel-int-ui-standalone-[最新平台版本]:[jewel版本]")

    // 可选,用于自定义装饰窗口:
    implementation("org.jetbrains.jewel:jewel-int-ui-decorated-window-[最新平台版本]:[jewel版本]")

    // 不要引入Material(我们使用Jewel)
    implementation(compose.desktop.currentOs) {
        exclude(group = "org.jetbrains.compose.material")
    }
}

对于IntelliJ Platform插件,你应该依赖适当的ide-laf-bridge-*构件:

dependencies {
    // 查看 https://github.com/JetBrains/Jewel/releases 获取发布说明
    // 平台版本是支持的主要IJP版本(例如,232或233分别代表2023.2和2023.3)
    implementation("org.jetbrains.jewel:jewel-ide-laf-bridge-[平台版本]:[jewel版本]")

    // 不要引入Material(我们使用Jewel)和Coroutines(IDE有自己的)
    api(compose.desktop.currentOs) {
        exclude(group = "org.jetbrains.compose.material")
        exclude(group = "org.jetbrains.kotlinx")
    }
}

[!提示] 使用版本目录更容易 — 你可以使用Jewel的版本目录作为参考。


使用ProGuard/混淆/最小化

Jewel不官方支持使用ProGuard来最小化和/或混淆你的代码,目前也没有这样的计划。 话虽如此,人们报告在使用它时取得了成功。请注意,不能保证它会继续有效, 而且你肯定需要制定一些规则。我们不提供任何官方规则集,但这些规则已知 对某些人有效: https://github.com/romainguy/kotlin-explorer/blob/main/compose-desktop.pro

[!重要] 我们不会接受因使用ProGuard或类似工具而导致的问题的错误报告。

依赖矩阵

Jewel正在持续开发中,我们专注于支持我们内部使用的Compose版本。 你可以在libs.versions.toml中看到最新支持的版本。

不同版本的Compose不能保证与不同版本的Jewel一起工作。

使用的Compose Compiler版本是与给定Kotlin版本兼容的最新版本。请查看 这里的Compose Compiler发布说明,其中指出了兼容性。

支持的最低Kotlin版本由支持的最低IntelliJ IDEA平台决定。

项目结构

该项目分为多个模块:

  1. buildSrc包含构建逻辑,包括:
    • jeweljewel-publish配置插件
    • jewel-check-public-apijewel-linting配置插件
    • 主题调色板生成器插件
    • Studio Releases生成器插件
  2. foundation包含基础的Jewel功能:
    • 没有强烈样式的基本组件(例如,SelectableLazyColumn, BasicLazyTree)
    • 带有几个基本组合局部变量的JewelTheme接口
    • 状态管理原语
    • Jewel注解
    • 其他一些原语
  3. ui包含所有样式化组件和自定义画家逻辑
  4. decorated-window包含在JetBrains Runtime上进行自定义窗口装饰的基本、无样式功能
  5. int-ui包含两个模块:
    • int-ui-standalone有一个可以在任何Compose for Desktop应用中使用的Int UI样式值的独立版本
    • int-ui-decorated-window有一个可以在任何Compose for Desktop应用中使用的自定义窗口装饰的Int UI样式值的独立版本
  6. ide-laf-bridge包含用于IntelliJ Platform插件的Swing LaF桥(详见下文)
  7. markdown包含几个模块:
    • core使用类似GitHub的样式,用Jewel解析和渲染Markdown文档的核心逻辑
    • extension包含几个可用于添加更多功能的基本CommonMark规范的扩展
    • ide-laf-bridge-styling包含Markdown渲染器的IntelliJ Platform桥主题
    • int-ui-standalone-styling包含Markdown渲染器的独立Int UI主题
  8. samples包含展示可用组件的示例应用:
    • standalone是一个常规CfD应用,使用独立主题定义和自定义窗口装饰
    • ide-plugin是一个展示如何使用Swing桥的IntelliJ插件

分支策略和IJ平台

主分支上的代码是针对当前最新的IntelliJ Platform版本开发和测试的。

当新的主要版本的EAP开始时,我们会切出一个releases/xxx发布分支,其中xxx是跟踪的主要 IJP版本。此时,主分支开始跟踪最新可用的主要IJP版本,并根据需要将更改cherry-pick到每个发布分支。 所有活跃的发布分支都具有相同的功能(在相应的IJP版本支持的情况下),但可能在平台版本特定的修复和内部实现上有所不同。

独立的Int UI主题将始终以与最新主要IJP版本相同的方式工作;发布分支将不 包括int-ui模块,该模块始终从主分支发布。

Jewel的发布总是从主分支上的标签切出;然后每个releases/xxx分支的HEAD被标记为 [mainTag]-xxx,并用于发布该主要IJP版本的构件。

[!重要] 我们只支持每个主要IJP版本的最新构建。例如,如果最新的233版本是2023.3.3, 我们只保证Jewel在该版本上工作。2023.3.0–2023.3.2版本可能有效,也可能无效。

[!注意] 当你针对Android Studio时,你可能会遇到问题,因为Studio附带了自己的(较旧的)Jewel 和Compose for Desktop版本。如果你想针对Android Studio,在Studio不再在类路径上泄露该依赖之前, 你需要对CfD和Jewel依赖进行阴影处理。你可以查看Package Search插件如何实现阴影处理。

Int UI独立主题

独立主题可以在任何Compose for Desktop应用中使用。你可以像使用普通主题一样使用它,并且可以随心所欲地自定义它。 默认情况下,它与官方Int UI规范相匹配。

有关如何设置独立应用的示例,你可以参考standalone示例

[!警告] 请注意,Jewel需要JetBrains Runtime才能正确工作。一些功能如字体加载依赖于它, 因为它具有其他JDK中不可用的额外功能和UI功能补丁。 我们不支持在任何其他JDK上运行Jewel。

要在非IntelliJ Platform环境中使用Jewel组件,你需要将UI层次结构包装在IntUiTheme 可组合函数中:

IntUiTheme(isDark = false) {
    // ...
}

如果你想对主题有更多控制,可以使用其他IntUiTheme重载,就像独立示例所做的那样。

自定义窗口装饰

JetBrains Runtime允许窗口具有自定义装饰,而不是常规标题栏。

独立示例中自定义窗口装饰的截图

独立示例应用展示了如何轻松获得类似JetBrains IDE的外观;如果你想进行非常 自定义,你只需依赖decorated-window模块,其中包含所有必需的原语,但不包含Int UI样式。

要获得类似IntelliJ的自定义标题栏,你需要将窗口装饰样式传递给主题调用,并在主题的顶层 添加DecoratedWindow可组合函数:

IntUiTheme(
   theme = themeDefinition,
   styling = ComponentStyling.default().decoratedWindow(
      titleBarStyle = TitleBarStyle.light()
   ),
) {
    DecoratedWindow(
        onCloseRequest = { exitApplication() },
    ) {
        // ...
    }
}

在IntelliJ Platform上运行:Swing桥

Jewel包含一个与IDE正确集成的关键元素:Swing组件 — 主题和LaF — 与Compose世界之间的桥梁。

这个桥梁确保我们获取当前IntelliJ主题中定义的颜色、排版、度量和图像,并将它们应用到Compose组件中。 这意味着Jewel将自动适应使用标准主题 机制的IntelliJ Platform主题。

[!注意] 使用非标准机制(例如为Swing组件提供自定义UI实现)的IntelliJ主题不受支持,也永远不会受支持。

如果你正在编写IntelliJ Platform插件,你应该使用SwingBridgeTheme而不是独立主题:

SwingBridgeTheme {
    // ...
}

支持的IntelliJ Platform版本

要在IntelliJ Platform中使用Jewel,你应该依赖适当的jewel-ide-laf-bridge-*构件, 它将带来必要的传递依赖。以下是当前支持的IntelliJ Platform版本和相应桥接代码所在的分支:

IntelliJ Platform 版本使用的分支
2024.2 (beta 1+)main
2024.1 (EAP 3+)releases/241
2023.3releases/233
2023.2 (已弃用)archived-releases/232
2023.1 或更早版本不支持

关于如何设置 IntelliJ 插件的示例,你可以参考 ide-plugin 样例

图标

在为 IntelliJ Platform 或独立应用构建时,你可以使用基于键的图标加载 API,它允许你以跨目标的方式加载图标。

从你的资源加载图标

要加载图标,你可以使用 Icon 可组合函数并提供带有资源路径的 PathIconKey

// 等同于旧的基于路径的 API
Icon(PathIconKey("icons/myIcon.svg"), contentDescription = "...")

构建你自己的 IconsKeys 文件

如果你想获得更好的体验,不必到处处理字符串和键,你可以构建自己的 *IconsKeys 文件。以我们的 AllIconsKeys 文件为参考。你可以手动维护它,或者根据需要自动生成它。

从 IntelliJ Platform 加载图标

如果你需要在独立应用中使用标准的 IntelliJ Platform 图标,比如那些在 AllIcons 中找到的图标,你需要进行一些设置以确保图标存在于类路径中并可以作为资源加载。

在你的构建脚本中添加以下内容:

dependencies {
   implementation("com.jetbrains.intellij.platform:icons:[ijpVersion]")
   // ...
}

repositories {
   // 根据你是否使用稳定版的 IJP 选择以下两者之一
   maven("https://www.jetbrains.com/intellij-repository/releases")
   maven("https://www.jetbrains.com/intellij-repository/snapshots")
}

[!注意] 如果你的目标是 IntelliJ 插件,你不需要这个额外的设置,因为平台本身提供了图标。

一旦图标在类路径上,你就可以使用 PlatformIcon 可组合函数:

// 对于在 AllIcons 中找到的平台图标
PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup")

旧 UI 和新 UI 图标

要使用的正确 IconKey 取决于图标是否有旧 UI 和新 UI 变体:

  • PathIconKey 表示来自资源的没有旧 UI 和新 UI 变体的图标
  • IntelliJIconKey 类似,但为旧 UI 和新 UI 变体提供路径

如果你只针对新 UI,你可以使用 PathIconKeyIntelliJIconKey 来加载图标。如果你针对旧 UI,你需要使用 IntelliJIconKey

图标运行时修补

Jewel 模拟了 IntelliJ Platform 在加载图标时幕后进行的操作。具体来说,资源在加载之前会经过一些转换。

例如,在 IDE 中,如果新 UI 处于活动状态,图标路径可能会被替换为不同的路径。SVG 图标中的一些关键颜色也会根据当前主题进行替换。参见文档

除此之外,即使在独立应用中,Jewel 也会根据当前主题选择适当的深色/浅色变体图标,对于位图图标,它会根据 LocalDensity 尝试选择 2x 变体。

如果你有一个_有状态的_图标,也就是说,如果你需要根据某些状态显示不同的图标,你可以使用 PainterProvider.getPainter(PainterHint...) 重载。然后你可以使用一个状态映射 PainterHint 让 Jewel 自动加载适当的图标:

// myState 实现 SelectableComponentState 并有一个 ToggleableState 属性
val myPainter by myPainterProvider.getPainter(
    if (myState.toggleableState == ToggleableState.Indeterminate) {
        IndeterminateHint
    } else {
        PainterHint.None
    },
    Selected(myState),
    Stateful(myState),
)

其中 IndeterminateHint 如下所示:

private object IndeterminateHint : PainterSuffixHint() {
    override fun suffix(): String = "Indeterminate"
}

假设 PainterProvider 的基本路径是 components/myIcon.svg,Jewel 将根据状态自动将其转换为正确的路径。如果你想了解更多关于这个系统的信息,请查看 PainterHint 接口及其实现。

字体

要加载系统字体,你可以通过其家族名称获取:

val myFamily = FontFamily("My Family")

如果你想使用 JetBrains Runtime 中嵌入的字体,你可以使用 EmbeddedFontFamily API:

import javax.swing.text.StyledEditorKit.FontFamilyAction

// 如果 JBR 中不存在匹配的字体家族,将返回 null
val myEmbeddedFamily = EmbeddedFontFamily("Embedded family")

// 在处理嵌入式字体时,建议加载一个后备字体家族
val myFamily = myEmbeddedFamily ?: FontFamily("Fallback family")

你可以通过使用 asComposeFontFamily() API 从任何 java.awt.Font(包括 JBFont)获取 FontFamily

val myAwtFamily = myFont.asComposeFontFamily()

// 这将尝试解析逻辑 AWT 字体
val myLogicalFamily = Font("Dialog").asComposeFontFamily()

// 这只在 IntelliJ Platform 中有效,
// 因为 JBFont 只在那里可用
val myLabelFamily = JBFont.label().asComposeFontFamily()

Swing 互操作性

由于这是 Compose for Desktop,你可以获得与 Swing 的良好互操作性。为了避免故障和 z 顺序问题,你应该在初始化 Compose 内容之前启用实验性 Swing 渲染管道

ide-laf-bridge 模块提供的 ToolWindow.addComposeTab() 扩展函数会为你处理这个问题。但是,如果你也想在其他场景和独立应用中启用它,你可以在 Compose 入口点(即在创建 ComposePanel 之前)调用 enableNewSwingCompositing() 函数。

[!注意] 新的 Swing 渲染管道是实验性的,在使用无限重复动画时可能会影响性能。这是 Compose Multiplatform 团队已知的问题,需要在 Java 运行时中进行更改才能解决。一旦在 JetBrains Runtime 中进行了必要的更改,我们将删除此通知。

使用 Jewel 编写的项目

以下是一些使用 Compose for Desktop 和 Jewel 的项目示例:

需要帮助?

你可以在 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.
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号