Menus and input
You can change any of Inkweaver’s menus, like the title screen, settings, and the save and load menus. 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.
You get a menu’s schema from the getMenuSchema selector. To change a menu, decorate getMenuSchema and check the menu’s name:
const SHOW_HINTS_DEFAULT = false;
decorateSelector("getMenuSchema", (context, next) => { const result = next(context);
switch (context.name) { case "settings": { 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 Settings 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 have it read from one of those three.
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 |
binding | A keyboard, mouse, touch, or gamepad shortcut the reader can change |
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. Readers can change their bindings from the Controls menu, so a binding’s inputs are stored as a setting, the same as a checkbox’s value.
Add bindings to the input menu. That’s the menu Inkweaver reads shortcuts from:
const OPEN_JOURNAL_DEFAULT = ["j", "", "gamepad:3"];
decorateSelector("getMenuSchema", (context, next) => { const result = next(context);
switch (context.name) { case "input": { const openJournal = { name: "openJournal", type: "binding", scope: "navigate", locales: { "en-US": "Open Journal", "ja-JP": "ジャーナルを開く", }, action: () => getCommands().openJournal(), value: () => getSettings().openJournalInputs ?? OPEN_JOURNAL_DEFAULT, setValue: (newValue) => updateSettings({ openJournalInputs: newValue }), };
return { schema: { ...result.schema, fields: [...result.schema.fields, openJournal], }, }; } default: { return result; } }});action runs whenever the reader presses one of the inputs that value returns. Until the reader changes them, those are j on the keyboard and the top face button on a gamepad. When they do change them, setValue stores the new list in your plugin’s settings.
The list goes by position. The first two entries are keyboard or mouse inputs, and the third is a gamepad button. An empty string leaves a position unused, which is why the example has one in the middle.
In the Controls menu, each binding is a row with two boxes for keyboard and mouse, and one for a gamepad button. The gamepad button is labeled the way the reader’s controller labels it. Keeping them separate means a reader who changes their gamepad bindings still has the keyboard to fall back on.
If two bindings in the same scope share an input, neither one runs, and the menu flags both so the reader can fix the clash.
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: "title" });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. If no menu is open, it opens the "title" menu. Otherwise, it closes the menu on top.
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 getMenuSchema to return a schema for it:
decorateSelector("getMenuSchema", (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.