/** * UniWindowController 的示例脚本 * * Author: Kirurobo http://twitter.com/kirurobo * License: MIT */ using System; using UnityEngine; using UnityEngine.Serialization; using UnityEngine.UI; namespace Kirurobo { /// /// 使用 Toggle 开关 WindowController 设置的示例 /// 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; /// /// 初始化 /// void Start() { // 查找 UniWindowController uniwinc = UniWindowController.current; // 查找 UniWindowDragMove uniWinMoveHandle = GameObject.FindAnyObjectByType(); // 获取 Canvas 的 RectTransform if (menuPanel) canvasRect = menuPanel.GetComponentInParent().GetComponent(); // 根据有效显示器数量创建选项 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)); } /// /// 显示带超时功能的消息 /// /// private void ShowEventMessage(string message) { lastEventOccurredTime = Time.time; if (messageText) messageText.text = message; Debug.Log(message); } /// /// 每帧执行的处理 /// 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 } } /// /// 重置持续触摸时间的记录 /// 仅在 Legacy Input Manager 中处理以避免警告 /// void ResetTouchDuration() { #if ENABLE_LEGACY_INPUT_MANAGER touchDuration = 0f; #endif } /// /// 适配显示器下拉列表更改时的处理 /// /// 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; } } /// /// 显示窗口位置和坐标 /// 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(); } } /// /// 聚焦时刷新 UI /// /// private void OnApplicationFocus(bool hasFocus) { if (hasFocus) { UpdateUI(); if (uniwinc) { OutputMessage("已聚焦"); } else { OutputMessage("未找到 UniWindowController"); } } } /// /// 在指定坐标显示上下文菜单 /// /// 指定中心坐标 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); } } /// /// 关闭上下文菜单 /// private void CloseMenu() { if (menuPanel) { menuPanel.gameObject.SetActive(false); } } /// /// 将实际状态反映到 UI 显示 /// 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); } } /// /// 更新点击测试相关的 UI /// 由于会自动变化,需要比 UpdateUI() 更频繁地更新 /// 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; } } } } /// /// 更新显示器选择下拉列表的选项 /// 之后需要调用 UpdateUI() /// 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; } } } /// /// 如果 UI 中有文本框,则显示消息。否则输出到控制台 /// /// public void OutputMessage(string text) { if (messageText) { messageText.text = text; } else { Debug.Log(text); } } /// /// 如果 UI 中有客户端大小文本框,则显示消息。否则输出到控制台 /// public void ShowClientSize() { if (!uniwinc) return; string text = "Client " + uniwinc.clientSize; if (clientSizeText) { clientSizeText.text = text; } else { Debug.Log(text); } } } }