// 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