diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..e93f70a --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 608491ce009b2494c894080b243698b9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Jovian.PopupSystem.Editor.asmdef b/Editor/Jovian.PopupSystem.Editor.asmdef new file mode 100644 index 0000000..431303e --- /dev/null +++ b/Editor/Jovian.PopupSystem.Editor.asmdef @@ -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 +} diff --git a/Editor/Jovian.PopupSystem.Editor.asmdef.meta b/Editor/Jovian.PopupSystem.Editor.asmdef.meta new file mode 100644 index 0000000..7fe74c2 --- /dev/null +++ b/Editor/Jovian.PopupSystem.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e663435ad13b46d40a29802dab7e52f5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PopupCategoryDrawer.cs b/Editor/PopupCategoryDrawer.cs new file mode 100644 index 0000000..997a7e5 --- /dev/null +++ b/Editor/PopupCategoryDrawer.cs @@ -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(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("k__BackingField"); + } + } +} diff --git a/Editor/PopupCategoryDrawer.cs.meta b/Editor/PopupCategoryDrawer.cs.meta new file mode 100644 index 0000000..e1c678f --- /dev/null +++ b/Editor/PopupCategoryDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9cc96a089d31fe14cbadf6c59ea0c9aa \ No newline at end of file diff --git a/Editor/PopupElementTypeDrawer.cs b/Editor/PopupElementTypeDrawer.cs new file mode 100644 index 0000000..dd12062 --- /dev/null +++ b/Editor/PopupElementTypeDrawer.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Jovian.PopupSystem.Editor { + /// + /// Custom PropertyDrawer for . Shows a dropdown with + /// built-in element types plus a "Custom..." option for free-text entry. + /// + [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(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("k__BackingField"); + } + } +} diff --git a/Editor/PopupElementTypeDrawer.cs.meta b/Editor/PopupElementTypeDrawer.cs.meta new file mode 100644 index 0000000..562f232 --- /dev/null +++ b/Editor/PopupElementTypeDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ea57be8755c60e4bbf156ac2b32c98c \ No newline at end of file diff --git a/Editor/PopupSettingsProvider.cs b/Editor/PopupSettingsProvider.cs new file mode 100644 index 0000000..8753416 --- /dev/null +++ b/Editor/PopupSettingsProvider.cs @@ -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(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(new[] { "popup", "tooltip", "hover", "delay" }) + }; + return provider; + } + } +} diff --git a/Editor/PopupSettingsProvider.cs.meta b/Editor/PopupSettingsProvider.cs.meta new file mode 100644 index 0000000..4fe744c --- /dev/null +++ b/Editor/PopupSettingsProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 63a03b88ed78a934094ce61f9d127ec9 \ No newline at end of file diff --git a/README.md b/README.md index e604791..68a01c1 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file +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(); + 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().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)`. | +| `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` | diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..35714da --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d33518dac7899604c9ceeda21897315d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..72bcd33 --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05f691cbfb7413f4fb735b2ec732ff06 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/FadePopupAnimator.cs b/Runtime/FadePopupAnimator.cs new file mode 100644 index 0000000..4ce532e --- /dev/null +++ b/Runtime/FadePopupAnimator.cs @@ -0,0 +1,56 @@ +using System; +using UnityEngine; + +namespace Jovian.PopupSystem { + /// + /// Default popup animator that fades CanvasGroup alpha. Each category gets its own instance + /// so concurrent show/hide animations don't corrupt each other. + /// + 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(); + } + } +} diff --git a/Runtime/FadePopupAnimator.cs.meta b/Runtime/FadePopupAnimator.cs.meta new file mode 100644 index 0000000..82e61f4 --- /dev/null +++ b/Runtime/FadePopupAnimator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 254bb620ce056dd41905258eb0f070d7 \ No newline at end of file diff --git a/Runtime/IPopupAnimator.cs b/Runtime/IPopupAnimator.cs new file mode 100644 index 0000000..1c82210 --- /dev/null +++ b/Runtime/IPopupAnimator.cs @@ -0,0 +1,37 @@ +using System; +using UnityEngine; + +namespace Jovian.PopupSystem { + /// + /// 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 + /// , not coroutines. + /// + public interface IPopupAnimator { + /// + /// Begins a show animation on the given CanvasGroup. Typically fades alpha from 0 to 1. + /// + /// The CanvasGroup to animate. + /// Animation duration in seconds. + /// Callback invoked when the animation finishes. May be null. + void Show(CanvasGroup canvasGroup, float duration, Action onComplete); + + /// + /// Begins a hide animation on the given CanvasGroup. Typically fades alpha to 0. + /// + /// The CanvasGroup to animate. + /// Animation duration in seconds. + /// Callback invoked when the animation finishes. May be null. + void Hide(CanvasGroup canvasGroup, float duration, Action onComplete); + + /// + /// Advances the animation by deltaTime. Call every frame from . + /// + void Tick(float deltaTime); + + /// + /// True if an animation is currently in progress. + /// + bool IsAnimating { get; } + } +} diff --git a/Runtime/IPopupAnimator.cs.meta b/Runtime/IPopupAnimator.cs.meta new file mode 100644 index 0000000..e4042d5 --- /dev/null +++ b/Runtime/IPopupAnimator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b146d885db3dcef4fa70abd3f43683d3 \ No newline at end of file diff --git a/Runtime/IPopupSystem.cs b/Runtime/IPopupSystem.cs new file mode 100644 index 0000000..5e384e1 --- /dev/null +++ b/Runtime/IPopupSystem.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using Jovian.PopupSystem.UI; +using UnityEngine; + +namespace Jovian.PopupSystem { + /// + /// Core interface for the popup system. Created per game state, not as a singleton. + /// Manages category registration, trigger discovery, popup display, and lifecycle. + /// + public interface IPopupSystem { + /// + /// Scans all components under the given parent and binds them + /// to this system. Each trigger receives a for behavior. + /// + void ScanTriggers(Transform parent); + + /// + /// Scans all 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). + /// + void InitializeTriggersInChildren(Transform parent, Action configureTrigger); + + /// + /// Returns the for the first registered trigger whose + /// GameObject name matches. Returns null if not found. + /// + PopupTriggerView GetTriggerHandler(string gameObjectName); + + /// + /// Returns all instances registered under the given category. + /// + IReadOnlyList GetTriggerHandlers(PopupCategory category); + + /// + /// Registers a popup category. Each category gets its own instance + /// (lazily created on first show) and its own . + /// + /// The category to register. + /// Fallback priority if not defined in . Higher dismisses lower. + void RegisterCategory(PopupCategory category, int priority = 0); + + /// + /// Shows a popup for the given category after the configured delay. The build callback + /// populates content via . Optionally anchors to a + /// RectTransform or follows the mouse if no anchor is provided. + /// + void Show(PopupCategory category, Action buildContent, + RectTransform anchor = null, AnchorSide? anchorSide = null); + + /// + /// Shows a popup for the given category at a fixed screen position. + /// + void ShowAtPosition(PopupCategory category, Action buildContent, + Vector2 screenPosition); + + /// + /// Hides the popup for the given category with a fade-out animation. + /// + void Hide(PopupCategory category); + + /// + /// Hides all visible popups across all categories. + /// + void HideAll(); + + /// + /// Drives delay timers, animations, and follow-mouse positioning. Call every frame. + /// + void Tick(float deltaTime); + + /// + /// Destroys all popup view GameObjects and clears registered categories. + /// Call when the owning game state exits. + /// + void Dispose(); + } +} diff --git a/Runtime/IPopupSystem.cs.meta b/Runtime/IPopupSystem.cs.meta new file mode 100644 index 0000000..9e3e3bc --- /dev/null +++ b/Runtime/IPopupSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 65d563f87bb09de48b8edd12101cdd11 \ No newline at end of file diff --git a/Runtime/Jovian.PopupSystem.asmdef b/Runtime/Jovian.PopupSystem.asmdef new file mode 100644 index 0000000..71c4c13 --- /dev/null +++ b/Runtime/Jovian.PopupSystem.asmdef @@ -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 +} diff --git a/Runtime/Jovian.PopupSystem.asmdef.meta b/Runtime/Jovian.PopupSystem.asmdef.meta new file mode 100644 index 0000000..91cdc89 --- /dev/null +++ b/Runtime/Jovian.PopupSystem.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 31bcaef770c58d94db7f78106f15fd4e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PopupCategory.cs b/Runtime/PopupCategory.cs new file mode 100644 index 0000000..a26322a --- /dev/null +++ b/Runtime/PopupCategory.cs @@ -0,0 +1,48 @@ +using System; +using UnityEngine; + +namespace Jovian.PopupSystem { + /// + /// 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. + /// + [Serializable] + public struct PopupCategory : IEquatable { + [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); + } + } +} diff --git a/Runtime/PopupCategory.cs.meta b/Runtime/PopupCategory.cs.meta new file mode 100644 index 0000000..4a1f739 --- /dev/null +++ b/Runtime/PopupCategory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bcd1fb0ef93fed0489a43977bc71594b \ No newline at end of file diff --git a/Runtime/PopupCategoryJsonConverter.cs b/Runtime/PopupCategoryJsonConverter.cs new file mode 100644 index 0000000..ee66b37 --- /dev/null +++ b/Runtime/PopupCategoryJsonConverter.cs @@ -0,0 +1,15 @@ +using System; +using Newtonsoft.Json; + +namespace Jovian.PopupSystem { + public sealed class PopupCategoryJsonConverter : JsonConverter { + 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); + } + } +} diff --git a/Runtime/PopupCategoryJsonConverter.cs.meta b/Runtime/PopupCategoryJsonConverter.cs.meta new file mode 100644 index 0000000..da576f7 --- /dev/null +++ b/Runtime/PopupCategoryJsonConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 683182ef8e2aa6b4daffc547c481b768 \ No newline at end of file diff --git a/Runtime/PopupContentBuilder.cs b/Runtime/PopupContentBuilder.cs new file mode 100644 index 0000000..e70beae --- /dev/null +++ b/Runtime/PopupContentBuilder.cs @@ -0,0 +1,115 @@ +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +namespace Jovian.PopupSystem { + /// + /// Fluent API struct for building popup content. Operates on the generic element cache + /// inside a . Received in the build callback passed to + /// . Use for fully custom elements, + /// or convenience methods for common types. + /// + public readonly struct PopupContentBuilder { + readonly PopupView view; + + /// + /// Creates a builder targeting the given popup view. + /// + public PopupContentBuilder(PopupView view) { + this.view = view; + } + + /// + /// Returns a cached or newly instantiated element by its registered type. + /// Use this for fully custom or game-specific element types. + /// + public GameObject Add(PopupElementType elementType) { + return view.GetElement(elementType); + } + + /// + /// Adds a text element using the given element type and sets its TMP_Text content. + /// + public PopupContentBuilder AddText(string text, PopupElementType elementType) { + var go = view.GetElement(elementType); + if(go == null) { + return this; + } + var tmp = go.GetComponentInChildren(); + if(tmp) { + tmp.text = text; + } + return this; + } + + /// + /// Adds a colored text element using the given element type. + /// Hex color can be with or without the # prefix. + /// + public PopupContentBuilder AddText(string text, string hexColor, PopupElementType elementType) { + var go = view.GetElement(elementType); + if(!go) { + return this; + } + var tmp = go.GetComponentInChildren(); + if(!tmp) { + return this; + } + var prefix = hexColor.Length > 0 && hexColor[0] == '#' ? "" : "#"; + tmp.text = $"{text}"; + return this; + } + + /// + /// Adds a name/value row using the given element type. The prefab must have + /// exactly two TMP_Text children (label first, value second). + /// + public PopupContentBuilder AddNameValue(string label, int value, PopupElementType elementType) { + return AddNameValueInternal(elementType, label, value.ToString()); + } + + /// + /// Adds a name/value row using the given element type. The prefab must have + /// exactly two TMP_Text children (label first, value second). + /// + 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(true); + if(children.Length >= 2) { + children[0].text = label; + children[1].text = value; + } + } + return this; + } + + /// + /// Adds an image element with the given sprite and optional height. + /// + public PopupContentBuilder AddImage(Sprite sprite, PopupElementType elementType, float height = 64f) { + var go = view.GetElement(elementType); + if(go != null) { + var image = go.GetComponentInChildren(); + if(image != null) { + image.sprite = sprite; + var rt = (RectTransform)image.transform; + rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height); + } + } + return this; + } + + /// + /// Adds a separator element using the given element type. + /// + public PopupContentBuilder AddSeparator(PopupElementType elementType) { + view.GetElement(elementType); + return this; + } + } +} diff --git a/Runtime/PopupContentBuilder.cs.meta b/Runtime/PopupContentBuilder.cs.meta new file mode 100644 index 0000000..1f09579 --- /dev/null +++ b/Runtime/PopupContentBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b9f4094c471d0464e95e330331acc813 \ No newline at end of file diff --git a/Runtime/PopupElementType.cs b/Runtime/PopupElementType.cs new file mode 100644 index 0000000..78637a8 --- /dev/null +++ b/Runtime/PopupElementType.cs @@ -0,0 +1,74 @@ +using System; +using UnityEngine; + +namespace Jovian.PopupSystem { + /// + /// 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. + /// + [Serializable] + public struct PopupElementType : IEquatable { + [SerializeField] private string id; + + /// The string identifier for this element type. + public string Id => id; + + public PopupElementType(string id) { + this.id = id; + } + + // --- Built-in types --- + + /// Bold header text element. + public static readonly PopupElementType Header = new("header"); + + /// Body text element. + public static readonly PopupElementType Text = new("text"); + + /// Label + value stat row element. + public static readonly PopupElementType LabelValueText = new("label_value_text"); + + /// Image/icon element. + public static readonly PopupElementType Image = new("image"); + + /// Horizontal separator line. + public static readonly PopupElementType Separator = new("separator"); + + // --- Variant helper --- + + /// + /// Creates a variant of this element type by appending a suffix. + /// e.g. PopupElementType.Header.Variant("gold") produces "header_gold". + /// + 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); + } + } +} diff --git a/Runtime/PopupElementType.cs.meta b/Runtime/PopupElementType.cs.meta new file mode 100644 index 0000000..edfe16d --- /dev/null +++ b/Runtime/PopupElementType.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5ba7e78e3d930334a935a600a8f67ded \ No newline at end of file diff --git a/Runtime/PopupEnums.cs b/Runtime/PopupEnums.cs new file mode 100644 index 0000000..6d447e1 --- /dev/null +++ b/Runtime/PopupEnums.cs @@ -0,0 +1,13 @@ +namespace Jovian.PopupSystem { + public enum AnchorSide { + Below, + Above, + Left, + Right + } + + public enum PopupPositionMode { + AnchorToElement, + FollowMouse + } +} diff --git a/Runtime/PopupEnums.cs.meta b/Runtime/PopupEnums.cs.meta new file mode 100644 index 0000000..8b8bf19 --- /dev/null +++ b/Runtime/PopupEnums.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 01e69680e496eca4daebbc1874e3a7d9 \ No newline at end of file diff --git a/Runtime/PopupSettings.cs b/Runtime/PopupSettings.cs new file mode 100644 index 0000000..f7b6081 --- /dev/null +++ b/Runtime/PopupSettings.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Jovian.PopupSystem { + /// + /// ScriptableObject holding all popup system configuration. Create via + /// Assets > Create > Jovian > Popup System > Popup Settings. + /// + [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 elementPrefabs = new(); + + [Header("Priority")] + public List categoryPriorities = new(); + + [Header("Per-Category Overrides")] + public List categoryDelayOverrides = new(); + + private Dictionary prefabLookup; + + /// + /// Returns the element prefab registered under the given type, or null if not found. + /// + public GameObject GetPrefab(PopupElementType elementType) { + if(prefabLookup == null) { + prefabLookup = new Dictionary(); + 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; + } + + /// + /// Returns the configured priority for a category, or 0 if not configured. + /// + public int GetPriority(PopupCategory category) { + foreach(var cp in categoryPriorities) { + if(cp.category == category) { + return cp.priority; + } + } + return 0; + } + + /// + /// Returns the configured delay override for a category, or the default popupDelay. + /// + 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; + } +} diff --git a/Runtime/PopupSettings.cs.meta b/Runtime/PopupSettings.cs.meta new file mode 100644 index 0000000..849d77c --- /dev/null +++ b/Runtime/PopupSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e38e313f3665d464b82b22699b2a4634 \ No newline at end of file diff --git a/Runtime/PopupSystem.cs b/Runtime/PopupSystem.cs new file mode 100644 index 0000000..00ae63e --- /dev/null +++ b/Runtime/PopupSystem.cs @@ -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 { + /// + /// Core implementation of . 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. + /// + public sealed class PopupSystem : IPopupSystem { + readonly PopupSettings settings; + readonly PopupReference viewPrefab; + readonly Func animatorFactory; + readonly Transform canvasParent; + readonly Dictionary categories = new(); + readonly List registeredTriggers = new(); + + /// + /// Creates a new popup system instance. + /// + /// Configuration ScriptableObject with delays, priorities, and display settings. + /// The PopupReference prefab to instantiate per category. + /// Optional parent Canvas transform. When provided, popup views are parented + /// here (inheriting CanvasScaler) and all PopupTrigger components are auto-scanned. + /// Optional factory for custom animators. Defaults to FadePopupAnimator. + public PopupSystem(PopupSettings settings, PopupReference viewPrefab, Transform canvasParent = null, Func 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); + } + } + + /// + 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() + }; + } + + /// + public void Show(PopupCategory category, Action 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; + } + + /// + public void ShowAtPosition(PopupCategory category, Action 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; + } + + /// + 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); + }); + } + } + + /// + public void HideAll() { + foreach(var kvp in categories) { + Hide(kvp.Key); + } + } + + /// + 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(); + } + } + } + + /// + public void ScanTriggers(Transform parent) { + var triggers = parent.GetComponentsInChildren(true); + foreach(var trigger in triggers) { + if(!registeredTriggers.Contains(trigger)) { + BindTrigger(trigger); + } + } + } + + /// + public PopupTriggerView GetTriggerHandler(string gameObjectName) { + foreach(var trigger in registeredTriggers) { + if(trigger != null && trigger.gameObject.name == gameObjectName) { + return trigger.Handler; + } + } + return null; + } + + /// + public IReadOnlyList GetTriggerHandlers(PopupCategory category) { + var result = new List(); + foreach(var trigger in registeredTriggers) { + if(trigger != null && trigger.Category == category && trigger.Handler != null) { + result.Add(trigger.Handler); + } + } + return result; + } + + /// + public void InitializeTriggersInChildren(Transform parent, Action configureTrigger) { + var triggers = parent.GetComponentsInChildren(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); + } + + /// + 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(); + 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 pendingBuild; + public RectTransform pendingAnchor; + public AnchorSide pendingAnchorSide; + public Vector2? pendingScreenPos; + } + } +} diff --git a/Runtime/PopupSystem.cs.meta b/Runtime/PopupSystem.cs.meta new file mode 100644 index 0000000..b4bdf62 --- /dev/null +++ b/Runtime/PopupSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 056ed88d12add674798ad4e6300bf222 \ No newline at end of file diff --git a/Runtime/PopupTriggerView.cs b/Runtime/PopupTriggerView.cs new file mode 100644 index 0000000..68931fc --- /dev/null +++ b/Runtime/PopupTriggerView.cs @@ -0,0 +1,54 @@ +using System; +using Jovian.PopupSystem.UI; +using UnityEngine; + +namespace Jovian.PopupSystem { + /// + /// Handles popup behavior for a . Holds the content callback and + /// forwards pointer events to the . This is the behavior layer; + /// the MonoBehaviour trigger is the reference holder that forwards events here. + /// + public sealed class PopupTriggerView { + readonly IPopupSystem popupSystem; + Action contentCallback; + + /// + /// Creates a new trigger view bound to the given popup system. + /// + public PopupTriggerView(IPopupSystem popupSystem) { + this.popupSystem = popupSystem; + } + + /// + /// Sets the content builder callback that will be invoked when the popup is shown. + /// + public void SetContent(Action callback) { + contentCallback = callback; + } + + /// + /// Called by when the pointer enters. Reads the trigger's + /// category, anchor side, and position mode to show the popup. + /// + 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); + } + } + + /// + /// Called by when the pointer exits. Hides the popup + /// for the trigger's category. + /// + public void OnPointerExit(PopupTrigger trigger) { + popupSystem?.Hide(trigger.Category); + } + } +} diff --git a/Runtime/PopupTriggerView.cs.meta b/Runtime/PopupTriggerView.cs.meta new file mode 100644 index 0000000..4a8141d --- /dev/null +++ b/Runtime/PopupTriggerView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1e2351b6a4b8bf046923790d4d09141c \ No newline at end of file diff --git a/Runtime/PopupView.cs b/Runtime/PopupView.cs new file mode 100644 index 0000000..19d60a3 --- /dev/null +++ b/Runtime/PopupView.cs @@ -0,0 +1,224 @@ +using System.Collections.Generic; +using Jovian.PopupSystem.UI; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.UI; + +namespace Jovian.PopupSystem { + /// + /// Behavior class for a popup view. Manages the generic grow-only element cache, + /// positioning, visibility, and layout. Operates on a + /// MonoBehaviour for scene references. + /// + public sealed class PopupView { + readonly PopupReference reference; + readonly PopupSettings settings; + + // Generic element cache: keyed by element type + readonly Dictionary> elementCache = new(); + readonly Dictionary elementIndex = new(); + + // Positioning state + PopupPositionMode positionMode; + RectTransform anchorTarget; + AnchorSide anchorSide; + Vector2 followOffset; + float screenEdgePadding; + float maxWidth; + bool isVisible; + bool layoutDirty; + Canvas rootCanvas; + Camera canvasCamera; + + /// The underlying MonoBehaviour reference holder. + public PopupReference Reference => reference; + + /// The CanvasGroup for animation control. + public CanvasGroup CanvasGroup => reference.CanvasGroup; + + /// The content RectTransform where elements are parented. + public RectTransform Content => reference.Content; + + /// The root Transform of the popup GameObject. + public Transform Transform => reference.transform; + + /// Whether the popup is currently visible. + public bool IsVisible => isVisible; + + /// + /// Creates a new popup view wrapping the given reference and settings. + /// + public PopupView(PopupReference reference, PopupSettings settings) { + this.reference = reference; + this.settings = settings; + } + + // --- Element cache (generic, grow-only) --- + + /// + /// 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. + /// + public GameObject GetElement(PopupElementType elementType) { + if(!elementCache.TryGetValue(elementType, out var cache)) { + cache = new List(); + 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; + } + + /// + /// Deactivates all cached content elements and marks layout as dirty. + /// + 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 --- + + /// Shows or hides the popup GameObject. Resets alpha to 0 when hiding. + public void SetVisible(bool visible) { + isVisible = visible; + reference.gameObject.SetActive(visible); + if(!visible) { + reference.CanvasGroup.alpha = 0f; + } + } + + /// Constrains the popup's horizontal size to the given maximum width in pixels. + 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 --- + + /// Configures the popup to anchor to a target element on the specified side. + public void SetAnchorMode(RectTransform target, AnchorSide side, float edgePadding) { + positionMode = PopupPositionMode.AnchorToElement; + anchorTarget = target; + anchorSide = side; + screenEdgePadding = edgePadding; + } + + /// Configures the popup to follow the mouse cursor with the given offset. + public void SetFollowMouseMode(Vector2 offset, float edgePadding) { + positionMode = PopupPositionMode.FollowMouse; + followOffset = offset; + screenEdgePadding = edgePadding; + } + + /// Positions the popup at a fixed screen coordinate with edge clamping. + public void SetFixedPosition(Vector2 screenPos, float edgePadding) { + positionMode = PopupPositionMode.AnchorToElement; + anchorTarget = null; + screenEdgePadding = edgePadding; + PositionAtScreenPoint(screenPos); + } + + /// Updates the popup position based on the current mode (follow mouse or anchored). + 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()?.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); + } + } +} diff --git a/Runtime/PopupView.cs.meta b/Runtime/PopupView.cs.meta new file mode 100644 index 0000000..d818321 --- /dev/null +++ b/Runtime/PopupView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 18ebd1b0205e20440aa4c4991b43cc46 \ No newline at end of file diff --git a/Runtime/UI.meta b/Runtime/UI.meta new file mode 100644 index 0000000..0cb98a2 --- /dev/null +++ b/Runtime/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4459a298af1a37f47af7b7b70655ee0d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UI/PopupReference.cs b/Runtime/UI/PopupReference.cs new file mode 100644 index 0000000..2418fc8 --- /dev/null +++ b/Runtime/UI/PopupReference.cs @@ -0,0 +1,23 @@ +using UnityEngine; + +namespace Jovian.PopupSystem.UI { + /// + /// Reference-only MonoBehaviour for a popup prefab. Holds serialized scene references + /// to the content container, canvas group, and background. All behavior is in + /// . + /// + public class PopupReference : MonoBehaviour { + [SerializeField] RectTransform content; + [SerializeField] CanvasGroup canvasGroup; + [SerializeField] RectTransform background; + + /// The content RectTransform where popup elements are parented. + public RectTransform Content => content; + + /// The CanvasGroup for fade animation control. + public CanvasGroup CanvasGroup => canvasGroup; + + /// The background RectTransform that sizes to content. + public RectTransform Background => background; + } +} diff --git a/Runtime/UI/PopupReference.cs.meta b/Runtime/UI/PopupReference.cs.meta new file mode 100644 index 0000000..3df659a --- /dev/null +++ b/Runtime/UI/PopupReference.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bc25da4712d7cc4419eb6f364e032431 \ No newline at end of file diff --git a/Runtime/UI/PopupTrigger.cs b/Runtime/UI/PopupTrigger.cs new file mode 100644 index 0000000..a1635d8 --- /dev/null +++ b/Runtime/UI/PopupTrigger.cs @@ -0,0 +1,48 @@ +using UnityEngine; +using UnityEngine.EventSystems; + +namespace Jovian.PopupSystem.UI { + /// + /// 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 . + /// + public class PopupTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler { + [SerializeField] PopupCategory category; + [SerializeField] AnchorSide anchorSide = AnchorSide.Below; + [SerializeField] PopupPositionMode positionMode = PopupPositionMode.AnchorToElement; + + PopupTriggerView handler; + + /// The popup category this trigger belongs to. + public PopupCategory Category => category; + + /// Which side of this element the popup anchors to. + public AnchorSide AnchorSide => anchorSide; + + /// Whether the popup anchors to this element or follows the mouse. + public PopupPositionMode PositionMode => positionMode; + + /// The bound behavior handler. Null until is called. + public PopupTriggerView Handler => handler; + + /// + /// Binds a to this trigger. Called automatically + /// by or . + /// + public void Bind(PopupTriggerView view) { + handler = view; + } + + /// Forwards pointer enter to the bound handler. + public void OnPointerEnter(PointerEventData eventData) { + handler?.OnPointerEnter(this); + } + + /// Forwards pointer exit to the bound handler. + public void OnPointerExit(PointerEventData eventData) { + handler?.OnPointerExit(this); + } + } +} diff --git a/Runtime/UI/PopupTrigger.cs.meta b/Runtime/UI/PopupTrigger.cs.meta new file mode 100644 index 0000000..a85049b --- /dev/null +++ b/Runtime/UI/PopupTrigger.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4ef9d21a19cd4db4f9d5491202547c05 \ No newline at end of file diff --git a/Samples~/Prefabs/PopupHeader.prefab b/Samples~/Prefabs/PopupHeader.prefab new file mode 100644 index 0000000..74de254 --- /dev/null +++ b/Samples~/Prefabs/PopupHeader.prefab @@ -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 diff --git a/Samples~/Prefabs/PopupIcon.prefab b/Samples~/Prefabs/PopupIcon.prefab new file mode 100644 index 0000000..5590a8d --- /dev/null +++ b/Samples~/Prefabs/PopupIcon.prefab @@ -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 diff --git a/Samples~/Prefabs/PopupReferencePrefab.prefab b/Samples~/Prefabs/PopupReferencePrefab.prefab new file mode 100644 index 0000000..3524044 --- /dev/null +++ b/Samples~/Prefabs/PopupReferencePrefab.prefab @@ -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 diff --git a/Samples~/Prefabs/PopupSeparator.prefab b/Samples~/Prefabs/PopupSeparator.prefab new file mode 100644 index 0000000..239dd2d --- /dev/null +++ b/Samples~/Prefabs/PopupSeparator.prefab @@ -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 diff --git a/Samples~/Prefabs/PopupStat.prefab b/Samples~/Prefabs/PopupStat.prefab new file mode 100644 index 0000000..650e19e --- /dev/null +++ b/Samples~/Prefabs/PopupStat.prefab @@ -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} diff --git a/Samples~/Prefabs/PopupText.prefab b/Samples~/Prefabs/PopupText.prefab new file mode 100644 index 0000000..ea0a248 --- /dev/null +++ b/Samples~/Prefabs/PopupText.prefab @@ -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 diff --git a/Samples~/README.md b/Samples~/README.md new file mode 100644 index 0000000..fc6d567 --- /dev/null +++ b/Samples~/README.md @@ -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 diff --git a/Samples~/Scripts/CodeOnlyPopupExample.cs b/Samples~/Scripts/CodeOnlyPopupExample.cs new file mode 100644 index 0000000..1cc2bae --- /dev/null +++ b/Samples~/Scripts/CodeOnlyPopupExample.cs @@ -0,0 +1,91 @@ +using Jovian.PopupSystem; +using Jovian.PopupSystem.UI; +using UnityEngine; + +/// +/// 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. +/// +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().SetData(...); + } + }, targetElement); + } + + if(Input.GetKeyDown(KeyCode.Escape)) { + popupSystem.HideAll(); + } + } + + void OnDestroy() { + popupSystem?.Dispose(); + } +} diff --git a/Samples~/Scripts/DynamicTriggersExample.cs b/Samples~/Scripts/DynamicTriggersExample.cs new file mode 100644 index 0000000..db26570 --- /dev/null +++ b/Samples~/Scripts/DynamicTriggersExample.cs @@ -0,0 +1,48 @@ +using Jovian.PopupSystem; +using Jovian.PopupSystem.UI; +using UnityEngine; + +/// +/// 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). +/// +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(); + } +} diff --git a/Samples~/Scripts/PopupSystemExample.cs b/Samples~/Scripts/PopupSystemExample.cs new file mode 100644 index 0000000..68cbafc --- /dev/null +++ b/Samples~/Scripts/PopupSystemExample.cs @@ -0,0 +1,67 @@ +using Jovian.PopupSystem; +using Jovian.PopupSystem.UI; +using UnityEngine; + +/// +/// 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. +/// +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(); + } +} diff --git a/Samples~/Settings/PopupSettings.asset b/Samples~/Settings/PopupSettings.asset new file mode 100644 index 0000000..8d3bd5b --- /dev/null +++ b/Samples~/Settings/PopupSettings.asset @@ -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: [] diff --git a/package.json b/package.json new file mode 100644 index 0000000..01375ee --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..dac6e10 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3120e52713c91c44d9790e65c94b0869 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: