Files
XUniWindowController/Samples~/UiSample/UiSampleController.cs
2026-06-14 22:49:28 +08:00

604 lines
22 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* UniWindowController 的示例脚本
*
* Author: Kirurobo http://twitter.com/kirurobo
* License: MIT
*/
using System;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Kirurobo
{
/// <summary>
/// 使用 Toggle 开关 WindowController 设置的示例
/// </summary>
public class UiSampleController : MonoBehaviour
{
private UniWindowController uniwinc;
private UniWindowMoveHandle uniWinMoveHandle;
private RectTransform canvasRect;
private float mouseMoveSS = 0f; // 鼠标轨迹平方和。[px^2]
private float mouseMoveSSThreshold = 36f; // 点击(非拖拽)阈值。[px^2]
private Vector3 lastMousePosition; // 右键点击位置。
private float lastEventOccurredTime = -5f; // 上次事件发生的时间戳 [s]
private float eventMessageTimeout = 1f; // 在此时间段内显示事件消息 [s]
#if ENABLE_LEGACY_INPUT_MANAGER
private float touchDuration = 0f;
private float touchDurationThreshold = 0.5f; // 长按时间阈值。[s]
#endif
public Toggle transparentToggle;
public Slider alphaSlider;
public Toggle topmostToggle;
public Toggle bottommostToggle;
[FormerlySerializedAs("maximizedToggle")] public Toggle zoomedToggle;
public Toggle dragMoveToggle;
public Toggle allowDropToggle;
public Dropdown fitWindowDropdown;
public Toggle showBorderlineToggle;
public Button widthDownButton;
public Button widthUpButton;
public Button heightDownButton;
public Button heightUpButton;
public Dropdown transparentTypeDropdown;
public Dropdown hitTestTypeDropdown;
public Toggle clickThroughToggle;
public Image pickedColorImage;
public Text pickedColorText;
public Text messageText;
public Text clientSizeText;
public Button menuCloseButton;
public RectTransform menuPanel;
public RectTransform borderlinePanel;
/// <summary>
/// 初始化
/// </summary>
void Start()
{
// 查找 UniWindowController
uniwinc = UniWindowController.current;
// 查找 UniWindowDragMove
uniWinMoveHandle = GameObject.FindAnyObjectByType<UniWindowMoveHandle>();
// 获取 Canvas 的 RectTransform
if (menuPanel) canvasRect = menuPanel.GetComponentInParent<Canvas>().GetComponent<RectTransform>();
// 根据有效显示器数量创建选项
UpdateMonitorDropdown();
// 将 Toggle 的选中状态与当前状态同步
UpdateUI();
if (uniwinc)
{
// 操作 UI 时将其反映到窗口
transparentToggle?.onValueChanged.AddListener(val => uniwinc.isTransparent = val);
alphaSlider?.onValueChanged.AddListener(val => uniwinc.alphaValue = val);
topmostToggle?.onValueChanged.AddListener(val => uniwinc.isTopmost = val);
bottommostToggle?.onValueChanged.AddListener(val => uniwinc.isBottommost = val);
zoomedToggle?.onValueChanged.AddListener(val => uniwinc.isZoomed = val);
allowDropToggle?.onValueChanged.AddListener(val => uniwinc.allowDropFiles = val);
fitWindowDropdown?.onValueChanged.AddListener(val => SetFitToMonitor(val));
widthDownButton?.onClick.AddListener(() => uniwinc.windowSize += new Vector2(-100, 0));
widthUpButton?.onClick.AddListener(() => uniwinc.windowSize += new Vector2(+100, 0));
heightDownButton?.onClick.AddListener(() => uniwinc.windowSize += new Vector2(0, -100));
heightUpButton?.onClick.AddListener(() => uniwinc.windowSize += new Vector2(0, +100));
clickThroughToggle?.onValueChanged.AddListener(val => uniwinc.isClickThrough = val);
transparentTypeDropdown?.onValueChanged.AddListener(val => uniwinc.SetTransparentType((UniWindowController.TransparentType)val));
hitTestTypeDropdown?.onValueChanged.AddListener(val => uniwinc.hitTestType = (UniWindowController.HitTestType)val);
menuCloseButton?.onClick.AddListener(CloseMenu);
if (uniWinMoveHandle) dragMoveToggle?.onValueChanged.AddListener(val => uniWinMoveHandle.enabled = val);
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
// 如果不是 Windows则禁用透明方式选择
//if (transparentTypeDropdown) transparentTypeDropdown.interactable = false;
//if (transparentTypeDropdown) transparentTypeDropdown.enabled = false;
if (transparentTypeDropdown) transparentTypeDropdown.gameObject.SetActive(false);
#endif
// 添加事件
uniwinc.OnStateChanged += (type) =>
{
UpdateUI();
//Debug.Log("Window state changed: " + type);
ShowEventMessage("状态改变: " + type);
//ShowEventMessage("State changed: " + type + "4:isKey 2:canBecomeKey, 1:canBecomeMain : " + uniwinc.GetDebugInfo().ToString());
ShowClientSize();
};
uniwinc.OnMonitorChanged += () => {
UpdateMonitorDropdown();
UpdateUI();
ShowEventMessage("分辨率已改变!");
ShowClientSize();
};
uniwinc.OnDropFiles += files =>
{
ShowEventMessage(string.Join(Environment.NewLine, files));
};
}
// 即使 UinWinC 未就绪也能运行的 Listener
showBorderlineToggle?.onValueChanged.AddListener(val => borderlinePanel.gameObject.SetActive(val));
}
/// <summary>
/// 显示带超时功能的消息
/// </summary>
/// <param name="message"></param>
private void ShowEventMessage(string message)
{
lastEventOccurredTime = Time.time;
if (messageText) messageText.text = message;
Debug.Log(message);
}
/// <summary>
/// 每帧执行的处理
/// </summary>
private void Update()
{
// 更新点击测试相关的显示
UpdateHitTestUI();
// 显示窗口位置和大小以确认运行状态
if ((lastEventOccurredTime + eventMessageTimeout) < Time.time)
{
ShowWindowMetrics();
}
// 鼠标右键点击显示菜单。移动距离在阈值以下视为点击。
if (InputProxy.GetMouseButtonDown(1))
{
lastMousePosition = InputProxy.mousePosition;
ResetTouchDuration();
}
if (InputProxy.GetMouseButton(1))
{
mouseMoveSS += (InputProxy.mousePosition - lastMousePosition).sqrMagnitude;
}
if (InputProxy.GetMouseButtonUp(1))
{
if (mouseMoveSS < mouseMoveSSThreshold)
{
ShowMenu(lastMousePosition);
}
mouseMoveSS = 0f;
ResetTouchDuration();
}
#if ENABLE_LEGACY_INPUT_MANAGER
// 长按也可显示菜单
if (Input.touchSupported && (Input.touchCount > 0))
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
lastMousePosition = Input.mousePosition;
ResetTouchDuration();
}
if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
{
mouseMoveSS += touch.deltaPosition.sqrMagnitude;
touchDuration += touch.deltaTime;
}
if (touch.phase == TouchPhase.Ended)
{
if ((mouseMoveSS < mouseMoveSSThreshold) && (touchDuration >= touchDurationThreshold))
{
ShowMenu(lastMousePosition);
}
mouseMoveSS = 0f;
ResetTouchDuration();
}
}
#elif ENABLE_INPUT_SYSTEM
// 目前 New Input System 不支持触摸
// EnhancedTouch 不能与 InputAction 同时使用?
#endif
// 按键也可更改设置
if (uniwinc)
{
// 切换透明模式
if (InputProxy.GetKeyUp("t"))
{
uniwinc.isTransparent = !uniwinc.isTransparent;
}
// 切换始终置顶
if (InputProxy.GetKeyUp("f"))
{
uniwinc.isTopmost = !uniwinc.isTopmost;
}
// 切换始终置底
if (InputProxy.GetKeyUp("b"))
{
uniwinc.isBottommost = !uniwinc.isBottommost;
}
// 切换最大化
if (InputProxy.GetKeyUp("z"))
{
uniwinc.isZoomed = !uniwinc.isZoomed;
}
// 切换自由定位
if (InputProxy.GetKeyUp("p"))
{
uniwinc.isFreePositioningEnabled = !uniwinc.isFreePositioningEnabled;
}
}
// 测试打开文件面板
if (InputProxy.GetKeyUp("o"))
{
FilePanel.Settings ds = new FilePanel.Settings
{
flags = FilePanel.Flag.AllowMultipleSelection,
title = "打开!",
filters = new FilePanel.Filter[]{
new FilePanel.Filter("Image files", "png", "jpg", "jpeg"),
new FilePanel.Filter("All files", "*"),
},
initialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
initialFile = "test.png",
};
FilePanel.OpenFilePanel(ds, (files) => ShowEventMessage(string.Join(Environment.NewLine, files)));
}
// 测试保存文件面板
if (InputProxy.GetKeyUp("s"))
{
FilePanel.Settings ds = new FilePanel.Settings
{
flags = FilePanel.Flag.AllowMultipleSelection,
title = "保存!",
filters = new FilePanel.Filter[]{
//// TODO: 指定文件类型时macOS 保存对话框打开会失败
//// 给 NSSavePanel.accessoryView 指定内容时会发生此问题。
//// NSOpenPanel 继承自它,但不会发生此问题。
// new FilePanel.Filter("Shell script", "sh"),
// new FilePanel.Filter("Log", "log"),
// new FilePanel.Filter("Plain text", "txt"),
// new FilePanel.Filter("All files", "*"),
},
initialFile = "Test.txt",
initialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
};
FilePanel.SaveFilePanel(ds, (files) => ShowEventMessage(string.Join(Environment.NewLine, files)));
}
// 按下 [ESC] 时退出或停止播放
if (InputProxy.GetKeyUp("escape"))
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
/// <summary>
/// 重置持续触摸时间的记录
/// 仅在 Legacy Input Manager 中处理以避免警告
/// </summary>
void ResetTouchDuration() {
#if ENABLE_LEGACY_INPUT_MANAGER
touchDuration = 0f;
#endif
}
/// <summary>
/// 适配显示器下拉列表更改时的处理
/// </summary>
/// <param name="val"></param>
void SetFitToMonitor(int val)
{
if (!uniwinc) return;
if (val < 1)
{
// 下拉列表第一项为不适配
uniwinc.shouldFitMonitor = false;
// 允许更改最大化状态
if (zoomedToggle) zoomedToggle.interactable = true;
}
else
{
// 由于从第二项开始显示器编号减1
uniwinc.monitorToFit = val - 1;
uniwinc.shouldFitMonitor = true; // 从false变为true时才会移动窗口因此先指定显示器编号再更改
// 禁止更改最大化状态
if (zoomedToggle) zoomedToggle.interactable = false;
}
}
/// <summary>
/// 显示窗口位置和坐标
/// </summary>
void ShowWindowMetrics()
{
if (uniwinc)
{
var winPos = uniwinc.windowPosition;
//var curPos = uniwinc.GetClientCursorPosition();
OutputMessage(
"位置: " + winPos
+ "\n大小: " + uniwinc.windowSize
+ "\n相对鼠标:" + (uniwinc.cursorPosition - winPos)
//+ "\nScr. Cur.:" + curPos
+ "\nUnity鼠标:" + (Vector2)InputProxy.mousePosition
);
ShowClientSize();
}
}
/// <summary>
/// 聚焦时刷新 UI
/// </summary>
/// <param name="hasFocus"></param>
private void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
UpdateUI();
if (uniwinc)
{
OutputMessage("已聚焦");
}
else
{
OutputMessage("未找到 UniWindowController");
}
}
}
/// <summary>
/// 在指定坐标显示上下文菜单
/// </summary>
/// <param name="position">指定中心坐标</param>
private void ShowMenu(Vector2 position)
{
if (menuPanel)
{
Vector2 pos = position * (canvasRect.sizeDelta.x / Screen.width);
float w = menuPanel.rect.width;
float h = menuPanel.rect.height;
// 以指定坐标为中心进行位置调整
pos.y = Mathf.Max(Mathf.Min(pos.y, Screen.height - h / 2f), h / 2f); // 如果超出则向上移动
pos.x = Mathf.Max(Mathf.Min(pos.x, Screen.width - w / 2f), w / 2f); // 如果超出右侧则向左移动
menuPanel.pivot = Vector2.one * 0.5f; // 设置为中心
menuPanel.anchorMin = Vector2.zero;
menuPanel.anchorMax = Vector2.zero;
menuPanel.anchoredPosition = pos;
menuPanel.gameObject.SetActive(true);
}
}
/// <summary>
/// 关闭上下文菜单
/// </summary>
private void CloseMenu()
{
if (menuPanel)
{
menuPanel.gameObject.SetActive(false);
}
}
/// <summary>
/// 将实际状态反映到 UI 显示
/// </summary>
private void UpdateUI()
{
if (uniwinc)
{
if (transparentToggle)
{
transparentToggle.SetIsOnWithoutNotify(uniwinc.isTransparent);
}
if (alphaSlider)
{
alphaSlider.SetValueWithoutNotify(uniwinc.alphaValue);
}
if (topmostToggle)
{
topmostToggle.SetIsOnWithoutNotify(uniwinc.isTopmost);
}
if (bottommostToggle)
{
bottommostToggle.SetIsOnWithoutNotify(uniwinc.isBottommost);
}
if (zoomedToggle)
{
zoomedToggle.SetIsOnWithoutNotify(uniwinc.isZoomed);
}
if (allowDropToggle)
{
allowDropToggle.SetIsOnWithoutNotify(uniwinc.allowDropFiles);
}
if (dragMoveToggle)
{
dragMoveToggle.isOn = (uniWinMoveHandle && uniWinMoveHandle.isActiveAndEnabled);
}
if (fitWindowDropdown)
{
if (uniwinc.shouldFitMonitor)
{
fitWindowDropdown.value = uniwinc.monitorToFit + 1;
if (zoomedToggle) zoomedToggle.interactable = false;
}
else
{
fitWindowDropdown.value = 0;
if (zoomedToggle) zoomedToggle.interactable = true;
}
fitWindowDropdown.RefreshShownValue();
}
if (transparentTypeDropdown)
{
transparentTypeDropdown.value = (int)uniwinc.transparentType;
transparentTypeDropdown.RefreshShownValue();
}
if (hitTestTypeDropdown)
{
hitTestTypeDropdown.value = (int)uniwinc.hitTestType;
hitTestTypeDropdown.RefreshShownValue();
}
// 同时更新点击测试部分的显示
UpdateHitTestUI();
}
// 即使没有 UniWinC 也能运行的部分
if (showBorderlineToggle && borderlinePanel)
{
borderlinePanel.gameObject.SetActive(showBorderlineToggle.isOn);
}
}
/// <summary>
/// 更新点击测试相关的 UI
/// 由于会自动变化,需要比 UpdateUI() 更频繁地更新
/// </summary>
public void UpdateHitTestUI()
{
if (uniwinc)
{
if (clickThroughToggle)
{
clickThroughToggle.SetIsOnWithoutNotify(uniwinc.isClickThrough);
if (uniwinc.hitTestType == UniWindowController.HitTestType.None)
{
clickThroughToggle.interactable = true;
}
else
{
clickThroughToggle.interactable = false;
}
}
if (uniwinc.hitTestType == UniWindowController.HitTestType.Opacity && uniwinc.isTransparent)
{
if (pickedColorImage)
{
pickedColorImage.color = uniwinc.pickedColor;
}
if (pickedColorText)
{
pickedColorText.text = $"Alpha:{uniwinc.pickedColor.a:P0}";
pickedColorText.color = Color.black;
}
}
else
{
if (pickedColorImage)
{
pickedColorImage.color = Color.gray;
}
if (pickedColorText)
{
pickedColorText.text = $"颜色拾取器已禁用";
pickedColorText.color = Color.gray;
}
}
}
}
/// <summary>
/// 更新显示器选择下拉列表的选项
/// 之后需要调用 UpdateUI()
/// </summary>
void UpdateMonitorDropdown()
{
if (!fitWindowDropdown) return;
// 删除除第一项以外的选项
fitWindowDropdown.options.RemoveRange(1, fitWindowDropdown.options.Count - 1);
if (!uniwinc)
{
fitWindowDropdown.value = 0;
}
else
{
int count = UniWindowController.GetMonitorCount();
for (int i = 0; i < count; i++)
{
fitWindowDropdown.options.Add(new Dropdown.OptionData("适配显示器 " + i));
}
if (uniwinc.monitorToFit >= count)
{
uniwinc.monitorToFit = count - 1;
}
}
}
/// <summary>
/// 如果 UI 中有文本框,则显示消息。否则输出到控制台
/// </summary>
/// <param name="text"></param>
public void OutputMessage(string text)
{
if (messageText)
{
messageText.text = text;
}
else
{
Debug.Log(text);
}
}
/// <summary>
/// 如果 UI 中有客户端大小文本框,则显示消息。否则输出到控制台
/// </summary>
public void ShowClientSize()
{
if (!uniwinc) return;
string text = "Client " + uniwinc.clientSize;
if (clientSizeText)
{
clientSizeText.text = text;
}
else
{
Debug.Log(text);
}
}
}
}