Menus and input
You can change any of Inkweaver’s menus, like the main menu, the pause menu, the options, and the save and load screens. That includes their background art and the options they show. You can also create menus of your own, and let readers control your features with keyboard, mouse, and gamepad shortcuts.
For the full details of every property, see the menu schema reference.
Menu schemas
Section titled “Menu schemas”Every menu is described by a schema: an object that lists the menu’s fields. Inkweaver uses the schema to build the menu’s interface when it’s shown.
The buildMenuSchema command builds a menu’s schema each time the menu is drawn. To change a menu, decorate buildMenuSchema and check the menu’s name:
const SHOW_HINTS_DEFAULT = false;
decorateCommand("buildMenuSchema", async (context, next) => { const result = await next(context);
switch (context.name) { case "options": { const showPuzzleHints = { name: "showPuzzleHints", type: "boolean", locales: { "en-US": "Show puzzle hints", "ja-JP": "パズルのヒントを表示", }, value: () => getSettings().showPuzzleHints ?? SHOW_HINTS_DEFAULT, setValue: (newValue) => updateSettings({ showPuzzleHints: newValue }), };
return { schema: { ...result.schema, fields: [...result.schema.fields, showPuzzleHints], }, }; } default: { return result; } }});This adds a “Show puzzle hints” option to the Options menu.
value returns the field’s current value, and setValue stores a new one when the reader changes it. Here, both use your plugin’s settings.
The menu calls value again whenever state, settings, or view state change, so the reader sees their choice as soon as setValue stores it. Because value runs often, keep it fast, and only read from state, settings, or view state.
Field types
Section titled “Field types”A field’s type decides what the reader sees:
| Type | Shows as |
|---|---|
button | A button that runs an action |
separator | A visual break between fields |
bookmark | A save slot |
boolean | A checkbox |
number | A slider, set by minimum, maximum, and step |
string | A text box |
enum | A drop-down list of options |
The example above is a boolean, so it shows as a checkbox. See field types for every property of each type.
Input bindings
Section titled “Input bindings”A binding is a shortcut: an input, like a key press or a click, that runs an action.
The getInputBindings selector returns every binding. To add your own, decorate it:
decorateSelector("getInputBindings", (context, next) => { const result = next(context);
const openJournal = { name: "openJournal", scope: "navigate", inputs: ["j", "gamepad:3"], action: () => getCommands().openJournal(), };
return { bindings: [...result.bindings, openJournal] };});action runs whenever the reader presses one of the binding’s inputs. Here, that’s j on the keyboard or the top face button on a gamepad.
Inkweaver ignores the reader’s input until action finishes.
If two bindings in the same scope share an input, the one earlier in the list runs.
For the input format, see input bindings.
Scopes
Section titled “Scopes”A binding’s scope decides where it can run. Only one scope is active at a time: the one on top of the scope stack. When the reader presses an input, Inkweaver checks the bindings for the active scope first, then falls back to "global". A binding scoped to "global" can run anywhere.
To add a scope for your own interface, extend the Scopes interface:
declare module "@inkweaverdev/inkweaver-sdk" { interface Scopes { journal: true; }}To make it active, push it with the pushScope command, and release it when you’re done:
const { release } = await getCommands().pushScope({ name: "journal" });
// Later, when the journal closes:release();While "journal" is on top of the scope stack, only bindings scoped to "journal" or "global" run.
Showing menus
Section titled “Showing menus”Menus sit in a stack. To show a menu, add it to the stack with the pushMenu command:
const { release } = await getCommands().pushMenu({ name: "options" });If the menu’s schema has a scope, pushMenu pushes that scope too.
To close the menu, call the release function that pushMenu returned. This removes the menu, and its scope if it has one, and shows whatever was underneath.
To open and close menus with a single command, use toggleMenu. With a story open and no menu showing, it opens the pause menu. With a menu showing, it closes the menu on top, unless there’s nothing to go back to.
await getCommands().toggleMenu();Creating a menu
Section titled “Creating a menu”First, add your menu’s name to the Menus interface:
declare module "@inkweaverdev/inkweaver-sdk" { interface Menus { journal: true; }}Then decorate buildMenuSchema to return a schema for it:
decorateCommand("buildMenuSchema", async (context, next) => { switch (context.name) { case "journal": { return { schema: { name: "journal", type: "journal", fields: [], }, }; } default: { return next(context); } }});Add the fields you want to show to fields. See the menu schema reference for every field type.
Menu artwork
Section titled “Menu artwork”Menu art works the same way as art for characters and scenes, and it supports variation sets and placeholder slots.
Inkweaver finds a menu’s image with the resolveImage command, using the menu’s schema as the key ({ menu: MenuSchema; type: "menu" }). It matches names the same way as images for characters and scenes.
Menus ignore placeholder slots by default. To use them, decorate the resolvePlaceholder command.