forked from sin365/AxibugEmuOnline
440 lines
14 KiB
C#
440 lines
14 KiB
C#
using DG.Tweening;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace AxibugEmuOnline.Client
|
|
{
|
|
public class OptionUI : CommandExecuter
|
|
{
|
|
[SerializeField]
|
|
RectTransform MenuRoot;
|
|
[SerializeField]
|
|
Selector SelectBorder;
|
|
|
|
[Space]
|
|
[Header("模板")]
|
|
[SerializeField] OptionUI_ExecuteItem TEMPLATE_EXECUTEITEM;
|
|
[SerializeField] OptionUI_ValueEditItem TEMPLATE_VALUEEDITITEM;
|
|
|
|
private OptionUI m_child;
|
|
private OptionUI m_parent;
|
|
|
|
public override bool AloneMode => true;
|
|
public override bool Enable => m_bPoped && (!m_child || !m_child.m_bPoped);
|
|
|
|
private bool m_bPoped;
|
|
private readonly List<OptionUI_MenuItem> m_runtimeMenuItems = new List<OptionUI_MenuItem>();
|
|
|
|
private int m_selectIndex = -1;
|
|
public int SelectIndex
|
|
{
|
|
get => m_selectIndex;
|
|
set
|
|
{
|
|
value = Mathf.Clamp(value, 0, m_runtimeMenuItems.Count - 1);
|
|
if (m_selectIndex == value) return;
|
|
|
|
var gap = value - m_selectIndex;
|
|
|
|
while (!m_runtimeMenuItems[value].Visible)
|
|
{
|
|
var temp = value;
|
|
if (gap > 0)
|
|
{
|
|
temp++;
|
|
}
|
|
else
|
|
{
|
|
temp--;
|
|
}
|
|
|
|
if (temp >= 0 && temp < m_runtimeMenuItems.Count)
|
|
value = temp;
|
|
}
|
|
|
|
m_selectIndex = value;
|
|
|
|
OptionUI_MenuItem optionUI_MenuItem = m_runtimeMenuItems[m_selectIndex];
|
|
optionUI_MenuItem.OnFocus();
|
|
var itemUIRect = optionUI_MenuItem.transform as RectTransform;
|
|
SelectBorder.Target = itemUIRect;
|
|
}
|
|
}
|
|
|
|
protected override void Awake()
|
|
{
|
|
TEMPLATE_EXECUTEITEM.gameObject.SetActiveEx(false);
|
|
TEMPLATE_VALUEEDITITEM.gameObject.SetActiveEx(false);
|
|
|
|
SelectBorder.gameObject.SetActiveEx(false);
|
|
base.Awake();
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
SelectBorder.Active = Enable;
|
|
UpdateMenuState();
|
|
|
|
base.Update();
|
|
}
|
|
|
|
private void UpdateMenuState()
|
|
{
|
|
if (checkDirty())
|
|
{
|
|
RebuildSelectIndex();
|
|
}
|
|
}
|
|
|
|
private void RebuildSelectIndex()
|
|
{
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
SelectIndex = Mathf.Clamp(SelectIndex, 0, m_runtimeMenuItems.Count - 1);
|
|
var selectItem = m_runtimeMenuItems[SelectIndex];
|
|
|
|
if (selectItem.Visible == false)
|
|
{
|
|
bool find = false;
|
|
int currentSelect = SelectIndex;
|
|
while (currentSelect > 0)
|
|
{
|
|
currentSelect--;
|
|
if (m_runtimeMenuItems[currentSelect].Visible)
|
|
{
|
|
find = true;
|
|
}
|
|
}
|
|
if (!find)
|
|
{
|
|
currentSelect = SelectIndex;
|
|
while (currentSelect < m_runtimeMenuItems.Count)
|
|
{
|
|
if (m_runtimeMenuItems[currentSelect].Visible)
|
|
{
|
|
find = true;
|
|
}
|
|
currentSelect++;
|
|
}
|
|
}
|
|
|
|
if (find)
|
|
SelectIndex = currentSelect;
|
|
}
|
|
else
|
|
{
|
|
var itemUIRect = selectItem.transform as RectTransform;
|
|
SelectBorder.Target = itemUIRect;
|
|
}
|
|
}
|
|
|
|
private bool checkDirty()
|
|
{
|
|
bool dirty = false;
|
|
foreach (var menuItem in m_runtimeMenuItems)
|
|
{
|
|
if (menuItem.gameObject.activeSelf != menuItem.Visible)
|
|
{
|
|
dirty = true;
|
|
menuItem.gameObject.SetActive(menuItem.Visible);
|
|
}
|
|
}
|
|
|
|
return dirty;
|
|
}
|
|
|
|
IKeyMapperChanger m_lastCS;
|
|
private Action m_onClose;
|
|
|
|
/// <summary>
|
|
/// 当菜单弹出时,动态添加一个菜单选项
|
|
/// </summary>
|
|
/// <param name="menu"></param>
|
|
public void AddOptionMenuWhenPoping(OptionMenu menu)
|
|
{
|
|
if (!m_bPoped) return;
|
|
|
|
CreateRuntimeMenuItem(menu);
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
OptionUI_MenuItem optionUI_MenuItem = m_runtimeMenuItems[m_selectIndex];
|
|
SelectBorder.Target = null;
|
|
SelectBorder.Target = optionUI_MenuItem.transform as RectTransform;
|
|
}
|
|
|
|
public void Pop<T>(List<T> menus, int defaultIndex = 0, Action onClose = null) where T : OptionMenu
|
|
{
|
|
m_onClose = onClose;
|
|
ReleaseRuntimeMenus();
|
|
foreach (var menu in menus) CreateRuntimeMenuItem(menu);
|
|
CommandDispatcher.Instance.RegistController(this);
|
|
SelectBorder.gameObject.SetActiveEx(true);
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
m_selectIndex = defaultIndex;
|
|
OptionUI_MenuItem optionUI_MenuItem = m_runtimeMenuItems[defaultIndex];
|
|
optionUI_MenuItem.OnFocus();
|
|
|
|
var itemUIRect = optionUI_MenuItem.transform as RectTransform;
|
|
SelectBorder.Target = itemUIRect;
|
|
SelectBorder.RefreshPosition();
|
|
|
|
if (!m_bPoped)
|
|
{
|
|
m_bPoped = true;
|
|
Vector2 start = new Vector2(0, MenuRoot.anchoredPosition.y);
|
|
Vector2 end = new Vector2(-MenuRoot.rect.width, MenuRoot.anchoredPosition.y);
|
|
DOTween.To(
|
|
() => start,
|
|
(value) =>
|
|
{
|
|
var moveDelta = value - start;
|
|
start = value;
|
|
|
|
var topParent = m_parent;
|
|
while (topParent != null && topParent.m_parent != null)
|
|
{
|
|
topParent = topParent.m_parent;
|
|
}
|
|
if (topParent != null)
|
|
{
|
|
topParent.MenuRoot.anchoredPosition += moveDelta;
|
|
}
|
|
else
|
|
{
|
|
MenuRoot.anchoredPosition += moveDelta;
|
|
}
|
|
},
|
|
end,
|
|
0.3f
|
|
).SetEase(Ease.OutCubic);
|
|
|
|
m_lastCS = CommandDispatcher.Instance.Current;
|
|
CommandDispatcher.Instance.Current = CommandDispatcher.Instance.Normal;
|
|
}
|
|
|
|
}
|
|
|
|
public void Hide()
|
|
{
|
|
if (m_bPoped)
|
|
{
|
|
Vector2 start = new Vector2(-MenuRoot.rect.width, MenuRoot.anchoredPosition.y);
|
|
Vector2 end = new Vector2(0, MenuRoot.anchoredPosition.y);
|
|
|
|
|
|
ReleaseRuntimeMenus();
|
|
m_runtimeMenuItems.Clear();
|
|
|
|
SelectBorder.gameObject.SetActiveEx(false);
|
|
|
|
CommandDispatcher.Instance.UnRegistController(this);
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
DOTween.To(
|
|
() => start,
|
|
(value) =>
|
|
{
|
|
var moveDelta = value - start;
|
|
start = value;
|
|
|
|
var topParent = m_parent;
|
|
while (topParent != null && topParent.m_parent != null)
|
|
{
|
|
topParent = topParent.m_parent;
|
|
}
|
|
if (topParent != null)
|
|
{
|
|
topParent.MenuRoot.anchoredPosition += moveDelta;
|
|
}
|
|
else
|
|
{
|
|
MenuRoot.anchoredPosition += moveDelta;
|
|
}
|
|
},
|
|
end,
|
|
0.3f
|
|
).SetEase(Ease.OutCubic);
|
|
|
|
m_bPoped = false;
|
|
|
|
CommandDispatcher.Instance.Current = m_lastCS;
|
|
|
|
m_onClose?.Invoke();
|
|
m_onClose = null;
|
|
}
|
|
}
|
|
|
|
private void CreateRuntimeMenuItem(OptionMenu menuData)
|
|
{
|
|
if (menuData is ExecuteMenu)
|
|
{
|
|
ExecuteMenu executeMenu = (ExecuteMenu)menuData;
|
|
var menuUI = Instantiate(TEMPLATE_EXECUTEITEM.gameObject, TEMPLATE_EXECUTEITEM.transform.parent).GetComponent<OptionUI_ExecuteItem>();
|
|
menuUI.gameObject.SetActive(true);
|
|
menuUI.SetData(this, executeMenu);
|
|
m_runtimeMenuItems.Add(menuUI);
|
|
}
|
|
else if (menuData is ValueSetMenu)
|
|
{
|
|
var valueSetMenu = (ValueSetMenu)menuData;
|
|
var menuUI = Instantiate(TEMPLATE_VALUEEDITITEM.gameObject, TEMPLATE_VALUEEDITITEM.transform.parent).GetComponent<OptionUI_ValueEditItem>();
|
|
menuUI.gameObject.SetActive(true);
|
|
menuUI.SetData(this, valueSetMenu);
|
|
m_runtimeMenuItems.Add(menuUI);
|
|
}
|
|
else throw new NotImplementedException($"暂不支持的菜单类型{menuData.GetType().Name}");
|
|
}
|
|
|
|
private void ReleaseRuntimeMenus()
|
|
{
|
|
foreach (var item in m_runtimeMenuItems)
|
|
{
|
|
item.OnHide();
|
|
Destroy(item.gameObject);
|
|
}
|
|
m_runtimeMenuItems.Clear();
|
|
}
|
|
|
|
protected override void OnCmdSelectItemDown()
|
|
{
|
|
SelectIndex++;
|
|
}
|
|
|
|
protected override void OnCmdSelectItemUp()
|
|
{
|
|
SelectIndex--;
|
|
}
|
|
|
|
protected override void OnCmdBack()
|
|
{
|
|
Hide();
|
|
}
|
|
|
|
protected override void OnCmdSelectItemLeft()
|
|
{
|
|
var executer = m_runtimeMenuItems[SelectIndex];
|
|
if (executer)
|
|
{
|
|
executer.OnLeft();
|
|
}
|
|
}
|
|
|
|
protected override void OnCmdSelectItemRight()
|
|
{
|
|
var executer = m_runtimeMenuItems[SelectIndex];
|
|
if (!executer.IsExpandMenu)
|
|
{
|
|
executer.OnRight();
|
|
return;
|
|
}
|
|
|
|
OnCmdEnter();
|
|
}
|
|
|
|
protected override bool OnCmdEnter()
|
|
{
|
|
var executer = m_runtimeMenuItems[SelectIndex];
|
|
bool cancelHide = false;
|
|
executer.OnExecute(this, ref cancelHide);
|
|
if (!cancelHide) Hide();
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 展开下级菜单
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="menus"></param>
|
|
/// <param name="defaultIndex"></param>
|
|
/// <param name="onClose"></param>
|
|
public void ExpandSubMenu<T>(List<T> menus, int defaultIndex = 0, Action onClose = null) where T : OptionMenu
|
|
{
|
|
if (m_child == null)
|
|
{
|
|
var sourcePrefab = Resources.Load<GameObject>("UIPrefabs/OptionUI");
|
|
m_child = Instantiate(sourcePrefab, transform).GetComponent<OptionUI>();
|
|
m_child.name = $"{name}_Sub";
|
|
m_child.m_parent = this;
|
|
}
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
m_child.Pop(menus, 0, onClose);
|
|
}
|
|
|
|
public void RemoveItem(OptionUI_MenuItem ui)
|
|
{
|
|
var index = m_runtimeMenuItems.IndexOf(ui);
|
|
if (index == -1) return;
|
|
|
|
m_runtimeMenuItems.Remove(ui);
|
|
ui.OnHide();
|
|
Destroy(ui.gameObject);
|
|
|
|
RebuildSelectIndex();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 带有执行行为的菜单
|
|
/// </summary>
|
|
public abstract class ExecuteMenu : OptionMenu
|
|
{
|
|
protected ExecuteMenu(string name, Sprite icon = null) : base(name, icon) { }
|
|
|
|
public abstract void OnExcute(OptionUI optionUI, ref bool cancelHide);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 带有展开行为的菜单
|
|
/// </summary>
|
|
public abstract class ExpandMenu : ExecuteMenu
|
|
{
|
|
protected ExpandMenu(string name, Sprite icon = null) : base(name, icon) { }
|
|
|
|
public sealed override void OnExcute(OptionUI optionUI, ref bool cancelHide)
|
|
{
|
|
cancelHide = true;
|
|
optionUI.ExpandSubMenu(GetOptionMenus());
|
|
}
|
|
|
|
protected abstract List<OptionMenu> GetOptionMenus();
|
|
}
|
|
|
|
/// <summary> 不要直接继承这个类 </summary>
|
|
public abstract class OptionMenu
|
|
{
|
|
public string Name { get; protected set; }
|
|
public Sprite Icon { get; protected set; }
|
|
public virtual bool Visible => true;
|
|
public virtual bool Enable => true;
|
|
|
|
protected OptionMenu(string name, Sprite icon = null)
|
|
{
|
|
Name = name;
|
|
Icon = icon;
|
|
}
|
|
|
|
public virtual void OnFocus() { }
|
|
public virtual void OnShow(OptionUI_MenuItem ui) { }
|
|
public virtual void OnHide() { }
|
|
}
|
|
/// <summary>
|
|
/// 带有值类型显示和编辑的菜单
|
|
/// </summary>
|
|
public abstract class ValueSetMenu : OptionMenu
|
|
{
|
|
protected ValueSetMenu(string name) : base(name) { }
|
|
|
|
public abstract Type ValueType { get; }
|
|
public abstract object ValueRaw { get; }
|
|
public abstract void OnValueChanged(object newValue);
|
|
public abstract object Min { get; }
|
|
public abstract object Max { get; }
|
|
}
|
|
}
|