Files
2026-04-06 20:38:58 +02:00
..
2026-04-06 10:44:16 +02:00
2026-04-06 10:06:09 +02:00
2026-04-06 10:44:16 +02:00
2026-04-06 10:44:16 +02:00
2026-04-06 10:44:16 +02:00

Jovian Popup System

A lightweight, low-allocation popup and tooltip system for Unity with category-based isolation, a generic element cache, type-safe element identifiers, and extensible animations.

Requirements

  • Unity 2022.3 or later
  • TextMeshPro 3.0.6+
  • Input System 1.18.0+
  • Newtonsoft JSON 3.2.1+

Install via the Unity Package Manager by adding the package from its local path or from a git URL.

Quick Start

1. Create a PopupSettings asset

In the Unity Editor, go to Assets > Create > Jovian > Popup System > Popup Settings. Configure element prefabs by adding entries to the Element Prefabs list, mapping PopupElementType values to your prefabs.

2. Build a PopupReference prefab

See the Prefab Setup section below.

3. Create and use PopupSystem

using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;

// Create the system. canvasRoot auto-scans all PopupTrigger components.
var popup = new PopupSystem(settings, viewPrefab, canvasRoot);

popup.RegisterCategory(PopupCategory.Item);
popup.RegisterCategory(PopupCategory.Character);

// Set content on auto-scanned triggers by name.
var handler = popup.GetTriggerHandler("ItemSlot");
handler?.SetContent(builder => {
    builder
        .AddText("Health Potion", PopupElementType.Header)
        .AddSeparator(PopupElementType.Separator)
        .AddText("Restores health.", PopupElementType.Text)
        .AddNameValue("Heal Amount", 50, PopupElementType.LabelValueText);
});

// Or show a popup directly from code.
popup.Show(PopupCategory.Item, builder => {
    builder
        .AddText("Iron Sword", PopupElementType.Header)
        .AddNameValue("Damage", 12, PopupElementType.LabelValueText);
}, anchorRect, AnchorSide.Right);

// Tick each frame.
popup.Tick(Time.deltaTime);

// Clean up when the game state exits.
popup.Dispose();

PopupElementType

PopupElementType is a type-safe struct identifying element prefabs in the registry. The Inspector shows a dropdown with built-in types plus a custom text field for game-specific elements.

Built-in types

Static Field ID Usage
PopupElementType.Header "header" Bold header text
PopupElementType.Text "text" Body text
PopupElementType.LabelValueText "label_value_text" Label + value row (two TMP_Text children)
PopupElementType.Image "image" Image/icon element
PopupElementType.Separator "separator" Horizontal divider line

Custom types

Define game-specific element types as static fields:

public static class MyPopupElements {
    public static readonly PopupElementType Badge = new("badge");
    public static readonly PopupElementType ProgressBar = new("progress_bar");
}

Variants

Create variants of built-in types using Variant():

// "header_gold" — a gold-styled header
PopupElementType.Header.Variant("gold")

// "separator_thick" — a thicker separator
PopupElementType.Separator.Variant("thick")

Register variant prefabs in PopupSettings with the variant ID (e.g. "header_gold").

PopupCategory

PopupCategory is a readonly struct that acts as a channel for isolating popups. Each category gets its own view instance.

Built-in categories

PopupCategory.Character  // Character tooltips
PopupCategory.Item       // Item tooltips
PopupCategory.Skill      // Skill tooltips
PopupCategory.General    // General-purpose tooltips

Custom categories

var lootCategory = new PopupCategory("Loot");
popup.RegisterCategory(lootCategory, priority: 5);

PopupSettings

ScriptableObject holding all configuration. Create via Assets > Create > Jovian > Popup System > Popup Settings.

Field Type Default Description
popupDelay float 0.4 Seconds before popup appears.
fadeDuration float 0.2 Fade animation duration.
defaultAnchorSide AnchorSide Below Default anchor side.
screenEdgePadding float 10 Minimum pixels from screen edge.
maxPopupWidth float 400 Maximum popup width in pixels.
sortingOrder int 100 Canvas sorting order.
followMouseOffset Vector2 (15, -15) Cursor offset in follow mode.
touchHoldDuration float 0.6 Touch hold duration.
gamepadFocusTrigger bool true Trigger on gamepad focus.
elementPrefabs List empty Element prefab registry (PopupElementType -> prefab).
categoryPriorities List empty Per-category priority overrides.
categoryDelayOverrides List empty Per-category delay overrides.

PopupContentBuilder

Fluent API struct for composing popup content. All methods take a PopupElementType to identify which prefab to use.

Methods

// Generic — returns raw GameObject for custom elements
builder.Add(PopupElementType.Header);
builder.Add(new PopupElementType("custom_widget"));

// Text — sets TMP_Text on the element
builder.AddText("Fireball", PopupElementType.Header);
builder.AddText("Body text here.", PopupElementType.Text);
builder.AddText("Colored text", "#FFD700", PopupElementType.Text);

// Name/Value — sets two TMP_Text children (label + value)
builder.AddNameValue("Damage", 120, PopupElementType.LabelValueText);
builder.AddNameValue("Range", "15m", PopupElementType.LabelValueText);

// Image — sets sprite and height
builder.AddImage(iconSprite, PopupElementType.Image, 64f);

// Separator
builder.AddSeparator(PopupElementType.Separator);

Full example

popup.Show(PopupCategory.Skill, builder => {
    builder
        .AddText("Fireball", PopupElementType.Header)
        .AddText("Hurls a ball of fire.", PopupElementType.Text)
        .AddSeparator(PopupElementType.Separator)
        .AddNameValue("Damage", 120, PopupElementType.LabelValueText)
        .AddNameValue("Mana Cost", 35, PopupElementType.LabelValueText)
        .AddSeparator(PopupElementType.Separator)
        .AddText("Requires: Level 5", "FF6666", PopupElementType.Text);
}, targetRect);

PopupTrigger

MonoBehaviour attached to UI elements for hover-based popups. Forwards pointer events to a PopupTriggerView behavior handler.

Inspector fields

Field Type Description
category PopupCategory Which category channel to use.
anchorSide AnchorSide Which side to anchor the popup.
positionMode PopupPositionMode AnchorToElement or FollowMouse.
var popup = new PopupSystem(settings, viewPrefab, canvasRoot);
popup.RegisterCategory(PopupCategory.Character);

// GetTriggerHandler returns PopupTriggerView (the behavior handler)
var handler = popup.GetTriggerHandler("HeroPortrait");
handler?.SetContent(builder => {
    builder
        .AddText("Kael", PopupElementType.Header)
        .AddNameValue("Health", 55, PopupElementType.LabelValueText);
});

// Or all handlers for a category
foreach(var h in popup.GetTriggerHandlers(PopupCategory.Item)) {
    h.SetContent(builder => builder.AddText("Item", PopupElementType.Header));
}

Approach 2: Dynamic triggers (for runtime-created UI)

popup.InitializeTriggersInChildren(slotsContainer, (trigger, view) => {
    var slotData = trigger.GetComponentInParent<SlotComponent>();
    view.SetContent(builder => {
        builder.AddText(slotData.itemName, PopupElementType.Header);
    });
});

Approach 3: Code-only (no PopupTrigger needed)

// Anchored
popupSystem.Show(PopupCategory.Item, builder => { ... }, targetRect, AnchorSide.Right);

// Follow mouse
popupSystem.Show(PopupCategory.General, builder => { ... });

// Fixed screen position
popupSystem.ShowAtPosition(PopupCategory.General, builder => { ... }, screenPos);

// Hide
popupSystem.Hide(PopupCategory.Item);
popupSystem.HideAll();

Priority System

Higher priority categories dismiss lower ones when shown:

popup.RegisterCategory(PopupCategory.Character, priority: 10);
popup.RegisterCategory(PopupCategory.Item, priority: 5);

Settings-based priorities take precedence over registration arguments.

Lifecycle Integration

Created per game state, not as a singleton:

var guiCanvas = guiReferences.GetComponentInParent<Canvas>().transform;
var popupSystem = new PopupSystem(popupSettings, popupViewPrefab, guiCanvas);
popupSystem.RegisterCategory(PopupCategory.Character, priority: 10);

// Tick each frame
popupSystem.Tick(Time.deltaTime);

// Dispose on state exit
popupSystem.Dispose();

IPopupAnimator

Extensible show/hide animation interface. Default: FadePopupAnimator (alpha lerp). Pass a factory to the constructor:

var popup = new PopupSystem(settings, viewPrefab, canvasRoot, () => new ScalePopupAnimator());

Prefab Setup

PopupReferencePrefab           Canvas, CanvasGroup, CanvasScaler, PopupReference
  Background                   Image, ContentSizeFitter (V=Preferred)
    Content                    VerticalLayoutGroup (Control Child Width, Force Expand Width)
  1. Root: Canvas (Screen Space Overlay), CanvasScaler (Scale With Screen Size), CanvasGroup, PopupReference. Point anchors, no ContentSizeFitter.
  2. Background: Image, ContentSizeFitter (Vertical = Preferred).
  3. Content: Stretched to Background, VerticalLayoutGroup (Control Child Size Width, Child Force Expand Width), no ContentSizeFitter.
  4. Element prefabs: Create separate prefabs, register in PopupSettings under Element Prefabs with their PopupElementType. No ContentSizeFitter on element prefabs.
  5. Wire PopupReference: Assign Content, CanvasGroup, and Background fields.

Optimization Notes

  • PopupCategory and PopupElementType are value-type structs — zero heap allocation
  • PopupContentBuilder is a readonly struct operating directly on cached elements
  • Content elements use a grow-only cache keyed by PopupElementType — activate/deactivate, never destroy after warmup
  • Delay timers and animation driven by float fields in Tick() — no coroutines
  • Screen rect uses static Vector3[4] buffer — no per-frame allocation
  • Layout rebuilds only on content change (dirty flag)
  • Each category gets its own IPopupAnimator — no concurrent animation corruption
  • MonoBehaviour (PopupReference, PopupTrigger) holds references only; all behavior in plain C# classes (PopupView, PopupTriggerView)

Samples

The Samples~ folder contains:

  • Prefabs: PopupReferencePrefab and all element prefabs
  • Settings: Pre-configured PopupSettings asset
  • Scripts: Three example scripts (auto-scanned triggers, dynamic triggers, code-only with variants)

Import via Unity Package Manager: select the package, expand Samples, click Import.

API Reference

Core Types

Type Namespace Description
IPopupSystem Jovian.PopupSystem Main interface for showing/hiding popups.
PopupSystem Jovian.PopupSystem Implementation. Constructor: (PopupSettings, PopupReference, Transform, Func<IPopupAnimator>).
PopupView Jovian.PopupSystem Behavior class: generic element cache, positioning, visibility.
PopupSettings Jovian.PopupSystem ScriptableObject configuration + element prefab registry.
PopupCategory Jovian.PopupSystem Struct identifying a popup channel.
PopupElementType Jovian.PopupSystem Struct identifying an element prefab type. Built-ins + Variant().
PopupContentBuilder Jovian.PopupSystem Fluent readonly struct for building content.

UI Types

Type Namespace Description
PopupReference Jovian.PopupSystem.UI MonoBehaviour reference holder (content, canvasGroup, background).
PopupTrigger Jovian.PopupSystem.UI MonoBehaviour reference holder for hover triggers.
PopupTriggerView Jovian.PopupSystem Behavior handler for triggers (SetContent, pointer forwarding).

Animation Types

Type Namespace Description
IPopupAnimator Jovian.PopupSystem Interface for custom show/hide animations.
FadePopupAnimator Jovian.PopupSystem Default fade animation via CanvasGroup alpha.

Enums

Type Values
AnchorSide Below, Above, Left, Right
PopupPositionMode AnchorToElement, FollowMouse