# 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