diff --git a/Editor/ComponentsAdditionalItems/CanvasScalerDefault.cs b/Editor/ComponentsAdditionalItems/CanvasScalerDefault.cs
new file mode 100644
index 0000000..a6cb782
--- /dev/null
+++ b/Editor/ComponentsAdditionalItems/CanvasScalerDefault.cs
@@ -0,0 +1,30 @@
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace NEG.Utils.Editor.ComponentsAdditionalItems
+{
+ public static class CanvasScalerDefault
+ {
+ [MenuItem("CONTEXT/CanvasScaler/Full HD horizontal", false, 2000)]
+ public static void SetFullHdHorizontal(MenuCommand command) => SetComponent(command, 1920, 1080);
+ [MenuItem("CONTEXT/CanvasScaler/Full HD vertical", false, 2000)]
+ public static void SetFullHdVertical(MenuCommand command) => SetComponent(command, 1080, 1920);
+ [MenuItem("CONTEXT/CanvasScaler/Full 2k horizontal", false, 2000)]
+ public static void Set2KHorizontal(MenuCommand command) => SetComponent(command, 2560, 1440 );
+ [MenuItem("CONTEXT/CanvasScaler/Full 2k vertical", false, 2000)]
+ public static void Set2KVertical(MenuCommand command) => SetComponent(command, 1440, 2560);
+ [MenuItem("CONTEXT/CanvasScaler/Full 4k horizontal", false, 2000)]
+ public static void Set4KHorizontal(MenuCommand command) => SetComponent(command, 3840, 2160);
+ [MenuItem("CONTEXT/CanvasScaler/Full 4k vertical", false, 2000)]
+ public static void Set4KVertical(MenuCommand command) => SetComponent(command, 2160, 3840);
+
+ private static void SetComponent(MenuCommand command, int width, int height)
+ {
+ var scaler = (CanvasScaler)command.context;
+ scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
+ scaler.matchWidthOrHeight = width > height ? 1f : 0f;
+ scaler.referenceResolution = new Vector2(width, height);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/ComponentsAdditionalItems/CanvasScalerDefault.cs.meta b/Editor/ComponentsAdditionalItems/CanvasScalerDefault.cs.meta
new file mode 100644
index 0000000..781e85f
--- /dev/null
+++ b/Editor/ComponentsAdditionalItems/CanvasScalerDefault.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c7ddebddb1a1c1947be05ac9e96aceb8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/NEG.Utils.Editor.asmdef b/Editor/NEG.Utils.Editor.asmdef
new file mode 100644
index 0000000..60746f0
--- /dev/null
+++ b/Editor/NEG.Utils.Editor.asmdef
@@ -0,0 +1,18 @@
+{
+ "name": "NEG.Utils.Editor",
+ "rootNamespace": "",
+ "references": [
+ "GUID:3c4294719a93e3c4e831a9ff0c261e8a"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Editor/NEG.Utils.Editor.asmdef.meta b/Editor/NEG.Utils.Editor.asmdef.meta
new file mode 100644
index 0000000..e1a6a84
--- /dev/null
+++ b/Editor/NEG.Utils.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5928dc8d9173fd348aa77d4593ca3fd8
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/NEG.meta b/NEG.meta
new file mode 100644
index 0000000..9b90a19
--- /dev/null
+++ b/NEG.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 61eeed9106e147d8a76d7f0140d3571d
+timeCreated: 1670708950
\ No newline at end of file
diff --git a/NEG/UI.meta b/NEG/UI.meta
new file mode 100644
index 0000000..3557a60
--- /dev/null
+++ b/NEG/UI.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: bbdebf147b9a40b0bc94b6f710c7aa6b
+timeCreated: 1670690396
\ No newline at end of file
diff --git a/NEG/UI/Area.meta b/NEG/UI/Area.meta
new file mode 100644
index 0000000..605a7f9
--- /dev/null
+++ b/NEG/UI/Area.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 853f6a7d0f224278afbb5457d0fb8bde
+timeCreated: 1670706939
\ No newline at end of file
diff --git a/NEG/UI/Area/IArea.cs b/NEG/UI/Area/IArea.cs
new file mode 100644
index 0000000..4c84e27
--- /dev/null
+++ b/NEG/UI/Area/IArea.cs
@@ -0,0 +1,9 @@
+using NEG.UI.WindowSlot;
+
+namespace NEG.UI.Area
+{
+ public interface IArea : ISlotsHolder, IUiElement
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/Area/IArea.cs.meta b/NEG/UI/Area/IArea.cs.meta
new file mode 100644
index 0000000..d37d498
--- /dev/null
+++ b/NEG/UI/Area/IArea.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f7cf5ef3a347e1c4b98411f4d564b988
+timeCreated: 1670690282
\ No newline at end of file
diff --git a/NEG/UI/IUiElement.cs b/NEG/UI/IUiElement.cs
new file mode 100644
index 0000000..6cfde2b
--- /dev/null
+++ b/NEG/UI/IUiElement.cs
@@ -0,0 +1,11 @@
+namespace NEG.UI
+{
+ public interface IUiElement
+ {
+ ///
+ /// Sets only visible state of element
+ ///
+ ///
+ void SetEnabled(bool setEnabled);
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/IUiElement.cs.meta b/NEG/UI/IUiElement.cs.meta
new file mode 100644
index 0000000..765fb83
--- /dev/null
+++ b/NEG/UI/IUiElement.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 296bf6969a6347f8aea788a7bdd086af
+timeCreated: 1670693177
\ No newline at end of file
diff --git a/NEG/UI/NEG.UI.asmdef b/NEG/UI/NEG.UI.asmdef
new file mode 100644
index 0000000..db95a9f
--- /dev/null
+++ b/NEG/UI/NEG.UI.asmdef
@@ -0,0 +1,14 @@
+{
+ "name": "NEG.UI",
+ "rootNamespace": "",
+ "references": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/NEG/UI/NEG.UI.asmdef.meta b/NEG/UI/NEG.UI.asmdef.meta
new file mode 100644
index 0000000..b5859a4
--- /dev/null
+++ b/NEG/UI/NEG.UI.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7361f1d9c43da6649923760766194746
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/NEG/UI/Popup.meta b/NEG/UI/Popup.meta
new file mode 100644
index 0000000..0b36d76
--- /dev/null
+++ b/NEG/UI/Popup.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 76fc21263637443ca8268859d4cb5378
+timeCreated: 1670707809
\ No newline at end of file
diff --git a/NEG/UI/Popup/DefaultPopupData.cs b/NEG/UI/Popup/DefaultPopupData.cs
new file mode 100644
index 0000000..bcbbefb
--- /dev/null
+++ b/NEG/UI/Popup/DefaultPopupData.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+
+namespace NEG.UI.Popup
+{
+ public class DefaultPopupData : PopupData
+ {
+ private readonly IDefaultPopup defaultPopup;
+
+ private readonly string title;
+ private readonly string content;
+ private readonly List<(string, Action)> options;
+
+ public DefaultPopupData(IDefaultPopup popup, string title, string content, List<(string, Action)> options) : base(popup)
+ {
+ defaultPopup = popup;
+ this.title = title;
+ this.content = content;
+ this.options = options;
+ }
+
+ public override void Show()
+ {
+ defaultPopup.SetContent(title, content, options);
+ base.Show();
+ }
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/Popup/DefaultPopupData.cs.meta b/NEG/UI/Popup/DefaultPopupData.cs.meta
new file mode 100644
index 0000000..e86aa41
--- /dev/null
+++ b/NEG/UI/Popup/DefaultPopupData.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 372c6df9ec044cb3adc86c7776b2ef61
+timeCreated: 1672432934
\ No newline at end of file
diff --git a/NEG/UI/Popup/IDefaultPopup.cs b/NEG/UI/Popup/IDefaultPopup.cs
new file mode 100644
index 0000000..6a9216d
--- /dev/null
+++ b/NEG/UI/Popup/IDefaultPopup.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+
+namespace NEG.UI.Popup
+{
+ public interface IDefaultPopup : IPopup
+ {
+ ///
+ /// Sets content based on provided data.
+ ///
+ /// popup title
+ /// popup content
+ /// list of tuples (name, action on click), to set buttons. Do not pass here popup closing logic, implementing class should do it
+ public void SetContent(string title, string content, List<(string name, Action action)> options);
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/Popup/IDefaultPopup.cs.meta b/NEG/UI/Popup/IDefaultPopup.cs.meta
new file mode 100644
index 0000000..e510e78
--- /dev/null
+++ b/NEG/UI/Popup/IDefaultPopup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0a6d0871ada44d1d95bea6c8e8701769
+timeCreated: 1672153906
\ No newline at end of file
diff --git a/NEG/UI/Popup/IPopup.cs b/NEG/UI/Popup/IPopup.cs
new file mode 100644
index 0000000..e3468c7
--- /dev/null
+++ b/NEG/UI/Popup/IPopup.cs
@@ -0,0 +1,26 @@
+using JetBrains.Annotations;
+using System;
+
+namespace NEG.UI.Popup
+{
+ [PublicAPI]
+ public interface IPopup
+ {
+ ///
+ /// Event to fire when popup is closed
+ ///
+ event Action OnPopupClosed;
+
+ ///
+ /// Show popup
+ ///
+ /// data assigned to popup, used to give info that popup is closed
+ public void Show(PopupData data);
+
+ ///
+ /// Close popup or mark as closed if not visible
+ ///
+ /// if true hide visually, without firing callbacks to properly close
+ void Close(bool silent = false);
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/Popup/IPopup.cs.meta b/NEG/UI/Popup/IPopup.cs.meta
new file mode 100644
index 0000000..54795d7
--- /dev/null
+++ b/NEG/UI/Popup/IPopup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 79767831e4324605974f3bb0bb5026fb
+timeCreated: 1670692499
\ No newline at end of file
diff --git a/NEG/UI/Popup/PopupData.cs b/NEG/UI/Popup/PopupData.cs
new file mode 100644
index 0000000..6bf528a
--- /dev/null
+++ b/NEG/UI/Popup/PopupData.cs
@@ -0,0 +1,50 @@
+using JetBrains.Annotations;
+using System;
+
+namespace NEG.UI.Popup
+{
+ [PublicAPI]
+ public class PopupData
+ {
+ ///
+ /// Event that is fired on closing popup.
+ ///
+ public event Action PopupClosedEvent
+ {
+ add => popup.OnPopupClosed += value;
+ remove => popup.OnPopupClosed -= value;
+ }
+
+ ///
+ /// Is this data is still valid. If set to false, popup will not show.
+ ///
+ public bool IsValid { get; protected set; }
+
+ private readonly IPopup popup;
+
+ ///
+ /// PopupData constructor.
+ ///
+ /// attached to this data, can be used by different data instances
+ public PopupData(IPopup popup)
+ {
+ this.popup = popup;
+ IsValid = true;
+ }
+
+ ///
+ /// Show popup and pass needed data.
+ ///
+ public virtual void Show() => popup.Show(this);
+
+ ///
+ /// Hide popup. Close visuals without firing events;
+ ///
+ public virtual void Hide() => popup.Close(true);
+
+ ///
+ /// Invalidate popup, will automatically skip this popup
+ ///
+ public virtual void Invalidate() => IsValid = false;
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/Popup/PopupData.cs.meta b/NEG/UI/Popup/PopupData.cs.meta
new file mode 100644
index 0000000..2148759
--- /dev/null
+++ b/NEG/UI/Popup/PopupData.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e6e5ebb367aa4dfba5e5f853c9b31a3d
+timeCreated: 1672430446
\ No newline at end of file
diff --git a/NEG/UI/PriorityQueue.cs b/NEG/UI/PriorityQueue.cs
new file mode 100644
index 0000000..74e3e1b
--- /dev/null
+++ b/NEG/UI/PriorityQueue.cs
@@ -0,0 +1,999 @@
+#nullable enable
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ported from:
+// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs
+ internal sealed class PriorityQueueDebugView
+ {
+ private readonly PriorityQueue _queue;
+ private readonly bool _sort;
+
+ public PriorityQueueDebugView(PriorityQueue queue)
+ {
+ ArgumentNullException.ThrowIfNull(queue);
+
+ _queue = queue;
+ _sort = true;
+ }
+
+ public PriorityQueueDebugView(PriorityQueue.UnorderedItemsCollection collection)
+ {
+ _queue = collection?._queue ?? throw new System.ArgumentNullException(nameof(collection));
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public (TElement Element, TPriority Priority)[] Items
+ {
+ get
+ {
+ List<(TElement Element, TPriority Priority)> list = new(_queue.UnorderedItems);
+ if (_sort) list.Sort((i1, i2) => _queue.Comparer.Compare(i1.Priority, i2.Priority));
+
+ return list.ToArray();
+ }
+ }
+ }
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ internal static class SR
+ {
+ internal const string ArgumentOutOfRange_NeedNonNegNum = "Non-negative number required.";
+ internal const string ArgumentOutOfRange_IndexMustBeLessOrEqual = "Index must be less or equal";
+ internal const string InvalidOperation_EmptyQueue = "The queue is empty.";
+ internal const string InvalidOperation_EnumFailedVersion = "Collection modified while iterating over it.";
+ internal const string Arg_NonZeroLowerBound = "Non-zero lower bound required.";
+ internal const string Arg_RankMultiDimNotSupported = "Multi-dimensional arrays not supported.";
+ internal const string Argument_InvalidArrayType = "Invalid array type.";
+ internal const string Argument_InvalidOffLen = "Invalid offset or length.";
+ }
+
+ internal static class ArgumentNullException
+ {
+ public static void ThrowIfNull(object o)
+ {
+ if (o == null)
+ throw new System.ArgumentNullException(); // hard to do it differently without C# 10's features
+ }
+ }
+
+ internal static class ArrayEx
+ {
+ internal const int MaxLength = int.MaxValue;
+ }
+
+ ///
+ /// Internal helper functions for working with enumerables.
+ ///
+ internal static class EnumerableHelpers
+ {
+ /// Converts an enumerable to an array using the same logic as List{T}.
+ /// The enumerable to convert.
+ /// The number of items stored in the resulting array, 0-indexed.
+ ///
+ /// The resulting array. The length of the array may be greater than ,
+ /// which is the actual number of elements in the array.
+ ///
+ internal static T[] ToArray(IEnumerable source, out int length)
+ {
+ if (source is ICollection ic)
+ {
+ int count = ic.Count;
+ if (count != 0)
+ {
+ // Allocate an array of the desired size, then copy the elements into it. Note that this has the same
+ // issue regarding concurrency as other existing collections like List. If the collection size
+ // concurrently changes between the array allocation and the CopyTo, we could end up either getting an
+ // exception from overrunning the array (if the size went up) or we could end up not filling as many
+ // items as 'count' suggests (if the size went down). This is only an issue for concurrent collections
+ // that implement ICollection, which as of .NET 4.6 is just ConcurrentDictionary.
+ var arr = new T[count];
+ ic.CopyTo(arr, 0);
+ length = count;
+ return arr;
+ }
+ }
+ else
+ {
+ using (var en = source.GetEnumerator())
+ {
+ if (en.MoveNext())
+ {
+ const int DefaultCapacity = 4;
+ var arr = new T[DefaultCapacity];
+ arr[0] = en.Current;
+ int count = 1;
+
+ while (en.MoveNext())
+ {
+ if (count == arr.Length)
+ {
+ // This is the same growth logic as in List:
+ // If the array is currently empty, we make it a default size. Otherwise, we attempt to
+ // double the size of the array. Doubling will overflow once the size of the array reaches
+ // 2^30, since doubling to 2^31 is 1 larger than Int32.MaxValue. In that case, we instead
+ // constrain the length to be Array.MaxLength (this overflow check works because of the
+ // cast to uint).
+ int newLength = count << 1;
+ if ((uint)newLength > ArrayEx.MaxLength)
+ newLength = ArrayEx.MaxLength <= count ? count + 1 : ArrayEx.MaxLength;
+
+ Array.Resize(ref arr, newLength);
+ }
+
+ arr[count++] = en.Current;
+ }
+
+ length = count;
+ return arr;
+ }
+ }
+ }
+
+ length = 0;
+ return Array.Empty();
+ }
+ }
+
+ ///
+ /// Represents a min priority queue.
+ ///
+ /// Specifies the type of elements in the queue.
+ /// Specifies the type of priority associated with enqueued elements.
+ ///
+ /// Implements an array-backed quaternary min-heap. Each element is enqueued with an associated priority
+ /// that determines the dequeue order: elements with the lowest priority get dequeued first.
+ ///
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(PriorityQueueDebugView<,>))]
+ public class PriorityQueue
+ {
+ ///
+ /// Specifies the arity of the d-ary heap, which here is quaternary.
+ /// It is assumed that this value is a power of 2.
+ ///
+ private const int Arity = 4;
+
+ ///
+ /// The binary logarithm of .
+ ///
+ private const int Log2Arity = 2;
+
+ ///
+ /// Custom comparer used to order the heap.
+ ///
+ private readonly IComparer? _comparer;
+
+ ///
+ /// Represents an implicit heap-ordered complete d-ary tree, stored as an array.
+ ///
+ private (TElement Element, TPriority Priority)[] _nodes;
+
+ ///
+ /// The number of nodes in the heap.
+ ///
+ private int _size;
+
+ ///
+ /// Lazily-initialized collection used to expose the contents of the queue.
+ ///
+ private UnorderedItemsCollection? _unorderedItems;
+
+ ///
+ /// Version updated on mutation to help validate enumerators operate on a consistent state.
+ ///
+ private int _version;
+
+#if DEBUG
+ static PriorityQueue()
+ {
+ Debug.Assert(Log2Arity > 0 && Math.Pow(2, Log2Arity) == Arity);
+ }
+#endif
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PriorityQueue()
+ {
+ _nodes = Array.Empty<(TElement, TPriority)>();
+ _comparer = InitializeComparer(null);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified initial capacity.
+ ///
+ /// Initial capacity to allocate in the underlying heap array.
+ ///
+ /// The specified was negative.
+ ///
+ public PriorityQueue(int initialCapacity)
+ : this(initialCapacity, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified custom priority comparer.
+ ///
+ ///
+ /// Custom comparer dictating the ordering of elements.
+ /// Uses if the argument is .
+ ///
+ public PriorityQueue(IComparer? comparer)
+ {
+ _nodes = Array.Empty<(TElement, TPriority)>();
+ _comparer = InitializeComparer(comparer);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified initial capacity and custom priority comparer.
+ ///
+ /// Initial capacity to allocate in the underlying heap array.
+ ///
+ /// Custom comparer dictating the ordering of elements.
+ /// Uses if the argument is .
+ ///
+ ///
+ /// The specified was negative.
+ ///
+ public PriorityQueue(int initialCapacity, IComparer? comparer)
+ {
+ if (initialCapacity < 0)
+ throw new ArgumentOutOfRangeException(
+ nameof(initialCapacity), initialCapacity, SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ _nodes = new (TElement, TPriority)[initialCapacity];
+ _comparer = InitializeComparer(comparer);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is populated with the specified elements and priorities.
+ ///
+ /// The pairs of elements and priorities with which to populate the queue.
+ ///
+ /// The specified argument was .
+ ///
+ ///
+ /// Constructs the heap using a heapify operation,
+ /// which is generally faster than enqueuing individual elements sequentially.
+ ///
+ public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items)
+ : this(items, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// that is populated with the specified elements and priorities,
+ /// and with the specified custom priority comparer.
+ ///
+ /// The pairs of elements and priorities with which to populate the queue.
+ ///
+ /// Custom comparer dictating the ordering of elements.
+ /// Uses if the argument is .
+ ///
+ ///
+ /// The specified argument was .
+ ///
+ ///
+ /// Constructs the heap using a heapify operation,
+ /// which is generally faster than enqueuing individual elements sequentially.
+ ///
+ public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items, IComparer? comparer)
+ {
+ ArgumentNullException.ThrowIfNull(items);
+
+ _nodes = EnumerableHelpers.ToArray(items, out _size);
+ _comparer = InitializeComparer(comparer);
+
+ if (_size > 1) Heapify();
+ }
+
+ ///
+ /// Gets the number of elements contained in the .
+ ///
+ public int Count => _size;
+
+ ///
+ /// Gets the priority comparer used by the .
+ ///
+ public IComparer Comparer => _comparer ?? Comparer.Default;
+
+ ///
+ /// Gets a collection that enumerates the elements of the queue in an unordered manner.
+ ///
+ ///
+ /// The enumeration does not order items by priority, since that would require N * log(N) time and N space.
+ /// Items are instead enumerated following the internal array heap layout.
+ ///
+ public UnorderedItemsCollection UnorderedItems => _unorderedItems ??= new UnorderedItemsCollection(this);
+
+ ///
+ /// Adds the specified element with associated priority to the .
+ ///
+ /// The element to add to the .
+ /// The priority with which to associate the new element.
+ public void Enqueue(TElement element, TPriority priority)
+ {
+ // Virtually add the node at the end of the underlying array.
+ // Note that the node being enqueued does not need to be physically placed
+ // there at this point, as such an assignment would be redundant.
+
+ int currentSize = _size++;
+ _version++;
+
+ if (_nodes.Length == currentSize) Grow(currentSize + 1);
+
+ if (_comparer == null)
+ MoveUpDefaultComparer((element, priority), currentSize);
+ else
+ MoveUpCustomComparer((element, priority), currentSize);
+ }
+
+ ///
+ /// Returns the minimal element from the without removing it.
+ ///
+ /// The is empty.
+ /// The minimal element of the .
+ public TElement Peek()
+ {
+ if (_size == 0) throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
+
+ return _nodes[0].Element;
+ }
+
+ ///
+ /// Removes and returns the minimal element from the .
+ ///
+ /// The queue is empty.
+ /// The minimal element of the .
+ public TElement Dequeue()
+ {
+ if (_size == 0) throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
+
+ var element = _nodes[0].Element;
+ RemoveRootNode();
+ return element;
+ }
+
+ ///
+ /// Removes the minimal element from the ,
+ /// and copies it to the parameter,
+ /// and its associated priority to the parameter.
+ ///
+ /// The removed element.
+ /// The priority associated with the removed element.
+ ///
+ /// if the element is successfully removed;
+ /// if the is empty.
+ ///
+ public bool TryDequeue([MaybeNullWhen(false)] out TElement element,
+ [MaybeNullWhen(false)] out TPriority priority)
+ {
+ if (_size != 0)
+ {
+ (element, priority) = _nodes[0];
+ RemoveRootNode();
+ return true;
+ }
+
+ element = default;
+ priority = default;
+ return false;
+ }
+
+ ///
+ /// Returns a value that indicates whether there is a minimal element in the
+ /// ,
+ /// and if one is present, copies it to the parameter,
+ /// and its associated priority to the parameter.
+ /// The element is not removed from the .
+ ///
+ /// The minimal element in the queue.
+ /// The priority associated with the minimal element.
+ ///
+ /// if there is a minimal element;
+ /// if the is empty.
+ ///
+ public bool TryPeek([MaybeNullWhen(false)] out TElement element,
+ [MaybeNullWhen(false)] out TPriority priority)
+ {
+ if (_size != 0)
+ {
+ (element, priority) = _nodes[0];
+ return true;
+ }
+
+ element = default;
+ priority = default;
+ return false;
+ }
+
+ ///
+ /// Adds the specified element with associated priority to the ,
+ /// and immediately removes the minimal element, returning the result.
+ ///
+ /// The element to add to the .
+ /// The priority with which to associate the new element.
+ /// The minimal element removed after the enqueue operation.
+ ///
+ /// Implements an insert-then-extract heap operation that is generally more efficient
+ /// than sequencing Enqueue and Dequeue operations: in the worst case scenario only one
+ /// shift-down operation is required.
+ ///
+ public TElement EnqueueDequeue(TElement element, TPriority priority)
+ {
+ if (_size != 0)
+ {
+ var root = _nodes[0];
+
+ if (_comparer == null)
+ {
+ if (Comparer.Default.Compare(priority, root.Priority) > 0)
+ {
+ MoveDownDefaultComparer((element, priority), 0);
+ _version++;
+ return root.Element;
+ }
+ }
+ else
+ {
+ if (_comparer.Compare(priority, root.Priority) > 0)
+ {
+ MoveDownCustomComparer((element, priority), 0);
+ _version++;
+ return root.Element;
+ }
+ }
+ }
+
+ return element;
+ }
+
+ ///
+ /// Enqueues a sequence of element/priority pairs to the .
+ ///
+ /// The pairs of elements and priorities to add to the queue.
+ ///
+ /// The specified argument was .
+ ///
+ public void EnqueueRange(IEnumerable<(TElement Element, TPriority Priority)> items)
+ {
+ ArgumentNullException.ThrowIfNull(items);
+
+ int count = 0;
+ var collection =
+ items as ICollection<(TElement Element, TPriority Priority)>;
+ if (collection is not null && (count = collection.Count) > _nodes.Length - _size) Grow(_size + count);
+
+ if (_size == 0)
+ {
+ // build using Heapify() if the queue is empty.
+
+ if (collection is not null)
+ {
+ collection.CopyTo(_nodes, 0);
+ _size = count;
+ }
+ else
+ {
+ int i = 0;
+ (TElement, TPriority)[] nodes = _nodes;
+ foreach ((var element, var priority) in items)
+ {
+ if (nodes.Length == i)
+ {
+ Grow(i + 1);
+ nodes = _nodes;
+ }
+
+ nodes[i++] = (element, priority);
+ }
+
+ _size = i;
+ }
+
+ _version++;
+
+ if (_size > 1) Heapify();
+ }
+ else
+ {
+ foreach ((var element, var priority) in items) Enqueue(element, priority);
+ }
+ }
+
+ ///
+ /// Enqueues a sequence of elements pairs to the ,
+ /// all associated with the specified priority.
+ ///
+ /// The elements to add to the queue.
+ /// The priority to associate with the new elements.
+ ///
+ /// The specified argument was .
+ ///
+ public void EnqueueRange(IEnumerable elements, TPriority priority)
+ {
+ ArgumentNullException.ThrowIfNull(elements);
+
+ int count;
+ if (elements is ICollection<(TElement Element, TPriority Priority)> collection &&
+ (count = collection.Count) > _nodes.Length - _size)
+ Grow(_size + count);
+
+ if (_size == 0)
+ {
+ // build using Heapify() if the queue is empty.
+
+ int i = 0;
+ (TElement, TPriority)[] nodes = _nodes;
+ foreach (var element in elements)
+ {
+ if (nodes.Length == i)
+ {
+ Grow(i + 1);
+ nodes = _nodes;
+ }
+
+ nodes[i++] = (element, priority);
+ }
+
+ _size = i;
+ _version++;
+
+ if (i > 1) Heapify();
+ }
+ else
+ {
+ foreach (var element in elements) Enqueue(element, priority);
+ }
+ }
+
+ ///
+ /// Removes all items from the .
+ ///
+ public void Clear()
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<(TElement, TPriority)>())
+ // Clear the elements so that the gc can reclaim the references
+ Array.Clear(_nodes, 0, _size);
+
+ _size = 0;
+ _version++;
+ }
+
+ ///
+ /// Ensures that the can hold up to
+ /// items without further expansion of its backing storage.
+ ///
+ /// The minimum capacity to be used.
+ ///
+ /// The specified is negative.
+ ///
+ /// The current capacity of the .
+ public int EnsureCapacity(int capacity)
+ {
+ if (capacity < 0)
+ throw new ArgumentOutOfRangeException(nameof(capacity), capacity, SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (_nodes.Length < capacity)
+ {
+ Grow(capacity);
+ _version++;
+ }
+
+ return _nodes.Length;
+ }
+
+ ///
+ /// Sets the capacity to the actual number of items in the ,
+ /// if that is less than 90 percent of current capacity.
+ ///
+ ///
+ /// This method can be used to minimize a collection's memory overhead
+ /// if no new elements will be added to the collection.
+ ///
+ public void TrimExcess()
+ {
+ int threshold = (int)(_nodes.Length * 0.9);
+ if (_size < threshold)
+ {
+ Array.Resize(ref _nodes, _size);
+ _version++;
+ }
+ }
+
+ ///
+ /// Grows the priority queue to match the specified min capacity.
+ ///
+ private void Grow(int minCapacity)
+ {
+ Debug.Assert(_nodes.Length < minCapacity);
+
+ const int GrowFactor = 2;
+ const int MinimumGrow = 4;
+
+ int newcapacity = GrowFactor * _nodes.Length;
+
+ // Allow the queue to grow to maximum possible capacity (~2G elements) before encountering overflow.
+ // Note that this check works even when _nodes.Length overflowed thanks to the (uint) cast
+ if ((uint)newcapacity > ArrayEx.MaxLength) newcapacity = ArrayEx.MaxLength;
+
+ // Ensure minimum growth is respected.
+ newcapacity = Math.Max(newcapacity, _nodes.Length + MinimumGrow);
+
+ // If the computed capacity is still less than specified, set to the original argument.
+ // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize.
+ if (newcapacity < minCapacity) newcapacity = minCapacity;
+
+ Array.Resize(ref _nodes, newcapacity);
+ }
+
+ ///
+ /// Removes the node from the root of the heap
+ ///
+ private void RemoveRootNode()
+ {
+ int lastNodeIndex = --_size;
+ _version++;
+
+ if (lastNodeIndex > 0)
+ {
+ var lastNode = _nodes[lastNodeIndex];
+ if (_comparer == null)
+ MoveDownDefaultComparer(lastNode, 0);
+ else
+ MoveDownCustomComparer(lastNode, 0);
+ }
+
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<(TElement, TPriority)>())
+ _nodes[lastNodeIndex] = default;
+ }
+
+ ///
+ /// Gets the index of an element's parent.
+ ///
+ private static int GetParentIndex(int index) => (index - 1) >> Log2Arity;
+
+ ///
+ /// Gets the index of the first child of an element.
+ ///
+ private static int GetFirstChildIndex(int index) => (index << Log2Arity) + 1;
+
+ ///
+ /// Converts an unordered list into a heap.
+ ///
+ private void Heapify()
+ {
+ // Leaves of the tree are in fact 1-element heaps, for which there
+ // is no need to correct them. The heap property needs to be restored
+ // only for higher nodes, starting from the first node that has children.
+ // It is the parent of the very last element in the array.
+
+ var nodes = _nodes;
+ int lastParentWithChildren = GetParentIndex(_size - 1);
+
+ if (_comparer == null)
+ for (int index = lastParentWithChildren; index >= 0; --index)
+ MoveDownDefaultComparer(nodes[index], index);
+ else
+ for (int index = lastParentWithChildren; index >= 0; --index)
+ MoveDownCustomComparer(nodes[index], index);
+ }
+
+ ///
+ /// Moves a node up in the tree to restore heap order.
+ ///
+ private void MoveUpDefaultComparer((TElement Element, TPriority Priority) node, int nodeIndex)
+ {
+ // Instead of swapping items all the way to the root, we will perform
+ // a similar optimization as in the insertion sort.
+
+ Debug.Assert(_comparer is null);
+ Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
+
+ var nodes = _nodes;
+
+ while (nodeIndex > 0)
+ {
+ int parentIndex = GetParentIndex(nodeIndex);
+ var parent = nodes[parentIndex];
+
+ if (Comparer.Default.Compare(node.Priority, parent.Priority) < 0)
+ {
+ nodes[nodeIndex] = parent;
+ nodeIndex = parentIndex;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ nodes[nodeIndex] = node;
+ }
+
+ ///
+ /// Moves a node up in the tree to restore heap order.
+ ///
+ private void MoveUpCustomComparer((TElement Element, TPriority Priority) node, int nodeIndex)
+ {
+ // Instead of swapping items all the way to the root, we will perform
+ // a similar optimization as in the insertion sort.
+
+ Debug.Assert(_comparer is not null);
+ Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
+
+ var comparer = _comparer;
+ var nodes = _nodes;
+
+ while (nodeIndex > 0)
+ {
+ int parentIndex = GetParentIndex(nodeIndex);
+ var parent = nodes[parentIndex];
+
+ if (comparer != null && comparer.Compare(node.Priority, parent.Priority) < 0)
+ {
+ nodes[nodeIndex] = parent;
+ nodeIndex = parentIndex;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ nodes[nodeIndex] = node;
+ }
+
+ ///
+ /// Moves a node down in the tree to restore heap order.
+ ///
+ private void MoveDownDefaultComparer((TElement Element, TPriority Priority) node, int nodeIndex)
+ {
+ // The node to move down will not actually be swapped every time.
+ // Rather, values on the affected path will be moved up, thus leaving a free spot
+ // for this value to drop in. Similar optimization as in the insertion sort.
+
+ Debug.Assert(_comparer is null);
+ Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
+
+ var nodes = _nodes;
+ int size = _size;
+
+ int i;
+ while ((i = GetFirstChildIndex(nodeIndex)) < size)
+ {
+ // Find the child node with the minimal priority
+ var minChild = nodes[i];
+ int minChildIndex = i;
+
+ int childIndexUpperBound = Math.Min(i + Arity, size);
+ while (++i < childIndexUpperBound)
+ {
+ var nextChild = nodes[i];
+ if (Comparer.Default.Compare(nextChild.Priority, minChild.Priority) < 0)
+ {
+ minChild = nextChild;
+ minChildIndex = i;
+ }
+ }
+
+ // Heap property is satisfied; insert node in this location.
+ if (Comparer.Default.Compare(node.Priority, minChild.Priority) <= 0) break;
+
+ // Move the minimal child up by one node and
+ // continue recursively from its location.
+ nodes[nodeIndex] = minChild;
+ nodeIndex = minChildIndex;
+ }
+
+ nodes[nodeIndex] = node;
+ }
+
+ ///
+ /// Moves a node down in the tree to restore heap order.
+ ///
+ private void MoveDownCustomComparer((TElement Element, TPriority Priority) node, int nodeIndex)
+ {
+ // The node to move down will not actually be swapped every time.
+ // Rather, values on the affected path will be moved up, thus leaving a free spot
+ // for this value to drop in. Similar optimization as in the insertion sort.
+
+ Debug.Assert(_comparer is not null);
+ Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
+
+ var comparer = _comparer;
+ var nodes = _nodes;
+ int size = _size;
+
+ int i;
+ while ((i = GetFirstChildIndex(nodeIndex)) < size)
+ {
+ // Find the child node with the minimal priority
+ var minChild = nodes[i];
+ int minChildIndex = i;
+
+ int childIndexUpperBound = Math.Min(i + Arity, size);
+ while (++i < childIndexUpperBound)
+ {
+ var nextChild = nodes[i];
+ if (comparer != null && comparer.Compare(nextChild.Priority, minChild.Priority) < 0)
+ {
+ minChild = nextChild;
+ minChildIndex = i;
+ }
+ }
+
+ // Heap property is satisfied; insert node in this location.
+ if (comparer != null && comparer.Compare(node.Priority, minChild.Priority) <= 0) break;
+
+ // Move the minimal child up by one node and continue recursively from its location.
+ nodes[nodeIndex] = minChild;
+ nodeIndex = minChildIndex;
+ }
+
+ nodes[nodeIndex] = node;
+ }
+
+ ///
+ /// Initializes the custom comparer to be used internally by the heap.
+ ///
+ private static IComparer? InitializeComparer(IComparer? comparer)
+ {
+ if (typeof(TPriority).IsValueType)
+ {
+ if (comparer == Comparer.Default)
+ // if the user manually specifies the default comparer,
+ // revert to using the optimized path.
+ return null;
+
+ return comparer;
+ }
+
+ // Currently the JIT doesn't optimize direct Comparer.Default.Compare
+ // calls for reference types, so we want to cache the comparer instance instead.
+ // TODO https://github.com/dotnet/runtime/issues/10050: Update if this changes in the future.
+ return comparer ?? Comparer.Default;
+ }
+
+ ///
+ /// Enumerates the contents of a , without any ordering guarantees.
+ ///
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(PriorityQueueDebugView<,>))]
+ public sealed class UnorderedItemsCollection : IReadOnlyCollection<(TElement Element, TPriority Priority)>,
+ ICollection
+ {
+ internal readonly PriorityQueue _queue;
+
+ internal UnorderedItemsCollection(PriorityQueue queue)
+ {
+ _queue = queue;
+ }
+
+ object ICollection.SyncRoot => this;
+ bool ICollection.IsSynchronized => false;
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+
+ if (array.Rank != 1) throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
+
+ if (array.GetLowerBound(0) != 0) throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
+
+ if (index < 0 || index > array.Length)
+ throw new ArgumentOutOfRangeException(nameof(index), index,
+ SR.ArgumentOutOfRange_IndexMustBeLessOrEqual);
+
+ if (array.Length - index < _queue._size) throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ try
+ {
+ Array.Copy(_queue._nodes, 0, array, index, _queue._size);
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
+ }
+ }
+
+ public int Count => _queue._size;
+
+ IEnumerator<(TElement Element, TPriority Priority)> IEnumerable<(TElement Element, TPriority Priority)>.
+ GetEnumerator() => GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ /// Returns an enumerator that iterates through the .
+ ///
+ /// An for the .
+ public Enumerator GetEnumerator() => new(_queue);
+
+ ///
+ /// Enumerates the element and priority pairs of a ,
+ /// without any ordering guarantees.
+ ///
+ public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>
+ {
+ private readonly PriorityQueue _queue;
+ private readonly int _version;
+ private int _index;
+
+ internal Enumerator(PriorityQueue queue)
+ {
+ _queue = queue;
+ _index = 0;
+ _version = queue._version;
+ Current = default;
+ }
+
+ ///
+ /// Releases all resources used by the .
+ ///
+ public void Dispose()
+ {
+ }
+
+ ///
+ /// Advances the enumerator to the next element of the .
+ ///
+ ///
+ /// if the enumerator was successfully advanced to the next element;
+ /// if the enumerator has passed the end of the collection.
+ ///
+ public bool MoveNext()
+ {
+ var localQueue = _queue;
+
+ if (_version == localQueue._version && (uint)_index < (uint)localQueue._size)
+ {
+ Current = localQueue._nodes[_index];
+ _index++;
+ return true;
+ }
+
+ return MoveNextRare();
+ }
+
+ private bool MoveNextRare()
+ {
+ if (_version != _queue._version)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+
+ _index = _queue._size + 1;
+ Current = default;
+ return false;
+ }
+
+ ///
+ /// Gets the element at the current position of the enumerator.
+ ///
+ public (TElement Element, TPriority Priority) Current { get; private set; }
+
+ object IEnumerator.Current => Current;
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _queue._version)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+
+ _index = 0;
+ Current = default;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/PriorityQueue.cs.meta b/NEG/UI/PriorityQueue.cs.meta
new file mode 100644
index 0000000..28b7de0
--- /dev/null
+++ b/NEG/UI/PriorityQueue.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e1b6a9a70d997fd468f30caa1e760078
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/NEG/UI/UiManager.cs b/NEG/UI/UiManager.cs
new file mode 100644
index 0000000..028a212
--- /dev/null
+++ b/NEG/UI/UiManager.cs
@@ -0,0 +1,169 @@
+using JetBrains.Annotations;
+using NEG.UI.Area;
+using NEG.UI.Popup;
+using NEG.UI.Window;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace NEG.UI
+{
+ [PublicAPI]
+ public abstract class UiManager
+ {
+ public static UiManager Instance { get; private set; }
+
+ ///
+ /// Current area shown on screen.
+ ///
+ public IArea CurrentArea
+ {
+ get => currentArea;
+ set
+ {
+ currentArea?.SetEnabled(false);
+
+ currentArea = value;
+
+ currentArea?.SetEnabled(true);
+ }
+ }
+
+ ///
+ /// Current window that is considered main (focused, lastly opened). Can be null.
+ ///
+ public IWindow CurrentMainWindow { get; protected set; }
+
+ private IArea currentArea;
+ private (PopupData data, int priority) currentShownPopup;
+ protected IDefaultPopup currentDefaultPopup;
+
+ private PriorityQueue popupsToShow = new();
+
+ //TODO: localize
+ private string localizedYes = "Yes", localizedNo = "No", localizedOk = "Ok";
+
+ protected UiManager(IArea startArea)
+ {
+ if (Instance != null)
+ {
+ Debug.LogError("Only one instance od UiManager is allowed");
+ return;
+ }
+
+ Instance = this;
+
+ CurrentArea = startArea;
+ }
+
+ ///
+ /// Show popup if there is non other currently shown. Otherwise add current popup to ordered queue and show it later. It will be closed after pressing ok button.
+ ///
+ /// popup title
+ /// popup content
+ /// text to show on ok button, empty for localized "Ok"
+ /// additional action on ok pressed
+ /// priority of popup (lower number -> show first)
+ /// force show current popup only if currently shown has lower priority
+ /// data for created popup, can be used to invalidate popup (will not show)
+ public PopupData ShowOkPopup(string title, string content, string okText = null, Action okPressed = null, int priority = 0, bool forceShow = false)
+ {
+ var data = new DefaultPopupData(currentDefaultPopup, title, content,
+ new List<(string, Action)>() { (okText ?? localizedOk, okPressed) });
+ ShowPopup(data, priority, forceShow);
+ return data;
+ }
+
+ ///
+ /// Show popup if there is non other currently shown. Otherwise add current popup to ordered queue and show it later. It will be closed after pressing yes or no button.
+ ///
+ /// popup title
+ /// popup content
+ /// text to show on yes button, empty for localized "Yes"
+ /// text to show on no button, empty for localized "No"
+ /// additional action on yes pressed
+ /// additional action on no pressed
+ /// priority of popup (lower number -> show first)
+ /// force show current popup only if currently shown has lower priority
+ /// data for created popup, can be used to invalidate popup (will not show)
+ public PopupData ShowYesNoPopup(string title, string content, string yesText = null, string noText = null, Action yesPressed = null, Action noPressed = null, int priority = 0, bool forceShow = false)
+ {
+ var data = new DefaultPopupData(currentDefaultPopup, title, content,
+ new List<(string, Action)>() { (yesText ?? localizedYes, yesPressed), (noText ?? localizedNo, noPressed) });
+ ShowPopup(data, priority, forceShow);
+ return data;
+ }
+
+ ///
+ /// Show popup if there is non other currently shown. Otherwise add current popup to ordered queue and show it later. It will be closed after pressing any button.
+ ///
+ /// popup title
+ /// popup content
+ /// list of actions
+ /// priority of popup (lower number -> show first)
+ /// force show current popup only if currently shown has lower priority
+ /// data for created popup, can be used to invalidate popup (will not show)
+ public PopupData ShowPopup(string title, string content, List<(string, Action)> actions, int priority = 0, bool forceShow = false)
+ {
+ var data = new DefaultPopupData(currentDefaultPopup, title, content, actions);
+ ShowPopup(data, priority, forceShow);
+ return data;
+ }
+
+ ///
+ /// Show popup if there is non other currently shown. Otherwise add current popup to ordered queue and show it later.
+ ///
+ /// popup data object
+ /// priority of popup (lower number -> show first)
+ /// force show current popup only if currently shown has lower priority
+ public void ShowPopup(PopupData data, int priority = 0, bool forceShow = false)
+ {
+ popupsToShow.Enqueue(data, priority);
+ UpdatePopupsState(forceShow, priority, data);
+ }
+
+ protected void PopupClosed(PopupData data)
+ {
+ if (currentShownPopup.data != data)
+ {
+ Debug.LogError("Popup was not shown");
+ return;
+ }
+ UpdatePopupsState(false);
+ }
+
+ protected void SetDefaultPopup(IDefaultPopup popup) => currentDefaultPopup = popup;
+
+ private void UpdatePopupsState(bool forceShow, int priority = 0, PopupData data = null)
+ {
+ if (forceShow)
+ {
+ if(currentShownPopup.priority <= priority)
+ return;
+
+ popupsToShow.Enqueue(currentShownPopup.data, currentShownPopup.priority);
+ ShowPopup(data, priority);
+ return;
+ }
+
+ if(!popupsToShow.TryDequeue(out var d, out int p))
+ return;
+
+ ShowPopup(d, p);
+ }
+
+ private void ShowPopup(PopupData data, int priority)
+ {
+ if (currentShownPopup.data != null)
+ {
+ currentShownPopup.data.PopupClosedEvent -= PopupClosed;
+ currentShownPopup.data.Hide();
+ }
+ currentShownPopup = (data, priority);
+ data.Show();
+ data.PopupClosedEvent += PopupClosed;
+ }
+ }
+}
+
+
diff --git a/NEG/UI/UiManager.cs.meta b/NEG/UI/UiManager.cs.meta
new file mode 100644
index 0000000..b87a94a
--- /dev/null
+++ b/NEG/UI/UiManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c322beedd2ec8f844903c18b1ef74b15
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/NEG/UI/UnityUi.meta b/NEG/UI/UnityUi.meta
new file mode 100644
index 0000000..beaa12a
--- /dev/null
+++ b/NEG/UI/UnityUi.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a0be272f427b40848b8aeff5b04770b4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/NEG/UI/UnityUi/Area.meta b/NEG/UI/UnityUi/Area.meta
new file mode 100644
index 0000000..abc61b9
--- /dev/null
+++ b/NEG/UI/UnityUi/Area.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a99aed857b7b9e7459811b14fefdb04f
+timeCreated: 1670706939
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Area/AutoWindowOpen.cs b/NEG/UI/UnityUi/Area/AutoWindowOpen.cs
new file mode 100644
index 0000000..b19f017
--- /dev/null
+++ b/NEG/UI/UnityUi/Area/AutoWindowOpen.cs
@@ -0,0 +1,15 @@
+using NEG.UI.UnityUi.Window;
+using NEG.UI.Window;
+using System;
+using UnityEngine;
+
+namespace NEG.UI.Area
+{
+ [Tooltip(tooltip: "Automatically open attached window on start")]
+ public class AutoWindowOpen : MonoBehaviour
+ {
+ [SerializeField] private MonoWindow window;
+
+ private void Start() => window.Open();
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Area/AutoWindowOpen.cs.meta b/NEG/UI/UnityUi/Area/AutoWindowOpen.cs.meta
new file mode 100644
index 0000000..b3ed13c
--- /dev/null
+++ b/NEG/UI/UnityUi/Area/AutoWindowOpen.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e439a77332dc4e3a881348c9506556c6
+timeCreated: 1673791915
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Area/MonoArea.cs b/NEG/UI/UnityUi/Area/MonoArea.cs
new file mode 100644
index 0000000..e5f92b0
--- /dev/null
+++ b/NEG/UI/UnityUi/Area/MonoArea.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using UnityEngine;
+using NEG.UI.Popup;
+using NEG.UI.UnityUi.Window;
+using NEG.UI.UnityUi.WindowSlot;
+using NEG.UI.Window;
+using NEG.UI.WindowSlot;
+using System;
+
+namespace NEG.UI.Area
+{
+ public class MonoArea : MonoBehaviour, IArea
+ {
+ public IEnumerable AvailableSlots => windowSlots;
+ public IWindowSlot DefaultWindowSlot => windowSlots[0];
+
+ [SerializeField] private bool setAsDefaultArea;
+
+ [SerializeField] private List windowSlots;
+
+ public virtual void SetEnabled(bool setEnabled) => gameObject.SetActive(setEnabled);
+
+ public virtual void OpenWindow(IWindow window, object data = null)
+ {
+ DefaultWindowSlot.AttachWindow(window);
+ window.SetData(data);
+ }
+
+ protected virtual void Awake()
+ {
+ if (setAsDefaultArea)
+ UiManager.Instance.CurrentArea = this;
+ }
+
+ protected virtual void OnDestroy()
+ {
+ if (ReferenceEquals(UiManager.Instance.CurrentArea, this))
+ UiManager.Instance.CurrentArea = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Area/MonoArea.cs.meta b/NEG/UI/UnityUi/Area/MonoArea.cs.meta
new file mode 100644
index 0000000..958b733
--- /dev/null
+++ b/NEG/UI/UnityUi/Area/MonoArea.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 39eb59ca1ef60934abb3f0c64169be65
+timeCreated: 1670707479
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Buttons.meta b/NEG/UI/UnityUi/Buttons.meta
new file mode 100644
index 0000000..edba027
--- /dev/null
+++ b/NEG/UI/UnityUi/Buttons.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: dcaa262d2bcd4b19aa25512a19830555
+timeCreated: 1670777190
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Buttons/BaseButton.cs b/NEG/UI/UnityUi/Buttons/BaseButton.cs
new file mode 100644
index 0000000..35dfbe9
--- /dev/null
+++ b/NEG/UI/UnityUi/Buttons/BaseButton.cs
@@ -0,0 +1,78 @@
+using FMOD.Studio;
+using FMODUnity;
+using System;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.UI;
+
+namespace NEG.UI.UnityUi.Buttons
+{
+ [RequireComponent(typeof(ButtonSerializeFields))]
+ public class BaseButton : MonoBehaviour, ISelectHandler, IDeselectHandler, IPointerEnterHandler, IPointerExitHandler
+ {
+ public event Action OnButtonPressed;
+
+ public bool Interactable { get => serializeFields.Button.interactable; set => serializeFields.Button.interactable = value; }
+
+ [SerializeField]
+ protected ButtonSerializeFields serializeFields;
+
+ private bool isHovered;
+
+ public virtual void OnSelect(BaseEventData eventData)
+ {
+ if (serializeFields.Text)
+ serializeFields.Text.color = serializeFields.SelectedTextColor;
+ }
+
+ public void OnDeselect(BaseEventData eventData)
+ {
+ if (serializeFields.Text)
+ serializeFields.Text.color = serializeFields.DeselectedTextColor;
+ }
+
+ public void OnPointerEnter(PointerEventData eventData)
+ {
+ isHovered = true;
+ if (serializeFields.Text)
+ serializeFields.Text.color = serializeFields.SelectedTextColor;
+ }
+
+ public void OnPointerExit(PointerEventData eventData)
+ {
+ isHovered = false;
+ if (serializeFields.Text)
+ serializeFields.Text.color = serializeFields.DeselectedTextColor;
+ }
+
+ public void SetText(string text)
+ {
+ if(serializeFields == null)
+ return;
+ if(serializeFields.Text == null)
+ return;
+ serializeFields.Text.text = text;
+ }
+
+ protected virtual void Awake()
+ {
+ if(serializeFields == null)
+ serializeFields = GetComponent();
+ serializeFields.Button.onClick.AddListener(OnClicked);
+ OnDeselect(null);
+ }
+
+ private void OnValidate()
+ {
+ if(serializeFields == null)
+ serializeFields = GetComponent();
+ }
+
+ protected virtual void OnClicked()
+ {
+ OnDeselect(null);
+ isHovered = false;
+ OnButtonPressed?.Invoke();
+ }
+ }
+}
diff --git a/NEG/UI/UnityUi/Buttons/BaseButton.cs.meta b/NEG/UI/UnityUi/Buttons/BaseButton.cs.meta
new file mode 100644
index 0000000..ea42d55
--- /dev/null
+++ b/NEG/UI/UnityUi/Buttons/BaseButton.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3a250ef2d0c34e7396a16fc5eddbdb01
+timeCreated: 1670777213
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Buttons/ButtonReaction.cs b/NEG/UI/UnityUi/Buttons/ButtonReaction.cs
new file mode 100644
index 0000000..9e8bf17
--- /dev/null
+++ b/NEG/UI/UnityUi/Buttons/ButtonReaction.cs
@@ -0,0 +1,15 @@
+using System;
+using UnityEngine;
+
+namespace NEG.UI.UnityUi.Buttons
+{
+ [RequireComponent(typeof(BaseButton))]
+ public abstract class ButtonReaction : MonoBehaviour
+ {
+ private void Awake() => GetComponent().OnButtonPressed += OnClicked;
+
+ private void OnDestroy() => GetComponent().OnButtonPressed -= OnClicked;
+
+ protected abstract void OnClicked();
+ }
+}
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Buttons/ButtonReaction.cs.meta b/NEG/UI/UnityUi/Buttons/ButtonReaction.cs.meta
new file mode 100644
index 0000000..1089ca5
--- /dev/null
+++ b/NEG/UI/UnityUi/Buttons/ButtonReaction.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a9761128a04b49c2a26eddfabe70331f
+timeCreated: 1675707257
\ No newline at end of file
diff --git a/NEG/UI/UnityUi/Buttons/ButtonSerializeFields.cs b/NEG/UI/UnityUi/Buttons/ButtonSerializeFields.cs
new file mode 100644
index 0000000..60e93a9
--- /dev/null
+++ b/NEG/UI/UnityUi/Buttons/ButtonSerializeFields.cs
@@ -0,0 +1,33 @@
+using FMODUnity;
+using System;
+using TMPro;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace NEG.UI.UnityUi.Buttons
+{
+ public class ButtonSerializeFields : MonoBehaviour
+ {
+ [field: SerializeField]
+ public Button Button { get; private set; }
+ [field: SerializeField]
+ public TMP_Text Text { get; private set; }
+ [field: SerializeField]
+ public Color SelectedTextColor { get; private set; }
+ [field: SerializeField]
+ public Color DeselectedTextColor { get; private set; }
+ [field: SerializeField]
+ public EventReference HoverEventRef { get; private set; }
+ [field: SerializeField]
+ public EventReference ClickEventRef { get; private set; }
+
+ private void OnValidate()
+ {
+ if(Button == null)
+ Button = GetComponent