// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved.
// This code can only be used under the standard Unity Asset Store End User License Agreement
// A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms
using System;
using System.Collections.Generic;
using Doozy.Editor.EditorUI.ScriptableObjects.Colors;
using Doozy.Editor.EditorUI.Utils;
using Doozy.Editor.Reactor.Internal;
using Doozy.Runtime.Colors;
using Doozy.Runtime.Reactor.Easings;
using Doozy.Runtime.Reactor.Extensions;
using Doozy.Runtime.Reactor.Internal;
using Doozy.Runtime.Reactor.Reactions;
using Doozy.Runtime.UIElements.Extensions;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;
// ReSharper disable MemberCanBePrivate.Global
namespace Doozy.Editor.EditorUI.Components
{
public sealed class FluidSideMenu : VisualElement
{
public const float EXPAND_COLLAPSE_DURATION = 0.3f;
public const Ease EXPAND_COLLAPSE_EASE = Ease.InOutExpo;
#region MenuState
private enum MenuState
{
Expanded,
IsExpanding,
Collapsed,
IsCollapsing
}
private MenuState currentMenuState { get; set; }
private bool areButtonLabelsHidden { get; set; }
#endregion
#region MenuLevel
// ReSharper disable InconsistentNaming
public enum MenuLevel
{
Level_0 = 0,
Level_1 = 1,
Level_2 = 2
}
// ReSharper restore InconsistentNaming
public MenuLevel menuLevel { get; private set; }
/// Get the background color of the menu for the current menu level
/// The background color of the menu for the current menu level
private Color MenuBackgroundColor()
{
switch (menuLevel)
{
case MenuLevel.Level_0: return EditorColors.Default.MenuBackgroundLevel0;
case MenuLevel.Level_1: return EditorColors.Default.MenuBackgroundLevel1;
case MenuLevel.Level_2: return EditorColors.Default.MenuBackgroundLevel2;
default: throw new ArgumentOutOfRangeException();
}
}
/// Get the color of the menu background based on the current menu level
/// The color of the menu background based on the current menu level
private EditorSelectableColorInfo ButtonContainerColor()
{
switch (menuLevel)
{
case MenuLevel.Level_0: return EditorSelectableColors.Default.MenuButtonBackgroundLevel0;
case MenuLevel.Level_1: return EditorSelectableColors.Default.MenuButtonBackgroundLevel1;
case MenuLevel.Level_2: return EditorSelectableColors.Default.MenuButtonBackgroundLevel2;
default: throw new ArgumentOutOfRangeException();
}
}
/// Apply the given menu level visual settings to all relevant elements
/// The level of the menu
/// This instance of the FluidSideMenu
public FluidSideMenu SetMenuLevel(MenuLevel level)
{
menuLevel = level;
UpdateColors();
UpdateButtonSizes();
return this;
}
/// Get the button ElementSize of the given menu level
/// The level of the menu
/// The button ElementSize of the given menu level
private static ElementSize GetButtonSize(MenuLevel level)
{
switch (level)
{
case MenuLevel.Level_0: return ElementSize.Large;
case MenuLevel.Level_1: return ElementSize.Normal;
case MenuLevel.Level_2: return ElementSize.Small;
default: throw new ArgumentOutOfRangeException();
}
}
/// Get the dynamic vertical spacing between buttons
/// The level of the menu
/// The dynamic vertical spacing between buttons for the given level
private static int GetSpaceBetweenButtons(MenuLevel level)
{
switch (level)
{
case MenuLevel.Level_0: return 16;
case MenuLevel.Level_1: return 12;
case MenuLevel.Level_2: return 8;
default: throw new ArgumentOutOfRangeException();
}
}
private void UpdateButtonSizes()
{
foreach (FluidToggleButtonTab button in buttons)
button.SetElementSize(GetButtonSize(menuLevel));
}
#endregion
//REFERENCES
public TemplateContainer templateContainer { get; }
public VisualElement layoutContainer { get; }
public VisualElement headerContainer { get; }
public VisualElement expandCollapseButtonContainer { get; }
public VisualElement menuInfoContainer { get; }
public Image menuInfoIcon { get; }
public Label menuInfoLabel { get; }
public VisualElement searchBoxContainer { get; }
public VisualElement toolbarContainer { get; }
public ScrollView buttonsScrollViewContainer { get; }
public VisualElement footerContainer { get; }
public Texture2DReaction menuInfoIconReaction { get; set; }
public FluidToggleGroup toggleGroup { get; }
public List buttons { get; }
public List spacesBetweenButtons { get; set; }
//SETTINGS
public int selectedMenuIndex { get; private set; }
public bool isExpanded => currentMenuState == MenuState.Expanded || currentMenuState == MenuState.IsExpanding;
public bool isCollapsed => currentMenuState == MenuState.Collapsed || currentMenuState == MenuState.IsCollapsing;
public bool hideToolbarWhenCollapsed { get; private set; }
public bool hasToolbar { get; private set; }
public bool showMenuInfoWhenCollapsed { get; set; }
#region Accent Color
private EditorSelectableColorInfo m_AccentColor;
public FluidSideMenu SetAccentColor(EditorSelectableColorInfo selectableColor)
{
if (selectableColor == null) return this;
m_AccentColor = selectableColor;
if (hasSearch)
{
searchBox.SetAccentColor(m_AccentColor);
}
if (isCollapsable)
{
expandButton.SetAccentColor(m_AccentColor);
collapseButton.SetAccentColor(m_AccentColor);
}
return this;
}
#endregion
#region Search
public bool hasSearch => searchBox != null;
public FluidSearchBox searchBox { get; private set; }
public FluidSideMenu AddSearch()
{
//CLEAR SEARCH BOX CONTAINER
searchBoxContainer.Clear();
//DISPLAY SEARCH BOX CONTAINER
searchBoxContainer.SetStyleDisplay(DisplayStyle.Flex);
//CREATE a new SEARCH BOX and add it to the SEARCH BOX CONTAINER
searchBoxContainer.Add(searchBox = new FluidSearchBox());
//SET SEARCH BOX ACCENT COLOR
searchBox.SetAccentColor(m_AccentColor);
//SET ALL THE BUTTONS TO CLEAR SEARCH WHEN CLICKED
foreach (FluidToggleButtonTab button in buttons)
button.OnClick += () => searchBox?.ClearSearch();
//CONNECT SEARCH BOX TO SIDE MENU TOGGLE GROUP
searchBox.ConnectToToggleGroup(toggleGroup);
//ADD SEARCH TAB BUTTON TO THE BUTTONS CONTAINER (a tab to view search results)
AddSearchButtonToButtonsContainer();
//SET CALLBACK - that when a search is over to select the previously selected button
searchBox.OnShowSearchResultsCallback += showResults =>
{
if (showResults == false && buttons.Count > 0)
buttons[selectedMenuIndex].isOn = true;
};
//SET CALLBACK - that when the collapse button is clicked to clear any ongoing search
collapseButton.OnClick += searchBox.ClearSearch;
return this;
}
public void SelectTheButtonThatWasSelectedBeforeSearchWasInitiated()
{
if (buttons.Count < 1)
return;
buttons[selectedMenuIndex].isOn = true;
}
public FluidSideMenu RemoveSearch()
{
searchBoxContainer.Clear();
searchBoxContainer.SetStyleDisplay(DisplayStyle.None);
searchBox?.OnShowSearchResultsCallback?.Invoke(false);
searchBox?.DisconnectFromToggleGroup();
if (buttons.Contains(searchBox?.searchTabButton))
buttons.Remove(searchBox?.searchTabButton);
RemoveSearchButtonFromButtonsContainer();
if (searchBox != null)
collapseButton.OnClick -= searchBox.ClearSearch;
searchBox = null;
return this;
}
#endregion
#region Expand Collapse
public FluidButton expandButton { get; }
public FluidButton collapseButton { get; }
public FloatReaction expandCollapseReaction { get; }
public UnityAction OnExpand;
public UnityAction OnCollapse;
public FluidSideMenu SetOnExpand(UnityAction callback)
{
OnExpand = callback;
return this;
}
public FluidSideMenu AddOnExpand(UnityAction callback)
{
OnExpand += callback;
return this;
}
public FluidSideMenu ClearOnExpand()
{
OnExpand = null;
return this;
}
public FluidSideMenu SetOnCollapse(UnityAction callback)
{
OnCollapse = callback;
return this;
}
public FluidSideMenu AddOnCollapse(UnityAction callback)
{
OnCollapse += callback;
return this;
}
public FluidSideMenu ClearOnCollapse()
{
OnCollapse = null;
return this;
}
public bool isCollapsable
{
get => expandCollapseButtonContainer.GetStyleDisplay() == DisplayStyle.Flex;
set => expandCollapseButtonContainer.SetStyleDisplay(value ? DisplayStyle.Flex : DisplayStyle.None);
}
public FluidSideMenu IsCollapsable(bool canCollapse)
{
isCollapsable = canCollapse;
return this;
}
public bool hasCustomWidth { get; private set; }
public int customWidth { get; private set; }
public FluidSideMenu SetCustomWidth(int width)
{
hasCustomWidth = true;
customWidth = Mathf.Max(CollapsedWidth() * 2, width);
UpdateVisualState();
return this;
}
public FluidSideMenu ClearCustomWidth()
{
hasCustomWidth = false;
UpdateVisualState();
return this;
}
private int ExpandedWidth()
{
int value = CollapsedWidth();
if (hasCustomWidth) return customWidth - value;
switch (menuLevel)
{
case MenuLevel.Level_0: return 208 - value;
case MenuLevel.Level_1: return 200 - value;
case MenuLevel.Level_2: return 194 - value;
default: throw new ArgumentOutOfRangeException();
}
}
private int CollapsedWidth()
{
switch (menuLevel)
{
case MenuLevel.Level_0: return 50;
case MenuLevel.Level_1: return 44;
case MenuLevel.Level_2: return 38;
default: throw new ArgumentOutOfRangeException();
}
}
#endregion
public FluidSideMenu SetMenuInfo(string menuName, IEnumerable textures)
{
showMenuInfoWhenCollapsed = true;
menuInfoContainer.SetStyleDisplay(DisplayStyle.Flex);
menuInfoLabel.SetText(menuName);
if (menuInfoIconReaction == null)
{
menuInfoIconReaction = menuInfoIcon.GetTexture2DReaction(textures).SetEditorHeartbeat().SetDuration(0.6f);
}
else
{
menuInfoIconReaction.SetTextures(textures);
}
return this;
}
public FluidSideMenu ClearMenuInfo()
{
showMenuInfoWhenCollapsed = true;
menuInfoContainer.SetStyleDisplay(DisplayStyle.None);
menuInfoLabel.SetText(string.Empty);
menuInfoIconReaction?.Recycle();
menuInfoIconReaction = null;
menuInfoIcon.SetStyleBackgroundImage((Texture2D)null);
return this;
}
public FluidSideMenu()
{
this.SetStyleFlexGrow(1);
Add(templateContainer = EditorLayouts.EditorUI.FluidSideMenu.CloneTree());
templateContainer
.AddStyle(EditorStyles.EditorUI.FluidSideMenu)
.SetStyleFlexGrow(1);
layoutContainer = templateContainer.Q(nameof(layoutContainer));
headerContainer = layoutContainer.Q(nameof(headerContainer));
expandCollapseButtonContainer = layoutContainer.Q(nameof(expandCollapseButtonContainer));
menuInfoContainer = layoutContainer.Q(nameof(menuInfoContainer));
menuInfoIcon = layoutContainer.Q(nameof(menuInfoIcon));
menuInfoLabel = layoutContainer.Q