using System.Collections;
using UnityEngine;
using UnityEngine.Events;
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
using UnityEngine.InputSystem;
#endif
using UnityEngine.UI;
namespace MoreMountains.Tools
{
///
/// A debug menu helper, meant to help create quick mobile friendly debug menus
///
public class MMDebugMenu : MonoBehaviour
{
/// the possible directions for the menu to appear
public enum ToggleDirections { TopToBottom, LeftToRight, RightToLeft, BottomToTop }
[Header("Data")]
/// the scriptable object containing the menu's data
public MMDebugMenuData Data;
[Header("Bindings")]
/// the container of the whole menu
public CanvasGroup MenuContainer;
/// the scrolling contents
public RectTransform Contents;
/// the menu's background image
public Image MenuBackground;
/// the icon used to close the menu
public Image CloseIcon;
/// the tab bar (where the tab buttons go)
public RectTransform TabBar;
/// the tab contents container (where the contents of the page will go)
public RectTransform TabContainer;
/// the tab manager
public MMDebugMenuTabManager TabManager;
/// the MoreMountains logo
public Image MMLogo;
[Header("Events")]
/// an event to call when the menu opens
public UnityEvent OnOpenEvent;
/// an event to call when the menu closes
public UnityEvent OnCloseEvent;
[Header("Test")]
/// whether or not this menu is active at this moment
[MMReadOnly]
public bool Active = false;
/// a test button to toggle the menu
[MMInspectorButton("ToggleMenu")]
public bool ToggleButton;
protected RectTransform _containerRect;
protected Vector3 _initialContainerPosition;
protected Vector3 _offPosition;
protected Vector3 _newPosition;
protected bool _toggling = false;
///
/// On Start we init our menu
///
protected virtual void Start()
{
Initialization();
}
///
/// Prepares transitions and grabs components
///
protected virtual void Initialization()
{
if (Data != null)
{
FillMenu();
}
CloseIcon.color = Data.TextColor;
_containerRect = MenuContainer.GetComponent();
_initialContainerPosition = _containerRect.localPosition;
MenuBackground.color = Data.BackgroundColor;
switch (Data.ToggleDirection)
{
case ToggleDirections.RightToLeft:
_offPosition = _initialContainerPosition + Vector3.right * _containerRect.rect.width;
break;
case ToggleDirections.LeftToRight:
_offPosition = _initialContainerPosition + Vector3.left * _containerRect.rect.width;
break;
case ToggleDirections.TopToBottom:
_offPosition = _initialContainerPosition + Vector3.up * _containerRect.rect.height;
break;
case ToggleDirections.BottomToTop:
_offPosition = _initialContainerPosition + Vector3.down * _containerRect.rect.height;
break;
}
_containerRect.localPosition = _offPosition;
}
///
/// Fills the menu based on the data's contents
///
public virtual void FillMenu(bool triggerEvents = false)
{
int tabCounter = 0;
if (MMLogo != null)
{
MMLogo.color = Data.TextColor;
}
foreach (Transform child in Contents.transform)
{
GameObject.Destroy(child.gameObject);
}
foreach (Transform child in TabBar.transform)
Destroy(child.gameObject);
TabManager.Tabs.Clear();
TabManager.TabsContents.Clear();
foreach(MMDebugMenuTabData tab in Data.Tabs)
{
if (!tab.Active)
{
continue;
}
// create tab in the menu
MMDebugMenuTab tabBarTab = Instantiate(Data.TabPrefab);
tabBarTab.SelectedBackgroundColor = Data.TextColor;
tabBarTab.SelectedTextColor = Data.BackgroundColor;
tabBarTab.DeselectedBackgroundColor = Data.BackgroundColor;
tabBarTab.DeselectedTextColor = Data.TextColor;
tabBarTab.TabText.text = tab.Name;
tabBarTab.TabText.font = Data.RegularFont;
tabBarTab.transform.SetParent(TabBar);
tabBarTab.Index = tabCounter;
tabBarTab.Manager = TabManager;
TabManager.Tabs.Add(tabBarTab);
// create tab contents
MMDebugMenuTabContents contents = Instantiate(Data.TabContentsPrefab);
contents.transform.SetParent(TabContainer);
RectTransform rectTransform = contents.GetComponent();
rectTransform.MMSetLeft(0f);
rectTransform.MMSetRight(0f);
rectTransform.MMSetTop(0f);
rectTransform.MMSetBottom(0f);
contents.Index = tabCounter;
FillTab(contents, tabCounter, triggerEvents);
if (tabCounter == Data.InitialActiveTabIndex)
{
contents.gameObject.SetActive(true);
tabBarTab.Select();
}
else
{
contents.gameObject.SetActive(false);
tabBarTab.Deselect();
}
TabManager.TabsContents.Add(contents);
tabCounter++;
}
// debug tab
if (Data.DisplayDebugTab)
{
MMDebugMenuTab tabBarTab = Instantiate(Data.TabPrefab);
tabBarTab.SelectedBackgroundColor = Data.TextColor;
tabBarTab.SelectedTextColor = Data.BackgroundColor;
tabBarTab.DeselectedBackgroundColor = Data.BackgroundColor;
tabBarTab.DeselectedTextColor = Data.TextColor;
tabBarTab.TabText.text = Data.DebugTabName;
tabBarTab.TabText.font = Data.RegularFont;
tabBarTab.transform.SetParent(TabBar);
tabBarTab.Index = tabCounter;
tabBarTab.Manager = TabManager;
TabManager.Tabs.Add(tabBarTab);
MMDebugMenuDebugTab debugTab = Instantiate(Data.DebugTabPrefab);
debugTab.DebugText.color = Data.TextColor;
debugTab.DebugText.font = Data.RegularFont;
debugTab.transform.SetParent(TabContainer);
debugTab.CommandPrompt.textComponent.font = Data.RegularFont;
debugTab.CommandPrompt.textComponent.color = Data.TextColor;
debugTab.CommandPromptCharacter.font = Data.RegularFont;
debugTab.CommandPromptCharacter.color = Data.TextColor;
MMDebugMenuTabContents debugTabContents = debugTab.GetComponent();
debugTabContents.Index = tabCounter;
TabManager.TabsContents.Add(debugTabContents);
RectTransform rectTransform = debugTabContents.GetComponent();
rectTransform.MMSetLeft(0f);
rectTransform.MMSetRight(0f);
rectTransform.MMSetTop(0f);
rectTransform.MMSetBottom(0f);
if (tabCounter == Data.InitialActiveTabIndex)
{
debugTab.gameObject.SetActive(true);
TabManager.Tabs[tabCounter].Select();
}
else
{
debugTab.gameObject.SetActive(false);
TabManager.Tabs[tabCounter].Deselect();
}
tabCounter++;
}
// fill with spacers
int spacerCount = Data.MaxTabs - tabCounter;
for (int i = 0; i < spacerCount; i++)
{
RectTransform spacer = Instantiate(Data.TabSpacerPrefab);
spacer.transform.SetParent(TabBar);
}
}
protected virtual void FillTab(MMDebugMenuTabContents tab, int index, bool triggerEvents = false)
{
Transform parent = tab.Parent;
foreach (MMDebugMenuItem item in Data.Tabs[index].MenuItems)
{
if (!item.Active)
{
continue;
}
switch (item.Type)
{
case MMDebugMenuItem.MMDebugMenuItemTypes.Button:
MMDebugMenuItemButton button;
button = (item.ButtonType == MMDebugMenuItem.MMDebugMenuItemButtonTypes.Border) ? Instantiate(Data.ButtonBorderPrefab) : Instantiate(Data.ButtonPrefab);
button.name = "MMDebugMenuItemButton_" + item.Name;
button.ButtonText.text = item.ButtonText;
button.ButtonEventName = item.ButtonEventName;
if (item.ButtonType == MMDebugMenuItem.MMDebugMenuItemButtonTypes.Border)
{
button.ButtonText.color = Data.AccentColor;
button.ButtonBg.color = Data.TextColor;
}
else
{
button.ButtonText.color = Data.BackgroundColor;
button.ButtonBg.color = Data.AccentColor;
}
button.ButtonText.font = Data.RegularFont;
button.transform.SetParent(parent);
break;
case MMDebugMenuItem.MMDebugMenuItemTypes.Checkbox:
MMDebugMenuItemCheckbox checkbox = Instantiate(Data.CheckboxPrefab);
checkbox.name = "MMDebugMenuItemCheckbox_" + item.Name;
checkbox.SwitchText.text = item.CheckboxText;
if (item.CheckboxInitialState)
{
checkbox.Switch.SetTrue();
}
else
{
checkbox.Switch.SetFalse();
}
checkbox.CheckboxEventName = item.CheckboxEventName;
checkbox.transform.SetParent(parent);
checkbox.Switch.GetComponent().color = Data.AccentColor;
checkbox.SwitchText.color = Data.TextColor;
checkbox.SwitchText.font = Data.RegularFont;
break;
case MMDebugMenuItem.MMDebugMenuItemTypes.Slider:
MMDebugMenuItemSlider slider = Instantiate(Data.SliderPrefab);
slider.name = "MMDebugMenuItemSlider_" + item.Name;
slider.Mode = item.SliderMode;
slider.RemapZero = item.SliderRemapZero;
slider.RemapOne = item.SliderRemapOne;
slider.TargetSlider.value = MMMaths.Remap(item.SliderInitialValue, item.SliderRemapZero, item.SliderRemapOne, 0f, 1f);
slider.transform.SetParent(parent);
slider.SliderText.text = item.SliderText;
slider.SliderText.color = Data.TextColor;
slider.SliderText.font = Data.RegularFont;
slider.SliderValueText.text = (item.SliderMode == MMDebugMenuItemSlider.Modes.Int) ? item.SliderInitialValue.ToString() : item.SliderInitialValue.ToString("F3");
slider.SliderValueText.color = Data.AccentColor;
slider.SliderValueText.font = Data.BoldFont;
slider.SliderKnob.color = Data.AccentColor;
slider.SliderLine.color = Data.TextColor;
slider.SliderEventName = item.SliderEventName;
break;
case MMDebugMenuItem.MMDebugMenuItemTypes.Spacer:
GameObject spacerPrefab = (item.SpacerType == MMDebugMenuItem.MMDebugMenuItemSpacerTypes.Small) ? Data.SpacerSmallPrefab : Data.SpacerBigPrefab;
GameObject spacer = Instantiate(spacerPrefab);
spacer.name = "MMDebugMenuItemSpacer_" + item.Name;
spacer.transform.SetParent(parent);
break;
case MMDebugMenuItem.MMDebugMenuItemTypes.Title:
MMDebugMenuItemTitle title = Instantiate(Data.TitlePrefab);
title.name = "MMDebugMenuItemSlider_" + item.Name;
title.TitleText.text = item.TitleText;
title.TitleText.color = Data.TextColor;
title.TitleText.font = Data.BoldFont;
title.TitleLine.color = Data.AccentColor;
title.transform.SetParent(parent);
break;
case MMDebugMenuItem.MMDebugMenuItemTypes.Choices:
MMDebugMenuItemChoices choicesPrefab;
if (item.ChoicesType == MMDebugMenuItem.MMDebugMenuItemChoicesTypes.TwoChoices)
{
choicesPrefab = Data.TwoChoicesPrefab;
}
else
{
choicesPrefab = Data.ThreeChoicesPrefab;
}
MMDebugMenuItemChoices choices = Instantiate(choicesPrefab);
choices.name = "MMDebugMenuItemChoices_" + item.Name;
choices.Choices[0].ButtonText.text = item.ChoiceOneText;
choices.Choices[1].ButtonText.text = item.ChoiceTwoText;
choices.Choices[0].ButtonEventName = item.ChoiceOneEventName;
choices.Choices[1].ButtonEventName = item.ChoiceTwoEventName;
if (item.ChoicesType == MMDebugMenuItem.MMDebugMenuItemChoicesTypes.ThreeChoices)
{
choices.Choices[2].ButtonEventName = item.ChoiceThreeEventName;
choices.Choices[2].ButtonText.text = item.ChoiceThreeText;
}
choices.OffColor = Data.BackgroundColor;
choices.OnColor = Data.TextColor;
choices.AccentColor = Data.AccentColor;
foreach (MMDebugMenuChoiceEntry entry in choices.Choices)
{
if (entry != null)
{
entry.ButtonText.font = Data.RegularFont;
}
}
choices.Select(item.SelectedChoice);
if (triggerEvents)
choices.TriggerButtonEvent(item.SelectedChoice);
choices.transform.SetParent(parent);
break;
case MMDebugMenuItem.MMDebugMenuItemTypes.Value:
MMDebugMenuItemValue value = Instantiate(Data.ValuePrefab);
value.name = "MMDebugMenuItemValue_" + item.Name;
value.LabelText.text = item.ValueLabel;
value.LabelText.color = Data.TextColor;
value.LabelText.font = Data.RegularFont;
value.ValueText.text = item.ValueInitialValue;
value.ValueText.color = Data.AccentColor;
value.ValueText.font = Data.BoldFont;
value.RadioReceiver.Channel = item.ValueMMRadioReceiverChannel;
value.transform.SetParent(parent);
break;
case MMDebugMenuItem.MMDebugMenuItemTypes.Text:
MMDebugMenuItemText textPrefab;
switch (item.TextType)
{
case MMDebugMenuItem.MMDebugMenuItemTextTypes.Tiny:
textPrefab = Data.TextTinyPrefab;
break;
case MMDebugMenuItem.MMDebugMenuItemTextTypes.Small:
textPrefab = Data.TextSmallPrefab;
break;
case MMDebugMenuItem.MMDebugMenuItemTextTypes.Long:
textPrefab = Data.TextLongPrefab;
break;
default:
textPrefab = Data.TextTinyPrefab;
break;
}
MMDebugMenuItemText text = Instantiate(textPrefab);
text.name = "MMDebugMenuItemText_" + item.Name;
text.ContentText.text = item.TextContents;
text.ContentText.color = Data.TextColor;
text.ContentText.font = Data.RegularFont;
text.transform.SetParent(parent);
break;
}
}
// we always add a spacer at the end because scrollviews are terrible
GameObject finalSpacer = Instantiate(Data.SpacerBigPrefab);
finalSpacer.name = "MMDebugMenuItemSpacer_FinalSpacer";
finalSpacer.transform.SetParent(parent);
}
///
/// Makes the menu appear
///
public virtual void OpenMenu()
{
OnOpenEvent?.Invoke();
StartCoroutine(ToggleCo(false));
}
///
/// Makes the menu disappear
///
public virtual void CloseMenu()
{
StartCoroutine(ToggleCo(true));
}
///
/// Closes or opens the menu depending on its current state
///
public virtual void ToggleMenu()
{
StartCoroutine(ToggleCo(Active));
}
///
/// A coroutine used to toggle the menu
///
///
///
protected virtual IEnumerator ToggleCo(bool active)
{
if (_toggling)
{
yield break;
}
if (!active)
{
OnOpenEvent?.Invoke();
_containerRect.gameObject.SetActive(true);
}
_toggling = true;
Active = active;
_newPosition = active ? _offPosition : _initialContainerPosition;
MMTween.MoveRectTransform(this, _containerRect, _containerRect.localPosition, _newPosition, null, 0f, Data.ToggleDuration, Data.ToggleCurve, ignoreTimescale:true);
yield return MMCoroutine.WaitForUnscaled(Data.ToggleDuration);
if (active)
{
OnCloseEvent?.Invoke();
_containerRect.gameObject.SetActive(false);
}
Active = !active;
_toggling = false;
}
///
/// On update we handle our input
///
protected virtual void Update()
{
HandleInput();
}
///
/// Looks for shortcut input
///
protected virtual void HandleInput()
{
bool input = false;
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
input = Keyboard.current[Data.ToggleKey].wasPressedThisFrame;
#else
input = Input.GetKeyDown(Data.ToggleShortcut);
#endif
if (input)
{
ToggleMenu();
}
}
///
/// Routes console logs to the MMDebugConsole
///
///
///
///
protected virtual void CaptureConsoleLog(string logString, string stackTrace, LogType type)
{
MMDebug.LogDebugToConsole(logString + " (" + type + ")", "#00FFFF", 3, false);
}
///
/// On Enable, we start listening for log messages
///
protected virtual void OnEnable()
{
Application.logMessageReceived += CaptureConsoleLog;
}
///
/// On Disable, we stop listening for log messages
///
protected virtual void OnDisable()
{
Application.logMessageReceived -= CaptureConsoleLog;
}
}
}