Slots implementation

This commit is contained in:
Hubert Mattusch 2023-01-12 17:47:18 +01:00
parent a5fc6179fb
commit cf69baab9f
46 changed files with 1587 additions and 180 deletions

View File

@ -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()
{

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: ffa2155176774ab691d499979707a1bb
guid: f7cf5ef3a347e1c4b98411f4d564b988
timeCreated: 1670690282

View File

@ -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();
}
}
}

View File

@ -1,5 +1,5 @@
{
"name": "NegUi",
"name": "NEG.UI",
"rootNamespace": "",
"references": [],
"includePlatforms": [],

View 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();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 372c6df9ec044cb3adc86c7776b2ef61
timeCreated: 1672432934

View 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);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0a6d0871ada44d1d95bea6c8e8701769
timeCreated: 1672153906

View File

@ -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);
}
}

View File

@ -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
View 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;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e6e5ebb367aa4dfba5e5f853c9b31a3d
timeCreated: 1672430446

999
NEG/UI/PriorityQueue.cs Normal file
View 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;
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1b6a9a70d997fd468f30caa1e760078
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a99aed857b7b9e7459811b14fefdb04f
timeCreated: 1670706939

View 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);
}
}
}

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: bbfe2293348b4c9096a3763479ec00c7
guid: 39eb59ca1ef60934abb3f0c64169be65
timeCreated: 1670707479

View File

@ -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);

View 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();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2fa43803dc014b7694d265d48c1fc680
timeCreated: 1672767354

View File

@ -1,4 +1,5 @@
using NEG.UI.Window;
using NEG.UI.UnityUi.Window;
using NEG.UI.Window;
using System;
using UnityEngine;

View File

@ -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;

View File

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

View 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);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9915ee4d467e47e485c4a48be295b5c2
timeCreated: 1672834830

View 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);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: db04ec3144594df1bf4739e142d1b064
timeCreated: 1672155707

View File

@ -5,7 +5,9 @@
"GUID:343deaaf83e0cee4ca978e7df0b80d21",
"GUID:7361f1d9c43da6649923760766194746",
"GUID:6055be8ebefd69e48b49212b09b47b2f",
"GUID:0c752da273b17c547ae705acf0f2adf2"
"GUID:0c752da273b17c547ae705acf0f2adf2",
"GUID:9e24947de15b9834991c9d8411ea37cf",
"GUID:84651a3751eca9349aac36a66bba901b"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 31e9b0c7a4425a248841ec2f02b92157
timeCreated: 1670707809

View 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();
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 831fb29e6c6d4997a7d471c646eec077
timeCreated: 1672154589

View 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);
}
}
}

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: d3057d16be014bd58847b118a24c21dd
guid: 5d3466b2fe771184eae1ca72c1006d0c
timeCreated: 1670707831

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 65adacad835c2cf4b9062296be6098d2
timeCreated: 1670707788

View 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>();
}
}
}

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 3807b2039b1941d999642e241ce1f463
guid: 85d136d6850728d4b96c26fa286ffe3c
timeCreated: 1670709296

View 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>();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9e3614c1d2b94294937351b640139829
timeCreated: 1672766736

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 47e385f6fb552d74ab4cf25ef6c76595
timeCreated: 1670707801

View File

@ -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
{

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 7bc46ac7f2454eaa934561b7ef20ff88
guid: e5a19b184ce9338449ad6ba899a26cda
timeCreated: 1670709404

View File

@ -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);
}
}

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 9358679df24f458f8836506344eb169d
guid: 79c0738c63b2b5c40b96acd89441f78f
timeCreated: 1670716632

View File

@ -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);
}
}

View File

@ -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();
}
}