2026-08-19 17:08:15 +08:00
|
|
|
|
using System.Collections.Generic;
|
2026-08-19 09:47:44 +08:00
|
|
|
|
using DG.Tweening;
|
|
|
|
|
|
using Kill.Managers;
|
2026-05-11 08:39:33 +08:00
|
|
|
|
using UnityEngine;
|
2026-08-19 09:47:44 +08:00
|
|
|
|
using UnityEngine.EventSystems;
|
2026-05-11 08:39:33 +08:00
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Kill.UI.Pages
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 首页设备列表项 - 显示单个设备信息
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// 支持左滑手势:左滑时内容元素向左滑出,露出右侧菜单,
|
|
|
|
|
|
/// 滑动超过菜单宽度1/3松手则完整展开,否则缩回
|
2026-05-11 08:39:33 +08:00
|
|
|
|
/// </summary>
|
2026-08-19 09:47:44 +08:00
|
|
|
|
public class HomePageDeviceItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
2026-05-11 08:39:33 +08:00
|
|
|
|
{
|
|
|
|
|
|
public Text deviceNameText;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 置顶标识
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public GameObject topIcon;
|
2026-05-11 08:39:33 +08:00
|
|
|
|
public GameObject selectedIndicator;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// <summary>菜单(子物体,锚点位于item最右侧),内容左移后露出</summary>
|
2026-08-19 09:47:44 +08:00
|
|
|
|
public RectTransform menuRect;
|
|
|
|
|
|
|
2026-05-11 08:39:33 +08:00
|
|
|
|
// 当前设备信息
|
|
|
|
|
|
public DeviceInfo deviceInfo;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 菜单按钮数组 0:重命名 1:分享 2:删除 3:置顶 4:取消置顶
|
|
|
|
|
|
/// 最多显示3个按钮 拥有的设备显示分享 被分享的设备显示删除
|
|
|
|
|
|
/// 根据置顶状态显示置顶/取消置顶按钮
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public Button[] menuButtons;
|
2026-08-19 09:47:44 +08:00
|
|
|
|
/// <summary>当前展开菜单的item(全局仅允许一个菜单展开)</summary>
|
|
|
|
|
|
private static HomePageDeviceItem openItem;
|
|
|
|
|
|
|
|
|
|
|
|
private float menuWidth;
|
|
|
|
|
|
private float currentOffset;
|
|
|
|
|
|
private float dragStartOffset;
|
|
|
|
|
|
private bool isMenuOpen;
|
|
|
|
|
|
private bool isDragging;
|
|
|
|
|
|
private Vector3 menuInitPos;
|
|
|
|
|
|
private Tweener tweener;
|
|
|
|
|
|
private Button button;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
private bool menuListenersBound;
|
|
|
|
|
|
/// <summary>除菜单外的内容子物体</summary>
|
|
|
|
|
|
private readonly List<RectTransform> contentRects = new List<RectTransform>();
|
|
|
|
|
|
/// <summary>各内容子物体的初始位置</summary>
|
|
|
|
|
|
private readonly List<Vector2> contentInitPositions = new List<Vector2>();
|
2026-08-19 09:47:44 +08:00
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
button = GetComponent<Button>();
|
2026-08-19 17:08:15 +08:00
|
|
|
|
// 点击item主体:关闭其他item展开的菜单,同时收起自己的
|
|
|
|
|
|
if (button != null)
|
|
|
|
|
|
button.onClick.AddListener(OnItemClicked);
|
2026-08-19 09:47:44 +08:00
|
|
|
|
InitSwipe();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// item主体点击:全局仅保留一个菜单,点击任意item时关闭其他item的展开菜单
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnItemClicked()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (openItem != null && openItem != this)
|
|
|
|
|
|
openItem.CloseMenu();
|
|
|
|
|
|
CloseMenu();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 09:47:44 +08:00
|
|
|
|
void OnDisable()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 防止拖拽中被禁用且未恢复的情况
|
|
|
|
|
|
if (button != null) button.interactable = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnDestroy()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 销毁时释放全局展开引用
|
|
|
|
|
|
if (openItem == this)
|
|
|
|
|
|
openItem = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 17:08:15 +08:00
|
|
|
|
public void InitDeviceItem(DeviceInfo device, bool isOwner)
|
2026-05-11 08:39:33 +08:00
|
|
|
|
{
|
|
|
|
|
|
deviceInfo = device;
|
|
|
|
|
|
deviceNameText.text = device.device_name;
|
|
|
|
|
|
selectedIndicator.SetActive(false);
|
2026-08-19 17:08:15 +08:00
|
|
|
|
if (topIcon != null) topIcon.SetActive(device.is_top);
|
|
|
|
|
|
SetupMenuButtons(isOwner);
|
|
|
|
|
|
BindMenuButtonClose();
|
2026-08-19 09:47:44 +08:00
|
|
|
|
InitSwipe();
|
2026-05-11 08:39:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据设备归属和置顶状态显示/隐藏菜单按钮
|
|
|
|
|
|
/// 0重命名 1分享 2删除 3置顶 4取消置顶
|
|
|
|
|
|
/// 拥有者:重命名+分享+置顶/取消置顶;被分享者:删除+置顶/取消置顶
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void SetupMenuButtons(bool isOwner)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (menuButtons == null || menuButtons.Length < 5) return;
|
|
|
|
|
|
menuButtons[0].gameObject.SetActive(true); // 重命名仅拥有者
|
|
|
|
|
|
menuButtons[1].gameObject.SetActive(isOwner); // 分享仅拥有者
|
|
|
|
|
|
menuButtons[2].gameObject.SetActive(!isOwner); // 删除(移除共享)仅被分享者
|
|
|
|
|
|
menuButtons[3].gameObject.SetActive(!deviceInfo.is_top); // 置顶:未置顶时显示
|
|
|
|
|
|
menuButtons[4].gameObject.SetActive(deviceInfo.is_top); // 取消置顶:已置顶时显示
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 点击菜单内任意按钮时自动收起菜单
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void BindMenuButtonClose()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (menuListenersBound || menuRect == null) return;
|
|
|
|
|
|
menuListenersBound = true;
|
|
|
|
|
|
foreach (var b in menuRect.GetComponentsInChildren<Button>(true))
|
|
|
|
|
|
b.onClick.AddListener(CloseMenu);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 08:39:33 +08:00
|
|
|
|
public void SetSelectedState(bool isSelected)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 设置选中状态
|
|
|
|
|
|
selectedIndicator.SetActive(isSelected);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 09:47:44 +08:00
|
|
|
|
/// <summary>
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// 初始化滑动数据:收集内容子物体、记录初始位置,重置为收起状态
|
2026-08-19 09:47:44 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void InitSwipe()
|
|
|
|
|
|
{
|
|
|
|
|
|
tweener?.Kill();
|
|
|
|
|
|
if (menuRect == null) return;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
// 先复位已记录的内容,再重新收集(兼容item复用)
|
|
|
|
|
|
ApplyOffset(0);
|
|
|
|
|
|
contentRects.Clear();
|
|
|
|
|
|
contentInitPositions.Clear();
|
|
|
|
|
|
foreach (Transform child in transform)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (child == menuRect) continue;
|
|
|
|
|
|
RectTransform rt = child as RectTransform;
|
|
|
|
|
|
if (rt == null) continue;
|
|
|
|
|
|
contentRects.Add(rt);
|
|
|
|
|
|
contentInitPositions.Add(rt.anchoredPosition);
|
|
|
|
|
|
}
|
2026-08-19 09:47:44 +08:00
|
|
|
|
menuWidth = menuRect.sizeDelta.x;
|
|
|
|
|
|
menuInitPos = menuRect.anchoredPosition3D;
|
|
|
|
|
|
isMenuOpen = false;
|
|
|
|
|
|
currentOffset = 0;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
ApplyOffset(0);
|
2026-08-19 09:47:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnBeginDrag(PointerEventData eventData)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (menuRect == null || menuWidth <= 0) return;
|
|
|
|
|
|
// 拖拽滑动时禁用按钮点击,避免与菜单滑动同时触发
|
|
|
|
|
|
if (button != null) button.interactable = false;
|
|
|
|
|
|
// 其他item的菜单展开时,先收起,保证全局只有一个菜单
|
|
|
|
|
|
if (openItem != null && openItem != this)
|
|
|
|
|
|
openItem.CloseMenu();
|
|
|
|
|
|
isDragging = true;
|
|
|
|
|
|
currentOffset = isMenuOpen ? -menuWidth : 0;
|
|
|
|
|
|
dragStartOffset = currentOffset;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
ApplyOffset(currentOffset);
|
2026-08-19 09:47:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnDrag(PointerEventData eventData)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isDragging) return;
|
|
|
|
|
|
// 累加位移并限制在 [-菜单宽度, 0],只允许左滑露出菜单
|
|
|
|
|
|
currentOffset += eventData.delta.x;
|
|
|
|
|
|
currentOffset = Mathf.Clamp(currentOffset, -menuWidth, 0);
|
2026-08-19 17:08:15 +08:00
|
|
|
|
ApplyOffset(currentOffset);
|
2026-08-19 09:47:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnEndDrag(PointerEventData eventData)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isDragging) return;
|
|
|
|
|
|
isDragging = false;
|
|
|
|
|
|
// 恢复按钮点击
|
|
|
|
|
|
if (button != null) button.interactable = true;
|
|
|
|
|
|
// 右滑超过菜单宽度的1/5则直接缩回(方便关闭已展开的菜单)
|
|
|
|
|
|
float dragDistance = currentOffset - dragStartOffset;
|
|
|
|
|
|
if (dragDistance > menuWidth / 5f)
|
|
|
|
|
|
{
|
|
|
|
|
|
AnimateTo(0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 左滑展示超过菜单宽度的1/3则完整滑出,否则缩回
|
|
|
|
|
|
float target = currentOffset < -menuWidth / 3f ? -menuWidth : 0;
|
|
|
|
|
|
AnimateTo(target);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 收起菜单(供菜单按钮点击等外部调用)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void CloseMenu()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isMenuOpen) return;
|
|
|
|
|
|
AnimateTo(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-19 17:08:15 +08:00
|
|
|
|
/// 将内容子物体和菜单一起平移到指定偏移(内容左移、菜单从屏幕外滑入露出)
|
2026-08-19 09:47:44 +08:00
|
|
|
|
/// </summary>
|
2026-08-19 17:08:15 +08:00
|
|
|
|
private void ApplyOffset(float offset)
|
2026-08-19 09:47:44 +08:00
|
|
|
|
{
|
2026-08-19 17:08:15 +08:00
|
|
|
|
for (int i = 0; i < contentRects.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector2 pos = contentInitPositions[i];
|
|
|
|
|
|
pos.x += offset;
|
|
|
|
|
|
contentRects[i].anchoredPosition = pos;
|
|
|
|
|
|
}
|
|
|
|
|
|
Vector3 menuPos = menuInitPos;
|
|
|
|
|
|
menuPos.x += offset;
|
|
|
|
|
|
menuRect.anchoredPosition3D = menuPos;
|
2026-08-19 09:47:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 平滑动画到目标偏移
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void AnimateTo(float target)
|
|
|
|
|
|
{
|
|
|
|
|
|
tweener?.Kill();
|
|
|
|
|
|
bool open = target < 0;
|
|
|
|
|
|
if (open)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 已有其他item的菜单展开时,先收起,保证全局只有一个菜单
|
|
|
|
|
|
if (openItem != null && openItem != this)
|
|
|
|
|
|
openItem.CloseMenu();
|
|
|
|
|
|
openItem = this;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (openItem == this)
|
|
|
|
|
|
{
|
|
|
|
|
|
openItem = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
isMenuOpen = open;
|
|
|
|
|
|
tweener = DOTween.To(() => currentOffset, v =>
|
|
|
|
|
|
{
|
|
|
|
|
|
currentOffset = v;
|
2026-08-19 17:08:15 +08:00
|
|
|
|
ApplyOffset(v);
|
2026-08-19 09:47:44 +08:00
|
|
|
|
}, target, 0.25f).SetEase(Ease.OutCubic);
|
|
|
|
|
|
}
|
2026-05-11 08:39:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|