Slots implementation
This commit is contained in:
parent
a5fc6179fb
commit
cf69baab9f
@ -8,18 +8,13 @@ namespace NEG.UI.Area
|
||||
public interface IArea : IUiElement
|
||||
{
|
||||
IEnumerable<IWindowSlot> AvailableSlots { get; }
|
||||
IEnumerable<IPopup> CurrentPopups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Open window
|
||||
/// </summary>
|
||||
/// <param name="window"></param>
|
||||
void OpenWindow(IWindow window);
|
||||
/// <summary>
|
||||
/// Open popup
|
||||
/// </summary>
|
||||
/// <param name="popup"></param>
|
||||
void OpenPopup(IPopup popup);
|
||||
/// <param name="data"></param>
|
||||
void OpenWindow(IWindow window, object data = null);
|
||||
|
||||
void CloseAllWindows()
|
||||
{
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffa2155176774ab691d499979707a1bb
|
||||
guid: f7cf5ef3a347e1c4b98411f4d564b988
|
||||
timeCreated: 1670690282
|
||||
@ -1,58 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using NEG.UI.Popup;
|
||||
using NEG.UI.Window;
|
||||
using NEG.UI.WindowSlot;
|
||||
|
||||
namespace NEG.UI.Area
|
||||
{
|
||||
public class MonoArea : MonoBehaviour, IArea
|
||||
{
|
||||
public IEnumerable<IWindowSlot> AvailableSlots => windowSlots;
|
||||
public IWindowSlot DefaultWindowSlot => windowSlots[0];
|
||||
public IEnumerable<IWindow> CurrentWindows { get; }
|
||||
public IEnumerable<IPopup> CurrentPopups => currentPopups;
|
||||
|
||||
[SerializeField] private List<MonoWindowSlot> windowSlots;
|
||||
|
||||
[SerializeField] private Queue<IPopup> currentPopups = new();
|
||||
|
||||
public void SetEnabled(bool setEnabled) => gameObject.SetActive(setEnabled);
|
||||
|
||||
public void OpenWindow(IWindow window) => DefaultWindowSlot.AttachWindow(window);
|
||||
|
||||
public void OpenPopup(IPopup popup)
|
||||
{
|
||||
currentPopups.Enqueue(popup);
|
||||
popup.OnPopupClosed += OnPopupClosed;
|
||||
popup.Show();
|
||||
UpdatePopupStates();
|
||||
}
|
||||
|
||||
private void UpdatePopupStates()
|
||||
{
|
||||
if(currentPopups.Count == 0)
|
||||
return;
|
||||
|
||||
while (currentPopups.TryPeek(out var popup))
|
||||
{
|
||||
if(popup.IsValid)
|
||||
popup.SetEnabled(true);
|
||||
|
||||
currentPopups.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPopupClosed(IPopup popup)
|
||||
{
|
||||
if (!currentPopups.Contains(popup))
|
||||
return;
|
||||
|
||||
if(currentPopups.Peek() != popup)
|
||||
return;
|
||||
|
||||
currentPopups.Dequeue();
|
||||
UpdatePopupStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "NegUi",
|
||||
"name": "NEG.UI",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
|
||||
28
NEG/UI/Popup/DefaultPopupData.cs
Normal file
28
NEG/UI/Popup/DefaultPopupData.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
NEG/UI/Popup/DefaultPopupData.cs.meta
Normal file
3
NEG/UI/Popup/DefaultPopupData.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 372c6df9ec044cb3adc86c7776b2ef61
|
||||
timeCreated: 1672432934
|
||||
10
NEG/UI/Popup/IDefaultPopup.cs
Normal file
10
NEG/UI/Popup/IDefaultPopup.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NEG.UI.Popup
|
||||
{
|
||||
public interface IDefaultPopup : IPopup
|
||||
{
|
||||
public void SetContent(string title, string content, List<(string, Action)> options);
|
||||
}
|
||||
}
|
||||
3
NEG/UI/Popup/IDefaultPopup.cs.meta
Normal file
3
NEG/UI/Popup/IDefaultPopup.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a6d0871ada44d1d95bea6c8e8701769
|
||||
timeCreated: 1672153906
|
||||
@ -5,41 +5,18 @@ using System;
|
||||
namespace NEG.UI.Popup
|
||||
{
|
||||
[PublicAPI]
|
||||
public interface IPopup : IUiElement
|
||||
public interface IPopup
|
||||
{
|
||||
/// <summary>
|
||||
/// Call when popup is not forcly closed
|
||||
/// Show popup
|
||||
/// </summary>
|
||||
event Action<IPopup> OnPopupClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Is popup still valid to show
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Mark popup as ready to show.
|
||||
/// </summary>
|
||||
internal void Show();
|
||||
/// <param name="data">data assigned to popup, used to give info that popup is closed</param>
|
||||
void Show(PopupData data);
|
||||
|
||||
/// <summary>
|
||||
/// Close popup or mark as closed if not visible
|
||||
/// </summary>
|
||||
/// <param name="isForced">Is closing by forced by system, ex. close area</param>
|
||||
void Close(bool isForced = false);
|
||||
}
|
||||
|
||||
public static class PopupExtensions
|
||||
{
|
||||
public static void Show(this IPopup popup, IArea area = null)
|
||||
{
|
||||
if (area != null)
|
||||
{
|
||||
area.OpenPopup(popup);
|
||||
return;
|
||||
}
|
||||
|
||||
UiManager.Instance.CurrentArea.OpenPopup(popup);
|
||||
}
|
||||
/// <param name="silence">if true hide visually, without firing callbacks</param>
|
||||
void Close(bool silence = false);
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
using NEG.UI.Area;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.Popup
|
||||
{
|
||||
public class MonoPopup : MonoBehaviour, IPopup
|
||||
{
|
||||
public event Action<IPopup> OnPopupClosed;
|
||||
public bool IsValid { get; private set; }
|
||||
|
||||
public void Close(bool isForced = false)
|
||||
{
|
||||
IsValid = false;
|
||||
gameObject.SetActive(false);
|
||||
if(!isForced)
|
||||
OnPopupClosed?.Invoke(this);
|
||||
}
|
||||
|
||||
public void SetEnabled(bool setEnabled) => gameObject.SetActive(setEnabled);
|
||||
|
||||
void IPopup.Show() => IsValid = true;
|
||||
}
|
||||
}
|
||||
20
NEG/UI/Popup/PopupData.cs
Normal file
20
NEG/UI/Popup/PopupData.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace NEG.UI.Popup
|
||||
{
|
||||
public class PopupData
|
||||
{
|
||||
public bool IsValid { get; protected set; }
|
||||
|
||||
private IPopup popup;
|
||||
|
||||
public PopupData(IPopup popup)
|
||||
{
|
||||
this.popup = popup;
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
public virtual void Show() => popup.Show(this);
|
||||
public virtual void Hide() => popup.Close(true);
|
||||
|
||||
public virtual void Invalidate() => IsValid = false;
|
||||
}
|
||||
}
|
||||
3
NEG/UI/Popup/PopupData.cs.meta
Normal file
3
NEG/UI/Popup/PopupData.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6e5ebb367aa4dfba5e5f853c9b31a3d
|
||||
timeCreated: 1672430446
|
||||
999
NEG/UI/PriorityQueue.cs
Normal file
999
NEG/UI/PriorityQueue.cs
Normal file
@ -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<TElement, TPriority>
|
||||
{
|
||||
private readonly PriorityQueue<TElement, TPriority> _queue;
|
||||
private readonly bool _sort;
|
||||
|
||||
public PriorityQueueDebugView(PriorityQueue<TElement, TPriority> queue)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(queue);
|
||||
|
||||
_queue = queue;
|
||||
_sort = true;
|
||||
}
|
||||
|
||||
public PriorityQueueDebugView(PriorityQueue<TElement, TPriority>.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal helper functions for working with enumerables.
|
||||
/// </summary>
|
||||
internal static class EnumerableHelpers
|
||||
{
|
||||
/// <summary>Converts an enumerable to an array using the same logic as List{T}.</summary>
|
||||
/// <param name="source">The enumerable to convert.</param>
|
||||
/// <param name="length">The number of items stored in the resulting array, 0-indexed.</param>
|
||||
/// <returns>
|
||||
/// The resulting array. The length of the array may be greater than <paramref name="length" />,
|
||||
/// which is the actual number of elements in the array.
|
||||
/// </returns>
|
||||
internal static T[] ToArray<T>(IEnumerable<T> source, out int length)
|
||||
{
|
||||
if (source is ICollection<T> 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<T>. 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<T>, which as of .NET 4.6 is just ConcurrentDictionary<TKey, TValue>.
|
||||
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<T>:
|
||||
// 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<T>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a min priority queue.
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Specifies the type of elements in the queue.</typeparam>
|
||||
/// <typeparam name="TPriority">Specifies the type of priority associated with enqueued elements.</typeparam>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
[DebuggerDisplay("Count = {Count}")]
|
||||
[DebuggerTypeProxy(typeof(PriorityQueueDebugView<,>))]
|
||||
public class PriorityQueue<TElement, TPriority>
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the arity of the d-ary heap, which here is quaternary.
|
||||
/// It is assumed that this value is a power of 2.
|
||||
/// </summary>
|
||||
private const int Arity = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The binary logarithm of <see cref="Arity" />.
|
||||
/// </summary>
|
||||
private const int Log2Arity = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Custom comparer used to order the heap.
|
||||
/// </summary>
|
||||
private readonly IComparer<TPriority>? _comparer;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an implicit heap-ordered complete d-ary tree, stored as an array.
|
||||
/// </summary>
|
||||
private (TElement Element, TPriority Priority)[] _nodes;
|
||||
|
||||
/// <summary>
|
||||
/// The number of nodes in the heap.
|
||||
/// </summary>
|
||||
private int _size;
|
||||
|
||||
/// <summary>
|
||||
/// Lazily-initialized collection used to expose the contents of the queue.
|
||||
/// </summary>
|
||||
private UnorderedItemsCollection? _unorderedItems;
|
||||
|
||||
/// <summary>
|
||||
/// Version updated on mutation to help validate enumerators operate on a consistent state.
|
||||
/// </summary>
|
||||
private int _version;
|
||||
|
||||
#if DEBUG
|
||||
static PriorityQueue()
|
||||
{
|
||||
Debug.Assert(Log2Arity > 0 && Math.Pow(2, Log2Arity) == Arity);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class.
|
||||
/// </summary>
|
||||
public PriorityQueue()
|
||||
{
|
||||
_nodes = Array.Empty<(TElement, TPriority)>();
|
||||
_comparer = InitializeComparer(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
||||
/// with the specified initial capacity.
|
||||
/// </summary>
|
||||
/// <param name="initialCapacity">Initial capacity to allocate in the underlying heap array.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// The specified <paramref name="initialCapacity" /> was negative.
|
||||
/// </exception>
|
||||
public PriorityQueue(int initialCapacity)
|
||||
: this(initialCapacity, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
||||
/// with the specified custom priority comparer.
|
||||
/// </summary>
|
||||
/// <param name="comparer">
|
||||
/// Custom comparer dictating the ordering of elements.
|
||||
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null" />.
|
||||
/// </param>
|
||||
public PriorityQueue(IComparer<TPriority>? comparer)
|
||||
{
|
||||
_nodes = Array.Empty<(TElement, TPriority)>();
|
||||
_comparer = InitializeComparer(comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
||||
/// with the specified initial capacity and custom priority comparer.
|
||||
/// </summary>
|
||||
/// <param name="initialCapacity">Initial capacity to allocate in the underlying heap array.</param>
|
||||
/// <param name="comparer">
|
||||
/// Custom comparer dictating the ordering of elements.
|
||||
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null" />.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// The specified <paramref name="initialCapacity" /> was negative.
|
||||
/// </exception>
|
||||
public PriorityQueue(int initialCapacity, IComparer<TPriority>? comparer)
|
||||
{
|
||||
if (initialCapacity < 0)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(initialCapacity), initialCapacity, SR.ArgumentOutOfRange_NeedNonNegNum);
|
||||
|
||||
_nodes = new (TElement, TPriority)[initialCapacity];
|
||||
_comparer = InitializeComparer(comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
||||
/// that is populated with the specified elements and priorities.
|
||||
/// </summary>
|
||||
/// <param name="items">The pairs of elements and priorities with which to populate the queue.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// The specified <paramref name="items" /> argument was <see langword="null" />.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Constructs the heap using a heapify operation,
|
||||
/// which is generally faster than enqueuing individual elements sequentially.
|
||||
/// </remarks>
|
||||
public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items)
|
||||
: this(items, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
||||
/// that is populated with the specified elements and priorities,
|
||||
/// and with the specified custom priority comparer.
|
||||
/// </summary>
|
||||
/// <param name="items">The pairs of elements and priorities with which to populate the queue.</param>
|
||||
/// <param name="comparer">
|
||||
/// Custom comparer dictating the ordering of elements.
|
||||
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null" />.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// The specified <paramref name="items" /> argument was <see langword="null" />.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Constructs the heap using a heapify operation,
|
||||
/// which is generally faster than enqueuing individual elements sequentially.
|
||||
/// </remarks>
|
||||
public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items, IComparer<TPriority>? comparer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
_nodes = EnumerableHelpers.ToArray(items, out _size);
|
||||
_comparer = InitializeComparer(comparer);
|
||||
|
||||
if (_size > 1) Heapify();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the <see cref="PriorityQueue{TElement, TPriority}" />.
|
||||
/// </summary>
|
||||
public int Count => _size;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority comparer used by the <see cref="PriorityQueue{TElement, TPriority}" />.
|
||||
/// </summary>
|
||||
public IComparer<TPriority> Comparer => _comparer ?? Comparer<TPriority>.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection that enumerates the elements of the queue in an unordered manner.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public UnorderedItemsCollection UnorderedItems => _unorderedItems ??= new UnorderedItemsCollection(this);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified element with associated priority to the <see cref="PriorityQueue{TElement, TPriority}" />.
|
||||
/// </summary>
|
||||
/// <param name="element">The element to add to the <see cref="PriorityQueue{TElement, TPriority}" />.</param>
|
||||
/// <param name="priority">The priority with which to associate the new element.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the minimal element from the <see cref="PriorityQueue{TElement, TPriority}" /> without removing it.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The <see cref="PriorityQueue{TElement, TPriority}" /> is empty.</exception>
|
||||
/// <returns>The minimal element of the <see cref="PriorityQueue{TElement, TPriority}" />.</returns>
|
||||
public TElement Peek()
|
||||
{
|
||||
if (_size == 0) throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
|
||||
|
||||
return _nodes[0].Element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes and returns the minimal element from the <see cref="PriorityQueue{TElement, TPriority}" />.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The queue is empty.</exception>
|
||||
/// <returns>The minimal element of the <see cref="PriorityQueue{TElement, TPriority}" />.</returns>
|
||||
public TElement Dequeue()
|
||||
{
|
||||
if (_size == 0) throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
|
||||
|
||||
var element = _nodes[0].Element;
|
||||
RemoveRootNode();
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the minimal element from the <see cref="PriorityQueue{TElement, TPriority}" />,
|
||||
/// and copies it to the <paramref name="element" /> parameter,
|
||||
/// and its associated priority to the <paramref name="priority" /> parameter.
|
||||
/// </summary>
|
||||
/// <param name="element">The removed element.</param>
|
||||
/// <param name="priority">The priority associated with the removed element.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the element is successfully removed;
|
||||
/// <see langword="false" /> if the <see cref="PriorityQueue{TElement, TPriority}" /> is empty.
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value that indicates whether there is a minimal element in the
|
||||
/// <see cref="PriorityQueue{TElement, TPriority}" />,
|
||||
/// and if one is present, copies it to the <paramref name="element" /> parameter,
|
||||
/// and its associated priority to the <paramref name="priority" /> parameter.
|
||||
/// The element is not removed from the <see cref="PriorityQueue{TElement, TPriority}" />.
|
||||
/// </summary>
|
||||
/// <param name="element">The minimal element in the queue.</param>
|
||||
/// <param name="priority">The priority associated with the minimal element.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if there is a minimal element;
|
||||
/// <see langword="false" /> if the <see cref="PriorityQueue{TElement, TPriority}" /> is empty.
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified element with associated priority to the <see cref="PriorityQueue{TElement, TPriority}" />,
|
||||
/// and immediately removes the minimal element, returning the result.
|
||||
/// </summary>
|
||||
/// <param name="element">The element to add to the <see cref="PriorityQueue{TElement, TPriority}" />.</param>
|
||||
/// <param name="priority">The priority with which to associate the new element.</param>
|
||||
/// <returns>The minimal element removed after the enqueue operation.</returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public TElement EnqueueDequeue(TElement element, TPriority priority)
|
||||
{
|
||||
if (_size != 0)
|
||||
{
|
||||
var root = _nodes[0];
|
||||
|
||||
if (_comparer == null)
|
||||
{
|
||||
if (Comparer<TPriority>.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a sequence of element/priority pairs to the <see cref="PriorityQueue{TElement, TPriority}" />.
|
||||
/// </summary>
|
||||
/// <param name="items">The pairs of elements and priorities to add to the queue.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// The specified <paramref name="items" /> argument was <see langword="null" />.
|
||||
/// </exception>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a sequence of elements pairs to the <see cref="PriorityQueue{TElement, TPriority}" />,
|
||||
/// all associated with the specified priority.
|
||||
/// </summary>
|
||||
/// <param name="elements">The elements to add to the queue.</param>
|
||||
/// <param name="priority">The priority to associate with the new elements.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// The specified <paramref name="elements" /> argument was <see langword="null" />.
|
||||
/// </exception>
|
||||
public void EnqueueRange(IEnumerable<TElement> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the <see cref="PriorityQueue{TElement, TPriority}" />.
|
||||
/// </summary>
|
||||
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++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the <see cref="PriorityQueue{TElement, TPriority}" /> can hold up to
|
||||
/// <paramref name="capacity" /> items without further expansion of its backing storage.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The minimum capacity to be used.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// The specified <paramref name="capacity" /> is negative.
|
||||
/// </exception>
|
||||
/// <returns>The current capacity of the <see cref="PriorityQueue{TElement, TPriority}" />.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the capacity to the actual number of items in the <see cref="PriorityQueue{TElement, TPriority}" />,
|
||||
/// if that is less than 90 percent of current capacity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method can be used to minimize a collection's memory overhead
|
||||
/// if no new elements will be added to the collection.
|
||||
/// </remarks>
|
||||
public void TrimExcess()
|
||||
{
|
||||
int threshold = (int)(_nodes.Length * 0.9);
|
||||
if (_size < threshold)
|
||||
{
|
||||
Array.Resize(ref _nodes, _size);
|
||||
_version++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grows the priority queue to match the specified min capacity.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the node from the root of the heap
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an element's parent.
|
||||
/// </summary>
|
||||
private static int GetParentIndex(int index) => (index - 1) >> Log2Arity;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the first child of an element.
|
||||
/// </summary>
|
||||
private static int GetFirstChildIndex(int index) => (index << Log2Arity) + 1;
|
||||
|
||||
/// <summary>
|
||||
/// Converts an unordered list into a heap.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a node up in the tree to restore heap order.
|
||||
/// </summary>
|
||||
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<TPriority>.Default.Compare(node.Priority, parent.Priority) < 0)
|
||||
{
|
||||
nodes[nodeIndex] = parent;
|
||||
nodeIndex = parentIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nodes[nodeIndex] = node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a node up in the tree to restore heap order.
|
||||
/// </summary>
|
||||
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.Compare(node.Priority, parent.Priority) < 0)
|
||||
{
|
||||
nodes[nodeIndex] = parent;
|
||||
nodeIndex = parentIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nodes[nodeIndex] = node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a node down in the tree to restore heap order.
|
||||
/// </summary>
|
||||
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<TPriority>.Default.Compare(nextChild.Priority, minChild.Priority) < 0)
|
||||
{
|
||||
minChild = nextChild;
|
||||
minChildIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Heap property is satisfied; insert node in this location.
|
||||
if (Comparer<TPriority>.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a node down in the tree to restore heap order.
|
||||
/// </summary>
|
||||
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.Compare(nextChild.Priority, minChild.Priority) < 0)
|
||||
{
|
||||
minChild = nextChild;
|
||||
minChildIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Heap property is satisfied; insert node in this location.
|
||||
if (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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the custom comparer to be used internally by the heap.
|
||||
/// </summary>
|
||||
private static IComparer<TPriority>? InitializeComparer(IComparer<TPriority>? comparer)
|
||||
{
|
||||
if (typeof(TPriority).IsValueType)
|
||||
{
|
||||
if (comparer == Comparer<TPriority>.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<T>.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<TPriority>.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the contents of a <see cref="PriorityQueue{TElement, TPriority}" />, without any ordering guarantees.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Count = {Count}")]
|
||||
[DebuggerTypeProxy(typeof(PriorityQueueDebugView<,>))]
|
||||
public sealed class UnorderedItemsCollection : IReadOnlyCollection<(TElement Element, TPriority Priority)>,
|
||||
ICollection
|
||||
{
|
||||
internal readonly PriorityQueue<TElement, TPriority> _queue;
|
||||
|
||||
internal UnorderedItemsCollection(PriorityQueue<TElement, TPriority> 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();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the <see cref="UnorderedItems" />.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Enumerator" /> for the <see cref="UnorderedItems" />.</returns>
|
||||
public Enumerator GetEnumerator() => new(_queue);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the element and priority pairs of a <see cref="PriorityQueue{TElement, TPriority}" />,
|
||||
/// without any ordering guarantees.
|
||||
/// </summary>
|
||||
public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>
|
||||
{
|
||||
private readonly PriorityQueue<TElement, TPriority> _queue;
|
||||
private readonly int _version;
|
||||
private int _index;
|
||||
|
||||
internal Enumerator(PriorityQueue<TElement, TPriority> queue)
|
||||
{
|
||||
_queue = queue;
|
||||
_index = 0;
|
||||
_version = queue._version;
|
||||
Current = default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the <see cref="Enumerator" />.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the enumerator to the next element of the <see cref="UnorderedItems" />.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the enumerator was successfully advanced to the next element;
|
||||
/// <see langword="false" /> if the enumerator has passed the end of the collection.
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at the current position of the enumerator.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
NEG/UI/PriorityQueue.cs.meta
Normal file
11
NEG/UI/PriorityQueue.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1b6a9a70d997fd468f30caa1e760078
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,13 +1,16 @@
|
||||
using NEG.UI.Area;
|
||||
using NEG.UI.Popup;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI
|
||||
{
|
||||
public class UiManager
|
||||
public abstract class UiManager
|
||||
{
|
||||
//TODO: use default unity selection
|
||||
//TODO: window snaping to slots
|
||||
//TODO: Default prefabs?
|
||||
|
||||
public static UiManager Instance { get; private set; }
|
||||
|
||||
@ -31,8 +34,15 @@ namespace NEG.UI
|
||||
}
|
||||
|
||||
private IArea currentArea;
|
||||
protected IDefaultPopup currentDefaultPopup;
|
||||
private (PopupData data, int priority) currentShownPopup;
|
||||
|
||||
public UiManager(IArea startArea)
|
||||
private PriorityQueue<PopupData, int> popupsToShow = new();
|
||||
|
||||
//TODO: localize
|
||||
private string localizedYes = "Yes", localizedNo = "No", localizedOk = "Ok";
|
||||
|
||||
protected UiManager(IArea startArea)
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
@ -44,6 +54,107 @@ namespace NEG.UI
|
||||
CurrentArea = startArea;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="title">popup title</param>
|
||||
/// <param name="content">popup content</param>
|
||||
/// <param name="okText">text to show on ok button, empty for localized "Ok"</param>
|
||||
/// <param name="okPressed">additional action on ok pressed</param>
|
||||
/// <param name="priority">priority of popup (lower number -> show first)</param>
|
||||
/// <param name="forceShow">force show current popup only if currently shown has lower priority</param>
|
||||
/// <returns>data for created popup, can be used to invalidate popup (will not show)</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="title">popup title</param>
|
||||
/// <param name="content">popup content</param>
|
||||
/// <param name="yesText">text to show on yes button, empty for localized "Yes"</param>
|
||||
/// <param name="noText">text to show on no button, empty for localized "No"</param>
|
||||
/// <param name="yesPressed">additional action on yes pressed</param>
|
||||
/// <param name="noPressed">additional action on no pressed</param>
|
||||
/// <param name="priority">priority of popup (lower number -> show first)</param>
|
||||
/// <param name="forceShow">force show current popup only if currently shown has lower priority</param>
|
||||
/// <returns>data for created popup, can be used to invalidate popup (will not show)</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="title">popup title</param>
|
||||
/// <param name="content">popup content</param>
|
||||
/// <param name="actions">list of actions</param>
|
||||
/// <param name="priority">priority of popup (lower number -> show first)</param>
|
||||
/// <param name="forceShow">force show current popup only if currently shown has lower priority</param>
|
||||
/// <returns>data for created popup, can be used to invalidate popup (will not show)</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show popup if there is non other currently shown. Otherwise add current popup to ordered queue and show it later.
|
||||
/// </summary>
|
||||
/// <param name="data">popup data object</param>
|
||||
/// <param name="priority">priority of popup (lower number -> show first)</param>
|
||||
/// <param name="forceShow">force show current popup only if currently shown has lower priority</param>
|
||||
public void ShowPopup(PopupData data, int priority = 0, bool forceShow = false)
|
||||
{
|
||||
popupsToShow.Enqueue(data, priority);
|
||||
UpdatePopupsState(forceShow, priority, data);
|
||||
}
|
||||
|
||||
public 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)
|
||||
{
|
||||
currentShownPopup = (data, priority);
|
||||
data.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
NEG/UI/UnityUi/Area.meta
Normal file
3
NEG/UI/UnityUi/Area.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a99aed857b7b9e7459811b14fefdb04f
|
||||
timeCreated: 1670706939
|
||||
30
NEG/UI/UnityUi/Area/MonoArea.cs
Normal file
30
NEG/UI/UnityUi/Area/MonoArea.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using NEG.UI.Popup;
|
||||
using NEG.UI.UnityUi.WindowSlot;
|
||||
using NEG.UI.Window;
|
||||
using NEG.UI.WindowSlot;
|
||||
|
||||
namespace NEG.UI.Area
|
||||
{
|
||||
public class MonoArea : MonoBehaviour, IArea
|
||||
{
|
||||
public IEnumerable<IWindowSlot> AvailableSlots => windowSlots;
|
||||
public IWindowSlot DefaultWindowSlot => windowSlots[0];
|
||||
public IEnumerable<IWindow> CurrentWindows { get; }
|
||||
|
||||
[SerializeField] private List<MonoWindowSlot> windowSlots;
|
||||
|
||||
[SerializeField] private Queue<IPopup> currentPopups = new();
|
||||
|
||||
public void SetEnabled(bool setEnabled) => gameObject.SetActive(setEnabled);
|
||||
|
||||
public void OpenWindow(IWindow window, object data = null)
|
||||
{
|
||||
DefaultWindowSlot.AttachWindow(window);
|
||||
window.SetData(data);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbfe2293348b4c9096a3763479ec00c7
|
||||
guid: 39eb59ca1ef60934abb3f0c64169be65
|
||||
timeCreated: 1670707479
|
||||
@ -12,6 +12,9 @@ namespace NEG.UI.UnityUi.Buttons
|
||||
{
|
||||
public event Action OnButtonPressed;
|
||||
|
||||
public bool Interactable { get => serializeFields.Button.interactable; set => serializeFields.Button.interactable = value; }
|
||||
|
||||
[SerializeField]
|
||||
protected ButtonSerializeFields serializeFields;
|
||||
|
||||
private bool isHovered;
|
||||
@ -42,13 +45,29 @@ namespace NEG.UI.UnityUi.Buttons
|
||||
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()
|
||||
{
|
||||
serializeFields = GetComponent<ButtonSerializeFields>();
|
||||
if(serializeFields == null)
|
||||
serializeFields = GetComponent<ButtonSerializeFields>();
|
||||
serializeFields.Button.onClick.AddListener(OnClicked);
|
||||
OnDeselect(null);
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if(serializeFields == null)
|
||||
serializeFields = GetComponent<ButtonSerializeFields>();
|
||||
}
|
||||
|
||||
protected virtual void OnClicked()
|
||||
{
|
||||
OnDeselect(null);
|
||||
|
||||
11
NEG/UI/UnityUi/Buttons/CloseAllWindows.cs
Normal file
11
NEG/UI/UnityUi/Buttons/CloseAllWindows.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.UnityUi.Buttons
|
||||
{
|
||||
[RequireComponent(typeof(BaseButton))]
|
||||
public class CloseAllWindows : MonoBehaviour
|
||||
{
|
||||
private void Awake() => GetComponent<BaseButton>().OnButtonPressed += OnClicked;
|
||||
private void OnClicked() => UiManager.Instance.CurrentArea.CloseAllWindows();
|
||||
}
|
||||
}
|
||||
3
NEG/UI/UnityUi/Buttons/CloseAllWindows.cs.meta
Normal file
3
NEG/UI/UnityUi/Buttons/CloseAllWindows.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fa43803dc014b7694d265d48c1fc680
|
||||
timeCreated: 1672767354
|
||||
@ -1,4 +1,5 @@
|
||||
using NEG.UI.Window;
|
||||
using NEG.UI.UnityUi.Window;
|
||||
using NEG.UI.Window;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using NEG.UI.UnityUi.Window;
|
||||
using NEG.UI.UnityUi.WindowSlot;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using NEG.UI.Window;
|
||||
using NEG.UI.WindowSlot;
|
||||
|
||||
8
NEG/UI/UnityUi/CarouselList.meta
Normal file
8
NEG/UI/UnityUi/CarouselList.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6bf45a37eca4dc4aa1926eb7dce5ab6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
71
NEG/UI/UnityUi/CarouselList/CarouselList.cs
Normal file
71
NEG/UI/UnityUi/CarouselList/CarouselList.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using NEG.UI.UnityUi.Buttons;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace NEG.UI.UnityUi
|
||||
{
|
||||
public class CarouselList : MonoBehaviour
|
||||
{
|
||||
public string CurrentOption { get; private set; }
|
||||
public int CurrentOptionId { get; private set; }
|
||||
|
||||
[SerializeField] private BaseButton nextButton;
|
||||
[SerializeField] private BaseButton prevButton;
|
||||
[SerializeField] private TMP_Text currentOptionText;
|
||||
|
||||
private List<string> options;
|
||||
|
||||
public void SetOptions(List<string> options)
|
||||
{
|
||||
this.options = options;
|
||||
SelectOption(0);
|
||||
}
|
||||
|
||||
public void SelectNextOption() => ChangeOption(true);
|
||||
public void SelectPrevOption() => ChangeOption(false);
|
||||
|
||||
public void SelectOption(int option)
|
||||
{
|
||||
if (option < 0 || option >= options.Count)
|
||||
{
|
||||
Debug.LogError("Invalid option number");
|
||||
return;
|
||||
}
|
||||
CurrentOptionId = option;
|
||||
CurrentOption = options[option];
|
||||
currentOptionText.text = CurrentOption;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select option with provided value. Use with caution, better use <see cref="SelectOption(int)"/>
|
||||
/// </summary>
|
||||
/// <param name="option">option value to select</param>
|
||||
public void SelectOption(string option)
|
||||
{
|
||||
if (options.Count == 0)
|
||||
{
|
||||
Debug.LogError("Carousel List cannot be empty when selecting option");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = options.IndexOf(option);
|
||||
if (index == -1)
|
||||
{
|
||||
Debug.LogError($"Option {option} not found");
|
||||
return;
|
||||
}
|
||||
|
||||
SelectOption(index);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
nextButton.OnButtonPressed += SelectNextOption;
|
||||
prevButton.OnButtonPressed += SelectPrevOption;
|
||||
}
|
||||
|
||||
private void ChangeOption(bool next) => SelectOption((CurrentOptionId + (next ? 1 : -1) + options.Count) % options.Count);
|
||||
}
|
||||
}
|
||||
3
NEG/UI/UnityUi/CarouselList/CarouselList.cs.meta
Normal file
3
NEG/UI/UnityUi/CarouselList/CarouselList.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9915ee4d467e47e485c4a48be295b5c2
|
||||
timeCreated: 1672834830
|
||||
49
NEG/UI/UnityUi/MonoUiManager.cs
Normal file
49
NEG/UI/UnityUi/MonoUiManager.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using NEG.UI.Area;
|
||||
using NEG.UI.Popup;
|
||||
using NEG.UI.UnityUi.Popup;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace NEG.UI.UnityUi
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements ui using UnityUI and Unity Event System with New Input System.
|
||||
/// <para>You have to provide prefabs with addresses:</para>
|
||||
/// <para> - NEG/UI/PopupCanvas - prefab with canvas to create popups (will be created on every scene)</para>
|
||||
/// <para> - NEG/UI/DefaultPopupPrefab - prefab of default popup with 2 options (has to have <see cref="MonoDefaultPopup"/> component)</para>
|
||||
/// </summary>
|
||||
public class MonoUiManager : UiManager
|
||||
{
|
||||
private readonly MonoDefaultPopup defaultPopupPrefab;
|
||||
private readonly GameObject canvasPrefab;
|
||||
|
||||
public MonoUiManager(IArea startArea) : base(startArea)
|
||||
{
|
||||
var prefabs =
|
||||
Addressables.LoadAssetsAsync<GameObject>(new List<string>() { "NEG/UI/PopupCanvas", "NEG/UI/DefaultPopupPrefab" }, (_) => { }, Addressables.MergeMode.Union).WaitForCompletion();
|
||||
|
||||
Assert.AreEqual(prefabs.Count, 2, "No prefabs was provided. Please check MonoUiManager class documentation");
|
||||
Assert.IsNotNull(prefabs[0].GetComponent<Canvas>());
|
||||
Assert.IsNotNull(prefabs[1].GetComponent<MonoDefaultPopup>());
|
||||
|
||||
canvasPrefab = prefabs[0];
|
||||
defaultPopupPrefab = prefabs[1].GetComponent<MonoDefaultPopup>();
|
||||
|
||||
SpawnDefaultPopup();
|
||||
|
||||
SceneManager.activeSceneChanged += (_, _) => SpawnDefaultPopup();
|
||||
}
|
||||
|
||||
private void SpawnDefaultPopup()
|
||||
{
|
||||
var canvas = Object.Instantiate(canvasPrefab);
|
||||
canvas.name = "DefaultPopupCanvas";
|
||||
SetDefaultPopup(Object.Instantiate(defaultPopupPrefab, canvas.transform));
|
||||
currentDefaultPopup.Close(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
3
NEG/UI/UnityUi/MonoUiManager.cs.meta
Normal file
3
NEG/UI/UnityUi/MonoUiManager.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db04ec3144594df1bf4739e142d1b064
|
||||
timeCreated: 1672155707
|
||||
@ -5,7 +5,9 @@
|
||||
"GUID:343deaaf83e0cee4ca978e7df0b80d21",
|
||||
"GUID:7361f1d9c43da6649923760766194746",
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f",
|
||||
"GUID:0c752da273b17c547ae705acf0f2adf2"
|
||||
"GUID:0c752da273b17c547ae705acf0f2adf2",
|
||||
"GUID:9e24947de15b9834991c9d8411ea37cf",
|
||||
"GUID:84651a3751eca9349aac36a66bba901b"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
3
NEG/UI/UnityUi/Popup.meta
Normal file
3
NEG/UI/UnityUi/Popup.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31e9b0c7a4425a248841ec2f02b92157
|
||||
timeCreated: 1670707809
|
||||
36
NEG/UI/UnityUi/Popup/MonoDefaultPopup.cs
Normal file
36
NEG/UI/UnityUi/Popup/MonoDefaultPopup.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using NEG.UI.Popup;
|
||||
using NEG.UI.UnityUi.Buttons;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.UnityUi.Popup
|
||||
{
|
||||
public class MonoDefaultPopup : MonoPopup, IDefaultPopup
|
||||
{
|
||||
[SerializeField] private TMP_Text titleText;
|
||||
[SerializeField] private TMP_Text contentText;
|
||||
[SerializeField] private Transform buttonsParent;
|
||||
[SerializeField] private BaseButton buttonPrefab;
|
||||
|
||||
public void SetContent(string title, string content, List<(string, Action)> options)
|
||||
{
|
||||
foreach (Transform child in buttonsParent)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
titleText.text = title;
|
||||
contentText.text = content;
|
||||
|
||||
foreach ((string text, Action action) item in options)
|
||||
{
|
||||
var button = Instantiate(buttonPrefab, buttonsParent);
|
||||
button.SetText(item.text);
|
||||
button.OnButtonPressed += item.action;
|
||||
button.OnButtonPressed += () => Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
NEG/UI/UnityUi/Popup/MonoDefaultPopup.cs.meta
Normal file
3
NEG/UI/UnityUi/Popup/MonoDefaultPopup.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 831fb29e6c6d4997a7d471c646eec077
|
||||
timeCreated: 1672154589
|
||||
28
NEG/UI/UnityUi/Popup/MonoPopup.cs
Normal file
28
NEG/UI/UnityUi/Popup/MonoPopup.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using NEG.UI.Popup;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.UnityUi.Popup
|
||||
{
|
||||
public class MonoPopup : MonoBehaviour, IPopup
|
||||
{
|
||||
protected PopupData data;
|
||||
|
||||
public void Show(PopupData data)
|
||||
{
|
||||
this.data = data;
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Close(bool silence = false)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
|
||||
if(silence)
|
||||
return;
|
||||
|
||||
UiManager.Instance.PopupClosed(data);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3057d16be014bd58847b118a24c21dd
|
||||
guid: 5d3466b2fe771184eae1ca72c1006d0c
|
||||
timeCreated: 1670707831
|
||||
3
NEG/UI/UnityUi/Window.meta
Normal file
3
NEG/UI/UnityUi/Window.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65adacad835c2cf4b9062296be6098d2
|
||||
timeCreated: 1670707788
|
||||
48
NEG/UI/UnityUi/Window/MonoWindow.cs
Normal file
48
NEG/UI/UnityUi/Window/MonoWindow.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using NEG.UI.Area;
|
||||
using NEG.UI.UnityUi.WindowSlot;
|
||||
using NEG.UI.Window;
|
||||
using NEG.UI.WindowSlot;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.UnityUi.Window
|
||||
{
|
||||
public class MonoWindow : MonoBehaviour, IWindow
|
||||
{
|
||||
public IWindowSlot Parent { get; private set; }
|
||||
public IWindowSlot ChildWindowSlot => childWindowArea;
|
||||
|
||||
[SerializeField] private MonoWindowSlot childWindowArea;
|
||||
[SerializeField] private WindowController controller;
|
||||
|
||||
void IWindow.SetOpenedState(IWindowSlot parentSlot)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
Parent = parentSlot;
|
||||
if (controller != null)
|
||||
controller.OnOpened();
|
||||
}
|
||||
|
||||
public void SetData(object data)
|
||||
{
|
||||
if (controller != null)
|
||||
controller.SetData(data);
|
||||
}
|
||||
|
||||
void IWindow.SetClosedState()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
Parent = null;
|
||||
if(childWindowArea != null)
|
||||
ChildWindowSlot.CloseAllWindows();
|
||||
}
|
||||
|
||||
private void Awake() => ((IWindow)this).SetClosedState();
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (controller == null)
|
||||
controller = GetComponent<WindowController>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3807b2039b1941d999642e241ce1f463
|
||||
guid: 85d136d6850728d4b96c26fa286ffe3c
|
||||
timeCreated: 1670709296
|
||||
18
NEG/UI/UnityUi/Window/WindowController.cs
Normal file
18
NEG/UI/UnityUi/Window/WindowController.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.UnityUi.Window
|
||||
{
|
||||
[RequireComponent(typeof(MonoWindow))]
|
||||
//Due to prefab variants we need this
|
||||
public abstract class WindowController : MonoBehaviour
|
||||
{
|
||||
protected MonoWindow window;
|
||||
|
||||
public abstract void SetData(object data);
|
||||
|
||||
public abstract void OnOpened();
|
||||
|
||||
protected virtual void Awake() => window = GetComponent<MonoWindow>();
|
||||
}
|
||||
}
|
||||
3
NEG/UI/UnityUi/Window/WindowController.cs.meta
Normal file
3
NEG/UI/UnityUi/Window/WindowController.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e3614c1d2b94294937351b640139829
|
||||
timeCreated: 1672766736
|
||||
3
NEG/UI/UnityUi/WindowSlot.meta
Normal file
3
NEG/UI/UnityUi/WindowSlot.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47e385f6fb552d74ab4cf25ef6c76595
|
||||
timeCreated: 1670707801
|
||||
@ -1,8 +1,9 @@
|
||||
using NEG.UI.Area;
|
||||
using NEG.UI.Window;
|
||||
using NEG.UI.WindowSlot;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.WindowSlot
|
||||
namespace NEG.UI.UnityUi.WindowSlot
|
||||
{
|
||||
public abstract class MonoWindowSlot : MonoBehaviour, IWindowSlot
|
||||
{
|
||||
@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bc46ac7f2454eaa934561b7ef20ff88
|
||||
guid: e5a19b184ce9338449ad6ba899a26cda
|
||||
timeCreated: 1670709404
|
||||
@ -1,4 +1,5 @@
|
||||
using NEG.UI.Window;
|
||||
using NEG.UI.UnityUi.WindowSlot;
|
||||
using NEG.UI.Window;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.WindowSlot
|
||||
@ -10,9 +11,9 @@ namespace NEG.UI.WindowSlot
|
||||
get => currentWindow;
|
||||
set
|
||||
{
|
||||
currentWindow?.Close();
|
||||
currentWindow?.SetClosedState();
|
||||
currentWindow = value;
|
||||
currentWindow?.Open(this);
|
||||
currentWindow?.SetOpenedState(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9358679df24f458f8836506344eb169d
|
||||
guid: 79c0738c63b2b5c40b96acd89441f78f
|
||||
timeCreated: 1670716632
|
||||
@ -1,4 +1,5 @@
|
||||
using NEG.UI.Area;
|
||||
using JetBrains.Annotations;
|
||||
using NEG.UI.Area;
|
||||
using NEG.UI.WindowSlot;
|
||||
using UnityEngine;
|
||||
|
||||
@ -7,34 +8,39 @@ namespace NEG.UI.Window
|
||||
public interface IWindow
|
||||
{
|
||||
IWindowSlot Parent { get; }
|
||||
IArea ChildWindowArea { get; }
|
||||
IWindowSlot ChildWindowSlot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Call internally by slot to open window
|
||||
/// Called internally by slot to open window.
|
||||
/// </summary>
|
||||
/// <param name="parentSlot">Slot that opens window</param>
|
||||
internal void Open(IWindowSlot parentSlot);
|
||||
/// <param name="parentSlot">slot that opens window</param>
|
||||
void SetOpenedState(IWindowSlot parentSlot);
|
||||
|
||||
void SetData(object data);
|
||||
|
||||
/// <summary>
|
||||
/// Call internally to close window by slot
|
||||
/// Called internally to close window by slot.
|
||||
/// </summary>
|
||||
internal void Close();
|
||||
void SetClosedState();
|
||||
}
|
||||
|
||||
public static class WindowInterfaceExtensions
|
||||
{
|
||||
//Open
|
||||
public static void Open(this IWindow window, IWindowSlot slot = null)
|
||||
public static void Open(this IWindow window, IWindowSlot slot = null, object data = null)
|
||||
{
|
||||
if(slot != null)
|
||||
if (slot != null)
|
||||
{
|
||||
slot.AttachWindow(window);
|
||||
window.SetData(data);
|
||||
}
|
||||
else
|
||||
UiManager.Instance.CurrentArea.OpenWindow(window);
|
||||
UiManager.Instance.CurrentArea.OpenWindow(window, data);
|
||||
}
|
||||
public static void Open(this IWindow window, IArea area) => area.OpenWindow(window);
|
||||
public static void OpenChildWindow(this IWindow window, IWindow windowToOpen, IWindowSlot slot = null)
|
||||
public static void Open(this IWindow window, IArea area, object data = null) => area.OpenWindow(window, data);
|
||||
public static void OpenChildWindow(this IWindow window, IWindow windowToOpen, object data = null)
|
||||
{
|
||||
if (window.ChildWindowArea == null)
|
||||
if (window.ChildWindowSlot == null)
|
||||
{
|
||||
Debug.LogError("This window doesn't contain area for child windows");
|
||||
return;
|
||||
@ -44,5 +50,6 @@ namespace NEG.UI.Window
|
||||
}
|
||||
|
||||
public static void Close(this IWindow window) => window.Parent.DetachWindow(window);
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
using NEG.UI.Area;
|
||||
using NEG.UI.Popup;
|
||||
using NEG.UI.WindowSlot;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NEG.UI.Window
|
||||
{
|
||||
public class MonoWindow : MonoBehaviour, IWindow
|
||||
{
|
||||
public IWindowSlot Parent { get; private set; }
|
||||
public IArea ChildWindowArea => childWindowArea;
|
||||
|
||||
[SerializeField] private MonoArea childWindowArea;
|
||||
|
||||
void IWindow.Open(IWindowSlot parentSlot)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
Parent = parentSlot;
|
||||
}
|
||||
|
||||
void IWindow.Close()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
Parent = null;
|
||||
}
|
||||
|
||||
private void Awake() => ((IWindow)this).Close();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user