NarutoUI 是一个面向 Minecraft 客户端模组的 Modern UI 配置界面库。
它不提供某一套固定的配置内容,而是提供一整套界面骨架:导航栏、页面、分区、字段、主题、背景、本地化、流光、文件选择、颜色选择和图片裁剪。其他模组只需要声明“要显示哪些页面、每个页面有哪些字段”,不需要重复编写布局和控件代码。
第一次使用?建议先读 EASY_START.md,它按“接入 → 最小界面 → 自定义布局 → 背景/流光”的顺序讲解。完整 API 参考见 API-TUTOR.md。想看 NarutoOverlay 的真实代码怎么用这套 API,读 EXAMPLE-BASED-TUTOR.md。
- 声明式页面 / 分区 / 字段 DSL
- 可链式构建的
MuiLayout(LinearLayout子类) - 可折叠卡片
MuiCollapsibleCard(加号/减号展开,带动画) - 通用控件
MuiNumberField/MuiChip/MuiTextAction/MuiLogo/MuiHintBadge/MuiColorField/MuiDescriptionRow - 可滚动、居中、限制宽度的页面
- 自动生成 section 卡片、行布局和分隔线
- switch、slider、value row、button、text 等常用字段
- 可配置主题、圆角、间距、内容宽度
- 透明、黑屏、纯色、单图、轮播背景
- 翻译键直接作为标签使用
- 独立的 shimmer 流光系统
- 颜色选择器、图片裁剪框、原生文件/文件夹选择器
- 每屏独立的控件工厂和 shimmer profile
- 进程内页面恢复与 stable recipe 注册
NarutoUI 是界面库,不负责保存配置。字段回调只负责修改你的配置对象,保存逻辑由调用方实现。
import me.kall.narutoui.mui.screen.MuiSettingsScreen;
import net.minecraft.client.gui.screens.Screen;
public final class MyConfigScreen {
public static Screen create(Screen parent) {
return MuiSettingsScreen.builder("Example Mod")
.subtitle("Client settings")
.page("General", page -> page
.section("Rendering", section -> section
.toggle("Enable HUD",
MyConfig::enabled,
MyConfig::setEnabled)
.slider("Scale", 50, 200,
MyConfig::scale,
MyConfig::setScale,
value -> value + "%")))
.create(parent);
}
}页面的 header、卡片、字段行、分隔线和控件样式都由 API 生成。
如果配置内容很少,不需要左侧页面导航,可以启用单页模式:
Screen screen = MuiSettingsScreen.builder("Example Mod")
.singlePage()
.page("General", page -> page
.section("Rendering", section -> section
.toggle("Enable HUD", MyConfig::enabled, MyConfig::setEnabled)
.slider("Scale", 50, 200,
MyConfig::scale, MyConfig::setScale,
value -> value + "%")))
.create(parent);单页模式下:
- 不创建左侧导航栏
- 只允许添加一个页面
- 页面直接占满内容区
railHeader/railFooter/subtitle/footer不再显示- 页面可以通过
.page("", ...)省略标题
如果添加了多个页面,构建时会抛出 IllegalStateException。
多页界面默认使用左侧竖向导航,也可以改成顶部横向导航:
MuiSettingsScreen.builder("Example Mod")
.topNavigation()
.page("General", page -> ...)
.page("About", page -> ...)
.create(parent);也可以显式指定:
.navigation(MuiNavigation.TOP)
.navigation(MuiNavigation.LEFT)顶部导航会显示标题、副标题和横向排列的页面项;页面较多时导航区域可以横向滚动。
API 自带一套 shimmer 控件工厂:
MuiSettingsScreen.builder("Example Mod")
.widgetFactory(MuiWidgetFactory.shimmer())
.theme(MuiTheme.modern(context)
.withPrimary(0xFFE0A64A)
.withOnPrimary(0xFF000000))
.page("General", page -> ...)
.create(parent);它会统一创建:
ShimmerTextViewShimmerSwitchShimmerSeekBarShimmerButton
实心按钮默认使用 theme.onPrimary(),文字不参与流光;描边按钮文字继续流光。调用方只需要提供主题颜色,不需要自己创建这些控件。
.section("Rendering", section -> section
.toggle("Enable HUD", "Draw the overlay", MyConfig::enabled, MyConfig::setEnabled)
.slider("Scale", 50, 200, MyConfig::scale, MyConfig::setScale, value -> value + "%")
.value("FPS", MyMetrics::fps)
.text("Changes apply immediately.")
.button("Reset", MyConfig::reset))常用字段:
toggle(title, BooleanSupplier, BooleanConsumer)toggle(title, description, BooleanSupplier, BooleanConsumer)slider(title, min, max, IntSupplier, IntConsumer)slider(..., IntFunction<CharSequence> formatter)value(title, CharSequence / IntSupplier)button(label, Runnable)text(CharSequence)custom(View / Consumer<LinearLayout>)
MuiLayout 是 LinearLayout 的链式封装。它本身仍然是 LinearLayout,可以直接传给任何接受 View 或 LinearLayout 的 API;所有配置方法都返回自身:
MuiLayout card = MuiLayout.card(context, theme)
.margin(0, 12, 0, 0)
.spacing(8)
.heading("Appearance")
.text("Changes apply immediately.")
.button("Reset", MyConfig::reset);可用的构建方法:
column(Context, MuiTheme)/row(Context, MuiTheme)/card(Context, MuiTheme)horizontal()/vertical()/gravity(int)/center()/centerVertical()padding(dp)/padding(left, top, right, bottom)margin(dp)/margin(left, top, right, bottom)(在加入父容器后仍然生效)spacing(dp)(使用布局的 divider 槽位实现子项间距)minHeight(dp)/background(color / Drawable)add(View)/add(View, LayoutParams)/addWeighted(View, weight)/addAll(View...)space(dp)/flexibleSpace()text(...)/heading(...)/divider()/button(...)
低层页面已经提供了对应的入口:
page.custom(ui -> {
ui.contentLayout()
.spacing(8)
.add(ui.switchRow("Enable HUD", null, true, value -> {}));
MuiLayout card = ui.sectionLayout("Rendering")
.add(ui.valueRow("Version", "1.0.0"));
});MuiSettingsSection 还提供 cardLayout() 和 customLayout(Consumer<MuiLayout>):
.section("Rendering", section -> section
.toggle("Enable HUD", MyConfig::enabled, MyConfig::setEnabled)
.customLayout(row -> row
.spacing(6)
.add(section.page().valueRow("Version", "1.0.0"))))MuiCollapsibleCard 把“常驻标题栏 + 可展开内容”封装成一个卡片。标题栏左侧是 + / -,点击标题栏会以动画展开或收起 body:
// enabledSwitch 是你自己的开关控件,例如 MuiControls.switchControl(...)
MuiCollapsibleCard.card(context, theme, "FPS", "1 / 4 字段 · (10, 5)", false)
.trailing(enabledSwitch)
.summary("1 / 4 字段 · (10, 5)")
.body(body -> body
.add(MuiControls.text(context, theme, "Content", 12, theme.onSurfaceVariant())));卡片、输入框、chip 和文本操作是彼此独立的组件,可以像 MuiLayout 一样自由组合:
MuiLayout.row(context, theme)
.spacing(6)
.add(MuiNumberField.create(context, theme, 10)
.bounds(0, 100)
.width(64)
.onChange(value -> { }))
.add(MuiChip.create(context, theme, "Label", true)
.onChanged(checked -> { }))
.add(MuiTextAction.create(context, theme, "Reset", () -> { }));MuiNumberField:只接受数字、支持范围 clamp、onChange回调和链式value/bounds/width/gravity/enabledMuiChip:背景参与 shimmer 的小开关 chip,文字固定黑色;支持label/checked/isChecked/enabled/onChangedMuiTextAction:紧凑的 primary 文本操作;支持text/label/enabledMuiLogo:带圆形 shimmer 背景的 rail / header 标志;支持glyph/size/color,自动管理 shimmer 注册MuiHintBadge:带 shimmer 描边的提示胶囊;支持text/color/textSize,文字和描边都可参与流光MuiColorField:色块 + 标签 + pick 按钮,适合边框色 / 强调色;支持label/color/enabledMuiDescriptionRow:标题 + 可选说明,适合用法提示和只读说明行;支持title/description
MuiLogo.create(context, theme, "N").color(accent).textColor(onAccent);
MuiHintBadge.create(context, theme, "ESC").color(accent).text("ESC");
MuiColorField.create(context, theme, "Border color", argb)
.label("Border color")
.buttonLabel("Pick")
.onPick(() -> { });
MuiDescriptionRow.create(context, theme, "Drag", "Drag a card to move it.")
.description("Changed");
MuiCardEditor已从 API 中移除,原来的Model/Field适配层不再需要。卡片直接组合MuiCollapsibleCard+ 这些通用控件;NarutoOverlay 的 Cards 页面就是这样实现的。
MuiTheme theme = MuiTheme.modern(context)
.withPrimary(0xFFE0A64A)
.withSurface(0xCC101116)
.withCornerRadius(14);
MuiBackground background = MuiBackground.slideshow(files, 5.0D, 0.8D, 0.35D);可用背景:
MuiBackground.transparent()
MuiBackground.black()
MuiBackground.color(0xCC101116)
MuiBackground.scrim()
MuiBackground.image(path, dim)
MuiBackground.image(path, cropX, cropY, cropWidth, cropHeight, dim)
MuiBackground.slideshow(files, interval, fade, dim)所有公开标签参数都可以直接写翻译键:
.page("mymod.config.general", page -> page
.section("mymod.config.rendering", section -> section
.toggle("mymod.config.enabled",
"mymod.config.enabled.desc",
MyConfig::enabled,
MyConfig::setEnabled)))标签会通过 MuiLocalization 解析;不存在对应键时回退到原始字符串。
带参数的文本:
.value("mymod.config.memory",
MuiLocalization.translatable("mymod.config.megabytes", 8192))MuiShimmerProfile profile = MuiShimmerProfile.create(new MyShimmerConfig());
MuiSettingsScreen.builder("Title")
.shimmerProfile(profile)
.page(...)
.create(parent);每个 profile 拥有独立的时间、速度、密度、强度和颜色 ramp。MuiShimmerSettings 用于配置默认 HUD profile。
自定义控件实现 ShimmerTarget 后,通过 MuiShimmer.attach/detach 接入即可。
MuiFilePicker.openImage("Choose an image", "Images", callback);
MuiFilePicker.openFolder(initialPath, "Choose a folder", callback);MuiColorPickerOverlay:HSV 色轮、亮度、Alpha、HEX 和 Apply/CancelMuiCropOverlay:固定宽高比的图片裁剪框
两者的标签都通过 Labels 传入,支持翻译键。
默认的 MuiConfigScreen.create(parent) 使用进程内 registry。MuiConfigFragment 有 public 无参构造,可以在 FragmentManager 重建时恢复当前页和滚动位置。
需要跨进程重启恢复时,注册 stable recipe:
MuiSettingsScreen.register("mymod:config", "My Mod", builder -> builder
.page("General", page -> page
.section("Rendering", section -> section
.toggle("mymod.config.enabled",
MyConfig::enabled,
MyConfig::setEnabled))));
Screen screen = MuiSettingsScreen.createRegistered("mymod:config", parent);recipe 必须在客户端初始化时重新注册,且不能依赖一次性局部状态。
gradlew build
产物:
build/libs/narutoui-xxx-xxx.jar
src/main/java/me/kall/narutoui/mui/
├── screen/ 入口、Fragment、页面、section、MuiScreenHost
├── widget/ 链式布局与通用控件
├── theme/ MuiTheme、控件工厂
├── background/ 静态/图片/幻灯片背景、可配置背景
├── shimmer/ 流光 profile、绘制原语、流光控件
├── overlay/ 颜色选择、裁剪浮层
└── util/ 本地化、文件/文件夹选择
- Modern UI 是硬依赖,配置界面只能在客户端创建。
- API 不负责配置持久化。
- 页面在 screen 创建时预先构建;页面很多时创建会变慢。
- Supplier 字段每 100 ms 轮询,不是事件推送。
- 静态 slider 数值标签不会自动刷新,需要实时读数时使用 supplier + formatter 版本。
- stable recipe 不是配置文件持久化;进程重启后需要在客户端初始化时重新注册。
LGPL-v3,作者 Kasualix 与 DeepSeek-V4.1-Flash-1017。