# 3. Glyph Prompts in UI Toolkit
Part of [[Context-Sensitive Prompts and Onboarding]]. Prev: [[2. Binding Display Strings and Device Changes]]. Next: [[4. Context-Sensitive Prompt Volumes]].
6000.5 runtime UI Toolkit is a **`PanelRenderer`** plus a Visual Tree asset ([[2.UI Toolkit Project Setup]]). `PanelRenderer` does **not** expose `rootVisualElement`. The official Scripting API gets you the root through **`RegisterUIReloadCallback`**. If a student project still uses `UIDocument` (the pre-`PanelRenderer` component the vault's UI Toolkit note mentions), `document.rootVisualElement` is the equivalent hook - do not mix both on one panel.
## 3.1 A small prompt UXML
Keep it tiny: a row with an optional icon and a label. Name the elements so C# can query them.
```xml
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
<ui:VisualElement name="prompt-root" class="prompt-root">
<ui:VisualElement name="prompt-row" class="prompt-row">
<ui:Image name="prompt-icon" class="prompt-icon" />
<ui:Label name="prompt-binding" class="prompt-binding" text="" />
<ui:Label name="prompt-verb" class="prompt-verb" text="Interact" />
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>
```
USS (sketch): hide the root with `display: none` or a class, show it with `.prompt-root--visible`. Size the icon to something that reads at HUD scale (Level 8: icons must be clear at a glance). Exact flex values are yours.
## 3.2 Icon lookup
```csharp
using System;
using UnityEngine;
[Serializable]
public struct ControlIcon
{
public string controlPath; // e.g. "buttonSouth"
public Sprite sprite;
}
[CreateAssetMenu(menuName = "Gameplay/Control Icon Set", fileName = "ControlIconSet")]
public sealed class ControlIconSet : ScriptableObject
{
public Sprite fallback;
public ControlIcon[] icons;
public Sprite Find(string controlPath)
{
if (string.IsNullOrEmpty(controlPath) || icons == null) return fallback;
for (int i = 0; i < icons.Length; i++)
{
if (icons[i].controlPath == controlPath)
return icons[i].sprite;
}
return fallback;
}
}
```
Hold one asset for gamepad, one for keyboard if you want distinct art. Keyboard prompts often look better as the display string ("E", "Shift") than as a sprite of every key.
## 3.3 The view, hooked to PanelRenderer
```csharp
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
/// <summary>
/// Binds a prompt row to an InputAction and a ControlIconSet.
/// NOT compiled in Unity.
/// </summary>
[RequireComponent(typeof(PanelRenderer))]
public sealed class PromptView : MonoBehaviour
{
[SerializeField] InputActionReference action;
[SerializeField] ControlIconSet gamepadIcons;
[SerializeField] ControlIconSet keyboardIcons;
[SerializeField] PromptBindingWatcher watcher;
[SerializeField] string defaultVerb = "Interact";
PanelRenderer _panel;
VisualElement _root;
Image _icon;
Label _binding;
Label _verb;
int _uiVersion = -1;
bool _visible;
public InputAction Action => action != null ? action.action : null;
void Awake()
{
_panel = GetComponent<PanelRenderer>();
_panel.RegisterUIReloadCallback(OnUIReload);
}
void OnDestroy()
{
if (_panel != null) _panel.UnregisterUIReloadCallback(OnUIReload);
}
void OnEnable()
{
if (watcher != null) watcher.Refreshed += Refresh;
}
void OnDisable()
{
if (watcher != null) watcher.Refreshed -= Refresh;
}
void OnUIReload(PanelRenderer _, VisualElement root, int version)
{
// Official sample: version changes only when the UI actually reloads.
if (_uiVersion == version) return;
_uiVersion = version;
_root = root.Q<VisualElement>("prompt-root");
_icon = root.Q<Image>("prompt-icon");
_binding = root.Q<Label>("prompt-binding");
_verb = root.Q<Label>("prompt-verb");
ApplyVisible();
Refresh();
}
public void SetVerb(string verb)
{
if (_verb != null) _verb.text = string.IsNullOrEmpty(verb) ? defaultVerb : verb;
}
public void SetVisible(bool visible)
{
_visible = visible;
ApplyVisible();
}
void ApplyVisible()
{
if (_root == null) return;
_root.EnableInClassList("prompt-root--visible", _visible);
_root.style.display = _visible ? DisplayStyle.Flex : DisplayStyle.None;
}
public void Refresh()
{
if (Action == null || _binding == null) return;
string scheme = null;
var player = GetComponent<PlayerInput>();
if (player != null) scheme = player.currentControlScheme;
BindingVisual visual = string.IsNullOrEmpty(scheme)
? new BindingVisual(Action.GetBindingDisplayString(), string.Empty, string.Empty)
: BindingPrompt.ForScheme(Action, scheme);
// If we asked for a scheme but still want path/layout, ForScheme already used the index overload.
Sprite sprite = null;
if (!string.IsNullOrEmpty(visual.controlPath))
{
bool pad = BindingPrompt.IsGamepad(visual.deviceLayout);
var set = pad ? gamepadIcons : keyboardIcons;
if (set != null) sprite = set.Find(visual.controlPath);
}
if (sprite != null && _icon != null)
{
_icon.sprite = sprite;
_icon.style.display = DisplayStyle.Flex;
_binding.text = string.Empty;
_binding.style.display = DisplayStyle.None;
}
else
{
if (_icon != null) _icon.style.display = DisplayStyle.None;
_binding.text = visual.displayString;
_binding.style.display = DisplayStyle.Flex;
}
}
}
```
`Image.sprite` and `Label.text` are the ordinary UI Toolkit element APIs used throughout [[3.HUD and Inventory Example]]. `EnableInClassList` and `DisplayStyle` are the same.
If `ForScheme` is used, `controlPath` is filled. If you only call `GetBindingDisplayString()` with no index, you get text but not a path - then the text branch runs, which is correct.
## 3.4 Optional: sprites inside the Label
The official 6000.5 manual [Include sprites in text](https://docs.unity3d.com/6000.5/Documentation/Manual/UIE-sprite.html) lets a Label contain a rich-text sprite tag, once you have a **Sprite Asset** on the **Panel Text Settings** default sprite-asset path:
```
<sprite="MyGlyphs" name="buttonSouth">
```
or, if it is the default asset, `<sprite name="buttonSouth">` (confirm the exact tag form in that manual for your Editor - the page documents `name` and `index` forms). This is useful when the binding sits *inside* a sentence ("Hold <sprite...> to lift"). For a HUD chip, a separate `Image` is easier to show and hide. Either way, refresh the string when the binding changes; do not leave a stale tag in the UXML.
## 3.5 Exercise
Hook `PromptView` to your Interact action. Start with no icon set assigned and confirm the display string appears. Then add one `buttonSouth` sprite and plug in a gamepad; the Image should replace the text. Rebind Interact and confirm the view updates only after `PromptBindingWatcher.RaiseRefreshed` (or `onControlsChanged`) fires.
## Sources
- `PanelRenderer` (6000.5): https://docs.unity.com/en-us/engine/6000.5/script-reference/unityengine/uielements/panelrenderer
- Include sprites in text (6000.5): https://docs.unity3d.com/6000.5/Documentation/Manual/UIE-sprite.html
- Panel Text Settings (6000.5): https://docs.unity3d.com/6000.5/Documentation/Manual/UIE-text-setting-asset.html