Folio Docs
Fogspire/Folio
git clone https://github.com/Fogspire/Folio.git Plugins/Folio

Input prompts

"Hold E to file a complaint." E has to be whatever the player bound, become a face button the moment they pick up a pad, and look like something without waiting on an artist.

Keyboard, mouse and gamepad prompts with their action names

What a prompt is made of

C++
Prompts = NewObject<UFolioPromptSet>(this);
Prompts->Add(TEXT("Interact"), TEXT("File a complaint"));
Prompts->Add(TEXT("Skip"), TEXT("Skip"));
Prompts->Refresh(GetOwningLocalPlayer());

Document->BindViewModel(TEXT("prompts"), Prompts);
RML
<span class="prompt" data-for="p : Prompts" data-if="p.bBound">
    <span class="pg pg-key">{{ p.Glyph.Cap }}</span>
    <span class="pg-label">{{ p.Label }}</span>
</span>

Game code names the actions once. Refresh resolves each to the key the player has bound and filters by what they are holding: a pad in hand never shows F. Call it when the screen opens, when the device changes, and after a rebind; it is cheap.

UFolioPromptSet::IsGamepadActive() says which device that is, and SetGamepadActive(bool) switches it as real input would, for an attract mode or a capture that has to show both.

bBound is false when nothing is bound. Hide the row: a prompt for an unbound action sends the player looking for a key that does nothing.

Where the picture comes from

A project picks the order of three sources per device family:

C++
// Keyboard drawn, gamepad from licensed art with a drawn fallback.
Folio::SetGlyphSources(
    { EFolioGlyphSource::Drawn },
    { EFolioGlyphSource::Image, EFolioGlyphSource::Drawn });
DrawnThe stylesheet's own shapes. No art, no atlas, no licence, sharp at any size, recolours with the theme.
SpriteA sprite named by the project's FFolioKeyGlyph table.
ImageA texture. This is how CommonInput's controller art arrives: the brush is resolved, its texture registered, and the prompt gets a folio:// source.

The default is drawn everywhere, so prompts work with no art and no CommonInput data. ResolveGlyph always fills in Cap whichever source wins, so a missing texture degrades to a keycap rather than a blank.

Two lists because the answer usually differs: a drawn keycap beats a picture of a keyboard, while a PlayStation face button has licensed art, and drawing your own is a trademark question.

Shape names the drawn shape as the class after pg-: key, face, bumper, trigger, dpad, stick, or mouse and its button (mouse left). So a pad's A is drawn as a face button, from one element:

RML
<span data-attr-class="'pg pg-' + p.Glyph.Shape">{{ p.Glyph.Cap }}</span>

The mouse, the stick and the d-pad have children of their own; rcss/recipes/prompts.rml shows each.

Kind says which won (drawn, sprite or image), so markup picks a branch instead of testing three fields for emptiness:

RML
<span class="pg pg-key"   data-if="p.Glyph.Kind == 'drawn'">{{ p.Glyph.Cap }}</span>
<span class="pg pg-sprite" data-if="p.Glyph.Kind == 'sprite'"><img data-attr-sprite="p.Glyph.Sprite"/></span>
<span class="pg pg-image"  data-if="p.Glyph.Kind == 'image'"><img data-attr-src="p.Glyph.Image"/></span>

The drawn set

/folio/lib/prompt.rcss draws keycaps, mouse buttons, gamepad faces, bumpers, triggers, sticks and the d-pad from boxes, borders and radii. rcss/recipes/prompts.rml shows every one.

If you restyle them:

  • A keycap's minimum width is what holds it open. An automatic width comes out as one side's padding plus the text, so a cap sized purely by its label runs its last letter under the border.
  • PlayStation's marks are drawn: cross from two bars, circle a ring, square an outline, triangle three bars each turned into one side. A letter would be wrong on that platform.
  • A face pad's children position against its padding box, so a 30dp pad with a 2dp border centres on 13, not 15. Getting this wrong pushes every mark down and right by the border width.
  • Corners beat joins. A chevron drawn as two bars leaves a wedge where their outer corners nearly meet; drawn as a square with two borders it cannot fail to meet itself.

Naming keys

Folio::LabelFor gives the keycap's text: the project's FFolioKeyGlyph row if there is one, else the key's own display name. A table is worth it for the long ones: Left Shift is a wide cap and LSHIFT is not.

C++
Folio::RegisterGlyphTable(MyGlyphTable);

Repeatable, and later rows win, so a platform can override the gamepad rows without restating the keyboard ones.

Where to go next