diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a3068a..a3038ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [0.1.75] - 2020-02-21 + +### Added +- IRepeatView +- X button to discard edits on a menu +- Navigator.GoBack +- Navigator.DebugVisit +- VioletButton.showModal +- VioletButton.closeModal +- VioletScreen.isEditing + + ## [0.1.74] - 2020-11-10 ### Added diff --git a/Editor/PrefabListener.cs b/Editor/PrefabListener.cs new file mode 100644 index 0000000..268d8ec --- /dev/null +++ b/Editor/PrefabListener.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using Stopwatch = System.Diagnostics.Stopwatch; + +namespace VioletUI { + public class PrefabListener : UnityEditor.AssetModificationProcessor { + static string[] OnWillSaveAssets(string[] paths) { + foreach (var path in paths) { + if (path.Contains(".prefab")) { + UpdateRepeatViews(path); + } + } + return paths; + } + + static void UpdateRepeatViews(string prefabPath) { + IRepeatView repeatView; + HashSet processed = new HashSet(); + + // https://docs.unity3d.com/ScriptReference/Resources.FindObjectsOfTypeAll.html?_ga=2.70405528.968528738.1611110066-121275311.1591173442 + foreach (var go in Resources.FindObjectsOfTypeAll()) { + // check hideFlags + try { + if (go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave) { + continue; + } + } catch(MissingReferenceException) { continue; } + // ignore unity things that aren't in a scene + if (EditorUtility.IsPersistent(go.transform.root.gameObject)) { continue; } + // ignore TidyBehaviours without RepeatViews and RepeatViews without ViewPrefabs + if ((repeatView = go.GetComponent())?.ViewPrefab == null) { continue; } + // ignore gameObjects that aren't in a scene + if (!go.gameObject.scene.path.Contains(".unity")) { continue; } + // ignore gameObjects we've already processed + if (processed.Contains(go.gameObject)) { continue; } + + if (PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(repeatView.ViewPrefab) == prefabPath) { + repeatView.RegenerateChildren(); + processed.Add(go.gameObject); + Violet.Log($"Updated RepeatView on {go.gameObject.name}"); + } + } + } + } +} \ No newline at end of file diff --git a/Editor/PrefabListener.cs.meta b/Editor/PrefabListener.cs.meta new file mode 100644 index 0000000..4f8f145 --- /dev/null +++ b/Editor/PrefabListener.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97174cb2aba0a4d92a2558fcdc1bf594 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ScreenEditor.cs b/Editor/ScreenEditor.cs index a115f11..ed05027 100644 --- a/Editor/ScreenEditor.cs +++ b/Editor/ScreenEditor.cs @@ -11,6 +11,9 @@ namespace VioletUI { [InitializeOnLoad] public static class ScreenEditor { + public static Color RedHue = new Color(1f, 0.2f, 0.2f); + public static Color GreenHue = new Color(0.2f, 1f, 0.2f); + //where to show the buttons private static GUIStyle singleStyle, leftStyle, rightStyle; @@ -25,6 +28,7 @@ static void DrawHierarchyItem(int instanceID, Rect rect) { if (Application.isPlaying) { return; } var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject; if (gameObject == null) { return; } + if (gameObject.scene == null) { return; } navigator = gameObject.GetComponent(); if (navigator != null) { @@ -41,13 +45,9 @@ static void DrawHierarchyItem(int instanceID, Rect rect) { static void DrawNavigator(Navigator navigator, Rect rect) { if (navigator.EditingScreen == null) { - if (Button(rect, "Add")) { + if (Button(rect, 36, "New")) { navigator.AddScreen(); } - } else if (navigator.transform.childCount > 1) { - if (Button(rect, "Lose", true, Color.red)) { - navigator.DiscardEdits(); - } } } @@ -62,7 +62,7 @@ static void DrawScreen(VioletScreen screen, Rect rect) { if (navigator.EditingScreen != null && screen != navigator.EditingScreen) { return; } - if (Button(rect, screen.isActiveAndEnabled ? "Save" : "Edit", screen.isActiveAndEnabled)) { + if (Button(rect, 36, 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}"); } @@ -72,12 +72,21 @@ static void DrawScreen(VioletScreen screen, Rect rect) { navigator.Edit(screen); } } + if (screen.isActiveAndEnabled) { + if (Button(rect, 18, "X", screen.isActiveAndEnabled, RedHue, -50)) { + if (navigator == null) { + throw new VioletException($"Can't find navigator in scene"); + } + navigator.DiscardEdits(); + } + } + } - static bool Button(Rect rect, string label, bool isActive = false, Color highlightColor = default) { + static bool Button(Rect rect, int buttonWidth, string label, bool isActive = false, Color highlightColor = default, int xOffset = 0) { // by default the button is 100% width // we move the left edge to make button fixed width, right aligned - var buttonWidth = 36; + rect.xMax = rect.xMax + xOffset; rect.xMin = rect.xMax - buttonWidth; // extend unity mini button style with small tweaks @@ -96,5 +105,6 @@ static bool Button(Rect rect, string label, bool isActive = false, Color highlig return response; } + } } \ No newline at end of file diff --git a/Editor/VioletButtonEditor.cs b/Editor/VioletButtonEditor.cs index 8984831..e1e7f05 100644 --- a/Editor/VioletButtonEditor.cs +++ b/Editor/VioletButtonEditor.cs @@ -10,6 +10,8 @@ public override void OnInspectorGUI() { base.OnInspectorGUI(); VioletButton button = (VioletButton)target; button.visitScreen = (ScreenId)EditorGUILayout.EnumPopup("Visit Screen", button.visitScreen); + button.showModal = (ScreenId)EditorGUILayout.EnumPopup("Show Modal", button.showModal); + button.closeModal = (bool)EditorGUILayout.Toggle("Close Modal", button.closeModal); } } } diff --git a/Runtime/IRepeatView.cs b/Runtime/IRepeatView.cs new file mode 100644 index 0000000..f02141f --- /dev/null +++ b/Runtime/IRepeatView.cs @@ -0,0 +1,6 @@ +using UnityEngine; + +public interface IRepeatView { + void RegenerateChildren(); + GameObject ViewPrefab { get; } +} \ No newline at end of file diff --git a/Runtime/IRepeatView.cs.meta b/Runtime/IRepeatView.cs.meta new file mode 100644 index 0000000..3779e3b --- /dev/null +++ b/Runtime/IRepeatView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e955fba276c604dadb224591c2599653 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Navigation/Navigator.cs b/Runtime/Navigation/Navigator.cs index a2e5e5b..13ffab1 100644 --- a/Runtime/Navigation/Navigator.cs +++ b/Runtime/Navigation/Navigator.cs @@ -4,7 +4,7 @@ using UnityEngine; using Sirenix.OdinInspector; using System.Threading; -using UniRx.Async; +using Cysharp.Threading.Tasks; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; @@ -35,9 +35,14 @@ public class Navigator : TidyBehaviour { public Action OnModalShow; public Action OnModalHide; + + public Action OnEditStart; + public Action OnEditFinish; #endregion #region local + [NonSerialized] protected Stack lastScreens = new Stack(); + [NonSerialized] protected Stack modals = new Stack(); [NonSerialized] protected Dictionary screens = new Dictionary(); [NonSerialized] protected ScreenId lastScreen = ScreenId.None; [NonSerialized] protected ScreenId currentScreen = ScreenId.None; @@ -59,11 +64,34 @@ void OnDisable() { canceler = null; } + /// + /// GoBack must be called after at least one Visit call and will revisit the previous screen + /// + /// visit success status + public async UniTask GoBack() { + if (lastScreens.Count == 0) { + UnityEngine.Debug.LogError($"Tried to go back but we haven't visited a screen yet."); + return false; + } + + var screen = lastScreens.Pop(); + return await Visit(lastScreen, false); + } + + public async UniTask DebugVisit(ScreenId screenId) { + if (currentScreen != ScreenId.None) { + screens[currentScreen].gameObject.SetActive(false); + } + await UniTask.DelayFrame(1); + screens[screenId].gameObject.SetActive(true); + return true; + } + /// /// Visit takes a and transitions the menu to that scene. /// /// - public async UniTask Visit(ScreenId screenId) { + public async UniTask Visit(ScreenId screenId, bool isForward = true) { Violet.LogVerbose($"Visiting {screenId}"); if (!screens.ContainsKey(screenId)) { throw new VioletException($"Tried to visit {screenId} but it doesn't exist in the current scene. You'll want to add the {screenId} prefab to this scene or to the MenuBuilder prefab. Or change the Home Screen to the screen you want."); @@ -85,6 +113,9 @@ public async UniTask Visit(ScreenId screenId) { hidePromise = screens[lastScreen].Hide(canceler.Token).ContinueWith((x) => { ok = x; OnDidLeave?.Invoke(lastScreen); + if (isForward) { + lastScreens.Push(lastScreen); + } }); } await hidePromise; @@ -104,9 +135,9 @@ public async UniTask Visit(ScreenId screenId) { } /// - /// Show another screen in addition to the current screen. + /// Show another screen in addition to the current screen and disable input listeners on the current screen. /// - /// It fires prior to setting the screen to active + /// It fires prior to setting the screen to active. /// /// Auto-generated id of screen to show as modal public async void ShowModal(ScreenId screenId) { @@ -114,27 +145,45 @@ public async void ShowModal(ScreenId screenId) { // it causes all input listeners to unsubscribe OnModalShow?.Invoke(screenId); await screens[screenId].Show(default); + modals.Push(screenId); currentModal = screenId; } /// - /// Hide the currently shown modal. + /// Hide the currently shown modal and re-enables input listeners on the current screen /// /// It fires after setting the modal to inactive. /// - public async void HideModal() { - if (currentModal == ScreenId.None) { Violet.LogWarning("Called HideModal but there is no current modal - check if HideModal is called twice or called before ShowModal"); return; } - await screens[currentModal].Hide(); - OnModalHide?.Invoke(currentModal); - currentModal = ScreenId.None; + public async virtual UniTask HideModal() { + if (currentModal == ScreenId.None) { return; } + var screenId = modals.Pop(); + await screens[screenId].Hide(); + currentModal = modals.Count > 0 ? modals.Peek() : ScreenId.None; + OnModalHide?.Invoke(screenId); + } + + public async virtual UniTask CloseAllModals() { + int attempts = 10; + while (currentModal != ScreenId.None) { + if (attempts-- < 0) { + throw new Exception($"Couldn't CloseAllModals. Stuck on {currentModal}"); + } + await HideModal(); + } } /// - /// Show an overlay screen in addition to the current screen. Triggers no events. + /// Show an overlay screen in addition to the current screen. Triggers no events and allows input on main screen. /// /// id of screen to set active public void ShowOverlay(ScreenId screenId) { - screens[screenId].gameObject.SetActive(true); + try { + screens[screenId].gameObject.SetActive(true); + } catch(KeyNotFoundException) { + // when editing screens in editor the screens dictionary may be empty + LoadScreens(); + screens[screenId].gameObject.SetActive(true); + } } /// @@ -142,7 +191,13 @@ public void ShowOverlay(ScreenId screenId) { /// /// id of screen to set inactive public void HideOverlay(ScreenId screenId) { - screens[screenId].gameObject.SetActive(false); + try { + screens[screenId].gameObject.SetActive(false); + } catch(KeyNotFoundException) { + // when editing screens in editor the screens dictionary may be empty + LoadScreens(); + screens[screenId].gameObject.SetActive(false); + } } // Sigh. @@ -220,7 +275,12 @@ void LoadScreens() { } screens[screenId] = screen; + +#if UNITY_EDITOR + screen.gameObject.SetActive(screen == EditingScreen); +#else screen.gameObject.SetActive(false); +#endif } OnReady?.Invoke(); @@ -246,6 +306,7 @@ public void Edit(VioletScreen screen) { screen.gameObject.SetActive(true); EditingScreen = screen; currentScreen = ScreenToScreenId(screen); + OnEditStart?.Invoke(currentScreen); } public void FinishEditing(VioletScreen screen = null) { @@ -255,12 +316,14 @@ public void FinishEditing(VioletScreen screen = null) { screen.gameObject.SetActive(false); if (screen == EditingScreen) { EditingScreen = null; }; homeScreen = originalHomeScreen; + OnEditFinish?.Invoke(ScreenToScreenId(screen)); } public void DiscardEdits() { if (EditingScreen == null) { EditingScreen = gameObject.GetComponentInChildren(); } - EditingScreen.RevertPrefab(); + OnEditFinish?.Invoke(ScreenToScreenId(EditingScreen)); EditingScreen.gameObject.SetActive(false); + EditingScreen.RevertPrefab(); EditingScreen = null; homeScreen = originalHomeScreen; } diff --git a/Runtime/Navigation/ScreenId.cs b/Runtime/Navigation/ScreenId.cs index 2377786..ddc1165 100644 --- a/Runtime/Navigation/ScreenId.cs +++ b/Runtime/Navigation/ScreenId.cs @@ -1,6 +1,7 @@ -// Names are automatically added through ScreenIdGenerator.cs, deletions are done manually :) -namespace VioletUI { - public enum ScreenId { - None = 0, - } +//Names are automatically added through ScreenIdGenerator.cs, deletions are done manually in Assets/Plugins/VioletUI/ScreenIds.json :) +public enum ScreenId { + None = 0, + ScreenA = 1, + ScreenB = 2, + Showhide = 3, } diff --git a/Runtime/Navigation/VioletButton.cs b/Runtime/Navigation/VioletButton.cs index dce1160..160c45b 100644 --- a/Runtime/Navigation/VioletButton.cs +++ b/Runtime/Navigation/VioletButton.cs @@ -9,6 +9,9 @@ namespace VioletUI { [ExecuteAlways] public class VioletButton : UnityEngine.UI.Button { public ScreenId visitScreen; + public ScreenId showModal; + public bool closeModal; + [NonSerialized] public bool isSelected; @@ -25,6 +28,14 @@ protected virtual void Submit() { Violet.LogVerbose($"Visiting {visitScreen} navigator={navigator}"); _ = navigator.Visit(visitScreen); } + if (closeModal) { + Violet.LogVerbose($"Hiding modal navigator={navigator}"); + _ = navigator.HideModal(); + } + if (showModal != ScreenId.None) { + Violet.LogVerbose($"Showing modal {showModal} navigator={navigator}"); + navigator.ShowModal(showModal); + } } protected override void OnEnable() { diff --git a/Runtime/Navigation/VioletScreen.cs b/Runtime/Navigation/VioletScreen.cs index fa0c2ea..db540d2 100644 --- a/Runtime/Navigation/VioletScreen.cs +++ b/Runtime/Navigation/VioletScreen.cs @@ -2,7 +2,7 @@ using UnityEngine; using System.Threading.Tasks; using System.Threading; -using UniRx.Async; +using Cysharp.Threading.Tasks; using Sirenix.OdinInspector; using System.Collections.Generic; using UnityEngine.Events; @@ -110,14 +110,16 @@ public void Update() { string prefabPath = ""; UnityEngine.Object prefab; - + public bool isEditing; public void PackPrefab() { var path = string.IsNullOrEmpty(prefabPath) ? $"Assets/Menus/{name}.prefab" : prefabPath; + isEditing = true; PrefabUtility.SaveAsPrefabAssetAndConnect(gameObject, path, InteractionMode.AutomatedAction); } public void RevertPrefab() { Violet.Log($"Reverting. You will lose work!"); + isEditing = false; if (prefab != null) { var revertedScreen = PrefabUtility.InstantiatePrefab(prefab, transform.parent) as GameObject; revertedScreen.transform.SetSiblingIndex(gameObject.transform.GetSiblingIndex()); @@ -158,7 +160,7 @@ public void UnpackPrefab() { void EditorSceneManager_sceneSaved(Scene scene) { try { - if (!gameObject.activeSelf) {return;} + if (!gameObject.activeSelf || gameObject.scene.name != scene.name) {return;} PackPrefab(); UnpackPrefab(); Violet.Log($"Saved {name} prefab"); diff --git a/Runtime/Views/RepeatView.cs b/Runtime/Views/RepeatView.cs index 9b017b8..9f34d26 100644 --- a/Runtime/Views/RepeatView.cs +++ b/Runtime/Views/RepeatView.cs @@ -7,8 +7,10 @@ #endif namespace VioletUI { - public abstract class RepeatView : View where TState : class, IState { - public GameObject ViewPrefab; + public abstract class RepeatView : View, IRepeatView where TState : class, IState { + [SerializeField] + GameObject viewPrefab; + public GameObject ViewPrefab => viewPrefab; public abstract IList Items { get; } public abstract IList LastItems { get; } @@ -17,40 +19,82 @@ internal override void RenderInternal(TState state, TState lastState) { base.RenderInternal(state, lastState); if (!IsDirtyInternal(state, lastState)) { return; } - RenderChildren(); + RenderChildrenInternal(); } internal override bool IsDirtyInternal(TState state, TState lastState) { - base.IsDirtyInternal(state, lastState); - if (lastState != null && Items.Count == LastItems?.Count) { throw new Bail($"No change in item count - {Items.Count}=={LastItems?.Count} "); } - if (ViewPrefab == null) { throw new Bail($"ViewPrefab is null"); } + if (lastState != null) { + base.IsDirtyInternal(state, lastState); + if (Items.Count == transform.childCount) { + throw new Bail($"No change in item count - {Items.Count}=={LastItems?.Count} "); + } + } + if (ViewPrefab == null) { + print($"ViewPrefab b null count view={GetType().Name}"); + throw new Bail($"ViewPrefab is null"); + } return true; } - void RenderChildren() { - // can't use foreach or for loop because it updates the array in place - try { - while(transform.childCount > 0) { - DestroyImmediate(transform.GetChild(0).gameObject); + internal void RenderChildrenInternal() { + var childCount = transform.childCount; + for (int i = childCount - 1; i >= Items.Count; i--) { + if (Application.isPlaying) { + Destroy(transform.GetChild(i).gameObject); + } else { + DestroyImmediate(transform.GetChild(i).gameObject); } - } catch (InvalidOperationException) { - return; } - - for (int i = 0; i < Items.Count; i++) { - var child = CreateChild(i); - var view = child.GetComponent>(); - if (view == null) {continue;} - try { - view.RenderInternal(State, default(TState)); - } catch (Bail) {} + var diff = Items.Count - childCount; + for (int i = 0; i < Math.Min(childCount, childCount - diff); i++) { + if (!transform.GetChild(i).gameObject.activeSelf) { + transform.GetChild(i).gameObject.SetActive(true); + } } + for (int i = 0; i < Items.Count - childCount; i++) { + CreateChild(i); + } } GameObject CreateChild(int index) { return Instantiate(ViewPrefab, transform); } + + public void RegenerateChildren() { +#if UNITY_EDITOR + Transform t = transform; + VioletScreen screen = default; + while (t != null) { + screen = t.GetComponent(); + if (screen != null) { break; } + t = t.parent; + } + + if (screen == null) { + Violet.LogError($"Can't regenerate children bc screen is null. name={gameObject.name} parent={transform.parent}"); + return; + } + + var wasEditing = screen.isEditing; + if (wasEditing) { + screen.UnpackPrefab(); + } + for (int i = transform.childCount - 1; i >= 0; i--) { + DestroyImmediate(transform.GetChild(i).gameObject); + } + + for (int i = 0; i < Items.Count; i++) { + CreateChild(i); + } + + if (wasEditing) { + screen.PackPrefab(); + } +#else + throw new NotImplementedException($"RegenerateChildren not available in builds"); +#endif + } } } diff --git a/Runtime/Views/View.cs b/Runtime/Views/View.cs index e7ded7b..cc80519 100644 --- a/Runtime/Views/View.cs +++ b/Runtime/Views/View.cs @@ -21,24 +21,43 @@ public abstract class View : TidyBehaviour where TState : class, IState /// OnShow is called when the gameObject becomes active in editor or playmode, prior to first Render. /// protected virtual void OnShow() { } + /// /// OnHide is called when the gameObject becomes inactive in editor or playmode, after last Render. /// protected virtual void OnHide() { } + + /// + /// Override Render to update referenced gameobjects with fields of the changed state. + /// + /// + protected virtual void Render(TState state) { } + /// /// IsDirty is used to short circuit expensive render calls by focusing on parts of the state you care about. + /// Do not call base.IsDirty as this only contains a warning about IsDirty not being implemented. /// - /// return `false` to avoid rendering + /// return `false` to avoid rendering after the first render on gameObject enable /// /// current value of state /// value of state prior to the action that triggered this render call /// - protected virtual bool IsDirty(TState state, TState lastState) { return true; } - /// - /// Override Render to update referenced gameobjects with fields of the changed state. - /// - /// - protected virtual void Render(TState state) { } + bool warned = false; + protected virtual bool IsDirty(TState state, TState lastState) { +#if UNITY_EDITOR + if (!warned) { + warned = true; + try { + UnityEngine.Debug.LogWarning($"{this.GetType().Name} has no isDirty method set. This is fine for prototyping but will cause performance issues in production."); + } catch(MissingReferenceException) { + return false; + } catch(NullReferenceException) { + return false; + } + } +#endif + return true; + } /// /// Dispatcher allows you to send Actions that change the State @@ -84,22 +103,17 @@ void RenderWrapper(TState state, TState lastState) { try { RenderInternal(state, lastState); } catch(NullReferenceException) { - UnityEngine.Debug.LogWarning($"Caught a null reference in render. ViewClass={this.GetType().Name}"); + UnityEngine.Debug.LogError($"Caught a null reference in render. ViewClass={this.GetType().Name}"); } catch(Exception e) { - // if (e is Bail) { return; } - UnityEngine.Debug.LogWarning($"Caught an error in render. ViewClass={this.GetType().Name}"); + if (e is Bail) { return; } + UnityEngine.Debug.LogError($"Caught an error in render. ViewClass={this.GetType().Name}"); } #else try { RenderInternal(state, lastState); } catch(Bail e) { - try { - Verbose($"{gameObject.name} bailed from render - {e.Message}"); - } catch (MissingReferenceException) {} - } catch(NullReferenceException) { - UnityEngine.Debug.LogWarning($"Caught a null reference in render. ViewClass={this.GetType().Name}"); - } catch(Exception) { - UnityEngine.Debug.LogWarning($"Caught an error in render. ViewClass={this.GetType().Name}"); + } catch(Exception e) { + UnityEngine.Debug.LogException(e); } #endif } @@ -141,8 +155,11 @@ void State_OnChange() { RenderWrapper(State, ForceRender ? default(TState) : LastState); } - void OnEnable() { - if (State == null) { if (Application.isPlaying) { Warn($"State is null in {name} OnEnable"); } return; } + protected virtual void OnEnable() { + if (State == null) { + if (Application.isPlaying) { UnityEngine.Debug.LogWarning($"State is null in {name} OnEnable"); } + return; + } State.OnChange -= State_OnChange; State.OnChange += State_OnChange; #if UNITY_EDITOR @@ -154,26 +171,26 @@ void OnEnable() { RenderWrapper(State, default(TState)); } - void OnDisable() { + protected virtual void OnDisable() { if (State == null) { if (Application.isPlaying) { Warn($"State is null in {name} OnDisable"); } return; } - State.OnChange -= State_OnChange; #if UNITY_EDITOR EditorApplication.update -= EditorUpdate; #endif + State.OnChange -= State_OnChange; OnHideInternal(); OnHide(); } void Warn(string msg) { -#if VIOLETDEV - Violet.LogWarning(msg); -#endif + if (Violet.logLevel != Violet.LogLevel.None && Violet.logLevel <= Violet.LogLevel.Warning) { + Violet.LogWarning($"{this.GetType().Name} - {msg}"); + } } void Verbose(string msg) { -#if VIOLET_VERBOSE - Violet.LogVerbose(msg); -#endif + if (Violet.logLevel != Violet.LogLevel.None && Violet.logLevel <= Violet.LogLevel.Debug) { + Violet.LogVerbose(msg); + } } #if UNITY_EDITOR @@ -186,6 +203,7 @@ protected virtual void EditorUpdate() { EditorApplication.update -= EditorUpdate; return; } + State.OnChange -= State_OnChange; State.OnChange += State_OnChange; } diff --git a/Runtime/Violet.cs b/Runtime/Violet.cs index 89a7382..3332600 100644 --- a/Runtime/Violet.cs +++ b/Runtime/Violet.cs @@ -67,4 +67,13 @@ public static string Color(string s, string hex = "b300ff") { return $"{s}"; } + public static LogLevel logLevel; + + public enum LogLevel { + None, + Debug, + Info, + Warning, + Error + } } \ No newline at end of file diff --git a/package.json b/package.json index 7fcb1cf..b880c90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.neilsarkar.violetui", - "version": "0.1.75", + "version": "0.1.76", "displayName": "Violet UI", "description": "State-based rendering with live updates in the Unity Editor", "unity": "2019.4",