audio-code 0.2.0

A simple, high level game engine for audio games: 3D sound, speech, virtual menus, physics and timing. No graphics required.
Documentation
# Virtual UI

`ctx.ui` provides a user interface that is spoken instead of drawn. Each element announces
itself through speech, plays short interface sounds, and is controlled from the keyboard.

Widgets live on a **stack**. The top widget receives the keyboard. When it closes, the
widget below speaks its current item again. This makes sub menus easy.

While any widget is open, `ctx.input` reports no keys to your game. This prevents the
Enter that picks a menu item from also making your character jump. Use `ctx.input.raw()`
if you need to see the keys anyway.

## Menus

```rust
ctx.ui.open(
    Menu::new("options", "Options")
        .slider("volume", "Volume", 80.0, 0.0, 100.0, 5.0)
        .choice("difficulty", "Difficulty", &["Easy", "Normal", "Hard"], 1)
        .checkbox("hints", "Speak hints", true)
        .text_field("name", "Player name", "Hero")
        .password_field("password", "Password")
        .label("Changes are saved automatically")
        .button("back", "Back"),
);
```

Every item starts with an **id** and a **label**. The id (`"volume"`) is for your code: it
is how you recognize the item in events and read its value. The label (`"Volume"`) is what
the player hears. The other arguments:

| Item | Arguments after id and label |
|------|------------------------------|
| `slider` | start value, minimum, maximum, step. `80.0, 0.0, 100.0, 5.0` starts at 80, goes from 0 to 100, and Left/Right change it by 5 (Page Up/Down by 50). The value is spoken as it is, so a range of 0 to 100 reads better than 0.0 to 1.0. |
| `choice` | the options, then the index of the option selected at the start (`1` = the second one, "Normal") |
| `checkbox` | whether it starts checked |
| `text_field` | the start text |
| `password_field`, `button`, `label` | nothing more |

| Key                 | Action                                                   |
|---------------------|----------------------------------------------------------|
| Up / Down / Tab     | previous / next item                                     |
| Home / End          | first / last item                                        |
| Enter / Space       | press button, toggle checkbox, next choice, edit text    |
| Left / Right        | slider minus/plus one step, previous/next choice, toggle |
| Page Up / Page Down | slider plus/minus ten steps                              |
| letters             | jump to the next item starting with that letter          |
| Escape              | close (unless `.closable(false)`)                        |

Options:

```rust
Menu::new("main", "Main menu")
    .wrap(true)               // jump from last to first item
    .closable(false)          // Escape does nothing (good for main menus)
    .positions(true)          // also say "2 of 5" (off by default)
    .close_on_activate(true)  // close after a button is pressed
    .focus("continue")        // start on this item
    .sounds(UiSounds::from_folder("ui/fantasy"));
```

The announcement sounds like `Options. Volume, 80`, or `Options. Volume, 80, 1 of 7` with
`.positions(true)`.

### Menu music

```rust
Menu::new("main", "Main menu")
    .button("play", "Play")
    .music("music/menu")   // loops while the menu is open
```

The music starts with a cross fade when the menu opens. Submenus and dialogs on top keep it
playing, unless they set `.music(..)` themselves; then their music plays until they close.
When the last menu with music closes, the music that played before (for example the level
music under a pause menu) comes back from the start, or the music fades out if there was
none. If the game starts its own music while the menu is open, for example
`ctx.audio.play_music("level1")` when "Play" is pressed, that music is left alone.

Custom widgets can have music too by implementing `Widget::music`.

## Reacting to the UI

There are two ways, and you can mix them.

**Polling in `update`:**

```rust
if ctx.ui.clicked("main", "play") { /* ... */ }
if let Some(item) = ctx.ui.activated("main") { /* any button */ }
if let Some(v) = ctx.ui.changed("options", "volume") {
    ctx.audio.set_master_volume(v.as_f32() / 100.0);
}
if ctx.ui.closed("options") { /* player pressed Escape */ }
let current = ctx.ui.value("options", "hints");  // while the menu is open
```

**The `on_ui` callback of your scene:**

```rust
fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
    match event {
        UiEvent::Activated { menu, item } if menu == "main" && item == "quit" => ctx.quit(),
        UiEvent::Changed { item, value, .. } if item == "volume" => {
            ctx.audio.set_master_volume(value.as_f32() / 100.0)
        }
        _ => {}
    }
}
```

`Value` has `as_bool()`, `as_f32()`, `as_index()`, `as_str()` and `to_text()`.

## Messages

```rust
ctx.ui.message("intro", "Welcome to the dungeon.\nFind the key.\nAvoid the rats.");
if ctx.ui.closed("intro") { /* ... */ }
```

The message is spoken in full. Up/Down/Home/End go through it line by line, and Enter,
Space or Escape close it.

## Yes / no questions

```rust
ctx.ui.confirm("quit", "Do you really want to quit?");
// or with options:
ctx.ui.open(Confirm::new("delete", "Delete save game?").labels("Delete", "Keep").default_no());

match ctx.ui.confirmed("quit") {
    Some(true) => ctx.quit(),
    Some(false) => {}  // also the result of Escape
    None => {}         // not answered this frame
}
```

Arrow keys switch the answer, and Enter confirms it. The first letter of an answer (here
"y" / "n") answers right away.

## Text input

```rust
ctx.ui.input("name", "Enter your name");
// or with options:
ctx.ui.open(TextInput::new("code", "Enter the code").numeric().max_length(4).required());

if let Some(text) = ctx.ui.submitted("name") { /* ... */ }
```

| Key                    | Action                               |
|------------------------|--------------------------------------|
| typing                 | inserts and speaks the character     |
| Left / Right           | moves the cursor, speaks the character |
| Control + Left / Right | moves by word                        |
| Home / End             | start / end                          |
| Backspace / Delete     | deletes, speaks what was deleted     |
| Up / Down              | speaks the whole text                |
| Enter                  | submit                               |
| Escape                 | cancel (`closed` event)              |

`.password()` speaks "star" instead of the characters.

## Language

The words the UI adds by itself ("checked", "Yes" / "No", "blank", character names) follow
the current language: English and German are built in, other languages come from your
translation files. Labels and titles are your own text, so pass `ctx.tr("menu.play")`
there. See [Translations](translations.md).

## Interface sounds

By default the UI uses small generated clicks, so no files are needed. Every sound can be
replaced. These are the slots (`UiSound`):

| Slot       | Played when                                              | Built-in   |
|------------|----------------------------------------------------------|------------|
| `Open`     | a widget opens                                           | rising chirp |
| `Close`    | the player closes a widget                               | falling chirp |
| `Navigate` | the focus moves to another item                          | short click |
| `Wrap`     | a menu jumps from the last to the first item or back     | double click (falls back to `Navigate`) |
| `Activate` | a button is pressed, text is submitted                   | two notes  |
| `Edge`     | the first or last item is reached                        | low thump  |
| `Change`   | a slider, checkbox or choice changes                     | click      |
| `Typing`   | a character is typed or deleted in a text field          | silent     |

### Your own click sounds from files

```rust
ctx.ui.set_sound(UiSound::Navigate, Some("ui/click"));   // sounds/ui/click.ogg
ctx.ui.set_sound(UiSound::Edge, None);                   // silence for this slot
ctx.ui.set_sounds(UiSounds::from_folder("ui"));          // sounds/ui/navigate.ogg, open.ogg, ...
ctx.ui.set_sounds(UiSounds::none());                     // no UI sounds at all
ctx.ui.set_sounds(UiSounds::builtin().with(UiSound::Activate, "ui/ok").without(UiSound::Wrap));
```

### Your own click sounds designed in code

`set_sound_data` takes anything that becomes sound data, usually a
[`Synth`](audio.md#designing-sounds-with-the-synth):

```rust
ctx.ui.set_sound_data(UiSound::Navigate, Synth::triangle(1800.0).length(0.025).decay(150.0).volume(0.4));
ctx.ui.set_sound_data(UiSound::Wrap, Synth::triangle(1800.0).sweep_to(2600.0).length(0.06).decay(60.0));
ctx.ui.set_sound_data(UiSound::Typing, Synth::noise().length(0.012).decay(300.0).volume(0.25));
ctx.ui.set_sound_data(
    UiSound::Activate,
    Synth::sine(880.0).length(0.04).then(Synth::sine(1320.0).length(0.08).decay(40.0)),
);
```

The `menus` example uses exactly these sounds.

### Sounds for one menu only

```rust
Menu::new("main", "Main menu")
    .sound(UiSound::Navigate, Some("ui/main_click"))   // only this menu
    .sound(UiSound::Open, None)                        // silent open
    // or replace all sounds of this menu:
    // .sounds(UiSounds::from_folder("ui/fantasy"))
```

UI sounds play in `Group::Ui`, which has its own volume:
`ctx.audio.set_group_volume(Group::Ui, 0.5)`.

## Managing the stack

```rust
ctx.ui.close();            // close the top widget silently
ctx.ui.close_id("pause");  // close "pause" and everything above it
ctx.ui.close_all();
ctx.ui.is_open();
ctx.ui.contains("options");
ctx.ui.top();              // Some("options")
```

## Custom widgets

Implement [`Widget`] to make your own element, for example an inventory grid or a key
binding recorder:

```rust
use audio_code::ui::{Status, UiContext, UiEvent, UiSound, Widget};

struct KeyRecorder;

impl Widget for KeyRecorder {
    fn id(&self) -> &str { "recorder" }

    fn open(&mut self, ui: &mut UiContext<'_>) {
        ui.play(UiSound::Open, None);
        ui.speech.speak("Press the key you want to use.");
    }

    fn update(&mut self, ui: &mut UiContext<'_>) -> Status {
        if let Some(key) = ui.input.keys_pressed().first() {
            ui.speech.speak(format!("{} selected", key.name()));
            ui.emit(UiEvent::Closed { id: "recorder".into() });
            return Status::Close;
        }
        Status::Open
    }
}

ctx.ui.open(KeyRecorder);
```

A widget that speaks fixed words of its own can translate them with `ui.lang.get(key)` (see
[Translations](translations.md)).

`Status::Push(Box::new(other_widget))` opens a child widget, and
`Status::CloseWith(value)` closes the widget and passes `value` to the widget below in
`Widget::resume`.

[`Widget`]: ../src/ui/mod.rs