Menu schema
A menu schema describes a menu and each of its fields. Inkweaver uses it to build the menu’s interface, and to store and act on the values the reader chooses. For a walkthrough, see Menus and input.
MenuSchema
Section titled “MenuSchema”Describes a menu.
interface MenuSchema { fields: Array<MenuField>; labels?: Array<string>; layer?: string; name: MenuName; scope?: Scope; type: string;}| Field | Description |
|---|---|
fields | The menu’s fields, in the order they’re shown. |
labels | Labels that choose between variations in the menu’s artwork, the same way labels choose a scene’s variations. |
layer | A selector for the layer of the menu’s artwork that the interface is drawn over. |
name | The menu’s name. |
scope | A scope to push when the menu opens, and release when it closes. |
type | The menu’s type. |
Labels
Section titled “Labels”A menu with no artwork of its own is drawn on the artwork of the menu beneath it on the stack, along with the labels of every menu drawn on that artwork. So an artist can make one PSD for every menu, and give each menu its own look with variation sets.
- A menu’s labels replace any labels beneath it in the same variation set.
- Labels that aren’t in any variation set don’t carry up to the next menu.
- A variation set that no label chooses shows its first variation.
layer is matched against the layers in the menu’s artwork, and the interface is drawn over the bounds of the layer it finds. Layer names match as classes, so a layer named UI matches .ui.
The layer has to be visible to be measured. To keep it from showing, leave it empty or set its opacity to zero. Don’t hide it.
name is keyof Menus. To declare a new menu, add its name to the Menus interface, then return a schema for that name from getMenuSchema.
Example
declare module "@inkweaverdev/inkweaver-sdk" { interface Menus { journal: true; }}decorateSelector("getMenuSchema", (context, next) => { const result = next(context);
switch (context.name) { case "journal": { return { schema: { ...result.schema, fields: [...result.schema.fields, journalOpacityField], }, }; } default: { return result; } }});Common fields
Section titled “Common fields”Every field in fields has these properties.
Identifies the field. Must be unique within the menu.
name: string;The field’s type, which decides how it’s shown and which other properties it has.
type: "button" | "separator" | "boolean" | "number" | "string" | "enum" | "binding";locales
Section titled “locales”The field’s text, one version per locale. The field is shown in the reader’s text language. If there’s no version for that language, it falls back to any version it has, then to the field’s name.
locales: Record<string, string>;Example
locales: { "en-US": "Show puzzle hints", "ja-JP": "パズルのヒントを表示",}Field types
Section titled “Field types”boolean, number, string, enum, and binding fields have a value function and a setValue function. The menu calls value after every change to state, settings, or view state, and shows the result right away. Keep value fast, and have it read from one of those three.
button
Section titled “button”A button that runs an action.
{ name: string; type: "button"; locales: Record<string, string>; action: () => Promise<void>;}separator
Section titled “separator”A visual break between groups of fields.
{ name: string; type: "separator"; locales: Record<string, string>;}boolean
Section titled “boolean”A toggle.
{ name: string; type: "boolean"; locales: Record<string, string>; value: () => boolean; setValue: (newValue: boolean) => void;}number
Section titled “number”A slider.
{ name: string; type: "number"; locales: Record<string, string>; minimum?: number; maximum?: number; step?: number; value: () => number; setValue: (newValue: number) => void;}Example
{ name: "journalOpacity", type: "number", minimum: 0.2, maximum: 1, step: 0.05, locales: { "en-US": "Background opacity" }, value: () => getSettings().journalOpacity ?? 0.9, setValue: (newValue) => updateSettings({ journalOpacity: newValue }),}string
Section titled “string”A text box.
{ name: string; type: "string"; locales: Record<string, string>; value: () => string; setValue: (newValue: string) => void;}A drop-down list. Each option has a value, and the text shown to the reader in locales.
{ name: string; type: "enum"; locales: Record<string, string>; options: Array<{ value: string; locales: Record<string, string>; }>; value: () => string; setValue: (newValue: string) => void;}Example
{ name: "textSpeed", type: "enum", locales: { "en-US": "Text speed" }, options: [ { value: "slow", locales: { "en-US": "Slow" } }, { value: "normal", locales: { "en-US": "Normal" } }, { value: "instant", locales: { "en-US": "Instant" } }, ], value: () => getSettings().textSpeed ?? "normal", setValue: (newValue) => updateSettings({ textSpeed: newValue }),}binding
Section titled “binding”A shortcut that runs an action when the reader presses one of its inputs.
{ name: string; type: "binding"; locales: Record<string, string>; scope: Scope; action: () => Promise<void>; value: () => Array<string>; setValue: (newValue: Array<string>) => void;}| Property | Description |
|---|---|
scope | Where the binding can run. See Scopes. |
action | Runs when the reader presses one of the binding’s inputs. |
value | Returns the binding’s inputs, in the input binding format. |
setValue | Stores the reader’s new list of inputs when they change it. |
The list of inputs 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. The menu shows the first three entries, and any entries after them still work.
When two bindings in the same scope share an input, neither one runs, and the menu flags both.
Example
{ name: "openJournal", type: "binding", scope: "navigate", locales: { "en-US": "Open journal" }, action: () => getCommands().openJournal(), value: () => getSettings().openJournalInputs ?? ["j", "", "gamepad:3"], setValue: (newValue) => updateSettings({ openJournalInputs: newValue }),}Input bindings
Section titled “Input bindings”An input is one of:
- One or more keys joined with
+. The main key is a lowercaseKeyboardEvent.keyvalue, and the space bar isspace. Modifiers can come in any order. click, for a mouse click or a tap.- A gamepad button.
When the reader records an input, it’s stored with its modifiers in alt, control, meta, shift order, then the key, joined with +.
Clicking an on-screen control, like a link or button, uses that control and doesn’t trigger any binding.
Examples
"escape";"meta + l";"shift + arrowright";"click";Gamepad bindings
Section titled “Gamepad bindings”A gamepad input is written as gamepad:<index>, where <index> is the button’s number in the standard gamepad layout. Buttons are numbered by position, so gamepad:0 is the bottom face button on every controller: A on Xbox, Cross on PlayStation, and B on Nintendo. The menu labels each button the way the reader’s controller does.
A gamepad binding runs once per press, so holding the button doesn’t repeat it. Every connected controller works.
Examples
"gamepad:0"; // bottom face button"gamepad:5"; // right bumper: RB, R1, or R"gamepad:9"; // start: Menu, Options, or Plus