主题系统(ThemeAPI / IThemeApi)

基本信息

属性 值
包路径 com.cleanroommc.modularui.theme(18 个文件)+ api/ITheme.java、api/IThemeApi.java
入口 IThemeApi.get() → ThemeAPI.INSTANCE(api/IThemeApi.java:107-109)
加载器 ThemeManager(theme/ThemeManager.java:42,@SideOnly(Side.CLIENT))
主题实现 AbstractTheme(:98 行处)→ DefaultTheme、SelectableTheme
重载时机 注册为资源重载监听器(ClientProxy.java:180)
指令 /reloadThemes(theme/ThemeReloadCommand.java:16-17,extends CommandBase)

功能

主题的三层结构

DEFAULT(DefaultTheme.INSTANCE,Java 硬编码兜底)
  ↑ parent 继承
JSON 主题文件(assets/<任意域>/themes/<name>.json)
  ↑ 由 themes.json 索引
themes.json 索引(assets/<任意域>/themes.json)

真实外观数据在 Java 里(DefaultTheme 由 ThemeAPI 的部件键推导默认值), JSON 只是覆盖层。仓库自带的 3 个 JSON 主题很薄:

文件 行数 内容
assets/modularui2/themes/vanilla.json 2(无末尾换行) 仅 {"parent": "DEFAULT"}
assets/modularui2/themes/vanilla_dark.json 9 覆盖 color: 0x444444、textColor: 0xEEEEEE、textShadow: false、scrollbar 子对象(color: 0x666666、textColor: 0xCCCCCC)
assets/modularui2/themes/context_menu.json 27 parent: DEFAULT + panel / button / toggleButton 三个部件覆盖

主题索引 themes.json

src/main/resources/assets/modularui/themes.json 的完整内容:

{
  "vanilla": "modularui2:vanilla",
  "vanilla_dark": "modularui2:vanilla_dark",
  "modularui.context_menu": "modularui2:context_menu",
  "screens": { "modularui:test": "vanilla_dark" }
}
  • 3 个主题名 → 主题资源位置
  • screens 段把界面 id 映射到主题(ThemeManager.loadScreenThemes,由 ThemeManager.java:81 调用)

⚠️ 该文件位于 assets/modularui/(旧的 modularui 域), 而主题 JSON 本身在 assets/modularui2/themes/。 这不是笔误但也非必需:AssetHelper.findAssets(String) (utils/AssetHelper.java:31-37)会遍历所有资源域查找 themes.json, 放在哪个域都能被发现。放在 modularui 域的效果是原版 ModularUI 也能读到这些主题。

主题查找优先级

ThemeAPI.getThemeIdForScreen(String mod, String name, String panelName) (theme/ThemeAPI.java:72-91)按以下顺序查找,命中即返回:

  1. mod:name:panel(JSON 声明)
  2. mod:name(JSON 声明)
  3. mod(JSON 声明,整 mod 默认)
  4. mod:name:panel(Java 声明)
  5. mod:name(Java 声明)
  6. mod(Java 声明)
  7. 都没有 → ModularUIConfig.useDarkThemeByDefault ? "vanilla_dark" : "vanilla"(:69)

对外 API

IThemeApi(api/IThemeApi.java)标注 @ApiStatus.NonExtendable(:30), 暴露 8 个方法/属性:

成员 行 用途
FALLBACK 部件键 :34 绝对兜底部件主题
registerTheme(String, JsonBuilder) :138 Java 侧注册主题(资源包优先级更高)
registerTheme(ThemeBuilder<?>) :145 上者的构建器重载
getJavaDefaultThemes(String) :155 取某主题已注册的 JSON 列表
getThemeForScreen(owner, name, panel, defaultTheme, fallbackTheme) :178 按界面取主题
registerThemeForScreen(String screen, String theme) :220 Java 侧绑定界面→主题
registerWidgetTheme(id, default, defaultHover, parser) :231 注册自定义部件主题
getWidgetThemeKeys() :238 取全部已注册部件键

部件主题 id 有格式校验:必须匹配 [a-zA-Z0-9$_-]+(ThemeAPI.java:25), 否则抛 IllegalArgumentException(ThemeAPI.java:111);重复注册抛 IllegalStateException (ThemeAPI.java:108)。

交互

触发 行为
客户端资源重载 ThemeManager.reload()(ThemeManager.java:47):发 ReloadThemeEvent.Pre → 清空主题 → 重新扫描 → 校验祖先链 → 发 ReloadThemeEvent.Post
/reloadThemes 指令 ThemeReloadCommand 手动重载
其他 mod 注册主题 IThemeApi.get().registerTheme(id, jsonBuilder)

ThemeAPI.onReload()(:135)每次重载都会 themes.clear() + jsonScreenThemes.clear() 并只重新放入 DEFAULT_THEME(:136-138), 所以其他 mod 必须在资源重载后重新注册自己的主题。

相关条目

  • 部件主题 - 13 个内置部件主题键与 JSON 属性
  • 配置项 - use_dark_theme_by_default 决定最后一级兜底
  • 控件总览 - 各控件使用哪些部件主题