Repository navigation
quickstart
下文的
0.3.0坐标指本地构建;该版本尚未发布。公开 beta3 仍为0.1.0-beta,不包含新控件。本地控件接线另见 controls及三端examples/a/<target>/.../ControlsRecipe.java。完整 pointer/key-up/wheel/char/window-focus/removed 转发均为库优先,不能只抄 press/release 或再次乘像素比。
This is the shortest path from an empty mod to a painted Skija screen. The compiled reference is examples/a (design package) and examples/b (raw Skija); this page is the wiring those examples assume.
Put one platform jar in mods/ next to your own mod:
| Minecraft | Loader | Jar |
|---|---|---|
| 1.20.1 | Forge | atomlib-Forge-1.20.1-vVERSION.jar |
| 1.21.1 | Fabric | atomlib-Fabric-1.21.1-vVERSION.jar |
| 1.21.1 | NeoForge | atomlib-NeoForge-1.21.1-vVERSION.jar |
Declare it in development against the same coordinate:
repositories { maven { url = uri('/absolute/path/to/AtomLib/build/test-maven') } }
dependencies {
// Fabric: modImplementation; Forge and NeoForge: implementation
implementation 'com.atom.lib:atomlib-1.20.1-forge:0.3.0'
}Do not compile or bundle a second Skija or LWJGL Yoga into your own artifact. AtomLib already provides both, including the Windows x64/ARM64 natives.
RuntimeHandle runtime = AtomLibRuntime.open("myui");
FontRegistry fonts = new FontRegistry(runtime);
TextService text = new TextService(fonts);
YogaLayoutEngine layout = new YogaLayoutEngine();AtomLib ships a default font: a subsetted Noto Sans SC (GB2312-complete hanzi,
kana, Latin-1 supplement, punctuation and kaomoji symbols; charset contract in
tools/default-font-charset.txt). Call fonts.registerDefaults() once and
text renders CJK out of the box. Host-supplied fonts keep priority: register
your own as the primary id, and the bundled faces join the fallback chain
behind whatever you set.
FontId body = new FontId("myui.body");
fonts.register(body, FontSource.file(Path.of("C:/Windows/Fonts/msyh.ttc")));
// or: fonts.register(body, FontSource.bytes(myBundledTtfBytes));
fonts.registerDefaults(); // appended behind your own fallbacks
// Set host fallback IDs before registerDefaults() so bundled defaults remain available.Emoji, Hangul words and GBK extensions are deliberately not in the default
package; hosts needing more coverage rerun the subsetting script
(tools/subset_default_font.py).
Yoga only knows boxes; your data never reaches it.
Viewport viewport = new Viewport(panelWidth, panelHeight, pixelRatio);
LayoutSnapshot measured = layout.layout(tree, viewport);
PresentationFrame frame = PresentationFrame.create(
tree, measured, viewport, Map.of(), new PresentationFrame.Offset(0, 0));PresentationFrame gives border, content and hit rectangles plus clipping for each NodeId, in paint order.
MinecraftRenderBridge.get().drawFrame((canvas, borrowed) -> {
for (NodeId id : frame.paintOrder()) {
LayoutBox box = frame.box(id);
// UiCards.drawCard(canvas, ...) or your own canvas.drawRect(...)
}
TextLayout line = text.layout(
new TextRequest("Hello", body, 16F, 0xFF000000, 2, true),
new Constraints(Constraints.Axis.atMost(boxWidth), Constraints.Axis.undefined()));
text.draw(canvas, line, x, y);
});Only Skija drawing belongs in that callback. Vanilla GuiGraphics work goes through vanillaPass, and GL state the host needs back is restored when the callback exits.
ScreenInputAdapter turns screen callbacks into InputDispatcher calls, so
you forward one line per callback instead of mapping events yourself. It has
no Minecraft types and is identical on every loader.
HitTester hit = (x, y) -> {
var order = frame.paintOrder();
for (int i = order.size() - 1; i >= 0; i--) {
NodeId id = order.get(i);
if (frame.box(id).hit().contains(x, y) && isInteractive(id)) return id;
}
return null;
};
InputDispatcher dispatcher = new InputDispatcher(hit);
dispatcher.setTargets(Map.of(buttonId, myButtonTarget));
ScreenInputAdapter input = new ScreenInputAdapter(dispatcher)
.onScroll((x, y, dx, dy) -> { myScrollView.scrollBy(dx, dy); return true; });Then forward from your Screen — one line per callback. Both mappings name the callbacks the same; the only version difference is the scroll signature (three arguments on 1.20.1, horizontal plus vertical on 1.21.1):
// 1.20.1 Forge (Mojmap) — mouseScrolled has one delta; 1.21.1 Fabric/NeoForge
// has mouseScrolled(double x, double y, double horizontalAmount, double verticalAmount)
@Override public boolean mouseClicked(double x, double y, int button) {
return input.press(x, y, button) || super.mouseClicked(x, y, button);
}
@Override public boolean mouseDragged(double x, double y, int button, double dx, double dy) {
return input.drag(x, y, button, dx, dy) || super.mouseDragged(x, y, button, dx, dy);
}
@Override public boolean mouseReleased(double x, double y, int button) {
return input.release(x, y, button) || super.mouseReleased(x, y, button);
}
@Override public boolean mouseScrolled(double x, double y, double delta) { // 1.20.1
return input.scroll(x, y, 0, delta) || super.mouseScrolled(x, y, delta);
}
@Override public void removed() { // same name on Fabric (Yarn) and Forge (Mojmap)
input.removed(); // ends the active gesture with a cancel, never a release
super.removed();
}Notes:
- Coordinates stay in Minecraft's GUI-scaled screen space, the same space
your viewport and hit rectangles use; no conversion is needed. If your
unit space differs,
input.scale(multiplier)multiplies every coordinate before the dispatcher sees it (it returns the adapter, so keep the result). - Scroll is not a
PointerEventtype. The dispatcher never sees wheel events; youronScrollhandler decides, and it can consultdispatcher.captureOwner()if it wants to respect an active drag. - While a gesture is captured, the dispatcher owns drag and release for the captured node — it never re-runs the hit test, and a press that opened a modal or removed the target is cancelled rather than committed.
- The adapter's press/drag/release return true only when the dispatcher
consumed the event; false falls through to
superso vanilla widgets and keyboard focus handling keep working.
Create the runtime, fonts, text service and Yoga engine on the render thread and keep them for the session. Yoga is single-threaded: layout, paint and close() must happen on that thread. Do not close the shared render bridge when a screen closes; AtomLib releases it on client shutdown.
Import com.atom.lib.design.atomchat for UiTokens, SkiaDraw, UiCards and AppIcons. Without a host the tokens use AtomChat's shipped defaults; call Design.host(DesignHost) to feed your own accent, corner radius, card tint and panel colours. See design-package.md.
可组合模态的稳定控件、滚动和关闭示例见 controls.md。当前源码候选也包含原版物品图标适配;真实游戏验证仍待完成。
原版物品图标接线与生命周期见 item-icons.md。先预留图像槽、产生当前帧布局,再在桥的 Skija pass 外准备并在本层采样。