Skip to content

Commit

Permalink
feat: Add PopupController with zone IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
adrenak committed Apr 26, 2021
1 parent f92029d commit 8f0e381
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 54 deletions.
2 changes: 1 addition & 1 deletion Assets/Adrenak.UGX/Runtime/Popup/AlertPopup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class AlertPopup : Popup<AlertPopupState, PopupResponse> {

Action OnAcknowledge;

async protected override UniTask<PopupResponse> WaitForResponseImpl() {
async protected override UniTask<PopupResponse> GetResponse() {
bool responded = false;
OnAcknowledge = () => responded = true;
while (!responded)
Expand Down
8 changes: 4 additions & 4 deletions Assets/Adrenak.UGX/Runtime/Popup/AskPopup.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;

using Cysharp.Threading.Tasks;

using UnityEngine;
using UnityEngine.UI;

Expand All @@ -10,11 +12,9 @@ public class AskPopupState : PopupState {
public string body;
public string positive;
public string negative;


}

public class AskPopupResponse : PopupResponse{
public class AskPopupResponse : PopupResponse {
public bool positive;
}

Expand All @@ -26,7 +26,7 @@ public class AskPopup : Popup<AskPopupState, AskPopupResponse> {
[SerializeField] Text negativeDisplay;
#pragma warning restore 0649

async protected override UniTask<AskPopupResponse> WaitForResponseImpl() {
async protected override UniTask<AskPopupResponse> GetResponse() {
bool? response = null;
OnConfirm = () => response = true;
OnDeny = () => response = false;
Expand Down
2 changes: 1 addition & 1 deletion Assets/Adrenak.UGX/Runtime/Popup/AskWithFlagPopup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class AskWithFlagPopup : Popup<AskWithFlagPopupState, AskWithFlagPopupRes
[SerializeField] Toggle flagDisplay;
#pragma warning restore 0649

async protected override UniTask<AskWithFlagPopupResponse> WaitForResponseImpl() {
async protected override UniTask<AskWithFlagPopupResponse> GetResponse() {
bool? response = null;
OnConfirm = () => response = true;
OnDeny = () => response = false;
Expand Down
2 changes: 1 addition & 1 deletion Assets/Adrenak.UGX/Runtime/Popup/NotificationPopup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class NotificationPopup : Popup<NotificationPopupState, PopupResponse> {
[SerializeField] Text title = null;
[SerializeField] Text description = null;

async protected override UniTask<PopupResponse> WaitForResponseImpl() {
async protected override UniTask<PopupResponse> GetResponse() {
await UniTask.Delay(State.delay, DelayType.DeltaTime, PlayerLoopTiming.Update);
return new PopupResponse();
}
Expand Down
60 changes: 32 additions & 28 deletions Assets/Adrenak.UGX/Runtime/Popup/Popup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,64 @@
using Cysharp.Threading.Tasks;

namespace Adrenak.UGX {
[SerializeField]
public class PopupState : ViewState { }

[Serializable]
public class PopupResponse { }

[RequireComponent(typeof(Window))]
public abstract class Popup<T, K> : StatefulView<T> where T : PopupState where K : PopupResponse {
static View activePopup;

async public UniTask<K> WaitForResponse() {
await UniTask.SwitchToMainThread();
var response = await WaitForResponseImpl();
public abstract class Popup<TState, Response> : StatefulView<TState> where TState : PopupState where Response : PopupResponse {
/// <summary>
/// Displays the popup and returns the response as a task
/// </summary>
async public UniTask<Response> Display() {
await UniTask.SwitchToMainThread();
return response;
}

async public void WaitForResponse(Action<K> responseCallback) =>
responseCallback?.Invoke(await WaitForResponse());

async public UniTask<K> Display() {
await UniTask.WaitWhile(() => activePopup != null);

activePopup = this;
await activePopup.window.OpenWindowAsync();
var response = await WaitForResponse();
await window.OpenWindowAsync();
var response = await GetResponse();
await window.CloseWindowAsync();
activePopup = null;

await UniTask.SwitchToMainThread();
return response;
}

async public void Display(Action<K> responseCallback) =>
/// <summary>
/// Displays the popup and returns the response as a callback
/// </summary>
async public void Display(Action<Response> responseCallback) =>
responseCallback?.Invoke(await Display());

async public UniTask<K> Display(T state) {
/// <summary>
/// Displays the popup with a given state and returns the response as a task
/// </summary>
async public UniTask<Response> SetStateAndDisplay(TState state) {
State = state;
return await Display();
}

async public void Display(T state, Action<K> responseCallback) =>
responseCallback?.Invoke(await Display(state));
/// <summary>
/// Displays the popup with a given state and returns the response as a callback
/// </summary>
async public void SetStateAndDisplay(TState state, Action<Response> responseCallback) =>
responseCallback?.Invoke(await SetStateAndDisplay(state));

async public UniTask<K> Display(Action<T> stateModifier) {
/// <summary>
/// Displays the popup with a given state modifier and returns the response as a task
/// </summary>
async public UniTask<Response> ModifyStateAndDisplay(Action<TState> stateModifier) {
stateModifier?.Invoke(State);
return await Display();
}

async public void Display(Action<T> stateModifier, Action<K> responseCallback) =>
responseCallback?.Invoke(await Display(stateModifier));
/// <summary>
/// Displays the popup with a given state modifier and returns the response as a callback
/// </summary>
async public void ModifyStateAndDisplay(Action<TState> stateModifier, Action<Response> responseCallback) =>
responseCallback?.Invoke(await ModifyStateAndDisplay(stateModifier));

protected override void HandleStateSet() => HandlePopupStateSet();

protected abstract void HandlePopupStateSet();

protected abstract UniTask<K> WaitForResponseImpl();
protected abstract UniTask<Response> GetResponse();
}
}
74 changes: 74 additions & 0 deletions Assets/Adrenak.UGX/Runtime/Popup/PopupController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Cysharp.Threading.Tasks;
using System.Collections.Generic;

using System;

namespace Adrenak.UGX {
public static class PopupController {
const string DEFAULT = "default";

readonly static Dictionary<string, bool> isShowingMap = new Dictionary<string, bool>();

/// <summary>
/// Returns whether any popup is active in a given zone ID
/// </summary>
/// <param name="zoneID"></param>
/// <returns></returns>
public static bool IsPopupActive(string zoneID = DEFAULT) {
if (isShowingMap.ContainsKey(zoneID))
return isShowingMap[zoneID];
return false;
}

/// <summary>
/// Displays a popup and returns the response as a task. Optionally takes the zone ID too.
/// </summary>
public async static UniTask<K> Display<T, K>(Popup<T, K> popup, string zoneID = DEFAULT) where T : PopupState where K : PopupResponse {
await UniTask.WaitWhile(() => IsPopupActive(zoneID));
isShowingMap.SetPair(zoneID, true);
var response = await popup.Display();
isShowingMap.SetPair(zoneID, false);
return response;
}

/// <summary>
/// Displays a popup and returns the response as a callback. Optionally takes the zone ID too.
/// </summary>
public async static void Display<T, K>(Popup<T, K> popup, Action<K> resultCallback, string zoneID = DEFAULT) where T : PopupState where K : PopupResponse =>
resultCallback?.Invoke(await Display(popup, zoneID));

/// <summary>
/// Takes a popup, it's state and displays it before returning the response as a task. Optionally takes the zone ID too.
/// </summary>
public async static UniTask<K> SetStateAndDisplay<T, K>(Popup<T, K> popup, T state, string zoneID = DEFAULT) where T : PopupState where K : PopupResponse {
await UniTask.WaitWhile(() => IsPopupActive(zoneID));
isShowingMap.SetPair(zoneID, true);
var response = await popup.SetStateAndDisplay(state);
isShowingMap.SetPair(zoneID, false);
return response;
}

/// <summary>
/// Takes a popup, it's state and displays it before returning the response as a callback. Optionally takes the zone ID too.
/// </summary>
public async static void SetStateAndDisplay<T, K>(Popup<T, K> popup, T state, Action<K> resultCallback, string zoneID = DEFAULT) where T : PopupState where K : PopupResponse =>
resultCallback?.Invoke(await SetStateAndDisplay(popup, state, zoneID));

/// <summary>
/// Takes a popup and a method for state change before displaying. Returns the response as a task and takes an optional zone ID
/// </summary>
public async static UniTask<K> ModifyStateAndDisplay<T, K>(Popup<T, K> popup, Action<T> modification, string zoneID = DEFAULT) where T : PopupState where K : PopupResponse {
await UniTask.WaitWhile(() => IsPopupActive(zoneID));
isShowingMap.SetPair(zoneID, true);
var response = await popup.ModifyStateAndDisplay(modification);
isShowingMap.SetPair(zoneID, false);
return response;
}

/// <summary>
/// Takes a popup and a method for state change before displaying. Returns the response as a callback and takes an optional zone ID
/// </summary>
public async static void ModifyStateAndDisplay<T, K>(Popup<T, K> popup, Action<T> modification, Action<K> resultCallback, string zoneID = DEFAULT) where T : PopupState where K : PopupResponse =>
resultCallback?.Invoke(await ModifyStateAndDisplay(popup, modification, zoneID));
}
}
11 changes: 11 additions & 0 deletions Assets/Adrenak.UGX/Runtime/Popup/PopupController.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 29 additions & 19 deletions Assets/Adrenak.UGX/Runtime/_Misc/UGXExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Adrenak.UGX {
public static class UGXExtensions {
public static void SetColor(this Image image, Color color){
public static void SetColor(this Image image, Color color) {
image.color = color;
}

Expand Down Expand Up @@ -96,32 +96,32 @@ public static bool IsVisible(this RectTransform rt, out bool? partially) {
// we return true and false for partially.
// Which basically means full visibility
int count = 0;
foreach (var point in points)
if (point.y >= -1
foreach (var point in points)
if (point.y >= -1
&& point.y <= Screen.height + 1
&& point.x >= -1
&& point.x <= Screen.width + 1
)
count++;
)
count++;

if(count == 4) {
if (count == 4) {
partially = false;
return true;
}
return true;
}

// Check if every point is either above, below,
// left of, or right of the screen. In which case
// we return invisible and false for partially.
// Which basically means full invisibility

// ABOVE
count = 0;
foreach (var point in points)
count += point.y > Screen.height + 1 ? 1 : 0;
if (count == 4) {
partially = false;
return false;
}
count = 0;
foreach (var point in points)
count += point.y > Screen.height + 1 ? 1 : 0;
if (count == 4) {
partially = false;
return false;
}

// BELOW
count = 0;
Expand Down Expand Up @@ -156,17 +156,27 @@ public static bool IsVisible(this RectTransform rt, out bool? partially) {
return true;
}

public static void EnsureContains<T, K>(this IDictionary<T, K> dict, T t, K k){
if (!dict.ContainsKey(t))
public static void SetPair<K, V>(this IDictionary<K, V> dict, K k, V v) {
if (!dict.ContainsKey(k))
dict.Add(k, v);
else
dict[k] = v;
}

public static bool EnsureContains<T, K>(this IDictionary<T, K> dict, T t, K k) {
if (!dict.ContainsKey(t)) {
dict.Add(t, k);
return false;
}
return true;
}

public static void EnsureContains<T>(this List<T> list, T t){
public static void EnsureContains<T>(this List<T> list, T t) {
if (!list.Contains(t))
list.Add(t);
}

public static void EnsureDoesntContain<T>(this List<T> list, T t){
public static void EnsureDoesntContain<T>(this List<T> list, T t) {
if (list.Contains(t))
list.Remove(t);
}
Expand Down

0 comments on commit 8f0e381

Please sign in to comment.