diff --git a/Editor/MonoBehaviourExtensions.cs b/Editor/MonoBehaviourExtensions.cs new file mode 100644 index 0000000..fb4464d --- /dev/null +++ b/Editor/MonoBehaviourExtensions.cs @@ -0,0 +1,46 @@ +using System.IO; +using UnityEngine; +using UnityEditor; + +namespace NEG.Utils.Editor +{ + public static class MonoBehaviourExtensions + { + [MenuItem("CONTEXT/MonoBehaviour/Change Script")] + public static void ChangeScript(MenuCommand command) + { + if (command.context == null) + return; + + var monoBehaviour = command.context as MonoBehaviour; + var monoScript = MonoScript.FromMonoBehaviour(monoBehaviour); + + string scriptPath = AssetDatabase.GetAssetPath(monoScript); + string directoryPath = new FileInfo(scriptPath).Directory?.FullName; + + // Allow the user to select which script to replace with + string newScriptPath = EditorUtility.OpenFilePanel("Select replacement script", directoryPath, "cs"); + + // Don't log anything if they cancelled the window + if (string.IsNullOrEmpty(newScriptPath)) return; + + // Load the selected asset + string relativePath = "Assets\\" + Path.GetRelativePath(Application.dataPath, newScriptPath); + var chosenTextAsset = AssetDatabase.LoadAssetAtPath(relativePath); + + if (chosenTextAsset == null) + { + Debug.LogWarning($"Selected script couldn't be loaded ({relativePath})"); + return; + } + + Undo.RegisterCompleteObjectUndo(command.context, "Changing component script"); + + var so = new SerializedObject(monoBehaviour); + var scriptProperty = so.FindProperty("m_Script"); + so.Update(); + scriptProperty.objectReferenceValue = chosenTextAsset; + so.ApplyModifiedProperties(); + } + } +} \ No newline at end of file diff --git a/Editor/MonoBehaviourExtensions.cs.meta b/Editor/MonoBehaviourExtensions.cs.meta new file mode 100644 index 0000000..c9d97d5 --- /dev/null +++ b/Editor/MonoBehaviourExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0501eedeaf1d4dcf8c4d0f1b7a0c1761 +timeCreated: 1684519404 \ No newline at end of file diff --git a/Editor/ScreenshotMaker.cs b/Editor/ScreenshotMaker.cs new file mode 100644 index 0000000..7304f1c --- /dev/null +++ b/Editor/ScreenshotMaker.cs @@ -0,0 +1,21 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace NEG.Editor +{ + public static class ScreenshotMaker + { + [MenuItem("CONTEXT/Camera/Save view")] + public static void Capture(MenuCommand command) + { + string path = EditorUtility.SaveFilePanel("Save screenshot", "", "screen.png", "png"); + if (path.Length == 0) + return; + + ScreenCapture.CaptureScreenshot(path); + + } + + } +} \ No newline at end of file diff --git a/Editor/ScreenshotMaker.cs.meta b/Editor/ScreenshotMaker.cs.meta new file mode 100644 index 0000000..1cf608a --- /dev/null +++ b/Editor/ScreenshotMaker.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7dba407e8e8740a1bed3ee5fa833b13c +timeCreated: 1687881092 \ No newline at end of file diff --git a/Editor/SerializationExtentions.cs b/Editor/SerializationExtentions.cs index e3ba2c3..2e37ad4 100644 --- a/Editor/SerializationExtentions.cs +++ b/Editor/SerializationExtentions.cs @@ -14,8 +14,8 @@ namespace NEG.Utils.Serialization { return @this.FindPropertyRelative(GetBackingFieldName(name)); } - - private static string GetBackingFieldName(string name) + + public static string GetBackingFieldName(string name) { #if NET_STANDARD || NET_STANDARD_2_1 return string.Create(1/*<*/ + name.Length + 16/*>k__BackingField*/, name, static (span, name) => diff --git a/Editor/TsvImporter.cs b/Editor/TsvImporter.cs new file mode 100644 index 0000000..77299f5 --- /dev/null +++ b/Editor/TsvImporter.cs @@ -0,0 +1,16 @@ +using System.IO; +using UnityEditor; +using UnityEditor.AssetImporters; +using UnityEditor.Experimental.AssetImporters; +using UnityEngine; + +[ScriptedImporter(1, "tsv")] +public class TsvImporter : ScriptedImporter +{ + public override void OnImportAsset(AssetImportContext ctx) + { + var textAsset = new TextAsset(File.ReadAllText(ctx.assetPath)); + ctx.AddObjectToAsset(Path.GetFileNameWithoutExtension(ctx.assetPath), textAsset); + ctx.SetMainObject(textAsset); + } +} diff --git a/Editor/TsvImporter.cs.meta b/Editor/TsvImporter.cs.meta new file mode 100644 index 0000000..cc19afd --- /dev/null +++ b/Editor/TsvImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3f8438db4084014bab0a063e4675d3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/KeyBasedFactory.cs b/KeyBasedFactory.cs new file mode 100644 index 0000000..c4d4d5f --- /dev/null +++ b/KeyBasedFactory.cs @@ -0,0 +1,54 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace NEG.Utils +{ + public class KeyBasedFactory + { + [PublicAPI] + protected Dictionary data; + + public KeyBasedFactory() + { + data = new Dictionary(); + } + + public void FireRegistration() + { + ScanAssembly(typeof(T2).Assembly); + + if(typeof(T2).Assembly.GetType().Assembly == typeof(T2).Assembly) + return; + + ScanAssembly(typeof(T2).Assembly.GetType().Assembly); + } + + private static void ScanAssembly(Assembly assembly) + { + foreach (var type in assembly.GetTypes()) + { + var methodFields = + type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + for (int i = 0; i < methodFields.Length; i++) + { + if (Attribute.GetCustomAttribute(methodFields[i], typeof(FactoryRegistration)) != null) + { + methodFields[i].Invoke(null, Array.Empty()); + } + } + } + } + + public void Register(T1 key, Type type) => data.Add(key, type); + + public T2 CreateInstance(T1 key, params object[] args) => (T2)Activator.CreateInstance(data[key], args); + } + + [AttributeUsage(AttributeTargets.Method)] + public class FactoryRegistration : Attribute + { + public FactoryRegistration() { } + } +} \ No newline at end of file diff --git a/KeyBasedFactory.cs.meta b/KeyBasedFactory.cs.meta new file mode 100644 index 0000000..1c03d8f --- /dev/null +++ b/KeyBasedFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2e0d3df1ddd34209bfb7262b4e51abfe +timeCreated: 1683917310 \ No newline at end of file diff --git a/NEG/UI/Area/IArea.cs b/NEG/UI/Area/IArea.cs index 4c84e27..11f0559 100644 --- a/NEG/UI/Area/IArea.cs +++ b/NEG/UI/Area/IArea.cs @@ -1,9 +1,11 @@ using NEG.UI.WindowSlot; +using NegUtils.NEG.UI; namespace NEG.UI.Area { - public interface IArea : ISlotsHolder, IUiElement + public interface IArea : ISlotsHolder, IControllable { - + void Open(); + void Close(); } } \ No newline at end of file diff --git a/NEG/UI/IControllable.cs b/NEG/UI/IControllable.cs new file mode 100644 index 0000000..b821427 --- /dev/null +++ b/NEG/UI/IControllable.cs @@ -0,0 +1,18 @@ +using System; + +namespace NegUtils.NEG.UI +{ + public interface IControllable + { + public class BackUsed + { + public bool Used { get; set; } + } + + event Action OnOpened; + event Action OnClosed; + event Action OnBackUsed; + + public void TryUseBack(ref BackUsed backUsed); + } +} \ No newline at end of file diff --git a/NEG/UI/IControllable.cs.meta b/NEG/UI/IControllable.cs.meta new file mode 100644 index 0000000..d595b1d --- /dev/null +++ b/NEG/UI/IControllable.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 175798310e4048eda768cf40e6ee6de3 +timeCreated: 1686596400 \ No newline at end of file diff --git a/NEG/UI/IController.cs b/NEG/UI/IController.cs new file mode 100644 index 0000000..ddb99ff --- /dev/null +++ b/NEG/UI/IController.cs @@ -0,0 +1,7 @@ +namespace NegUtils.NEG.UI +{ + public interface IController + { + IControllable Controllable { get; } + } +} \ No newline at end of file diff --git a/NEG/UI/IController.cs.meta b/NEG/UI/IController.cs.meta new file mode 100644 index 0000000..f16c28d --- /dev/null +++ b/NEG/UI/IController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b88f4a93020a4bc6bde40e0438d296a9 +timeCreated: 1686595825 \ No newline at end of file diff --git a/NEG/UI/IUiElement.cs b/NEG/UI/IUiElement.cs deleted file mode 100644 index 6cfde2b..0000000 --- a/NEG/UI/IUiElement.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace NEG.UI -{ - public interface IUiElement - { - /// - /// Sets only visible state of element - /// - /// - void SetEnabled(bool setEnabled); - } -} \ No newline at end of file diff --git a/NEG/UI/IUiElement.cs.meta b/NEG/UI/IUiElement.cs.meta deleted file mode 100644 index 765fb83..0000000 --- a/NEG/UI/IUiElement.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 296bf6969a6347f8aea788a7bdd086af -timeCreated: 1670693177 \ No newline at end of file diff --git a/NEG/UI/NEG.UI.asmdef b/NEG/UI/NEG.UI.asmdef index db95a9f..62f697c 100644 --- a/NEG/UI/NEG.UI.asmdef +++ b/NEG/UI/NEG.UI.asmdef @@ -1,7 +1,7 @@ { "name": "NEG.UI", "rootNamespace": "", - "references": [], + "references": ["GUID:3c4294719a93e3c4e831a9ff0c261e8a"], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, diff --git a/NEG/UI/UiManager.cs b/NEG/UI/UiManager.cs index c54c6df..c25e4b3 100644 --- a/NEG/UI/UiManager.cs +++ b/NEG/UI/UiManager.cs @@ -2,6 +2,8 @@ using JetBrains.Annotations; using NEG.UI.Area; using NEG.UI.Popup; using NEG.UI.Window; +using NEG.Utils; +using NegUtils.NEG.UI; using System; using System.Collections.Generic; using UnityEngine; @@ -9,9 +11,9 @@ using UnityEngine; namespace NEG.UI { [PublicAPI] - public abstract class UiManager + public abstract class UiManager : IDisposable { - public static UiManager Instance { get; private set; } + public static UiManager Instance { get; protected set; } /// /// Current area shown on screen. @@ -21,11 +23,11 @@ namespace NEG.UI get => currentArea; set { - currentArea?.SetEnabled(false); + currentArea?.Close(); currentArea = value; - currentArea?.SetEnabled(true); + currentArea?.Open(); } } @@ -124,13 +126,36 @@ namespace NEG.UI UpdatePopupsState(forceShow, priority, data); } + public void UseBack() + { + IControllable.BackUsed backUsed = new(); + + CurrentMainWindow?.TryUseBack(ref backUsed); + if(backUsed.Used) + return; + CurrentArea.TryUseBack(ref backUsed); + } + + public void RefreshPopups() { if(currentShownPopup.data is { IsValid: true }) return; UpdatePopupsState(false); } - + + public virtual void Dispose() => Instance = null; + + public void SetMainWindow(IWindow window) => CurrentMainWindow = window; + + public void OnWindowClosed(IWindow window) + { + if(CurrentMainWindow != window) + return; + + //TODO: select new main window + } + protected void PopupClosed(PopupData data) { if (currentShownPopup.data != data) @@ -140,14 +165,15 @@ namespace NEG.UI } 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) + if(currentShownPopup.data != null && currentShownPopup.priority >= priority) return; popupsToShow.Enqueue(currentShownPopup.data, currentShownPopup.priority); @@ -159,6 +185,8 @@ namespace NEG.UI { if(!d.IsValid) continue; + if(d == currentShownPopup.data) + continue; ShowPopup(d, p); return; } diff --git a/NEG/UI/UnityUi/Area/AutoWindowOpen.cs b/NEG/UI/UnityUi/Area/AutoWindowOpen.cs index b19f017..48e1f64 100644 --- a/NEG/UI/UnityUi/Area/AutoWindowOpen.cs +++ b/NEG/UI/UnityUi/Area/AutoWindowOpen.cs @@ -1,6 +1,7 @@ using NEG.UI.UnityUi.Window; using NEG.UI.Window; using System; +using KBCore.Refs; using UnityEngine; namespace NEG.UI.Area diff --git a/NEG/UI/UnityUi/Area/CloseMainWindowOnBack.cs b/NEG/UI/UnityUi/Area/CloseMainWindowOnBack.cs new file mode 100644 index 0000000..1334558 --- /dev/null +++ b/NEG/UI/UnityUi/Area/CloseMainWindowOnBack.cs @@ -0,0 +1,19 @@ +using KBCore.Refs; +using NEG.UI.UnityUi; +using NEG.UI.Window; +using NegUtils.NEG.UI; +using System; +using UnityEngine; + +namespace NEG.UI.Area +{ + public class CloseMainWindowOnBack : MonoController + { + protected override void OnBackUsed(IControllable.BackUsed backUsed) + { + base.OnBackUsed(backUsed); + UiManager.Instance.CurrentMainWindow.Close(); + backUsed.Used = true; + } + } +} \ No newline at end of file diff --git a/NEG/UI/UnityUi/Area/CloseMainWindowOnBack.cs.meta b/NEG/UI/UnityUi/Area/CloseMainWindowOnBack.cs.meta new file mode 100644 index 0000000..4260da6 --- /dev/null +++ b/NEG/UI/UnityUi/Area/CloseMainWindowOnBack.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6ec2bf64cda740a1849d024d4f163a01 +timeCreated: 1686598066 \ No newline at end of file diff --git a/NEG/UI/UnityUi/Area/MonoArea.cs b/NEG/UI/UnityUi/Area/MonoArea.cs index e5f92b0..2829582 100644 --- a/NEG/UI/UnityUi/Area/MonoArea.cs +++ b/NEG/UI/UnityUi/Area/MonoArea.cs @@ -5,37 +5,55 @@ using NEG.UI.UnityUi.Window; using NEG.UI.UnityUi.WindowSlot; using NEG.UI.Window; using NEG.UI.WindowSlot; +using NegUtils.NEG.UI; using System; namespace NEG.UI.Area { public class MonoArea : MonoBehaviour, IArea { + public event Action OnOpened; + public event Action OnClosed; + public event Action OnBackUsed; + public IEnumerable AvailableSlots => windowSlots; public IWindowSlot DefaultWindowSlot => windowSlots[0]; [SerializeField] private bool setAsDefaultArea; [SerializeField] private List windowSlots; - - public virtual void SetEnabled(bool setEnabled) => gameObject.SetActive(setEnabled); - - public virtual void OpenWindow(IWindow window, object data = null) + + public void Open() { - DefaultWindowSlot.AttachWindow(window); - window.SetData(data); + gameObject.SetActive(true); + OnOpened?.Invoke(null); } - protected virtual void Awake() + public void Close(){ + gameObject.SetActive(false); + OnClosed?.Invoke(); + } + + public void OpenWindow(IWindow window, object data = null) => DefaultWindowSlot.AttachWindow(window, data); + + private void Awake() { if (setAsDefaultArea) UiManager.Instance.CurrentArea = this; } - protected virtual void OnDestroy() + private void Start() + { + if(!setAsDefaultArea) + Close(); + } + + private void OnDestroy() { if (ReferenceEquals(UiManager.Instance.CurrentArea, this)) UiManager.Instance.CurrentArea = null; } + + public void TryUseBack(ref IControllable.BackUsed backUsed) => OnBackUsed?.Invoke(backUsed); } } \ No newline at end of file diff --git a/NEG/UI/UnityUi/Buttons/BaseButton.cs b/NEG/UI/UnityUi/Buttons/BaseButton.cs index 35dfbe9..939bdbf 100644 --- a/NEG/UI/UnityUi/Buttons/BaseButton.cs +++ b/NEG/UI/UnityUi/Buttons/BaseButton.cs @@ -1,77 +1,96 @@ -using FMOD.Studio; -using FMODUnity; +using KBCore.Refs; +using NEG.UI.UnityUi.Buttons.Reaction; +using NEG.UI.UnityUi.Buttons.Settings; using System; +using System.Collections.Generic; +using TMPro; using UnityEngine; using UnityEngine.EventSystems; +using UnityEngine.Serialization; using UnityEngine.UI; namespace NEG.UI.UnityUi.Buttons { - [RequireComponent(typeof(ButtonSerializeFields))] + [DefaultExecutionOrder(-1)] + [RequireComponent(typeof(Button))] public class BaseButton : MonoBehaviour, ISelectHandler, IDeselectHandler, IPointerEnterHandler, IPointerExitHandler { + public delegate void SelectionHandler(bool isSilent); + /// + /// is silent + /// + public event SelectionHandler OnSelected; + public event SelectionHandler OnDeselected; public event Action OnButtonPressed; + + public bool Interactable { get => button.interactable; set => button.interactable = value; } + + public TMP_Text Text => text; + + [SerializeField, Self(Flag.Optional)] private Button button; + [SerializeField, Child(Flag.Optional)] private TMP_Text text; + [SerializeField, Child(Flag.Optional)] private Image icon; + + [SerializeField] private ButtonSettings groupButtonSettings; + + private readonly Dictionary behaviours = new Dictionary(); + + public virtual void OnSelect(BaseEventData eventData) => OnSelected?.Invoke(eventData is SilentEventData); - public bool Interactable { get => serializeFields.Button.interactable; set => serializeFields.Button.interactable = value; } - - [SerializeField] - protected ButtonSerializeFields serializeFields; + public void OnDeselect(BaseEventData eventData) => OnDeselected?.Invoke(eventData is SilentEventData); - private bool isHovered; - - public virtual void OnSelect(BaseEventData eventData) - { - if (serializeFields.Text) - serializeFields.Text.color = serializeFields.SelectedTextColor; - } - - public void OnDeselect(BaseEventData eventData) - { - if (serializeFields.Text) - serializeFields.Text.color = serializeFields.DeselectedTextColor; - } - - public void OnPointerEnter(PointerEventData eventData) - { - isHovered = true; - if (serializeFields.Text) - serializeFields.Text.color = serializeFields.SelectedTextColor; - } + public void OnPointerEnter(PointerEventData eventData) => EventSystem.current.SetSelectedGameObject(gameObject); public void OnPointerExit(PointerEventData eventData) { - isHovered = false; - if (serializeFields.Text) - serializeFields.Text.color = serializeFields.DeselectedTextColor; + if(EventSystem.current.currentSelectedGameObject == gameObject) + EventSystem.current.SetSelectedGameObject(null); } - public void SetText(string text) + public void SetText(string txt) { - if(serializeFields == null) + if(text == null) return; - if(serializeFields.Text == null) + text.text = txt; + } + + public void AddOrOverrideSetting(SettingData data) + { + if (behaviours.TryGetValue(data.Key, out var setting)) + { + setting.ChangeData(data); return; - serializeFields.Text.text = text; + } + behaviours.Add(data.Key, MonoUiManager.Instance.BehavioursFactory.CreateInstance(data.Key, this, data)); + } + + public void RemoveSetting(string key) + { + if (!behaviours.TryGetValue(key, out var setting)) + { + Debug.LogError($"Behaviour with key {key} was not found"); + return; + } + setting.Dispose(); + behaviours.Remove(key); } protected virtual void Awake() { - if(serializeFields == null) - serializeFields = GetComponent(); - serializeFields.Button.onClick.AddListener(OnClicked); - OnDeselect(null); + button.onClick.AddListener(OnClicked); + if (groupButtonSettings == null) + MonoUiManager.Instance.DefaultUiSettings.Apply(this); + else + groupButtonSettings.Apply(this); } - private void OnValidate() - { - if(serializeFields == null) - serializeFields = GetComponent(); - } + private void Start() => OnDeselect(null); + + private void OnValidate() => this.ValidateRefs(); protected virtual void OnClicked() { OnDeselect(null); - isHovered = false; OnButtonPressed?.Invoke(); } } diff --git a/NEG/UI/UnityUi/Buttons/BaseButton.cs.meta b/NEG/UI/UnityUi/Buttons/BaseButton.cs.meta index ea42d55..5a38446 100644 --- a/NEG/UI/UnityUi/Buttons/BaseButton.cs.meta +++ b/NEG/UI/UnityUi/Buttons/BaseButton.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 3a250ef2d0c34e7396a16fc5eddbdb01 -timeCreated: 1670777213 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: -1 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NEG/UI/UnityUi/Buttons/ButtonReaction.cs b/NEG/UI/UnityUi/Buttons/ButtonReaction.cs index 1f30b1d..c2c2590 100644 --- a/NEG/UI/UnityUi/Buttons/ButtonReaction.cs +++ b/NEG/UI/UnityUi/Buttons/ButtonReaction.cs @@ -1,4 +1,5 @@ using System; +using KBCore.Refs; using UnityEngine; namespace NEG.UI.UnityUi.Buttons @@ -6,9 +7,13 @@ namespace NEG.UI.UnityUi.Buttons [RequireComponent(typeof(BaseButton))] public abstract class ButtonReaction : MonoBehaviour { - protected virtual void Awake() => GetComponent().OnButtonPressed += OnClicked; + [SerializeField, Self(Flag.Optional)] protected BaseButton button; + + protected virtual void Awake() => button.OnButtonPressed += OnClicked; - protected virtual void OnDestroy() => GetComponent().OnButtonPressed -= OnClicked; + protected virtual void OnDestroy() => button.OnButtonPressed -= OnClicked; + + private void OnValidate() => this.ValidateRefs(); protected abstract void OnClicked(); } diff --git a/NEG/UI/UnityUi/Buttons/ButtonSerializeFields.cs b/NEG/UI/UnityUi/Buttons/ButtonSerializeFields.cs deleted file mode 100644 index 60e93a9..0000000 --- a/NEG/UI/UnityUi/Buttons/ButtonSerializeFields.cs +++ /dev/null @@ -1,33 +0,0 @@ -using FMODUnity; -using System; -using TMPro; -using UnityEngine; -using UnityEngine.UI; - -namespace NEG.UI.UnityUi.Buttons -{ - public class ButtonSerializeFields : MonoBehaviour - { - [field: SerializeField] - public Button Button { get; private set; } - [field: SerializeField] - public TMP_Text Text { get; private set; } - [field: SerializeField] - public Color SelectedTextColor { get; private set; } - [field: SerializeField] - public Color DeselectedTextColor { get; private set; } - [field: SerializeField] - public EventReference HoverEventRef { get; private set; } - [field: SerializeField] - public EventReference ClickEventRef { get; private set; } - - private void OnValidate() - { - if(Button == null) - Button = GetComponent