Confirmation Popup

ChoicePopupController is a UIViewComponent that renders an N-button modal — Yes/No, Save/Discard/Cancel, or any custom set. Buttons are built dynamically from a ChoicePopupChoice[] list (text + color), and there are two ways to drive it:

  • As a navigation destination (recommended) — register one popup prefab in your nav graph and push it from anywhere with per-call ChoicePopupArgs. The push is awaitable: PushAsync<int> resolves with the clicked choice index.
  • Embedded in a host view (legacy) — a node-less ChoicePopupController on the same GameObject as its caller, driven through OnButtonClick + Fade.FadeIn().

Confirmation Popup Example

As a navigation destination

One prefab in a global navigation graph serves every confirmation in the game — no per-screen popup copies. Register the popup prefab on a NavNode (typically dismissMode: Pop with a Dim backdrop, hidden until pushed), then push it with args:

csharp
async void OnReturnToMenuClicked() { var result = await LunaNavigation.PushAsync<int>(_confirmDest, new ChoicePopupArgs { Header = "Return to Main Menu", Body = "Unsaved progress will be lost.", Choices = new[] { new ChoicePopupChoice("Return", "red"), new ChoicePopupChoice("Cancel", "slate"), }, }); if (!result.IsDismissed && result.Value == 0) { // First choice (index 0) — the affirmative action by convention. ReturnToMainMenu(); } }
  • ChoicePopupArgs fields are optional — a null/empty Header, Body, or Choices keeps the value serialized on the prefab, so a generic "Are you sure?" popup works with new ChoicePopupArgs { Header = "Delete Save 3" }.
  • Index 0 = affirmative. The controller pops the clicked index via LunaNavigation.Pop(i), resolving the PushAsync<int> task. Keep the convention that the first choice is the "do it" action so callers can test result.Value == 0.
  • Dismissal is a "no". Escape or a backdrop click pops the node without a result — result.IsDismissed is true and no choice index is delivered. Treat it like the cancel button.
  • Capture any state the confirm acts on before the push — the host view stays interactive-adjacent underneath, and the await can outlive a selection change (see how GameSaveViewList captures the slot in Save & Load).

The GameFull sample registers exactly one such node (confirm, in its global graph) and routes the pause menu's return-to-menu confirm and all save-list overwrite/delete confirms through it.

Embedded in a host view (legacy)

A ChoicePopupController without a nav node — e.g. living on the same GameObject as the view that uses it — is driven manually. On load it forces itself start-hidden (a node-less popup would otherwise show its placeholder texts the moment the host view opens), and a click just fades it back out instead of popping:

csharp
[SerializeField] ChoicePopupController _popup; void OnDeleteClicked() { _popup.TextHeader = "Delete save?"; _popup.TextBody = "This cannot be undone."; _popup.Choices = new[] { new ChoicePopupChoice("Delete", "red"), new ChoicePopupChoice("Cancel", "slate"), }; _popup.OnButtonClick += OnConfirmDelete; _popup.UIView.Fade.FadeIn(); } void OnConfirmDelete(int index) { if (index == 0) DeleteSave(); _popup.OnButtonClick -= OnConfirmDelete; }

Prefer the nav-destination mode for new work — the embedded mode duplicates a popup per host view and can't be awaited.

Setup

1. UXML

The popup expects three named elements in the UXML hosted by the view's PanelRenderer:

ElementName
LabelHeader
LabelBody
VisualElementButtonContainer

The controller fills ButtonContainer with one Button per ChoicePopupChoice on fade-in.

2. Add component + configure

Add ChoicePopupController to the GameObject. In the inspector:

  • _textHeader / _textBody — default labels (used when a push passes no override).
  • _choices — the default button list (text + color name).
  • _buttonClasses — extra USS classes applied to every generated button (e.g. "btn-xl").

Confirmation Popup Inspector

For the nav-destination mode, also register the prefab on a NavNode in your graph and reference that destination key from callers.

API

csharp
namespace CupkekGames.Luna; public class ChoicePopupController : UIViewComponent

Properties

PropertyTypeDescription
ChoicesChoicePopupChoice[]Button list. Re-assigning rebuilds buttons on next fade-in.
TextHeaderstringRead/write the header label.
TextBodystringRead/write the body label.

Events

csharp
public event Action<int> OnButtonClick;

Fires with the index of the clicked choice (in both modes, before the popup resolves). After a click all buttons are disabled, then the popup pops (nav mode) or fades out (embedded mode).

Subclassing hooks

The per-push pipeline is virtual, so specialized confirms can extend the base instead of duplicating it — the Showcase sample's purchase modal subclasses ChoicePopupController with extra preview/burst behavior this way:

VirtualCalledOverride to
ApplyArgs(ChoicePopupArgs)On fade-in, when the push carried argsApply extra fields from a ChoicePopupArgs subclass.
RebuildButtons()On every fade-inReplace dynamic button generation (e.g. fixed UXML buttons).
ResolveChoice(int)After a clickDelay or decorate the resolution (default: Pop(i) with a node, FadeOut() without).

ChoicePopupArgs

csharp
public class ChoicePopupArgs { public string Header; public string Body; public ChoicePopupChoice[] Choices; }

Passed as the push args; read on fade-in via GetArgs<ChoicePopupArgs>(). Subclass it to carry extra data for a ChoicePopupController subclass.

ChoicePopupChoice

csharp
public ChoicePopupChoice(string text, string color);

color is a Luna color-class name ("lime", "red", "slate", etc. — see colors). Applied as a USS class on the button alongside btn.

Default behaviour

  • Escape: closes the popup — in nav mode the node's dismiss pops it (an awaiting PushAsync sees IsDismissed); a UIViewActionEscape(Fade.FadeOut) covers the embedded mode.
  • Initial focus: First button gets keyboard focus on fade-in (gamepad-friendly).
  • Cleanup: On fade-out, the button container is cleared.

See also

  • Input Popup — the text-input sibling; same args/await pattern with a confirmed string
  • Navigation — destinations, PushAsync<T> / Pop<T>, dismiss modes
  • Notifications
  • Tooltip — non-blocking hover variant

Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL