594 lines
19 KiB
C#
594 lines
19 KiB
C#
|
/********************************************************************
|
||
|
文件: FrameBase.cs
|
||
|
作者: 梦语
|
||
|
邮箱: 1982614048@qq.com
|
||
|
创建时间: 2024/03/29 17:28:19
|
||
|
最后修改: 梦语
|
||
|
最后修改时间: 2024/04/04 16:58:56
|
||
|
功能: 界面基类
|
||
|
*********************************************************************/
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Reflection;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Events;
|
||
|
using UnityEngine.EventSystems;
|
||
|
using UnityEngine.UI;
|
||
|
|
||
|
namespace Ether
|
||
|
{
|
||
|
public abstract class FrameBase
|
||
|
{
|
||
|
public virtual string PrefabPath { get; }
|
||
|
|
||
|
public bool IsActive { get; set; } = false;
|
||
|
public UIElement UIElement { get; private set; }
|
||
|
|
||
|
public FrameData frameData;
|
||
|
|
||
|
public FrameBase()
|
||
|
{
|
||
|
UIElement = new UIElement();
|
||
|
UIElementAttribute elementAttribute = GetType().GetCustomAttribute<UIElementAttribute>();
|
||
|
UIElement.prefabPath = (elementAttribute != null && !string.IsNullOrEmpty(elementAttribute.framePrefabPath)) ? elementAttribute.framePrefabPath : PrefabPath;
|
||
|
UIElement.tier = elementAttribute == null ? FrameTier.Middle : elementAttribute.tier;
|
||
|
UIElement.frameType = elementAttribute == null ? FrameType.Frame : elementAttribute.frameType;
|
||
|
UIElement.frameName = GetType().Name;
|
||
|
}
|
||
|
|
||
|
protected virtual void OnInit() { }
|
||
|
protected virtual void OnOpenAnim() { UIElement.Root?.SetActive(true); }
|
||
|
public virtual void OnOpenFrame() { }
|
||
|
public virtual void OnCloseFrame() { }
|
||
|
public virtual void OnSubscribe() { }
|
||
|
public virtual void OnUnSubscribe() { }
|
||
|
public virtual void OnFocus(PointerEventData pointerEventData) { }
|
||
|
public virtual void OnUpdate(float deltaTime) { }
|
||
|
|
||
|
public void SetVisible(bool isShow, Action callback = null, FrameData openFrameData = null)
|
||
|
{
|
||
|
IsActive = isShow;
|
||
|
if (isShow)
|
||
|
{
|
||
|
frameData = openFrameData;
|
||
|
OnShow(callback);
|
||
|
UIElement.Root.transform.SetAsLastSibling();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
OnHide(callback);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private void OnShow(Action callback)
|
||
|
{
|
||
|
Debug.Log($"界面{UIElement.frameName}打开");
|
||
|
BindRoot();
|
||
|
OnOpenAnim();
|
||
|
OnInit();
|
||
|
OnSubscribe();
|
||
|
OnOpenFrame();
|
||
|
callback?.Invoke();
|
||
|
}
|
||
|
|
||
|
private void OnHide(Action callback)
|
||
|
{
|
||
|
Debug.Log($"界面{UIElement.frameName}关闭");
|
||
|
OnUnSubscribe();
|
||
|
OnCloseFrame();
|
||
|
UIElement.Root.SetVisible(false, () =>
|
||
|
{
|
||
|
callback?.Invoke();
|
||
|
GameObject.Destroy(UIElement.Root);
|
||
|
UIElement.Root = null;
|
||
|
childFindCache.Clear();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 绑定UIRoot
|
||
|
/// </summary>
|
||
|
public void BindRoot()
|
||
|
{
|
||
|
UIElement.Root = UIManager.Inst.GetFrameRes(this);
|
||
|
Debug.Log($"Frame:{UIElement.frameName} Root初始化成功。。。。{UIElement.Root.name}");
|
||
|
OnClick(UIElement.Root, OnFocus);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 返回上个界面
|
||
|
/// </summary>
|
||
|
public virtual void Back()
|
||
|
{
|
||
|
UIManager.Inst.BackFrame(UIElement.frameName, UIElement.frameType);
|
||
|
}
|
||
|
|
||
|
public void Close()
|
||
|
{
|
||
|
SwitchHideFrame(UIElement.frameName);
|
||
|
}
|
||
|
|
||
|
//========================================= 工具 ============================================
|
||
|
#region 切换面板
|
||
|
/// <summary>
|
||
|
/// 切换到另个界面
|
||
|
/// </summary>
|
||
|
protected void SwitchFrame<T>() where T : FrameBase
|
||
|
{
|
||
|
string targetViewName = typeof(T).Name;
|
||
|
UIManager.Inst.CloseFrame(UIElement.frameName, () =>
|
||
|
{
|
||
|
UIManager.Inst.OpenFrame(targetViewName);
|
||
|
});
|
||
|
//UIMgr.Ins.SwitchPanel(SwitchPanelType.SwitchShowAndHide, targetViewName, frameName);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 切换到另个界面
|
||
|
/// </summary>
|
||
|
protected void SwitchFrame(string targetViewName)
|
||
|
{
|
||
|
UIManager.Inst.CloseFrame(UIElement.frameName, () =>
|
||
|
{
|
||
|
UIManager.Inst.OpenFrame(targetViewName);
|
||
|
});
|
||
|
//UIMgr.Ins.SwitchPanel(SwitchPanelType.SwitchShowAndHide, targetViewName, frameName);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 打开另个界面,不隐藏自己
|
||
|
/// </summary>
|
||
|
protected void SwitchOnlyFrame<T>() where T : FrameBase
|
||
|
{
|
||
|
string targetViewName = typeof(T).Name;
|
||
|
UIManager.Inst.OpenFrame(targetViewName);
|
||
|
//UIMgr.Ins.SwitchPanel(SwitchPanelType.SwitchOnlyShow, targetViewName);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 打开另个界面,不隐藏自己
|
||
|
/// </summary>
|
||
|
protected void SwitchOnlyFrame(string targetViewName)
|
||
|
{
|
||
|
UIManager.Inst.OpenFrame(targetViewName);
|
||
|
//UIMgr.Ins.SwitchPanel(SwitchPanelType.SwitchOnlyShow, targetViewName);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 隐藏界面某个界面
|
||
|
/// </summary>
|
||
|
protected void SwitchHideFrame(string targetViewName)
|
||
|
{
|
||
|
UIManager.Inst.CloseFrame(targetViewName);
|
||
|
//UIMgr.Ins.SwitchPanel(SwitchPanelType.SwitchOnlyHide, targetViewName);
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
|
||
|
//========================================= 事件工具 ============================================
|
||
|
#region 查找物体
|
||
|
/// <summary>
|
||
|
/// 子物体缓存
|
||
|
/// </summary>
|
||
|
Dictionary<string, Transform> childFindCache = new Dictionary<string, Transform>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// 检查缓存中内容
|
||
|
/// </summary>
|
||
|
/// <param name="path"></param>
|
||
|
/// <returns></returns>
|
||
|
private Transform CheckChildCache(string path)
|
||
|
{
|
||
|
if (childFindCache.ContainsKey(path))
|
||
|
{
|
||
|
return childFindCache[path];
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 添加子物体到缓存
|
||
|
/// </summary>
|
||
|
/// <param name="path"></param>
|
||
|
/// <param name="trans"></param>
|
||
|
/// <returns></returns>
|
||
|
private Transform AddChildCache(string path, Transform trans)
|
||
|
{
|
||
|
if (childFindCache.ContainsKey(path))
|
||
|
{
|
||
|
childFindCache[path] = trans;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
childFindCache.Add(path, trans);
|
||
|
}
|
||
|
|
||
|
return trans;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 通过名称查找物体
|
||
|
/// </summary>
|
||
|
protected Transform GetChildForName(string name)
|
||
|
{
|
||
|
var tempTrans = CheckChildCache(name);
|
||
|
if (tempTrans != null)
|
||
|
{
|
||
|
return tempTrans;
|
||
|
}
|
||
|
|
||
|
Transform[] trans = UIElement.Root.GetComponentsInChildren<Transform>(true);
|
||
|
|
||
|
foreach (Transform tran in trans)
|
||
|
{
|
||
|
if (tran.name == name)
|
||
|
{
|
||
|
return AddChildCache(name, tran);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 通过名称查找物体组件
|
||
|
/// </summary>
|
||
|
protected T GetComponentForName<T>(string name)
|
||
|
{
|
||
|
return GetChildForName(name).GetComponent<T>();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 查找当前界面的某个物体
|
||
|
/// </summary>
|
||
|
protected Transform GetChild(string path)
|
||
|
{
|
||
|
var tempTrans = CheckChildCache(path);
|
||
|
if (tempTrans != null)
|
||
|
{
|
||
|
return tempTrans;
|
||
|
}
|
||
|
|
||
|
//Debug.Log("FindChild:" + path + " Root:" + Root);
|
||
|
return AddChildCache(path, UIElement.Root.transform.Find(path));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 查找当前界面的某个物体的组件
|
||
|
/// </summary>
|
||
|
protected T GetComponent<T>(string path) where T : Component
|
||
|
{
|
||
|
//Debug.Log("GetComponent:" + typeof(T) + " : " + path);
|
||
|
return GetChild(path).GetComponent<T>();
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region 按钮事件
|
||
|
protected void OnBtnLeftClick(string path, Action action)
|
||
|
{
|
||
|
ButtonEx btn = GetComponent<ButtonEx>(path);
|
||
|
OnBtnLeftClick(btn, action);
|
||
|
}
|
||
|
|
||
|
protected void OnBtnLeftClick(GameObject obj, Action action)
|
||
|
{
|
||
|
ButtonEx btn = obj.GetComponent<ButtonEx>();
|
||
|
OnBtnLeftClick(btn, action);
|
||
|
}
|
||
|
|
||
|
protected void OnBtnLeftClick(ButtonEx btn, Action action)
|
||
|
{
|
||
|
btn.OnLeftClick.RemoveAllListeners();
|
||
|
btn.OnLeftClick.AddListener(() =>
|
||
|
{
|
||
|
action?.Invoke();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
protected void OnBtnRightClick(string path, Action action)
|
||
|
{
|
||
|
ButtonEx btn = GetComponent<ButtonEx>(path);
|
||
|
OnBtnRightClick(btn, action);
|
||
|
}
|
||
|
|
||
|
protected void OnBtnRightClick(GameObject obj, Action action)
|
||
|
{
|
||
|
ButtonEx btn = obj.GetComponent<ButtonEx>();
|
||
|
OnBtnRightClick(btn, action);
|
||
|
}
|
||
|
|
||
|
protected void OnBtnRightClick(ButtonEx btn, Action action)
|
||
|
{
|
||
|
btn.OnRightClick.RemoveAllListeners();
|
||
|
btn.OnRightClick.AddListener(() =>
|
||
|
{
|
||
|
action?.Invoke();
|
||
|
});
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region 文本
|
||
|
protected void SetText(string path, string text)
|
||
|
{
|
||
|
Text label = GetComponent<Text>(path);
|
||
|
SetText(label, text);
|
||
|
}
|
||
|
|
||
|
protected void SetText(GameObject obj, string text)
|
||
|
{
|
||
|
Text label = obj.GetComponent<Text>();
|
||
|
SetText(label, text);
|
||
|
}
|
||
|
|
||
|
protected void SetText(Text label, string text)
|
||
|
{
|
||
|
label.text = text;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region 图片
|
||
|
protected void SetSprite(string path, string spritePath)
|
||
|
{
|
||
|
Image img = GetComponent<Image>(path);
|
||
|
SetSprite(img, spritePath);
|
||
|
}
|
||
|
|
||
|
protected void SetSprite(GameObject obj, string spritePath)
|
||
|
{
|
||
|
Image img = obj.GetComponent<Image>();
|
||
|
SetSprite(img, spritePath);
|
||
|
}
|
||
|
|
||
|
protected void SetSprite(Image img, string spritePath)
|
||
|
{
|
||
|
img.sprite = LoaderTools.LoadAsset<Sprite>(spritePath);
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region 输入框
|
||
|
protected void SetInputFieldValue(string path, string text)
|
||
|
{
|
||
|
GetComponent<InputField>(path).text = text;
|
||
|
}
|
||
|
|
||
|
protected void SetInputFieldValue(GameObject obj, string text)
|
||
|
{
|
||
|
obj.GetComponent<InputField>().text = text;
|
||
|
}
|
||
|
|
||
|
protected void SetInputFieldValue(InputField input, string text)
|
||
|
{
|
||
|
input.text = text;
|
||
|
}
|
||
|
|
||
|
protected void OnInputFieldValueChanged(string path, Action<string> action)
|
||
|
{
|
||
|
InputField input = GetComponent<InputField>(path);
|
||
|
OnInputFieldValueChanged(input, action);
|
||
|
}
|
||
|
|
||
|
protected void OnInputFieldValueChanged(GameObject obj, Action<string> action)
|
||
|
{
|
||
|
InputField input = obj.GetComponent<InputField>();
|
||
|
OnInputFieldValueChanged(input, action);
|
||
|
}
|
||
|
protected void OnInputFieldValueChanged(InputField input, Action<string> action)
|
||
|
{
|
||
|
input.onValueChanged.RemoveAllListeners();
|
||
|
input.onValueChanged.AddListener((str) =>
|
||
|
{
|
||
|
action?.Invoke(str);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
protected void OnInputFieldEnd(string path, Action<string> action)
|
||
|
{
|
||
|
InputField input = GetComponent<InputField>(path);
|
||
|
OnInputFieldEnd(input, action);
|
||
|
}
|
||
|
|
||
|
protected void OnInputFieldEnd(GameObject obj, Action<string> action)
|
||
|
{
|
||
|
InputField input = obj.GetComponent<InputField>();
|
||
|
OnInputFieldEnd(input, action);
|
||
|
}
|
||
|
|
||
|
protected void OnInputFieldEnd(InputField input, Action<string> action)
|
||
|
{
|
||
|
input.onEndEdit.AddListener((str) =>
|
||
|
{
|
||
|
action?.Invoke(str);
|
||
|
});
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region 复选框
|
||
|
protected void OnToggleChange(string path, Action<bool> action)
|
||
|
{
|
||
|
Toggle toggle = GetComponent<Toggle>(path);
|
||
|
OnToggleChange(toggle, action);
|
||
|
}
|
||
|
|
||
|
protected void OnToggleChange(GameObject obj, Action<bool> action)
|
||
|
{
|
||
|
Toggle toggle = obj.GetComponent<Toggle>();
|
||
|
OnToggleChange(toggle, action);
|
||
|
}
|
||
|
|
||
|
protected void OnToggleChange(Toggle toggle, Action<bool> action)
|
||
|
{
|
||
|
toggle.onValueChanged.AddListener((isChange) =>
|
||
|
{
|
||
|
action?.Invoke(isChange);
|
||
|
});
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region 滑动条
|
||
|
protected void OnSliderChange(string path, Action<float> action)
|
||
|
{
|
||
|
Slider slider = GetComponent<Slider>(path);
|
||
|
OnSliderChange(slider, action);
|
||
|
}
|
||
|
|
||
|
protected void OnSliderChange(GameObject obj, Action<float> action)
|
||
|
{
|
||
|
Slider slider = obj.GetComponent<Slider>();
|
||
|
OnSliderChange(slider, action);
|
||
|
}
|
||
|
|
||
|
protected void OnSliderChange(Slider slider, Action<float> action)
|
||
|
{
|
||
|
slider.onValueChanged.AddListener((value) =>
|
||
|
{
|
||
|
action?.Invoke(value);
|
||
|
});
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region EventTrigger事件
|
||
|
/// <summary>
|
||
|
/// EventTrigger监听
|
||
|
/// </summary>
|
||
|
public void AddTriggersListener(GameObject obj, EventTriggerType eventID, Action<BaseEventData> unityAction)
|
||
|
{
|
||
|
EventTrigger trigger = obj.GetComponent<EventTrigger>();
|
||
|
if (trigger == null)
|
||
|
{
|
||
|
trigger = obj.AddComponent<EventTrigger>();
|
||
|
}
|
||
|
|
||
|
if (trigger.triggers.Count == 0)
|
||
|
{
|
||
|
trigger.triggers = new List<EventTrigger.Entry>();
|
||
|
}
|
||
|
|
||
|
UnityAction<BaseEventData> callback = new UnityAction<BaseEventData>(unityAction);
|
||
|
EventTrigger.Entry entry = new EventTrigger.Entry();
|
||
|
entry.eventID = eventID;
|
||
|
entry.callback.AddListener(callback);
|
||
|
trigger.triggers.Add(entry);
|
||
|
}
|
||
|
|
||
|
protected void OnClick(GameObject obj, Action<PointerEventData> clickEvent)
|
||
|
{
|
||
|
AddTriggersListener(obj, EventTriggerType.PointerClick, (baseEventData) =>
|
||
|
{
|
||
|
PointerEventData eventData = (PointerEventData)baseEventData;
|
||
|
clickEvent?.Invoke(eventData);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
protected void OnClick(string path, Action<PointerEventData> clickEvent)
|
||
|
{
|
||
|
GameObject obj = GetChild(path).gameObject;
|
||
|
OnClick(obj, clickEvent);
|
||
|
}
|
||
|
|
||
|
protected void OnClickDown(GameObject obj, Action<PointerEventData> clickDownEvent)
|
||
|
{
|
||
|
AddTriggersListener(obj, EventTriggerType.PointerDown, (baseEventData) =>
|
||
|
{
|
||
|
PointerEventData eventData = (PointerEventData)baseEventData;
|
||
|
clickDownEvent?.Invoke(eventData);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
protected void OnClickDown(string path, Action<PointerEventData> clickDownEvent)
|
||
|
{
|
||
|
GameObject obj = GetChild(path).gameObject;
|
||
|
OnClickDown(obj, clickDownEvent);
|
||
|
}
|
||
|
|
||
|
|
||
|
protected void OnClickUp(GameObject obj, Action<PointerEventData> clickUpEvent)
|
||
|
{
|
||
|
AddTriggersListener(obj, EventTriggerType.PointerUp, (baseEventData) =>
|
||
|
{
|
||
|
PointerEventData eventData = (PointerEventData)baseEventData;
|
||
|
clickUpEvent?.Invoke(eventData);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
protected void OnClickUp(string path, Action<PointerEventData> clickUpEvent)
|
||
|
{
|
||
|
GameObject obj = GetChild(path).gameObject;
|
||
|
OnClickUp(obj, clickUpEvent);
|
||
|
}
|
||
|
|
||
|
protected void OnDrag(GameObject obj, Action<PointerEventData> dragEvent)
|
||
|
{
|
||
|
AddTriggersListener(obj, EventTriggerType.Drag, (baseEventData) =>
|
||
|
{
|
||
|
PointerEventData eventData = (PointerEventData)baseEventData;
|
||
|
dragEvent?.Invoke(eventData);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
protected void OnDrag(string path, Action<PointerEventData> dragEvent)
|
||
|
{
|
||
|
GameObject obj = GetChild(path).gameObject;
|
||
|
OnDrag(obj, dragEvent);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// UI物体拖拽
|
||
|
/// </summary>
|
||
|
/// <param name="obj">拖拽的物体</param>
|
||
|
/// <param name="dragEndCallBack">拖拽结束回调</param>
|
||
|
internal void OnDragMove(GameObject obj, Action<PointerEventData> dragEndCallBack = null)
|
||
|
{
|
||
|
RectTransform canvas = UIManager.Inst.MainCanvas.GetComponent<RectTransform>(); ; //得到canvas的ugui坐标
|
||
|
RectTransform objRect = obj.GetComponent<RectTransform>(); //得到图片的ugui坐标
|
||
|
Vector2 offset = new Vector3(); //用来得到鼠标和图片的差值
|
||
|
Vector3 imgReduceScale = new Vector3(0.8f, 0.8f, 1); //设置图片缩放
|
||
|
Vector3 imgNormalScale = new Vector3(1, 1, 1); //正常大小
|
||
|
|
||
|
AddTriggersListener(obj, EventTriggerType.BeginDrag, (BaseEventData) =>
|
||
|
{
|
||
|
PointerEventData eventData = (PointerEventData)BaseEventData;
|
||
|
|
||
|
Vector2 mouseDown = eventData.position; //记录鼠标按下时的屏幕坐标
|
||
|
Vector2 mouseUguiPos = new Vector2(); //定义一个接收返回的ugui坐标
|
||
|
|
||
|
bool isRect = RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, mouseDown, eventData.enterEventCamera, out mouseUguiPos);
|
||
|
if (isRect) //如果在
|
||
|
{
|
||
|
//计算图片中心和鼠标点的差值
|
||
|
offset = objRect.anchoredPosition - mouseUguiPos;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
AddTriggersListener(obj, EventTriggerType.Drag, (BaseEventData) =>
|
||
|
{
|
||
|
PointerEventData eventData = (PointerEventData)BaseEventData;
|
||
|
Vector2 mouseDrag = eventData.position; //当鼠标拖动时的屏幕坐标
|
||
|
Vector2 uguiPos = new Vector2(); //用来接收转换后的拖动坐标
|
||
|
//和上面类似
|
||
|
bool isRect = RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, mouseDrag, eventData.enterEventCamera, out uguiPos);
|
||
|
|
||
|
if (isRect)
|
||
|
{
|
||
|
//设置图片的ugui坐标与鼠标的ugui坐标保持不变
|
||
|
objRect.anchoredPosition = offset + uguiPos;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
AddTriggersListener(obj, EventTriggerType.EndDrag, (BaseEventData) =>
|
||
|
{
|
||
|
dragEndCallBack?.Invoke((PointerEventData)BaseEventData);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
internal void OnDragMove(string path, Action<PointerEventData> action = null)
|
||
|
{
|
||
|
GameObject obj = GetChild(path).gameObject;
|
||
|
OnDragMove(obj, action);
|
||
|
}
|
||
|
#endregion
|
||
|
}
|
||
|
}
|