diff --git a/Editor/EventExtensions.cs b/Editor/EventExtensions.cs
deleted file mode 100644
index b492efb..0000000
--- a/Editor/EventExtensions.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using UnityEngine;
-
-///
-/// Utility extensions for semantic Event queries. //TODO: Go through this
-///
-public static class EventExtensions {
- ///
- /// Check if an Event is a context click.
- ///
- /// Event to query.
- /// Context button (default is RMB).
- /// True, if the Event was a context click.
- public static bool IsContextClick(this Event current, int contextButton = 1) {
-
- return current.type == EventType.MouseDown && current.button == contextButton;
-
- }
-
- ///
- /// Check if an Event is a left click.
- ///
- /// Event to query.
- /// True, if the Event was a left click.
- public static bool IsLeftClick(this Event current) {
-
- return current.type == EventType.MouseDown && current.button == 0;
-
- }
-
- ///
- /// Check if an Event is a drag.
- ///
- /// Event to query.
- /// Drag button (default is LMB).
- /// True, if the Event was a drag.
- public static bool IsDrag(this Event current, int dragButton = 0) {
-
- return current.type == EventType.MouseDrag && current.button == dragButton;
- }
-
- ///
- /// Check if an Event is arrow key.
- ///
- /// true, if arrow key was ised, false otherwise.
- /// Current.
- public static bool IsArrowKey(this Event current) {
-
- return current.keyCode == KeyCode.LeftArrow || current.keyCode == KeyCode.RightArrow || current.keyCode == KeyCode.UpArrow || current.keyCode == KeyCode.DownArrow;
-
- }
-
-}
-
diff --git a/Editor/RectExtensions.cs b/Editor/RectExtensions.cs
deleted file mode 100644
index 230dc94..0000000
--- a/Editor/RectExtensions.cs
+++ /dev/null
@@ -1,315 +0,0 @@
-using UnityEngine;
-
-///
-/// Utility extensions for manipulating Rect values.
-///
-public static class RectExtMethods {
- public static Rect VioletSetPosition(this Rect rect, Vector2 position) {
- return rect.VioletSetPosition(position.x, position.y);
- }
-
- public static Rect VioletSetPosition(this Rect rect, float x, float y) {
- return new Rect(x, y, rect.width, rect.height);
- }
-
- public static Rect VioletDisplaceX(this Rect rect, float x) {
- return new Rect(rect.x + x, rect.y, rect.width, rect.height);
- }
-
- public static Rect VioletDisplaceY(this Rect rect, float y) {
- return new Rect(rect.x, rect.y + y, rect.width, rect.height);
- }
-
- public static Rect VioletDisplace(this Rect rect, float x, float y) {
- return new Rect(rect.x + x, rect.y + y, rect.width, rect.height);
- }
-
- public static Rect VioletDisplace(this Rect rect, Vector2 displacement) {
- return new Rect(rect.position + displacement, rect.size);
- }
-
- public static Rect VioletSetWidth(this Rect rect, float width) {
- return new Rect(rect.x, rect.y, width, rect.height);
- }
-
- public static Rect VioletSetHeight(this Rect rect, float height) {
- return new Rect(rect.x, rect.y, rect.width, height);
- }
-
- public static Rect VioletTransform(this Rect rect, Matrix4x4 matrix) {
- return new Rect(matrix.MultiplyPoint3x4(rect.position), matrix.MultiplyVector(rect.size));
- }
-
- public static float ShortestSide(this Rect rect) {
- return Mathf.Min(rect.width, rect.height);
- }
-
- public static float longestSide(this Rect rect) {
- return Mathf.Max(rect.width, rect.height);
- }
-
- public static Rect VioletSetSize(this Rect rect, float size) {
- return rect.VioletSetSize(Vector2.one * size);
- }
-
- public static Rect VioletSetSize(this Rect rect, float width, float height) {
- return rect.VioletSetSize(new Vector2(width, height));
- }
-
- public static Rect VioletSetSize(this Rect rect, Vector2 size) {
- return new Rect(rect.position, size);
- }
-
- public static Rect VioletSetSizeCentered(this Rect rect, float size) {
- return rect.VioletSetSizeCentered(Vector2.one * size);
- }
-
- public static Rect VioletSetSizeCentered(this Rect rect, float width, float height) {
- return rect.VioletSetSizeCentered(new Vector2(width, height));
- }
-
- public static Rect VioletSetSizeCentered(this Rect rect, Vector2 size) {
- return new Rect(rect.center - size * 0.5f, size);
- }
-
- #region Transalation
- public static Rect VioletTranslate(this Rect rect, float x, float y) {
- return VioletTranslate(rect, new Vector2(x, y));
- }
-
- public static Rect VioletTranslate(this Rect rect, Vector2 translation) {
- return new Rect(rect.position + translation, rect.size);
- }
- #endregion
-
- #region Insetting
- ///
- /// Split a Rect by insetting from the right edge.
- ///
- /// Rect to split.
- /// Inset width.
- /// Inset Rect.
- ///
- public static Rect VioletSplitRight(this Rect rect, float inset, out Rect result) {
- result = new Rect(rect.x + rect.width - inset, rect.y, inset, rect.height);
-
- return rect.VioletPadRight(inset);
- }
-
- ///
- /// Split a Rect by insetting from the left edge.
- ///
- /// Rect to split.
- /// Inset width.
- /// Inset Rect.
- ///
- public static Rect VioletSplitLeft(this Rect rect, float inset, out Rect result) {
- result = new Rect(rect.x, rect.y, inset, rect.height);
-
- return rect.VioletPadLeft(inset);
- }
-
- ///
- /// Split a Rect by insetting from the bottom edge.
- ///
- /// Rect to split.
- /// Inset height.
- /// Inset Rect.
- ///
- public static Rect VioletSplitBottom(this Rect rect, float inset, out Rect result) {
- result = new Rect(rect.x, rect.y + rect.height - inset, rect.width, inset);
-
- return rect.VioletPadBottom(inset);
- }
-
- ///
- /// Split a Rect by insetting from the top edge.
- ///
- /// Rect to split.
- /// Inset height.
- /// Inset Rect.
- ///
- public static Rect VioletSplitTop(this Rect rect, float inset, out Rect result) {
- result = new Rect(rect.x, rect.y, rect.width, inset);
-
- return rect.VioletPadTop(inset);
- }
- #endregion
-
- #region Padding
- ///
- /// Pad a Rect from all sides.
- ///
- /// Rect to pad.
- /// Amount to pad.
- /// Padded Rect.
- public static Rect VioletPad(this Rect rect, float padding) {
- return VioletPad(rect, padding, padding, padding, padding);
- }
-
- ///
- /// Pad a Rect from its left edge.
- ///
- /// Rect to pad.
- /// Amount to pad.
- /// Padded Rect.
- public static Rect VioletPadLeft(this Rect rect, float padding) {
- return VioletPad(rect, padding, 0.0f, 0.0f, 0.0f);
- }
-
- ///
- /// Pad a Rect from its right edge.
- ///
- /// Rect to pad.
- /// Amount to pad.
- /// Padded Rect.
- public static Rect VioletPadRight(this Rect rect, float padding) {
- return VioletPad(rect, 0.0f, padding, 0.0f, 0.0f);
- }
-
- ///
- /// Pad a Rect from its top edge.
- ///
- /// Rect to pad.
- /// Amount to pad.
- /// Padded Rect.
- public static Rect VioletPadTop(this Rect rect, float padding) {
- return VioletPad(rect, 0.0f, 0.0f, padding, 0.0f);
- }
-
- ///
- /// Pad a Rect from its bottom edge.
- ///
- /// Rect to pad.
- /// Amount to pad.
- /// Padded Rect.
- public static Rect VioletPadBottom(this Rect rect, float padding) {
- return VioletPad(rect, 0.0f, 0.0f, 0.0f, padding);
- }
-
- ///
- /// Pad a Rect from its left and right edges.
- ///
- /// Rect to pad.
- /// Amount to pad.
- /// Padded Rect.
- public static Rect VioletPadHorizontal(this Rect rect, float padding) {
- return VioletPad(rect, padding, padding, 0.0f, 0.0f);
- }
-
- ///
- /// Pad a Rect from its top and bottom edges.
- ///
- /// Rect to pad.
- /// Amount to pad.
- /// Padded Rect.
- public static Rect VioletPadVertical(this Rect rect, float padding) {
- return VioletPad(rect, 0.0f, 0.0f, padding, padding);
- }
-
- ///
- /// Pad a Rect from its left, right, top, and bottom edges.
- ///
- /// Rect to pad.
- /// Amount to pad left edge.
- /// Amount to pad right edge.
- /// Amount to pad top edge.
- /// Amount to pad left edge.
- /// Padded Rect.
- public static Rect VioletPad(this Rect rect, float left, float right, float top, float bottom) {
- return new Rect(rect.x + left, rect.y + top, rect.width - left - right, rect.height - top - bottom);
- }
- #endregion
-
- #region Sequencing
- public static Rect VioletStepDown(this Rect rect, float padding = 0.0f) {
- return new Rect(rect.x, rect.y + rect.height + padding, rect.width, rect.height);
- }
-
- public static Rect VioletStepUp(this Rect rect, float padding = 0.0f) {
- return new Rect(rect.x, rect.y - rect.height - padding, rect.width, rect.height);
- }
-
- public static Rect VioletStepLeft(this Rect rect, float padding = 0.0f) {
- return new Rect(rect.x - rect.width - padding, rect.y, rect.width, rect.height);
- }
-
- public static Rect VioletStepRight(this Rect rect, float padding = 0.0f) {
- return new Rect(rect.x + rect.width + padding, rect.y, rect.width, rect.height);
- }
- #endregion
-
- #region Extrusions
- public static Rect VioletExtrudeLeft(this Rect rect, float width) {
- return new Rect(rect.x - width, rect.y, width, rect.height);
- }
-
- public static Rect VioletExtrudeRight(this Rect rect, float width) {
- return new Rect(rect.x + rect.width, rect.y, width, rect.height);
- }
-
- public static Rect VioletExtrudeTop(this Rect rect, float height) {
- return new Rect(rect.x, rect.y - height, rect.width, height);
- }
-
- public static Rect VioletExtrudeBottom(this Rect rect, float height) {
- return new Rect(rect.x, rect.y + rect.height, rect.width, height);
- }
- #endregion
-
-
- #region Backfills
- public static Rect VioletBackfillLeft(this Rect rect, float width) {
- return rect.VioletPadRight(rect.width - width);
- }
-
- public static Rect VioletBackfillRight(this Rect rect, float width) {
- return rect.VioletPadLeft(rect.width - width);
- }
-
- public static Rect VioletBackfillTop(this Rect rect, float height) {
- return rect.VioletPadBottom(rect.height - height);
- }
-
- public static Rect VioletBackfillBottom(this Rect rect, float height) {
- return rect.VioletPadTop(rect.height - height);
- }
- #endregion
-
- #region Events
- public static bool WasContextClicked(this Rect rect, int contextButton = 1, bool useEvent = true) {
- if (Event.current.IsContextClick(contextButton) && rect.Contains(Event.current.mousePosition)) {
- if (useEvent) {
- Event.current.Use();
- }
-
- return true;
- }
-
- return false;
- }
-
- public static bool WasLeftClicked(this Rect rect, bool useEvent = true) {
- if (Event.current.IsLeftClick() && rect.Contains(Event.current.mousePosition)) {
- if (useEvent) {
- Event.current.Use();
- }
-
- return true;
- }
-
- return false;
- }
- public static bool WasDragged(this Rect rect, int dragButton = 0, bool useEvent = true) {
- if (Event.current.IsDrag(dragButton) && rect.Contains(Event.current.mousePosition)) {
- if (useEvent) {
- Event.current.Use();
- }
-
- return true;
- }
-
- return false;
- }
- #endregion
-}
\ No newline at end of file
diff --git a/Editor/ScreenEditor.cs b/Editor/ScreenEditor.cs
index d0ad2bb..f173432 100644
--- a/Editor/ScreenEditor.cs
+++ b/Editor/ScreenEditor.cs
@@ -9,159 +9,88 @@
namespace VioletUI {
//run this class when unity opens
[InitializeOnLoad]
- public class ScreenEditor {
-
- //width of inspector buttons
- private const float BUTTON_WIDTH = 60f;
+ public static class ScreenEditor {
//where to show the buttons
private static GUIStyle singleStyle, leftStyle, rightStyle;
- //hash set of all menus in scene
- private static HashSet menus = new HashSet();
-
- //menu screens to remove
- private static List garbage = new List();
+ static Navigator navigator;
+ static Color violet = new Color(0.898f, 0.745f, 0.935f);
+ static Color saturatedViolet;
- //constructor
static ScreenEditor() {
-
- //only show while !playing
- if (!EditorApplication.isPlaying) {
-
- //actual editor event functions
- EditorApplication.hierarchyWindowItemOnGUI += DrawHierarchyItem;
- EditorApplication.update += Update;
-
- }
-
+ EditorApplication.hierarchyWindowItemOnGUI -= DrawHierarchyItem;
+ EditorApplication.hierarchyWindowItemOnGUI += DrawHierarchyItem;
+ float h,s,v;
+ Color.RGBToHSV(violet, out h, out s, out v);
+ saturatedViolet = Color.HSVToRGB(h, 1f, 1f);
}
- //remove all menu screens that are no longer used to avoid memory leak
- private static void Clean(HashSet menus) {
-
- //clear the unused menus
- garbage.Clear();
-
-
- //add all menus that no longer exist to the garbage list
- foreach (var menu in menus) {
- if (menu == null) {
- garbage.Add(menu);
- }
- }
+ static void DrawHierarchyItem(int instanceID, Rect rect) {
+ var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
+ if (gameObject == null) { return; }
- //remove the garbage from the menus list
- foreach (var menu in garbage) {
- menus.Remove(menu);
+ navigator = gameObject.GetComponent();
+ if (navigator != null) {
+ DrawNavigator(navigator, rect);
+ return;
}
- }
-
- //update visuals
- private static void Update() {
- //remove the garbage from the menu list
- Clean(menus);
-
- foreach (var menu in Object.FindObjectsOfType()) {
- menus.Add(menu);
+ var screen = gameObject.GetComponent();
+ if (screen != null) {
+ DrawScreen(screen, rect);
+ return;
}
}
- private static GUIStyle InitStyle(ref GUIStyle style, GUIStyle original, TextAnchor alignment) {
- if (style == null) {
- style = new GUIStyle(original);
- style.alignment = alignment;
- style.richText = true;
+ static void DrawNavigator(Navigator navigator, Rect rect) {
+ if (navigator.EditingScreen == null) {
+ if (Button(rect, "Add")) {
+ navigator.AddScreen();
+ }
+ } else if (navigator.transform.childCount > 1) {
+ if (Button(rect, "Save", true)) {
+ navigator.FinishEditing();
+ }
}
-
- return style;
}
- private static bool Button(Rect rect, string label, string tooltip, Color color, FontStyle fontStyle = FontStyle.Normal, int dot = 0, int i = 0, int n = 1) {
- rect = rect.VioletBackfillRight(BUTTON_WIDTH);
- GUIStyle style = null;
+ static void DrawScreen(Screen screen, Rect rect) {
+ var navigator = screen.transform.parent.GetComponent();
+ if (navigator.EditingScreen != null && screen != navigator.EditingScreen) { return; }
- if (n == 1) {
- style = InitStyle(ref singleStyle, EditorStyles.miniButton, TextAnchor.MiddleLeft);
- } else if (n == 2) {
- switch (i) {
- case 0:
- style = InitStyle(ref leftStyle, EditorStyles.miniButtonLeft, TextAnchor.MiddleLeft);
- break;
- case 1:
- style = InitStyle(ref rightStyle, EditorStyles.miniButtonRight, TextAnchor.MiddleRight);
- break;
+ if (Button(rect, screen.isActiveAndEnabled ? "Save" : "Edit", screen.isActiveAndEnabled)) {
+ if (navigator == null) {
+ throw new VioletException($"Tried to edit {screen.name} without a Navigator. Try adding a Navigator component to {screen.transform.parent.name}");
+ }
+ if (screen.isActiveAndEnabled) {
+ navigator.FinishEditing(screen);
+ } else {
+ navigator.Edit(screen);
}
}
-
- var content = new GUIContent(" " + label, null, tooltip);
-
- style.fontStyle = fontStyle;
-
- var response = GUI.Button(rect, content, singleStyle);
-
- style.fontStyle = FontStyle.Normal;
-
- var shadowColor = Color.Lerp(color, Color.black, 0.5f);
- shadowColor.a = 0.8f;
-
- if (dot > 0) {
- var dotRect = rect.VioletBackfillRight(rect.height).VioletDisplaceX(1f);
- var dotColor = dot == 2 ? color : Color.grey;
-
- dotColor.a = 0.5f;
-
- GUI.Box(dotRect, GUIContent.none);
- EditorGUI.DrawRect(dotRect.VioletPad(3f).VioletDisplaceX(-1f), dotColor);
-
- }
-
- rect = rect.VioletSetWidth(3f).VioletDisplaceX(4f).VioletPadVertical(1f);
-
- EditorGUI.DrawRect(rect.VioletExtrudeLeft(1f), shadowColor);
- EditorGUI.DrawRect(rect.VioletExtrudeRight(1f), shadowColor);
-
- color.a = 0.5f;
-
- EditorGUI.DrawRect(rect, color);
-
- return response;
}
- private static bool TryGetManagerColor(int instanceID, out Color color) {
- color = new Color(0.898f, 0.745f, 0.935f);
- return true;
- }
+ static bool Button(Rect rect, string label, bool isActive = false) {
+ // by default the button is 100% width
+ // we move the left edge to make button 60 pixels wide, right aligned
+ var buttonWidth = 60;
+ rect.xMin = rect.xMax - buttonWidth;
+
+ // extend unity mini button style with small tweaks
+ var style = new GUIStyle(EditorStyles.miniButton);
+ style.padding = new RectOffset(3, 0, 1, 1);
+ style.fixedHeight -= 2;
+ style.fontStyle = isActive ? FontStyle.Bold : FontStyle.Normal;
+ style.alignment = TextAnchor.MiddleLeft;
+
+ // set color to violet if active
+ var originalColor = GUI.color;
+ GUI.color = isActive ? violet : originalColor;
+ var response = GUI.Button(rect, new GUIContent(label), style);
+ GUI.color = originalColor;
- private static void DrawHierarchyItem(int instanceID, Rect rect) {
- var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
-
- Color violet = new Color(0.898f, 0.745f, 0.935f);
- float h = 0f, s = 0f, v = 0f;
- Color.RGBToHSV(violet, out h, out s, out v);
- Color saturatedViolet = Color.HSVToRGB(h, 1f, 1f);
- if (gameObject == null) { return; }
-
- // TODO: just find the parent of any number of Navigation Screens
- if (gameObject.GetComponent() != null) {
- if (Button(rect, "Save", "", saturatedViolet, FontStyle.Normal)) {
- foreach (var menu in menus) {
- menu.gameObject.GetComponent()?.StopEditing();
- }
- }
- return;
- }
-
- if (gameObject.GetComponent() != null) {
- if (Button(rect, "Edit", "", saturatedViolet, FontStyle.Normal, gameObject.activeSelf ? 2 : 1)) {
- foreach (var menu in menus) {
- menu.gameObject.GetComponent()?.StopEditing();
- }
- gameObject.GetComponent().StartEditing();
- }
- return;
- }
+ return response;
}
}
}
\ No newline at end of file
diff --git a/Runtime/Exceptions.cs b/Runtime/Exceptions.cs
index cc0d7a2..8d112c3 100644
--- a/Runtime/Exceptions.cs
+++ b/Runtime/Exceptions.cs
@@ -9,4 +9,8 @@ public Bail(string message) : base(message) { }
public class VioletException : Exception {
public VioletException(string message) : base(message) { }
}
+
+ public class VioletEnumException : VioletException {
+ public VioletEnumException(string message) : base(message) { }
+ }
}
\ No newline at end of file
diff --git a/Runtime/Navigation/NavigationController.cs b/Runtime/Navigation/NavigationController.cs
deleted file mode 100644
index 928bf77..0000000
--- a/Runtime/Navigation/NavigationController.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using UnityEngine;
-
-namespace VioletUI {
- public class NavigationController : TidyBehaviour {
- }
-}
diff --git a/Runtime/Navigation/NavigationScreen.cs b/Runtime/Navigation/NavigationScreen.cs
deleted file mode 100644
index 6c0c658..0000000
--- a/Runtime/Navigation/NavigationScreen.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using UnityEngine;
-#if UNITY_EDITOR
-using UnityEditor;
-#endif
-
-namespace VioletUI {
-
- public class NavigationScreen : TidyBehaviour {
- #if UNITY_EDITOR
- public static bool IsSavingPrefab;
-
- public void StartEditing() {
- IsSavingPrefab = false;
- try {
- PrefabUtility.UnpackPrefabInstance(gameObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
- } catch (ArgumentException e) {
- UnityEngine.Debug.LogWarning($"VioletUI: Couldn't unpack prefab for {name}. Make sure the NavigationScreen is a prefab. {e}");
- }
- gameObject.SetActive(true);
- }
-
- public void StopEditing() {
- IsSavingPrefab = true;
- PrefabUtility.SaveAsPrefabAssetAndConnect(gameObject, $"Assets/Menus/{name}.prefab", InteractionMode.AutomatedAction);
- gameObject.SetActive(false);
- }
- #endif
- }
-}
\ No newline at end of file
diff --git a/Runtime/Navigation/Navigator.cs b/Runtime/Navigation/Navigator.cs
new file mode 100644
index 0000000..1787896
--- /dev/null
+++ b/Runtime/Navigation/Navigator.cs
@@ -0,0 +1,237 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+using System.Threading;
+using UniRx.Async;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace VioletUI {
+ ///
+ /// Screens maintains a map of enum to menu screens.
+ ///
+ /// It is primarily used to navigate between screens, and also exposes the and lifecycle events.
+ ///
+ [ExecuteAlways]
+ public class Navigator : TidyBehaviour {
+ public ScreenId homeScreen = ScreenId.None;
+ public bool hasCamera;
+ [ShowIf("hasCamera")]
+ public Camera worldCamera;
+
+ public Action OnReady;
+
+ public Action OnWillVisit;
+ public Action OnWillLeave;
+ public Action OnDidVisit;
+ public Action OnDidLeave;
+
+ public Action OnModalShow;
+ public Action OnModalHide;
+
+ Dictionary screens = new Dictionary();
+ ScreenId lastScreen = ScreenId.None;
+ ScreenId currentScreen = ScreenId.None;
+ ScreenId currentModal = ScreenId.None;
+
+ [NonSerialized]
+ CancellationTokenSource canceler = null;
+
+ void Awake() {
+ if (!Application.isPlaying) { return; }
+ LoadScreens();
+ VisitFirstScreen();
+ }
+ void LoadScreens() {
+ if (transform.childCount == 0) {
+ throw new Exception($"Tried to create a NavigationController with no children - try adding some NavigationScreens to {gameObject.name}");
+ }
+
+ ScreenId screenId = ScreenId.None;
+ foreach (Screen screen in GetComponentsInChildren(true)) {
+ var isValid = Enum.TryParse(screen.name, out screenId);
+ if (!isValid) {
+ throw new VioletException($"{screen.name} does not have a valid ScreenId. Make sure this screen is added to MenuBuilder.");
+ }
+
+ if (hasCamera) {
+ if (worldCamera == null) {
+ throw new VioletException($"{name} does not have a camera attached. Either set hasCamera to false or attach a camera.");
+ }
+ var canvas = screen.gameObject.GetComponent