bevy_minibuffer
This is a developer console for the Bevy game engine. It is inspired by the user interface of classic Unix text editors rather than the Unix shell.
[!CAUTION]
bevy_minibufferis currently in the early stages of development and is subject to breaking changes.
Example
The video above shows the demo-async example.
Features
- Input prompt, i.e, a "minibuffer"
- Specialized prompts: checkboxes, radio buttons, numbers, passwords, confirm, and toggle.
- Tab completion
- Map to values
- Run acts, i.e., commands
- Bind key sequences to acts
keyseq! { Ctrl-A Alt-C Shift-T S } - Write your own acts (acts are just systems)
- Support for async acts behind "async" feature flag
- Basic acts:
run_act,list_acts,list_key_bindings,toggle_visibility, anddescribe_key. - Adds only one root entity named "minibuffer"
Goals
- Easily opt-in to basic functionality
- Easily add acts, i.e., commands
- Easily bind key chord sequences to acts
- Easily solicit user for input
- Tab completion where possible
- A la carte usage supported
- Easily exclude from build
Antigoals
- No default kitchen sink
The default functionality should be a blank slate that does nothing if no
commands or key bindings have been added. Basic functions like run_act and
the ":" key binding should be opt-in.
- No general-purpose text editing
- No windows or panels
Try to force everything through the minibuffer at the bottom of the screen. It can resize to accommodate more than one-line of text.
Examples
An example for every goal.
Easily opt-in to basic functionality
MinibufferPlugins does not include any built-in acts or key bindings, but it is
expected that many users will want some kind of basic functionality. BasicActs
provides the following acts and key bindings:
| ACT | KEY BINDING |
|---|---|
| describe_key | Ctrl-H K |
| run_act | : |
| Alt-X | |
| list_acts | Ctrl-H A |
| list_key_bindings | Ctrl-H B |
| toggle_visibility | ` |
BasicActs is thought to constitute the bare minimum number of acts for a
useable and discoverable console.
# use *;
# use *;
Easily add acts, i.e., commands
Acts are systems. Any system[^1] will do.
NOTE: We add BasicActs acts here only because there would be no way to run an
act otherwise. To run an act without BasicActs, one would need a key binding.
# use *;
# use *;
[^1]: Any system with no input or output. This does not exclude pipelines, however, which are used extensively with asynchronous systems.
Easily bind key chord sequences to acts
We can bind key chord Ctrl-W or even a key chord sequence Ctrl-W Alt-O Super-R Shift-L D to an act.
# use *;
# use *;
Easily solicit user for input
Ask the user for information. Notice that no acts are added. One can use
Minibuffer from within any system without having to "buy-in" to the rest of it.
# use *;
# use *;
Minibuffer supports the following prompts:
- Checkboxes
- Confirm
- Numbers
- u8, u16, u32, u64, i*, f*, usize, isize
- Radio buttons
- Toggle
- TextField
- Tab completion
See the "demo-async" example to see more prompts in action.
Tab completion where possible
Text centric user interfaces ought to support tab completion where possible.
Use a Vec
One can provide a list of strings for simple completions.
# use *;
# use *;
Use a Trie
One can provide a trie for more performant completion.
# use *;
# use *;
# use Trie;
Use a HashMap
One can provide a hash map that will provide completions and mapping to values.
# use *;
# use *;
# use HashMap;
Use a map::Trie
One can provide a trie that maps to an arbitary value type V and receive the
value V type in response in addition to the string. This is more performant
than a hash map.
# use *;
# use *;
# use Trie;
A la carte usage is supported
Minibuffer is a collection of a few distinct pieces:
- Acts, i.e., commands
- Key sequence bindings
- A "mini-buffer" or buffer, i.e., the small panel at the bottom of the screen to query and respond to the user.
One can use acts without key bindings.
One can use acts without the buffer.
One can use the buffer without acts. That is, one can use Minibuffer and
MinibufferAsync system parameters from within any system; they are not
reserved for usage only within Act systems.
One can use the buffer without key bindings.
One can use key bindings without the buffer.
What can one not use a la carte?
One cannot use key bindings without acts in Minibuffer. If one desires key sequence bindings only, it may be better to use Minibuffer's key-binding dependency bevy-input-sequence.
One cannot query the user without the buffer. If one desires that, consider looking at Minibuffer's custom MVC middleware that is 'View' agnostic: bevy_asky. It allows one to designate the view and the destination and in general expects the consumer to implement their own conventions and policy.
Easily exclude from build
I believe a project with a "minibuffer" feature flag and rust conditional compilation facilities ought to make it easy and practical to exclude it from a release build. But I'd like to affirm that in practice before considering this goal achieved.
# use *;
# use *;
Async
An "async" feature flag makes the MinibufferAsync system parameter available.
Unlike the regular Minibuffer system parameter, MinibufferAsync can be
captured by closures.
Although one can technically achieve the same behavior with Minibuffer, there
are cases like those with many queries in succession where using
MinibufferAsync is more straightforward to write and read.
# use *;
# use *;
/// Ask the user for their name. Say hello.
async
The preceding async function ask_name() returns a future, technically a impl Future<Output = Result<(), Error>>. That has to go somewhere so that it will be evaluated.
There are a series of pipe-able systems in the sink module:
-
sink::futureaccepts any future and runs it. -
sink::future_resultaccepts any future that returns a result and runs it but if the result is an error, it reports that error to the minibuffer. -
sink::resultaccepts any result. If it is an error, it is reported on the minibuffer.
Acts and Plugins
An ActsPlugin is a Plugin that contains Acts. Three ActsPlugins are
available in this crate: BasicActs, UniversalArgActs, and TapeActs.
Basic acts of console-ness
BasicActs has the bare necessities of acts:
- run_act
Asks for what act to run, provides tab completion.
- list_acts
Lists acts and their key bindings.
- list_key_bindings
Lists key bindings and their acts.
- describe_key
Listens for key chords and reveals what act they would run.
- toggle_visibility
Hides and shows the minibuffer.
But one can trim it down further if one likes by calling take_acts(),
manipulating them, and submitting that to add_acts(). For instance to only add
'run_act', one could do the following:
# use *;
# use *;
Universal argument acts
UniversalArgActs provides a univeral argument that acts can use by accessing
the resource Res<UniveralArg>. It simply holds an option of a signed number.
;
One uses it like so, type Ctrl-U 1 0 and this would place 10 into the
UniversalArg resource. It is cleared after the next act runs. See the example.
How is a number universal?
Although universal argument accepts a number, one can use it as an on or off flag too. 'Ctrl-U' ends up functioning like an adverb.
Suppose we had an act called 'open-door' but it only worked with the closest door. What if we also wanted to open all doors? We could write another act to do that 'open-all-doors' and find another key binding but let us consider what this might look like with universal argument.
- Type 'Alt-X open-door' to open the closest door.
- Type 'Ctrl-U Alt-X open-door' to open all the doors.
Likewise this will work on key bindings, so if 'open-door' is bound to 'O D', then one could do this:
- Type 'O D' to open the closest door.
- Type 'Ctrl-U O D' to open all the doors.
# use *;
# use *;
What if UniversalActs is not added?
The resource Res<UniversalArg> is present even if UniversalActs has not been
added. Without UniveralActs lies inert. But it is present so that any act may
opt-in to accepting univeral arguments while still allowing the user to opt-out
of UniversalActs.
Tape acts
TapeActs provides interactive scriptability to Minibuffer. It provides a
"tape" one can record their actions to. One can think of tapes like keyboard
macros but they do not record key presses and replay them. Instead the acts that
are run are recorded. These can be replayed, or they can generate a script
one can integrate back into their application.
tape_record
The 'tape_record' act starts a tape recording. It requests a "name" for the tape which is a key or key chord of your choice. Once recording starts, it will continue until one runs 'tape_record' again.
tape_play
The 'tape_play' act requests a key and plays that tape if it exists.
tape_copy
The 'tape_copy' act prints a function that replicates what the tape does. If the feature "clipboard" is present, it will also copy it to the clipboard.
repeat
The 'repeat' act is bound to the '.' key and does not require a tape. It merely repeats the last act one invoked. This is similar to vi's repeat last change command.
For fun these tape acts have sound effects of an analog tape deck if the "fun" feature for bevy_minibuffer is enabled. It is off by default. Click on the movie above to hear the sounds.
Features
- "async" makes
MinibufferAsyncavailable. - "clipboard" makes clipboard accessible, used by 'tape_copy' act.
- "fun" adds a tape icon and tape decks sounds to tape acts.
- "dev-capture" is not for general use and is for generating videos as shown in this README.
FAQ
Why are Minibuffer commands called acts?
Bevy has a foundational trait called Command. Minibuffer's commands are called
Acts to avoid the confusion of having two very different Command types.
Why not a shell?
If one surveys developer consoles, one will find that many have taken inspiration from command-line interfaces, Unix shells being the most prevalent. And the Unix shell is great; I love it and use it daily. However, I do not believe it represents the best interaction model for game developer consoles.
A non-interactive Unix command requires one to provide the arguments it expects. Failure to do so results in an error message. Often one must consult the command's help or documentation to determine the right arguments. This is tolerable partly because we can then script these interactions.
In general the Unix shell trades interactive convenience for non-interactive scriptability, and it is a good trade. EDIT: What I had written next was this:
Minibuffer does not provide interactive scriptability but that means we can make it a better interactive experience.
I realized that is not true and wrote TapeActs to disprove myself.
Instead of being required to know the arguments for any given command, Minibuffer acts ask the user for what is required. It is a "pull" model of interaction versus a "push" model.
Why doesn't the shell use a pull model?
In some sense the shell can't use a pull model because its commands run in another process and it does not invoke commands to interrogate them. Imagine the danger if a shell invoked a command to interrogate it, e.g., user types 'shutdown', hits Tab, and their computer turns off. Bash programmable completions broadens the scope of what is tab completable, but notice that the completion does not run the command; it runs a bash function.
TODO
- Use a "real" cursor/selection highlight.
- Add case-insensitive tab-completion example.
- Make a tape recording recursable.
- Record universal args on tape.
- Make it possible to have vim-like prompt (no space after ":").
- Add
HashMap<String,V>completer. - Make universal-arg work without async.
- Re-write asky to be bevy native.
Design Questions
Re: No windows antigoal
The minibuffer can show more than one line of text, but what to do if its asked to show multiple pages of text?
This is an unresolved issue.
Compatibility
| bevy_minibuffer | bevy |
|---|---|
| 0.4.0 | 0.16 |
| 0.3.0 | 0.15 |
| 0.2.0 | 0.15 |
| 0.1.0 | 0.14 |
License
This crate is licensed under the MIT License or the Apache License 2.0.