added code from unity

This commit is contained in:
Sebastian Bularca
2026-04-06 20:45:03 +02:00
parent 0075992205
commit 0f675b9981
56 changed files with 3448 additions and 2 deletions

8
Editor.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 608491ce009b2494c894080b243698b9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
{
"name": "Jovian.PopupSystem.Editor",
"rootNamespace": "Jovian.PopupSystem.Editor",
"references": [
"Jovian.PopupSystem"
],
"includePlatforms": ["Editor"],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e663435ad13b46d40a29802dab7e52f5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Jovian.PopupSystem.Editor {
[CustomPropertyDrawer(typeof(PopupCategory))]
public sealed class PopupCategoryDrawer : PropertyDrawer {
private static readonly string[] builtInIds = {
"Character",
"Item",
"Skill",
"General"
};
private const string customLabel = "Custom...";
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
var idProp = FindIdProperty(property);
if(idProp == null) {
return EditorGUIUtility.singleLineHeight;
}
var currentValue = idProp.stringValue ?? "";
var isCustom = Array.IndexOf(builtInIds, currentValue) < 0;
// Two lines when in custom mode: dropdown + text field
return isCustom
? EditorGUIUtility.singleLineHeight * 2 + 2
: EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
var idProp = FindIdProperty(property);
if(idProp == null) {
EditorGUI.LabelField(position, label.text, "Cannot resolve PopupCategory id field");
EditorGUI.EndProperty();
return;
}
var currentValue = idProp.stringValue ?? "";
var builtInIndex = Array.IndexOf(builtInIds, currentValue);
var isCustom = builtInIndex < 0 && !string.IsNullOrEmpty(currentValue);
// Build display options: built-in entries + "Custom..."
var options = new List<string>(builtInIds);
options.Add(customLabel);
// Determine selected index
int selectedIndex;
if(builtInIndex >= 0) {
selectedIndex = builtInIndex;
}
else {
selectedIndex = options.Count - 1; // "Custom..."
}
// First line: dropdown
var dropdownRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
var newIndex = EditorGUI.Popup(dropdownRect, label.text, selectedIndex, options.ToArray());
if(newIndex != selectedIndex) {
if(newIndex < builtInIds.Length) {
idProp.stringValue = builtInIds[newIndex];
}
else {
// Switched to custom — seed with a placeholder if currently built-in
if(!isCustom) {
idProp.stringValue = "NewCategory";
}
}
}
// Second line: editable text field when custom
var finalIsCustom = newIndex >= builtInIds.Length || (newIndex == selectedIndex && isCustom);
if(finalIsCustom) {
var textRect = new Rect(
position.x + EditorGUIUtility.labelWidth + 2,
position.y + EditorGUIUtility.singleLineHeight + 2,
position.width - EditorGUIUtility.labelWidth - 2,
EditorGUIUtility.singleLineHeight);
var newValue = EditorGUI.TextField(textRect, idProp.stringValue);
if(newValue != idProp.stringValue) {
idProp.stringValue = newValue;
}
}
EditorGUI.EndProperty();
}
private static SerializedProperty FindIdProperty(SerializedProperty property) {
// readonly struct with [SerializeField] readonly string id
// Unity serializes this as "id" directly
var prop = property.FindPropertyRelative("id");
if(prop != null) {
return prop;
}
// Fallback: auto-property backing field pattern
return property.FindPropertyRelative("<id>k__BackingField");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9cc96a089d31fe14cbadf6c59ea0c9aa

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Jovian.PopupSystem.Editor {
/// <summary>
/// Custom PropertyDrawer for <see cref="PopupElementType"/>. Shows a dropdown with
/// built-in element types plus a "Custom..." option for free-text entry.
/// </summary>
[CustomPropertyDrawer(typeof(PopupElementType))]
public sealed class PopupElementTypeDrawer : PropertyDrawer {
private static readonly string[] builtInIds = {
"header",
"text",
"label_value_text",
"image",
"separator"
};
private const string customLabel = "Custom...";
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
var idProp = FindIdProperty(property);
if(idProp == null) {
return EditorGUIUtility.singleLineHeight;
}
var currentValue = idProp.stringValue ?? "";
var isCustom = Array.IndexOf(builtInIds, currentValue) < 0 && !string.IsNullOrEmpty(currentValue);
return isCustom
? EditorGUIUtility.singleLineHeight * 2 + 2
: EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
var idProp = FindIdProperty(property);
if(idProp == null) {
EditorGUI.LabelField(position, label.text, "Cannot resolve PopupElementType id field");
EditorGUI.EndProperty();
return;
}
var currentValue = idProp.stringValue ?? "";
var builtInIndex = Array.IndexOf(builtInIds, currentValue);
var isCustom = builtInIndex < 0 && !string.IsNullOrEmpty(currentValue);
var options = new List<string>(builtInIds);
options.Add(customLabel);
int selectedIndex;
if(builtInIndex >= 0) {
selectedIndex = builtInIndex;
}
else {
selectedIndex = options.Count - 1;
}
var dropdownRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
var newIndex = EditorGUI.Popup(dropdownRect, label.text, selectedIndex, options.ToArray());
if(newIndex != selectedIndex) {
if(newIndex < builtInIds.Length) {
idProp.stringValue = builtInIds[newIndex];
}
else {
if(!isCustom) {
idProp.stringValue = "custom_element";
}
}
}
var finalIsCustom = newIndex >= builtInIds.Length || (newIndex == selectedIndex && isCustom);
if(finalIsCustom) {
var textRect = new Rect(
position.x + EditorGUIUtility.labelWidth + 2,
position.y + EditorGUIUtility.singleLineHeight + 2,
position.width - EditorGUIUtility.labelWidth - 2,
EditorGUIUtility.singleLineHeight);
var newValue = EditorGUI.TextField(textRect, idProp.stringValue);
if(newValue != idProp.stringValue) {
idProp.stringValue = newValue;
}
}
EditorGUI.EndProperty();
}
private static SerializedProperty FindIdProperty(SerializedProperty property) {
var prop = property.FindPropertyRelative("id");
if(prop != null) {
return prop;
}
return property.FindPropertyRelative("<id>k__BackingField");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9ea57be8755c60e4bbf156ac2b32c98c

View File

@@ -0,0 +1,80 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Jovian.PopupSystem.Editor {
public sealed class PopupSettingsProvider : SettingsProvider {
private PopupSettings settings;
private PopupSettingsProvider(string path, SettingsScope scope)
: base(path, scope) { }
private SerializedObject serializedSettings;
public override void OnActivate(string searchContext, UnityEngine.UIElements.VisualElement rootElement) {
var guids = AssetDatabase.FindAssets("t:PopupSettings");
if(guids.Length > 0) {
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
settings = AssetDatabase.LoadAssetAtPath<PopupSettings>(path);
}
if(settings != null) {
serializedSettings = new SerializedObject(settings);
}
}
public override void OnGUI(string searchContext) {
if(settings == null) {
EditorGUILayout.HelpBox("No PopupSettings asset found. Create one via Assets > Create > Jovian > Popup System > Popup Settings.", MessageType.Warning);
return;
}
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Popup System Settings", EditorStyles.boldLabel);
EditorGUILayout.Space(5);
serializedSettings.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(serializedSettings.FindProperty("popupDelay"));
EditorGUILayout.PropertyField(serializedSettings.FindProperty("fadeDuration"));
EditorGUILayout.PropertyField(serializedSettings.FindProperty("defaultAnchorSide"));
EditorGUILayout.PropertyField(serializedSettings.FindProperty("screenEdgePadding"));
EditorGUILayout.PropertyField(serializedSettings.FindProperty("maxPopupWidth"));
EditorGUILayout.PropertyField(serializedSettings.FindProperty("sortingOrder"));
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Follow Mouse", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serializedSettings.FindProperty("followMouseOffset"));
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Input", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serializedSettings.FindProperty("touchHoldDuration"));
EditorGUILayout.PropertyField(serializedSettings.FindProperty("gamepadFocusTrigger"));
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Element Prefabs", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serializedSettings.FindProperty("elementPrefabs"), true);
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Priority", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serializedSettings.FindProperty("categoryPriorities"), true);
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Per-Category Overrides", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serializedSettings.FindProperty("categoryDelayOverrides"), true);
if(EditorGUI.EndChangeCheck()) {
serializedSettings.ApplyModifiedProperties();
EditorUtility.SetDirty(settings);
}
}
[SettingsProvider]
public static SettingsProvider CreateProvider() {
var provider = new PopupSettingsProvider("Project/Jovian/Popup System", SettingsScope.Project) {
keywords = new HashSet<string>(new[] { "popup", "tooltip", "hover", "delay" })
};
return provider;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 63a03b88ed78a934094ce61f9d127ec9

345
README.md
View File

@@ -1,3 +1,344 @@
# unity-popup-system # 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. 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](#prefab-setup) section below.
### 3. Create and use PopupSystem
```csharp
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:
```csharp
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()`:
```csharp
// "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
```csharp
PopupCategory.Character // Character tooltips
PopupCategory.Item // Item tooltips
PopupCategory.Skill // Skill tooltips
PopupCategory.General // General-purpose tooltips
```
### Custom categories
```csharp
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
```csharp
// 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
```csharp
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`. |
### Approach 1: Auto-scanned triggers (recommended for static UI)
```csharp
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)
```csharp
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)
```csharp
// 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:
```csharp
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:
```csharp
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:
```csharp
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` |

7
README.md.meta Normal file
View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d33518dac7899604c9ceeda21897315d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 05f691cbfb7413f4fb735b2ec732ff06
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,56 @@
using System;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// Default popup animator that fades CanvasGroup alpha. Each category gets its own instance
/// so concurrent show/hide animations don't corrupt each other.
/// </summary>
public sealed class FadePopupAnimator : IPopupAnimator {
private CanvasGroup target;
private float timer;
private float elapsed;
private float startAlpha;
private float endAlpha;
private Action onFinish;
public bool IsAnimating => target != null;
public void Show(CanvasGroup canvasGroup, float duration, Action onComplete) {
target = canvasGroup;
timer = Mathf.Max(duration, 0.001f);
elapsed = 0f;
startAlpha = 0f;
endAlpha = 1f;
onFinish = onComplete;
canvasGroup.alpha = 0f;
}
public void Hide(CanvasGroup canvasGroup, float duration, Action onComplete) {
target = canvasGroup;
timer = Mathf.Max(duration, 0.001f);
elapsed = 0f;
startAlpha = canvasGroup.alpha;
endAlpha = 0f;
onFinish = onComplete;
}
public void Tick(float deltaTime) {
if(target == null) {
return;
}
elapsed += deltaTime;
var t = Mathf.Clamp01(elapsed / timer);
target.alpha = Mathf.Lerp(startAlpha, endAlpha, t);
if(!(t >= 1f)) {
return;
}
var callback = onFinish;
target = null;
onFinish = null;
callback?.Invoke();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 254bb620ce056dd41905258eb0f070d7

37
Runtime/IPopupAnimator.cs Normal file
View File

@@ -0,0 +1,37 @@
using System;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// Interface for popup show/hide animations. Each registered category gets its own instance
/// to prevent state corruption during concurrent animations. Driven by float timers in
/// <see cref="Tick"/>, not coroutines.
/// </summary>
public interface IPopupAnimator {
/// <summary>
/// Begins a show animation on the given CanvasGroup. Typically fades alpha from 0 to 1.
/// </summary>
/// <param name="canvasGroup">The CanvasGroup to animate.</param>
/// <param name="duration">Animation duration in seconds.</param>
/// <param name="onComplete">Callback invoked when the animation finishes. May be null.</param>
void Show(CanvasGroup canvasGroup, float duration, Action onComplete);
/// <summary>
/// Begins a hide animation on the given CanvasGroup. Typically fades alpha to 0.
/// </summary>
/// <param name="canvasGroup">The CanvasGroup to animate.</param>
/// <param name="duration">Animation duration in seconds.</param>
/// <param name="onComplete">Callback invoked when the animation finishes. May be null.</param>
void Hide(CanvasGroup canvasGroup, float duration, Action onComplete);
/// <summary>
/// Advances the animation by deltaTime. Call every frame from <see cref="IPopupSystem.Tick"/>.
/// </summary>
void Tick(float deltaTime);
/// <summary>
/// True if an animation is currently in progress.
/// </summary>
bool IsAnimating { get; }
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b146d885db3dcef4fa70abd3f43683d3

79
Runtime/IPopupSystem.cs Normal file
View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using Jovian.PopupSystem.UI;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// Core interface for the popup system. Created per game state, not as a singleton.
/// Manages category registration, trigger discovery, popup display, and lifecycle.
/// </summary>
public interface IPopupSystem {
/// <summary>
/// Scans all <see cref="PopupTrigger"/> components under the given parent and binds them
/// to this system. Each trigger receives a <see cref="PopupTriggerView"/> for behavior.
/// </summary>
void ScanTriggers(Transform parent);
/// <summary>
/// Scans all <see cref="PopupTrigger"/> components under the given parent, binds them,
/// and invokes the configure callback with both the trigger (for hierarchy queries) and
/// its view (for setting content).
/// </summary>
void InitializeTriggersInChildren(Transform parent, Action<PopupTrigger, PopupTriggerView> configureTrigger);
/// <summary>
/// Returns the <see cref="PopupTriggerView"/> for the first registered trigger whose
/// GameObject name matches. Returns null if not found.
/// </summary>
PopupTriggerView GetTriggerHandler(string gameObjectName);
/// <summary>
/// Returns all <see cref="PopupTriggerView"/> instances registered under the given category.
/// </summary>
IReadOnlyList<PopupTriggerView> GetTriggerHandlers(PopupCategory category);
/// <summary>
/// Registers a popup category. Each category gets its own <see cref="PopupReference"/> instance
/// (lazily created on first show) and its own <see cref="IPopupAnimator"/>.
/// </summary>
/// <param name="category">The category to register.</param>
/// <param name="priority">Fallback priority if not defined in <see cref="PopupSettings"/>. Higher dismisses lower.</param>
void RegisterCategory(PopupCategory category, int priority = 0);
/// <summary>
/// Shows a popup for the given category after the configured delay. The build callback
/// populates content via <see cref="PopupContentBuilder"/>. Optionally anchors to a
/// RectTransform or follows the mouse if no anchor is provided.
/// </summary>
void Show(PopupCategory category, Action<PopupContentBuilder> buildContent,
RectTransform anchor = null, AnchorSide? anchorSide = null);
/// <summary>
/// Shows a popup for the given category at a fixed screen position.
/// </summary>
void ShowAtPosition(PopupCategory category, Action<PopupContentBuilder> buildContent,
Vector2 screenPosition);
/// <summary>
/// Hides the popup for the given category with a fade-out animation.
/// </summary>
void Hide(PopupCategory category);
/// <summary>
/// Hides all visible popups across all categories.
/// </summary>
void HideAll();
/// <summary>
/// Drives delay timers, animations, and follow-mouse positioning. Call every frame.
/// </summary>
void Tick(float deltaTime);
/// <summary>
/// Destroys all popup view GameObjects and clears registered categories.
/// Call when the owning game state exits.
/// </summary>
void Dispose();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 65d563f87bb09de48b8edd12101cdd11

View File

@@ -0,0 +1,19 @@
{
"name": "Jovian.PopupSystem",
"rootNamespace": "Jovian.PopupSystem",
"references": [
"Unity.TextMeshPro",
"Unity.InputSystem"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"Newtonsoft.Json.dll"
],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 31bcaef770c58d94db7f78106f15fd4e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

48
Runtime/PopupCategory.cs Normal file
View File

@@ -0,0 +1,48 @@
using System;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// Value type identifying a popup channel. Each category gets its own popup view instance.
/// Compared by string ID using ordinal comparison. Define custom categories as static fields.
/// </summary>
[Serializable]
public struct PopupCategory : IEquatable<PopupCategory> {
[SerializeField] string id;
public string Id => id;
public PopupCategory(string id) {
this.id = id;
}
public static readonly PopupCategory Character = new("Character");
public static readonly PopupCategory Item = new("Item");
public static readonly PopupCategory Skill = new("Skill");
public static readonly PopupCategory General = new("General");
public bool Equals(PopupCategory other) {
return string.Equals(id, other.id, StringComparison.Ordinal);
}
public override bool Equals(object obj) {
return obj is PopupCategory other && Equals(other);
}
public override int GetHashCode() {
return id != null ? id.GetHashCode() : 0;
}
public override string ToString() {
return id ?? string.Empty;
}
public static bool operator ==(PopupCategory left, PopupCategory right) {
return left.Equals(right);
}
public static bool operator !=(PopupCategory left, PopupCategory right) {
return !left.Equals(right);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bcd1fb0ef93fed0489a43977bc71594b

View File

@@ -0,0 +1,15 @@
using System;
using Newtonsoft.Json;
namespace Jovian.PopupSystem {
public sealed class PopupCategoryJsonConverter : JsonConverter<PopupCategory> {
public override void WriteJson(JsonWriter writer, PopupCategory value, JsonSerializer serializer) {
writer.WriteValue(value.Id);
}
public override PopupCategory ReadJson(JsonReader reader, Type objectType, PopupCategory existingValue, bool hasExistingValue, JsonSerializer serializer) {
var id = reader.Value as string;
return new PopupCategory(id);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 683182ef8e2aa6b4daffc547c481b768

View File

@@ -0,0 +1,115 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Jovian.PopupSystem {
/// <summary>
/// Fluent API struct for building popup content. Operates on the generic element cache
/// inside a <see cref="PopupView"/>. Received in the build callback passed to
/// <see cref="IPopupSystem.Show"/>. Use <see cref="Add"/> for fully custom elements,
/// or convenience methods for common types.
/// </summary>
public readonly struct PopupContentBuilder {
readonly PopupView view;
/// <summary>
/// Creates a builder targeting the given popup view.
/// </summary>
public PopupContentBuilder(PopupView view) {
this.view = view;
}
/// <summary>
/// Returns a cached or newly instantiated element by its registered type.
/// Use this for fully custom or game-specific element types.
/// </summary>
public GameObject Add(PopupElementType elementType) {
return view.GetElement(elementType);
}
/// <summary>
/// Adds a text element using the given element type and sets its TMP_Text content.
/// </summary>
public PopupContentBuilder AddText(string text, PopupElementType elementType) {
var go = view.GetElement(elementType);
if(go == null) {
return this;
}
var tmp = go.GetComponentInChildren<TMP_Text>();
if(tmp) {
tmp.text = text;
}
return this;
}
/// <summary>
/// Adds a colored text element using the given element type.
/// Hex color can be with or without the # prefix.
/// </summary>
public PopupContentBuilder AddText(string text, string hexColor, PopupElementType elementType) {
var go = view.GetElement(elementType);
if(!go) {
return this;
}
var tmp = go.GetComponentInChildren<TMP_Text>();
if(!tmp) {
return this;
}
var prefix = hexColor.Length > 0 && hexColor[0] == '#' ? "" : "#";
tmp.text = $"<color={prefix}{hexColor}>{text}</color>";
return this;
}
/// <summary>
/// Adds a name/value row using the given element type. The prefab must have
/// exactly two TMP_Text children (label first, value second).
/// </summary>
public PopupContentBuilder AddNameValue(string label, int value, PopupElementType elementType) {
return AddNameValueInternal(elementType, label, value.ToString());
}
/// <summary>
/// Adds a name/value row using the given element type. The prefab must have
/// exactly two TMP_Text children (label first, value second).
/// </summary>
public PopupContentBuilder AddNameValue(string label, string value, PopupElementType elementType) {
return AddNameValueInternal(elementType, label, value);
}
private PopupContentBuilder AddNameValueInternal(PopupElementType elementType, string label, string value) {
var go = view.GetElement(elementType);
if(go != null) {
var children = go.GetComponentsInChildren<TMP_Text>(true);
if(children.Length >= 2) {
children[0].text = label;
children[1].text = value;
}
}
return this;
}
/// <summary>
/// Adds an image element with the given sprite and optional height.
/// </summary>
public PopupContentBuilder AddImage(Sprite sprite, PopupElementType elementType, float height = 64f) {
var go = view.GetElement(elementType);
if(go != null) {
var image = go.GetComponentInChildren<Image>();
if(image != null) {
image.sprite = sprite;
var rt = (RectTransform)image.transform;
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
}
}
return this;
}
/// <summary>
/// Adds a separator element using the given element type.
/// </summary>
public PopupContentBuilder AddSeparator(PopupElementType elementType) {
view.GetElement(elementType);
return this;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b9f4094c471d0464e95e330331acc813

View File

@@ -0,0 +1,74 @@
using System;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// Value type identifying a popup element prefab. Compared by string ID using ordinal
/// comparison. Built-in types cover common popup elements. Define custom types as static
/// fields or create instances for game-specific elements and variants.
/// </summary>
[Serializable]
public struct PopupElementType : IEquatable<PopupElementType> {
[SerializeField] private string id;
/// <summary>The string identifier for this element type.</summary>
public string Id => id;
public PopupElementType(string id) {
this.id = id;
}
// --- Built-in types ---
/// <summary>Bold header text element.</summary>
public static readonly PopupElementType Header = new("header");
/// <summary>Body text element.</summary>
public static readonly PopupElementType Text = new("text");
/// <summary>Label + value stat row element.</summary>
public static readonly PopupElementType LabelValueText = new("label_value_text");
/// <summary>Image/icon element.</summary>
public static readonly PopupElementType Image = new("image");
/// <summary>Horizontal separator line.</summary>
public static readonly PopupElementType Separator = new("separator");
// --- Variant helper ---
/// <summary>
/// Creates a variant of this element type by appending a suffix.
/// e.g. <c>PopupElementType.Header.Variant("gold")</c> produces <c>"header_gold"</c>.
/// </summary>
public PopupElementType Variant(string variant) {
return new PopupElementType($"{id}_{variant}");
}
// --- Equality ---
public bool Equals(PopupElementType other) {
return string.Equals(id, other.id, StringComparison.Ordinal);
}
public override bool Equals(object obj) {
return obj is PopupElementType other && Equals(other);
}
public override int GetHashCode() {
return id != null ? id.GetHashCode() : 0;
}
public override string ToString() {
return id ?? string.Empty;
}
public static bool operator ==(PopupElementType left, PopupElementType right) {
return left.Equals(right);
}
public static bool operator !=(PopupElementType left, PopupElementType right) {
return !left.Equals(right);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5ba7e78e3d930334a935a600a8f67ded

13
Runtime/PopupEnums.cs Normal file
View File

@@ -0,0 +1,13 @@
namespace Jovian.PopupSystem {
public enum AnchorSide {
Below,
Above,
Left,
Right
}
public enum PopupPositionMode {
AnchorToElement,
FollowMouse
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 01e69680e496eca4daebbc1874e3a7d9

96
Runtime/PopupSettings.cs Normal file
View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// ScriptableObject holding all popup system configuration. Create via
/// Assets > Create > Jovian > Popup System > Popup Settings.
/// </summary>
[CreateAssetMenu(fileName = "PopupSettings", menuName = "Jovian/Popup System/Popup Settings")]
public class PopupSettings : ScriptableObject {
[Header("General")]
public float popupDelay = 0.4f;
public float fadeDuration = 0.2f;
public AnchorSide defaultAnchorSide = AnchorSide.Below;
public float screenEdgePadding = 10f;
public float maxPopupWidth = 400f;
public int sortingOrder = 100;
[Header("Follow Mouse")]
public Vector2 followMouseOffset = new(15f, -15f);
[Header("Input")]
public float touchHoldDuration = 0.6f;
public bool gamepadFocusTrigger = true;
[Header("Element Prefabs")]
public List<PopupElementEntry> elementPrefabs = new();
[Header("Priority")]
public List<CategoryPriority> categoryPriorities = new();
[Header("Per-Category Overrides")]
public List<CategoryDelay> categoryDelayOverrides = new();
private Dictionary<PopupElementType, GameObject> prefabLookup;
/// <summary>
/// Returns the element prefab registered under the given type, or null if not found.
/// </summary>
public GameObject GetPrefab(PopupElementType elementType) {
if(prefabLookup == null) {
prefabLookup = new Dictionary<PopupElementType, GameObject>();
foreach(var entry in elementPrefabs) {
if(!string.IsNullOrEmpty(entry.type.Id) && entry.prefab != null) {
prefabLookup[entry.type] = entry.prefab;
}
}
}
prefabLookup.TryGetValue(elementType, out var result);
return result;
}
/// <summary>
/// Returns the configured priority for a category, or 0 if not configured.
/// </summary>
public int GetPriority(PopupCategory category) {
foreach(var cp in categoryPriorities) {
if(cp.category == category) {
return cp.priority;
}
}
return 0;
}
/// <summary>
/// Returns the configured delay override for a category, or the default popupDelay.
/// </summary>
public float GetDelay(PopupCategory category) {
foreach(var cd in categoryDelayOverrides) {
if(cd.category == category) {
return cd.delay;
}
}
return popupDelay;
}
}
[Serializable]
public sealed class PopupElementEntry {
public PopupElementType type;
public GameObject prefab;
}
[Serializable]
public sealed class CategoryPriority {
public PopupCategory category;
public int priority;
}
[Serializable]
public sealed class CategoryDelay {
public PopupCategory category;
public float delay;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e38e313f3665d464b82b22699b2a4634

282
Runtime/PopupSystem.cs Normal file
View File

@@ -0,0 +1,282 @@
using System;
using System.Collections.Generic;
using Jovian.PopupSystem.UI;
using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace Jovian.PopupSystem {
/// <summary>
/// Core implementation of <see cref="IPopupSystem"/>. Created per game state, not as a singleton.
/// Manages category registration, trigger discovery, popup lifecycle, priority dismissal, and
/// tick-driven delay timers and animations. Pass a canvasParent to auto-scan triggers on construction.
/// </summary>
public sealed class PopupSystem : IPopupSystem {
readonly PopupSettings settings;
readonly PopupReference viewPrefab;
readonly Func<IPopupAnimator> animatorFactory;
readonly Transform canvasParent;
readonly Dictionary<PopupCategory, ViewState> categories = new();
readonly List<PopupTrigger> registeredTriggers = new();
/// <summary>
/// Creates a new popup system instance.
/// </summary>
/// <param name="settings">Configuration ScriptableObject with delays, priorities, and display settings.</param>
/// <param name="viewPrefab">The PopupReference prefab to instantiate per category.</param>
/// <param name="canvasParent">Optional parent Canvas transform. When provided, popup views are parented
/// here (inheriting CanvasScaler) and all PopupTrigger components are auto-scanned.</param>
/// <param name="animatorFactory">Optional factory for custom animators. Defaults to FadePopupAnimator.</param>
public PopupSystem(PopupSettings settings, PopupReference viewPrefab, Transform canvasParent = null, Func<IPopupAnimator> animatorFactory = null) {
this.settings = settings;
this.viewPrefab = viewPrefab;
this.canvasParent = canvasParent;
this.animatorFactory = animatorFactory ?? (() => new FadePopupAnimator());
// Auto-scan if a parent was provided
if(canvasParent != null) {
ScanTriggers(canvasParent);
}
}
/// <inheritdoc />
public void RegisterCategory(PopupCategory category, int priority = 0) {
if(categories.ContainsKey(category)) {
return;
}
var effectivePriority = settings.GetPriority(category);
if(effectivePriority == 0) {
effectivePriority = priority;
}
categories[category] = new ViewState {
priority = effectivePriority,
delay = settings.GetDelay(category),
animator = animatorFactory()
};
}
/// <inheritdoc />
public void Show(PopupCategory category, Action<PopupContentBuilder> buildContent,
RectTransform anchor = null, AnchorSide? anchorSide = null) {
if(!categories.TryGetValue(category, out var state)) {
return;
}
DismissLowerPriority(state.priority);
state.pendingBuild = buildContent;
state.pendingAnchor = anchor;
state.pendingAnchorSide = anchorSide ?? settings.defaultAnchorSide;
state.pendingScreenPos = null;
state.delayTimer = state.delay;
state.isPending = true;
}
/// <inheritdoc />
public void ShowAtPosition(PopupCategory category, Action<PopupContentBuilder> buildContent,
Vector2 screenPosition) {
if(!categories.TryGetValue(category, out var state)) {
return;
}
DismissLowerPriority(state.priority);
state.pendingBuild = buildContent;
state.pendingAnchor = null;
state.pendingScreenPos = screenPosition;
state.delayTimer = state.delay;
state.isPending = true;
}
/// <inheritdoc />
public void Hide(PopupCategory category) {
if(!categories.TryGetValue(category, out var state)) {
return;
}
state.isPending = false;
if(state.view != null && state.view.IsVisible) {
state.animator.Hide(state.view.CanvasGroup, settings.fadeDuration, () => {
state.view.SetVisible(false);
});
}
}
/// <inheritdoc />
public void HideAll() {
foreach(var kvp in categories) {
Hide(kvp.Key);
}
}
/// <inheritdoc />
public void Tick(float deltaTime) {
foreach(var kvp in categories) {
kvp.Value.animator.Tick(deltaTime);
}
foreach(var kvp in categories) {
var state = kvp.Value;
if(!state.isPending) {
continue;
}
state.delayTimer -= deltaTime;
if(state.delayTimer > 0f) {
continue;
}
state.isPending = false;
ShowImmediate(state);
}
foreach(var kvp in categories) {
var state = kvp.Value;
if(state.view != null && state.view.IsVisible && state.isFollowMouse) {
state.view.UpdatePosition();
}
}
}
/// <inheritdoc />
public void ScanTriggers(Transform parent) {
var triggers = parent.GetComponentsInChildren<PopupTrigger>(true);
foreach(var trigger in triggers) {
if(!registeredTriggers.Contains(trigger)) {
BindTrigger(trigger);
}
}
}
/// <inheritdoc />
public PopupTriggerView GetTriggerHandler(string gameObjectName) {
foreach(var trigger in registeredTriggers) {
if(trigger != null && trigger.gameObject.name == gameObjectName) {
return trigger.Handler;
}
}
return null;
}
/// <inheritdoc />
public IReadOnlyList<PopupTriggerView> GetTriggerHandlers(PopupCategory category) {
var result = new List<PopupTriggerView>();
foreach(var trigger in registeredTriggers) {
if(trigger != null && trigger.Category == category && trigger.Handler != null) {
result.Add(trigger.Handler);
}
}
return result;
}
/// <inheritdoc />
public void InitializeTriggersInChildren(Transform parent, Action<PopupTrigger, PopupTriggerView> configureTrigger) {
var triggers = parent.GetComponentsInChildren<PopupTrigger>(true);
foreach(var trigger in triggers) {
if(!registeredTriggers.Contains(trigger)) {
BindTrigger(trigger);
}
configureTrigger(trigger, trigger.Handler);
}
}
private void BindTrigger(PopupTrigger trigger) {
var handler = new PopupTriggerView(this);
trigger.Bind(handler);
registeredTriggers.Add(trigger);
}
/// <inheritdoc />
public void Dispose() {
foreach(var kvp in categories) {
if(kvp.Value.view?.Reference != null) {
Object.Destroy(kvp.Value.view.Reference.gameObject);
}
}
categories.Clear();
}
private void ShowImmediate(ViewState state) {
EnsureView(state);
state.view.ClearContent();
var builder = new PopupContentBuilder(state.view);
state.pendingBuild?.Invoke(builder);
// Activate before layout rebuild so Unity has an active hierarchy to calculate
state.view.CanvasGroup.alpha = 0f;
state.view.SetVisible(true);
// Force full layout rebuild so positioning has correct size on first show
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(state.view.Content);
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)state.view.Transform);
if(state.pendingScreenPos.HasValue) {
state.view.SetFixedPosition(state.pendingScreenPos.Value, settings.screenEdgePadding);
state.isFollowMouse = false;
}
else if(state.pendingAnchor != null) {
state.view.SetAnchorMode(state.pendingAnchor, state.pendingAnchorSide, settings.screenEdgePadding);
state.isFollowMouse = false;
}
else {
state.view.SetFollowMouseMode(settings.followMouseOffset, settings.screenEdgePadding);
state.isFollowMouse = true;
}
state.view.UpdatePosition();
state.animator.Show(state.view.CanvasGroup, settings.fadeDuration, null);
}
private void EnsureView(ViewState state) {
if(state.view != null) {
return;
}
PopupReference popupRef;
if(canvasParent != null) {
popupRef = Object.Instantiate(viewPrefab, canvasParent);
}
else {
popupRef = Object.Instantiate(viewPrefab);
}
var canvas = popupRef.GetComponent<Canvas>();
if(canvas != null) {
canvas.overrideSorting = true;
canvas.sortingOrder = settings.sortingOrder;
}
state.view = new PopupView(popupRef, settings);
state.view.SetVisible(false);
state.view.SetMaxWidth(settings.maxPopupWidth);
}
private void DismissLowerPriority(int showingPriority) {
foreach(var kvp in categories) {
var state = kvp.Value;
if(state.priority < showingPriority && state.view != null && state.view.IsVisible) {
state.isPending = false;
state.animator.Hide(state.view.CanvasGroup, settings.fadeDuration, () => {
state.view.SetVisible(false);
});
}
}
}
private sealed class ViewState {
public PopupView view;
public IPopupAnimator animator;
public int priority;
public float delay;
public float delayTimer;
public bool isPending;
public bool isFollowMouse;
public Action<PopupContentBuilder> pendingBuild;
public RectTransform pendingAnchor;
public AnchorSide pendingAnchorSide;
public Vector2? pendingScreenPos;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 056ed88d12add674798ad4e6300bf222

View File

@@ -0,0 +1,54 @@
using System;
using Jovian.PopupSystem.UI;
using UnityEngine;
namespace Jovian.PopupSystem {
/// <summary>
/// Handles popup behavior for a <see cref="PopupTrigger"/>. Holds the content callback and
/// forwards pointer events to the <see cref="IPopupSystem"/>. This is the behavior layer;
/// the MonoBehaviour trigger is the reference holder that forwards events here.
/// </summary>
public sealed class PopupTriggerView {
readonly IPopupSystem popupSystem;
Action<PopupContentBuilder> contentCallback;
/// <summary>
/// Creates a new trigger view bound to the given popup system.
/// </summary>
public PopupTriggerView(IPopupSystem popupSystem) {
this.popupSystem = popupSystem;
}
/// <summary>
/// Sets the content builder callback that will be invoked when the popup is shown.
/// </summary>
public void SetContent(Action<PopupContentBuilder> callback) {
contentCallback = callback;
}
/// <summary>
/// Called by <see cref="PopupTrigger"/> when the pointer enters. Reads the trigger's
/// category, anchor side, and position mode to show the popup.
/// </summary>
public void OnPointerEnter(PopupTrigger trigger) {
if(popupSystem == null || contentCallback == null) {
return;
}
if(trigger.PositionMode == PopupPositionMode.AnchorToElement) {
popupSystem.Show(trigger.Category, contentCallback, (RectTransform)trigger.transform, trigger.AnchorSide);
}
else {
popupSystem.Show(trigger.Category, contentCallback);
}
}
/// <summary>
/// Called by <see cref="PopupTrigger"/> when the pointer exits. Hides the popup
/// for the trigger's category.
/// </summary>
public void OnPointerExit(PopupTrigger trigger) {
popupSystem?.Hide(trigger.Category);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1e2351b6a4b8bf046923790d4d09141c

224
Runtime/PopupView.cs Normal file
View File

@@ -0,0 +1,224 @@
using System.Collections.Generic;
using Jovian.PopupSystem.UI;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace Jovian.PopupSystem {
/// <summary>
/// Behavior class for a popup view. Manages the generic grow-only element cache,
/// positioning, visibility, and layout. Operates on a <see cref="PopupReference"/>
/// MonoBehaviour for scene references.
/// </summary>
public sealed class PopupView {
readonly PopupReference reference;
readonly PopupSettings settings;
// Generic element cache: keyed by element type
readonly Dictionary<PopupElementType, List<GameObject>> elementCache = new();
readonly Dictionary<PopupElementType, int> elementIndex = new();
// Positioning state
PopupPositionMode positionMode;
RectTransform anchorTarget;
AnchorSide anchorSide;
Vector2 followOffset;
float screenEdgePadding;
float maxWidth;
bool isVisible;
bool layoutDirty;
Canvas rootCanvas;
Camera canvasCamera;
/// <summary>The underlying MonoBehaviour reference holder.</summary>
public PopupReference Reference => reference;
/// <summary>The CanvasGroup for animation control.</summary>
public CanvasGroup CanvasGroup => reference.CanvasGroup;
/// <summary>The content RectTransform where elements are parented.</summary>
public RectTransform Content => reference.Content;
/// <summary>The root Transform of the popup GameObject.</summary>
public Transform Transform => reference.transform;
/// <summary>Whether the popup is currently visible.</summary>
public bool IsVisible => isVisible;
/// <summary>
/// Creates a new popup view wrapping the given reference and settings.
/// </summary>
public PopupView(PopupReference reference, PopupSettings settings) {
this.reference = reference;
this.settings = settings;
}
// --- Element cache (generic, grow-only) ---
/// <summary>
/// Returns the next available cached element for the given type, or instantiates
/// a new one from the settings prefab registry. The element is activated and placed
/// at the end of the content layout.
/// </summary>
public GameObject GetElement(PopupElementType elementType) {
if(!elementCache.TryGetValue(elementType, out var cache)) {
cache = new List<GameObject>();
elementCache[elementType] = cache;
elementIndex[elementType] = 0;
}
var index = elementIndex[elementType];
if(index < cache.Count) {
var existing = cache[index];
existing.SetActive(true);
existing.transform.SetAsLastSibling();
elementIndex[elementType] = index + 1;
return existing;
}
var prefab = settings.GetPrefab(elementType);
if(prefab == null) {
Debug.LogWarning($"[PopupView] No prefab registered for element '{elementType}'");
return null;
}
var created = Object.Instantiate(prefab, reference.Content);
created.SetActive(true);
created.transform.SetAsLastSibling();
cache.Add(created);
elementIndex[elementType] = index + 1;
return created;
}
/// <summary>
/// Deactivates all cached content elements and marks layout as dirty.
/// </summary>
public void ClearContent() {
foreach(var kvp in elementCache) {
var cache = kvp.Value;
var activeCount = elementIndex[kvp.Key];
for(int i = 0; i < activeCount; i++) {
cache[i].SetActive(false);
}
elementIndex[kvp.Key] = 0;
}
layoutDirty = true;
}
// --- Visibility ---
/// <summary>Shows or hides the popup GameObject. Resets alpha to 0 when hiding.</summary>
public void SetVisible(bool visible) {
isVisible = visible;
reference.gameObject.SetActive(visible);
if(!visible) {
reference.CanvasGroup.alpha = 0f;
}
}
/// <summary>Constrains the popup's horizontal size to the given maximum width in pixels.</summary>
public void SetMaxWidth(float maxPopupWidth) {
maxWidth = maxPopupWidth;
if(maxWidth > 0f) {
var rt = (RectTransform)reference.transform;
var size = rt.sizeDelta;
size.x = Mathf.Min(size.x, maxWidth);
rt.sizeDelta = size;
}
}
// --- Positioning ---
/// <summary>Configures the popup to anchor to a target element on the specified side.</summary>
public void SetAnchorMode(RectTransform target, AnchorSide side, float edgePadding) {
positionMode = PopupPositionMode.AnchorToElement;
anchorTarget = target;
anchorSide = side;
screenEdgePadding = edgePadding;
}
/// <summary>Configures the popup to follow the mouse cursor with the given offset.</summary>
public void SetFollowMouseMode(Vector2 offset, float edgePadding) {
positionMode = PopupPositionMode.FollowMouse;
followOffset = offset;
screenEdgePadding = edgePadding;
}
/// <summary>Positions the popup at a fixed screen coordinate with edge clamping.</summary>
public void SetFixedPosition(Vector2 screenPos, float edgePadding) {
positionMode = PopupPositionMode.AnchorToElement;
anchorTarget = null;
screenEdgePadding = edgePadding;
PositionAtScreenPoint(screenPos);
}
/// <summary>Updates the popup position based on the current mode (follow mouse or anchored).</summary>
public void UpdatePosition() {
if(positionMode == PopupPositionMode.FollowMouse) {
PositionAtScreenPoint(Mouse.current.position.ReadValue() + followOffset);
}
else if(anchorTarget != null) {
PositionAnchoredTo(anchorTarget, anchorSide);
}
}
private void CacheCanvas() {
if(rootCanvas != null) {
return;
}
rootCanvas = reference.GetComponentInParent<Canvas>()?.rootCanvas;
canvasCamera = rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay
? rootCanvas.worldCamera : null;
}
private void PositionAnchoredTo(RectTransform target, AnchorSide side) {
CacheCanvas();
var targetRect = GetScreenRect(target, canvasCamera);
var popupRect = GetScreenRect((RectTransform)reference.transform, canvasCamera);
var popupSize = popupRect.size;
var pos = side switch {
AnchorSide.Below => new Vector2(targetRect.center.x - popupSize.x * 0.5f, targetRect.yMin - popupSize.y),
AnchorSide.Above => new Vector2(targetRect.center.x - popupSize.x * 0.5f, targetRect.yMax),
AnchorSide.Left => new Vector2(targetRect.xMin - popupSize.x, targetRect.center.y - popupSize.y * 0.5f),
AnchorSide.Right => new Vector2(targetRect.xMax, targetRect.center.y - popupSize.y * 0.5f),
_ => targetRect.center
};
PositionAtScreenPoint(pos);
}
private void PositionAtScreenPoint(Vector2 screenPos) {
CacheCanvas();
var rt = (RectTransform)reference.transform;
if(layoutDirty) {
LayoutRebuilder.ForceRebuildLayoutImmediate(reference.Content);
layoutDirty = false;
}
var popupSize = GetScreenRect(rt, canvasCamera).size;
// Clamp to screen
screenPos.x = Mathf.Clamp(screenPos.x, screenEdgePadding, Screen.width - popupSize.x - screenEdgePadding);
screenPos.y = Mathf.Clamp(screenPos.y, screenEdgePadding, Screen.height - popupSize.y - screenEdgePadding);
// Convert screen position to parent local space
var parentRt = rt.parent as RectTransform;
if(parentRt != null) {
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRt, screenPos, canvasCamera, out var localPos);
rt.localPosition = localPos;
}
else {
rt.position = screenPos;
}
}
private static readonly Vector3[] cornersBuffer = new Vector3[4];
private static Rect GetScreenRect(RectTransform rt, Camera camera) {
rt.GetWorldCorners(cornersBuffer);
var min = RectTransformUtility.WorldToScreenPoint(camera, cornersBuffer[0]);
var max = RectTransformUtility.WorldToScreenPoint(camera, cornersBuffer[2]);
return new Rect(min, max - min);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 18ebd1b0205e20440aa4c4991b43cc46

8
Runtime/UI.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4459a298af1a37f47af7b7b70655ee0d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using UnityEngine;
namespace Jovian.PopupSystem.UI {
/// <summary>
/// Reference-only MonoBehaviour for a popup prefab. Holds serialized scene references
/// to the content container, canvas group, and background. All behavior is in
/// <see cref="PopupView"/>.
/// </summary>
public class PopupReference : MonoBehaviour {
[SerializeField] RectTransform content;
[SerializeField] CanvasGroup canvasGroup;
[SerializeField] RectTransform background;
/// <summary>The content RectTransform where popup elements are parented.</summary>
public RectTransform Content => content;
/// <summary>The CanvasGroup for fade animation control.</summary>
public CanvasGroup CanvasGroup => canvasGroup;
/// <summary>The background RectTransform that sizes to content.</summary>
public RectTransform Background => background;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bc25da4712d7cc4419eb6f364e032431

View File

@@ -0,0 +1,48 @@
using UnityEngine;
using UnityEngine.EventSystems;
namespace Jovian.PopupSystem.UI {
/// <summary>
/// Reference holder MonoBehaviour for popup triggers. Attach to any UI element with a
/// Graphic component (Image, TMP_Text, etc.) that has Raycast Target enabled.
/// Configure category, anchor side, and position mode in the Inspector.
/// Forwards pointer events to the bound <see cref="PopupTriggerView"/>.
/// </summary>
public class PopupTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {
[SerializeField] PopupCategory category;
[SerializeField] AnchorSide anchorSide = AnchorSide.Below;
[SerializeField] PopupPositionMode positionMode = PopupPositionMode.AnchorToElement;
PopupTriggerView handler;
/// <summary>The popup category this trigger belongs to.</summary>
public PopupCategory Category => category;
/// <summary>Which side of this element the popup anchors to.</summary>
public AnchorSide AnchorSide => anchorSide;
/// <summary>Whether the popup anchors to this element or follows the mouse.</summary>
public PopupPositionMode PositionMode => positionMode;
/// <summary>The bound behavior handler. Null until <see cref="Bind"/> is called.</summary>
public PopupTriggerView Handler => handler;
/// <summary>
/// Binds a <see cref="PopupTriggerView"/> to this trigger. Called automatically
/// by <see cref="IPopupSystem.ScanTriggers"/> or <see cref="IPopupSystem.InitializeTriggersInChildren"/>.
/// </summary>
public void Bind(PopupTriggerView view) {
handler = view;
}
/// <summary>Forwards pointer enter to the bound handler.</summary>
public void OnPointerEnter(PointerEventData eventData) {
handler?.OnPointerEnter(this);
}
/// <summary>Forwards pointer exit to the bound handler.</summary>
public void OnPointerExit(PointerEventData eventData) {
handler?.OnPointerExit(this);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4ef9d21a19cd4db4f9d5491202547c05

View File

@@ -0,0 +1,160 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &7034836061828108288
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1241885294582312999}
- component: {fileID: 1942692710355281447}
- component: {fileID: 6612787789151041457}
- component: {fileID: 743893275569378137}
m_Layer: 5
m_Name: PopupHeader
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1241885294582312999
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 741.369, y: -210.32233}
m_SizeDelta: {x: 214.8432, y: 16}
m_Pivot: {x: 0, y: 1}
--- !u!222 &1942692710355281447
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_CullTransparentMesh: 1
--- !u!114 &6612787789151041457
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 5
m_fontSizeMax: 16
m_fontStyle: 17
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &743893275569378137
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7034836061828108288}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: 1
m_LayoutPriority: 1

View File

@@ -0,0 +1,98 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &5887814251614319338
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3721371113883694790}
- component: {fileID: 5474191059961008429}
- component: {fileID: 7093821785826926595}
- component: {fileID: 6531872381202374245}
m_Layer: 5
m_Name: PopupIcon
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3721371113883694790
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 989.9083, y: -555.6331}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5474191059961008429
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_CullTransparentMesh: 1
--- !u!114 &7093821785826926595
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: ea02ea44fa86ee445be0f7ca82098b75, type: 3}
m_Type: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &6531872381202374245
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5887814251614319338}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1

View File

@@ -0,0 +1,292 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1599460330468667833
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 623181081371217224}
- component: {fileID: 1835601435911948781}
- component: {fileID: 695849844002832445}
- component: {fileID: 3081303906751693297}
- component: {fileID: -4214546602618600400}
- component: {fileID: 7236468329886607693}
m_Layer: 5
m_Name: PopupReferencePrefab
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &623181081371217224
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8899521584296352500}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 300, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!225 &1835601435911948781
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!114 &695849844002832445
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bc25da4712d7cc4419eb6f364e032431, type: 3}
m_Name:
m_EditorClassIdentifier: Jovian.PopupSystem::Jovian.PopupSystem.UI.PopupReference
content: {fileID: 176628901263125209}
canvasGroup: {fileID: 1835601435911948781}
background: {fileID: 8899521584296352500}
--- !u!223 &3081303906751693297
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 2
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 0
m_AdditionalShaderChannelsFlag: 25
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 100
m_TargetDisplay: 0
--- !u!114 &-4214546602618600400
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0.5
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 1
--- !u!114 &7236468329886607693
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1599460330468667833}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &3774543863822571266
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 176628901263125209}
- component: {fileID: 2618782962856630769}
- component: {fileID: 4695064839375127024}
- component: {fileID: 8584485639437491102}
- component: {fileID: 7537071497970891545}
m_Layer: 5
m_Name: Content
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &176628901263125209
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8899521584296352500}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!114 &2618782962856630769
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
m_Padding:
m_Left: 8
m_Right: 8
m_Top: 8
m_Bottom: 8
m_ChildAlignment: 0
m_Spacing: 3.82
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 1
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!222 &4695064839375127024
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_CullTransparentMesh: 1
--- !u!114 &8584485639437491102
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0.1509434, g: 0.116711326, b: 0.088999644, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 52125a3c3df558448a5af5a04dbf8d2d, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &7537071497970891545
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3774543863822571266}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &7921791084240859601
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8899521584296352500}
- component: {fileID: 4414385109869753182}
m_Layer: 5
m_Name: Background
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8899521584296352500
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7921791084240859601}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 176628901263125209}
m_Father: {fileID: 623181081371217224}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -0.000015258789, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!222 &4414385109869753182
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7921791084240859601}
m_CullTransparentMesh: 1

View File

@@ -0,0 +1,98 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6770634903822758885
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6952702389074691349}
- component: {fileID: 8646545094697898389}
- component: {fileID: 4190588985333916705}
- component: {fileID: 4150277454754236166}
m_Layer: 5
m_Name: PopupSeparator
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6952702389074691349
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 5.3594}
m_Pivot: {x: 0, y: 1}
--- !u!222 &8646545094697898389
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_CullTransparentMesh: 1
--- !u!114 &4190588985333916705
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 0.61809045, b: 0.15566039, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &4150277454754236166
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6770634903822758885}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: -1
m_LayoutPriority: 1

View File

@@ -0,0 +1,376 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3992841297615171806
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 292870618973774166}
- component: {fileID: 4060824052845831606}
- component: {fileID: 572642145995679888}
m_Layer: 5
m_Name: label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &292870618973774166
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3992841297615171806}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1843470073663794312}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 177.619, y: 25.6566}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4060824052845831606
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3992841297615171806}
m_CullTransparentMesh: 1
--- !u!114 &572642145995679888
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3992841297615171806}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 5
m_fontSizeMax: 16
m_fontStyle: 17
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &6246834368258800846
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1843470073663794312}
- component: {fileID: 7829576473989485776}
- component: {fileID: 5426390226938478828}
- component: {fileID: 2356989198378852018}
m_Layer: 0
m_Name: PopupStat
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1843470073663794312
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 292870618973774166}
- {fileID: 8849778187297648043}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 835.73267, y: -543.18}
m_SizeDelta: {x: 308.5346, y: 25.640076}
m_Pivot: {x: 0, y: 1}
--- !u!114 &7829576473989485776
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.HorizontalLayoutGroup
m_Padding:
m_Left: 18
m_Right: 10
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 0
m_Spacing: 5.4
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &5426390226938478828
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 0
--- !u!114 &2356989198378852018
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6246834368258800846}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: 1
m_LayoutPriority: 1
--- !u!1 &9078345592336978365
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8849778187297648043}
- component: {fileID: 7828883841352225497}
- component: {fileID: 6763883082192230688}
m_Layer: 5
m_Name: value
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8849778187297648043
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9078345592336978365}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1843470073663794312}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 178.0124, y: 25.64}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7828883841352225497
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9078345592336978365}
m_CullTransparentMesh: 1
--- !u!114 &6763883082192230688
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9078345592336978365}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 1.5
m_fontSizeMax: 16
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}

View File

@@ -0,0 +1,184 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3157287847714375358
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3436634297340404643}
- component: {fileID: 386873398398603487}
- component: {fileID: 2506259255305457008}
- component: {fileID: 8858913019524689709}
- component: {fileID: 6923921070612219825}
m_Layer: 5
m_Name: PopupText
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3436634297340404643
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 705.51843, y: -303.7433}
m_SizeDelta: {x: 133.05402, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!222 &386873398398603487
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_CullTransparentMesh: 1
--- !u!114 &2506259255305457008
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text New TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew Text New TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew Text New TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew TextNew
TextNew TextNew TextNew Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_sharedMaterial: {fileID: 9074173216178389243, guid: a0ee74bf6f853704a8a568d5ef638ee9, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 12
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 1.5
m_fontSizeMax: 12
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &8858913019524689709
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: 1
m_LayoutPriority: 1
--- !u!114 &6923921070612219825
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3157287847714375358}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2

58
Samples~/README.md Normal file
View File

@@ -0,0 +1,58 @@
# Popup System Samples
## Contents
### Prefabs
Reference prefabs for the popup system. Copy these into your project as a starting point. After copying, add entries to your `PopupSettings` asset's Element Prefabs list mapping `PopupElementType` values to these prefabs.
| Prefab | PopupElementType | Description |
|---|---|---|
| `PopupReferencePrefab` | N/A | Main popup container with Canvas, CanvasGroup, Background, and Content |
| `PopupHeader` | `Header` ("header") | Header text element (TMP_Text, bold, larger font) |
| `PopupText` | `Text` ("text") | Body text element (TMP_Text, regular) |
| `PopupStat` | `LabelValueText` ("label_value_text") | Label + value row (HorizontalLayoutGroup with two TMP_Text children) |
| `PopupIcon` | `Image` ("image") | Image element for icons or artwork |
| `PopupSeparator` | `Separator` ("separator") | Horizontal divider line (Image, thin) |
To add variants, duplicate a prefab, style it differently, and register it with a variant type. Use `PopupElementType.Header.Variant("gold")` in code, and set the type to "header_gold" in the PopupSettings Inspector.
### Settings
| Asset | Description |
|---|---|
| `PopupSettings` | Pre-configured PopupSettings ScriptableObject with sensible defaults |
### Scripts
| Script | Description |
|---|---|
| `PopupSystemExample` | Basic setup with auto-scanned triggers, `GetTriggerHandler` by name, and `GetTriggerHandlers` by category |
| `DynamicTriggersExample` | Setting up triggers on dynamically instantiated UI using `InitializeTriggersInChildren` with (trigger, view) callback |
| `CodeOnlyPopupExample` | Showing popups from code (anchored, fixed position, follow mouse), `PopupElementType` variants, and generic `Add()` for custom types |
### Architecture Overview
```
PopupTrigger (MonoBehaviour) -- reference holder, forwards pointer events
|
PopupTriggerView (C# class) -- behavior: calls IPopupSystem.Show/Hide
|
IPopupSystem / PopupSystem -- manages categories, delays, priority, triggers
|
PopupView (C# class) -- behavior: generic element cache, positioning
|
PopupReference (MonoBehaviour) -- reference holder: content, canvasGroup, background
|
PopupSettings (ScriptableObject) -- configuration + element prefab registry (PopupElementType -> prefab)
|
PopupElementType (struct) -- type-safe element identifier (Header, Text, LabelValueText, Image, Separator + custom)
```
## How to use
1. Import the samples via the Unity Package Manager (select the package, expand Samples, click Import)
2. Copy the prefabs into your project's Prefabs folder
3. Create a PopupSettings asset or use the provided one
4. Add Element Prefab entries to PopupSettings using the dropdown (Header, Text, LabelValueText, Image, Separator) mapped to the copied prefabs
5. Reference the example scripts for integration patterns

View File

@@ -0,0 +1,91 @@
using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using UnityEngine;
/// <summary>
/// Example: Showing popups from code without PopupTrigger components.
///
/// Use this approach for confirmation dialogs, tutorial tips, or any popup
/// that is triggered by game logic rather than hover events.
/// Also demonstrates PopupElementType variants and the generic Add() method.
/// </summary>
public class CodeOnlyPopupExample : MonoBehaviour {
[SerializeField] PopupSettings popupSettings;
[SerializeField] PopupReference popupReferencePrefab;
[SerializeField] Transform canvasRoot;
[SerializeField] RectTransform targetElement;
// Custom element type defined in game code
static readonly PopupElementType BadgeElement = new("badge");
IPopupSystem popupSystem;
void Start() {
popupSystem = new PopupSystem(popupSettings, popupReferencePrefab, canvasRoot);
popupSystem.RegisterCategory(PopupCategory.General, priority: 1);
}
void Update() {
popupSystem?.Tick(Time.deltaTime);
// Show anchored to an element
if(Input.GetKeyDown(KeyCode.Alpha1)) {
popupSystem.Show(PopupCategory.General, builder => {
builder
.AddText("Anchored Popup", PopupElementType.Header)
.AddText("This popup is anchored to a UI element.", PopupElementType.Text);
}, targetElement, AnchorSide.Right);
}
// Show at a fixed screen position
if(Input.GetKeyDown(KeyCode.Alpha2)) {
popupSystem.ShowAtPosition(PopupCategory.General, builder => {
builder
.AddText("Fixed Position", PopupElementType.Header)
.AddText("This popup appears at the center of the screen.", PopupElementType.Text);
}, new Vector2(Screen.width * 0.5f, Screen.height * 0.5f));
}
// Show following the mouse
if(Input.GetKeyDown(KeyCode.Alpha3)) {
popupSystem.Show(PopupCategory.General, builder => {
builder
.AddText("Follow Mouse", PopupElementType.Header)
.AddText("This popup follows the cursor.", PopupElementType.Text);
});
}
// Demonstrate variant elements
// Requires "header_gold" and "separator_thick" entries in PopupSettings.elementPrefabs
if(Input.GetKeyDown(KeyCode.Alpha4)) {
popupSystem.Show(PopupCategory.General, builder => {
builder
.AddText("Legendary Item", PopupElementType.Header.Variant("gold"))
.AddSeparator(PopupElementType.Separator.Variant("thick"))
.AddNameValue("Damage", "150", PopupElementType.LabelValueText)
.AddText("A weapon forged in dragon fire.", "FF6600", PopupElementType.Text);
}, targetElement, AnchorSide.Below);
}
// Demonstrate generic Add() for fully custom elements
// Requires a "badge" entry in PopupSettings.elementPrefabs
if(Input.GetKeyDown(KeyCode.Alpha5)) {
popupSystem.Show(PopupCategory.General, builder => {
builder.AddText("Custom Element", PopupElementType.Header);
var badge = builder.Add(BadgeElement);
if(badge != null) {
// Access any components on the custom prefab
// badge.GetComponent<MyBadgeComponent>().SetData(...);
}
}, targetElement);
}
if(Input.GetKeyDown(KeyCode.Escape)) {
popupSystem.HideAll();
}
}
void OnDestroy() {
popupSystem?.Dispose();
}
}

View File

@@ -0,0 +1,48 @@
using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using UnityEngine;
/// <summary>
/// Example: Using PopupSystem with dynamically instantiated UI elements.
///
/// When UI elements are created at runtime (e.g. inventory slots, party member portraits),
/// use InitializeTriggersInChildren to scan, bind, and configure triggers after instantiation.
/// The callback receives both the trigger (MonoBehaviour, for hierarchy queries) and the
/// view (behavior handler, for setting content).
/// </summary>
public class DynamicTriggersExample : MonoBehaviour {
[SerializeField] PopupSettings popupSettings;
[SerializeField] PopupReference popupReferencePrefab;
[SerializeField] Transform canvasRoot;
[SerializeField] Transform slotsContainer;
[SerializeField] GameObject slotPrefab;
IPopupSystem popupSystem;
void Start() {
popupSystem = new PopupSystem(popupSettings, popupReferencePrefab, canvasRoot);
popupSystem.RegisterCategory(PopupCategory.Item, priority: 5);
for(int i = 0; i < 5; i++) {
Instantiate(slotPrefab, slotsContainer);
}
popupSystem.InitializeTriggersInChildren(slotsContainer, (trigger, view) => {
var slotName = trigger.gameObject.name;
view.SetContent(builder => {
builder
.AddText(slotName, PopupElementType.Header)
.AddSeparator(PopupElementType.Separator)
.AddText("This is a dynamically created slot.", PopupElementType.Text);
});
});
}
void Update() {
popupSystem?.Tick(Time.deltaTime);
}
void OnDestroy() {
popupSystem?.Dispose();
}
}

View File

@@ -0,0 +1,67 @@
using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using UnityEngine;
/// <summary>
/// Example: Setting up PopupSystem with auto-scanned triggers.
///
/// 1. Attach this to a GameObject in your scene.
/// 2. Assign the PopupSettings asset and PopupReference prefab.
/// 3. Set canvasRoot to the root Canvas that contains your UI and PopupTrigger components.
/// 4. Place PopupTrigger components on any UI elements that should show popups on hover.
/// 5. The system auto-scans triggers on creation. Use GetTriggerHandler() to set content.
///
/// Element prefabs are configured in PopupSettings via PopupElementType.
/// Add entries mapping types (header, text, label_value_text, separator, image) to prefabs.
/// </summary>
public class PopupSystemExample : MonoBehaviour {
[SerializeField] PopupSettings popupSettings;
[SerializeField] PopupReference popupReferencePrefab;
[SerializeField] Transform canvasRoot;
IPopupSystem popupSystem;
void Start() {
popupSystem = new PopupSystem(popupSettings, popupReferencePrefab, canvasRoot);
popupSystem.RegisterCategory(PopupCategory.Character, priority: 10);
popupSystem.RegisterCategory(PopupCategory.Item, priority: 5);
popupSystem.RegisterCategory(PopupCategory.General, priority: 1);
// Set content on an auto-scanned trigger by GameObject name.
var characterHandler = popupSystem.GetTriggerHandler("CharacterPortrait");
characterHandler?.SetContent(builder => {
builder
.AddText("Kael", PopupElementType.Header)
.AddText("Human Warrior", "CCCCCC", PopupElementType.Text)
.AddSeparator(PopupElementType.Separator)
.AddNameValue("Health", 55, PopupElementType.LabelValueText)
.AddNameValue("Mana", 42, PopupElementType.LabelValueText)
.AddNameValue("Level", 1, PopupElementType.LabelValueText)
.AddSeparator(PopupElementType.Separator)
.AddNameValue("Might", 8, PopupElementType.LabelValueText)
.AddNameValue("Reflex", 2, PopupElementType.LabelValueText)
.AddNameValue("Knowledge", 5, PopupElementType.LabelValueText)
.AddNameValue("Perception", 1, PopupElementType.LabelValueText);
});
// Set content on all triggers of a category.
foreach(var handler in popupSystem.GetTriggerHandlers(PopupCategory.Item)) {
handler.SetContent(builder => {
builder
.AddText("Health Potion", PopupElementType.Header)
.AddSeparator(PopupElementType.Separator)
.AddText("Restores a moderate amount of health.", PopupElementType.Text)
.AddNameValue("Heal Amount", 50, PopupElementType.LabelValueText);
});
}
}
void Update() {
popupSystem?.Tick(Time.deltaTime);
}
void OnDestroy() {
popupSystem?.Dispose();
}
}

View File

@@ -0,0 +1,44 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e38e313f3665d464b82b22699b2a4634, type: 3}
m_Name: PopupSettings
m_EditorClassIdentifier: Jovian.PopupSystem::Jovian.PopupSystem.PopupSettings
popupDelay: 0.4
fadeDuration: 0.2
defaultAnchorSide: 3
screenEdgePadding: 10
maxPopupWidth: 400
sortingOrder: 100
followMouseOffset: {x: 15, y: -15}
touchHoldDuration: 0.6
gamepadFocusTrigger: 1
elementPrefabs:
- type:
id: header
prefab: {fileID: 7034836061828108288, guid: dfc1bc0bd5b4905409615c3e770a5b77, type: 3}
- type:
id: image
prefab: {fileID: 5887814251614319338, guid: 5e715f4b614d02b4fa0b4d3fcfe3c053, type: 3}
- type:
id: label_value_text
prefab: {fileID: 6770634903822758885, guid: 7ccdfa1a2079db044be4b1684303ec7f, type: 3}
- type:
id: separator
prefab: {fileID: 6770634903822758885, guid: 7ccdfa1a2079db044be4b1684303ec7f, type: 3}
- type:
id: text
prefab: {fileID: 3157287847714375358, guid: bfa97c92d1878cc448ddc7dc456f4b17, type: 3}
categoryPriorities:
- category:
id: Character
priority: 0
categoryDelayOverrides: []

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "com.jovian.popup-system",
"version": "0.1.0",
"displayName": "Jovian Popup System",
"description": "A lightweight, low-allocation popup and tooltip system with category-based isolation, fluent content builder, and extensible animations.",
"unity": "2022.3",
"dependencies": {
"com.unity.textmeshpro": "3.0.6",
"com.unity.inputsystem": "1.18.0",
"com.unity.nuget.newtonsoft-json": "3.2.1"
},
"keywords": [
"popup",
"tooltip",
"ui"
],
"author": {
"name": "Jovian"
}
}

7
package.json.meta Normal file
View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3120e52713c91c44d9790e65c94b0869
PackageManifestImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: