-
Notifications
You must be signed in to change notification settings - Fork 0
Building Item GUI Bundles
The item GUI renders a container screen you describe in JSON: one background texture, a set of slots, some labels, and a library of items to put in the slots. You compile that description into a bundle with mcw-itemgui-preprocess, and the widget loads the bundle.
Slots and items are separate on purpose. The bundle defines what the screen looks like and which items exist. The embed decides which item goes in which slot, so one bundle can serve every recipe on your page. See items in the Item GUI reference.
git clone https://github.com/KabanFriends/mcwidgets.git
cd mcwidgets
npm ci
npm run build
You need Node 20.19 or later, or Node 22.12 or later. The build produces the preprocessor at packages/itemgui/bin/mcw-itemgui-preprocess.mjs.
Building needs network access. The preprocessor downloads vanilla item renders and container textures from public CDNs, pinned to one Minecraft version.
Three files, no images of your own.
my-gui/
guis/
crafting.json
items.json
guis/crafting.json describes the screen. The texture is a crop of the vanilla crafting table page, so the preprocessor downloads it for you.
{
"id": "crafting",
"texture": { "vanilla": "container/crafting_table", "w": 176, "h": 166 },
"slots": [
{ "name": "in1", "x": 30, "y": 17 },
{ "name": "in2", "x": 48, "y": 17 },
{ "name": "in3", "x": 66, "y": 17 },
{ "name": "in4", "x": 30, "y": 35 },
{ "name": "in5", "x": 48, "y": 35 },
{ "name": "in6", "x": 66, "y": 35 },
{ "name": "in7", "x": 30, "y": 53 },
{ "name": "in8", "x": 48, "y": 53 },
{ "name": "in9", "x": 66, "y": 53 },
{ "name": "result", "x": 124, "y": 35 }
],
"texts": [
{ "x": 29, "y": 6, "text": "Crafting" }
]
}items.json is the library of things that can appear in those slots.
{
"items": [
"stick",
{ "name": "diamond", "tooltip": "Diamond" },
{
"name": "diamond_sword",
"tooltip": [
"Diamond Sword",
{ "text": "Very sharp", "color": "gray", "italic": true }
]
}
]
}Build it:
node packages/itemgui/bin/mcw-itemgui-preprocess.mjs \
--input ./my-gui \
--output ./public/crafting
1 GUI(s), 3 item(s) -> wrote ./public/crafting/bundle.json
Then embed it, choosing the items per recipe:
<iframe
src="https://mcwidgets.kaban.sh/itemgui/embed.html?bundle=https://example.com/crafting/bundle.json&items=in1=diamond,in2=diamond,in3=diamond,in5=stick,in8=stick,result=diamond_sword"
style="width: 100%; height: 220px; border: 0;"
title="Crafting a diamond sword"
></iframe>my-gui/
guis/ every .json under here, at any depth, is one GUI
items.json the item library
textures/ your own PNG files, referenced by path from the input root
Only guis/ and items.json are required, and both can be moved with --gui and --items.
Custom texture paths in your JSON are resolved from the input root, so "texture": "textures/my_gui.png" means my-gui/textures/my_gui.png. Any directory name works. textures/ is just a habit.
Every JSON file here accepts _comment keys, at any level, and ignores them. Every other unknown field is an error, so a misspelled hilight stops the build instead of silently doing nothing.
| Field | Type | Notes |
|---|---|---|
id |
string | Required. Unique in the bundle. This is what gui selects at embed time. |
texture |
string or object | Required. See below. |
slots |
array | Required, may be empty. |
texts |
array | Optional. |
A path to your own PNG, resolved from the input root:
"texture": "textures/my_screen.png"Your file is copied into the bundle byte for byte, and its size is read from the PNG header. Draw it at Minecraft's own scale, one image pixel per GUI pixel, not at 2x or 4x.
Or a crop of a vanilla texture page:
"texture": { "vanilla": "container/crafting_table", "w": 176, "h": 166 }vanilla is a path under the game's textures/gui/ directory, without the extension. The preprocessor downloads the page and crops the top left w by h, which is the region the game itself blits. A crop larger than the source page is an error.
The texture's size is the widget's minimum size, and the GUI is centred in any container larger than that.
| Field | Type | Default |
|---|---|---|
x, y
|
integer, at least -8 | required |
name |
string |
slot1, slot2, and so on, by position |
w, h
|
integer, at least 1 | 16 |
highlight |
boolean | true |
x and y are the top left of the item cell, in the same coordinates the game's own slot definitions use, relative to the texture's top left. Copy them straight out of the game's screen code or measure them off your PNG.
name is what an embed writes in items, so pick names your page's authors will enjoy typing. result beats slot10.
highlight: false suppresses the hover ring for a cell that is decorative rather than a real slot. The tooltip still appears, which is what an armour-slot placeholder or a label chip wants.
A slot whose cell falls outside the texture produces a warning and is still emitted, so a deliberate overhang is possible. Negative coordinates are allowed down to -8.
| Field | Type | Default |
|---|---|---|
text |
text component | required |
x, y
|
integer | required |
shadow |
boolean | false |
Labels are drawn under the items, in the texture's coordinates. shadow adds the 1 pixel drop shadow the game uses for text over dark backgrounds. Container labels have no shadow, which is why the default is off.
text accepts anything Text and fonts describes, including translate components.
items.json is one object with one items array. Names must be unique across the whole library.
"stick"The name and the item id are the same, and there is no tooltip.
{ "name": "diamond", "tooltip": "Diamond" }The name is used as the item id unless you give an id, which lets you have two library entries for the same item:
{ "name": "cursed_sword", "id": "minecraft:iron_sword", "tooltip": "Cursed Sword" }An id with no namespace is read as minecraft:.
{ "name": "soul_shard", "texture": "textures/soul_shard.png", "tooltip": "Soul Shard" }texture replaces the icon entirely and id is not consulted. The file is resolved from the input root and copied into the bundle unchanged. Draw it 16 by 16, or at whatever size you want it to occupy in GUI pixels.
A group cycles through other items in place, the way the game cycles through the possible ingredients of a recipe.
{
"name": "any_wool",
"interval": 1.5,
"items": [
{ "name": "white_wool", "tooltip": "White Wool" },
{ "name": "orange_wool", "tooltip": "Orange Wool" },
"diamond"
]
}| Field | Type | Default |
|---|---|---|
name |
string | required |
items |
array of names or inline definitions | required, non-empty |
interval |
seconds, greater than 0 | 1 |
Members are either the name of another library item or an inline definition, which becomes a library entry of its own. Groups cannot contain groups.
Bind the group's name to a slot and the slot cycles. The tooltip changes with it. Set the widget's static option to freeze every group on its first member.
A group carries no icon and no tooltip of its own. Those come from whichever member is showing.
| Option | Effect |
|---|---|
--input <dir> |
Input directory. Required. |
--output <dir> |
Output directory. Default ./dist. |
--items <file> |
Item library path. Default <input>/items.json. |
--gui <file> |
One GUI definition. Repeatable, and replaces the guis/ scan entirely. |
--bake-lang <file.json> |
Resolve translate components at build time. See Text and fonts. |
--icon <id>=<file or url> |
Replace one vanilla item's icon. Repeatable. |
--icon-map <file.json> |
The same overrides as a JSON object of id to file or URL. |
--icon-source <ref> |
Git ref of the item render source. Default master. |
--source <string> |
A provenance string recorded in the bundle's metadata. |
--single-file |
Inline every texture and icon as a data: URI. |
--quiet |
Suppress warnings. |
--json-errors |
Print errors and warnings as JSON. |
--version |
Print the preprocessor version. |
--help |
Print the option list. |
public/crafting/
bundle.json
guis/crafting.png one per GUI, named by its id
icons/minecraft/stick.png one per vanilla item referenced
items/soul_shard.png your custom textures, path preserved
bundle.json references everything by relative path, resolved against the bundle's own URL, so move the directory as a unit. Serve all of it. If the page that runs the widget is on another origin, bundle.json needs Access-Control-Allow-Origin and the images beside it do not.
--single-file folds every image into bundle.json as a data: URI, giving you one file to host at the cost of about a third more bytes and no separate image caching.
Put more definitions under guis/. Each needs a unique id, and they share the one item library.
An embed then has to say which one it wants with the gui setting. Leave it out and the mount fails with the valid ids listed, which is a better outcome than rendering an arbitrary one.
The build stops, naming the file, when:
- Two GUIs share an
id, or two library items share aname. - A field is unknown, anywhere in either file.
- A slot's
xoryis not an integer, or is below -8. - A referenced texture file is missing.
- A vanilla crop is larger than the page it comes from.
- A group has an empty
itemsarray, contains another group, or references a name that is not in the library. - A definition mixes
itemswithtexture,id, ortooltip. -
intervalappears on something that is not a group.
A vanilla icon that cannot be downloaded becomes the missing-texture square and prints a warning, because one absent icon is not worth failing a build over.
Embedding
Reference
Building bundles
Running it yourself