/* * UniWindowController.cs * * Author: Kirurobo http://twitter.com/kirurobo * License: MIT */ using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; #if UNITY_EDITOR using UnityEditor; using System.Reflection; using UnityEngine.Events; using System.Linq; #endif #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif namespace Kirurobo { /// @cond DOXYGEN_SHOW_INTERNAL_CLASSES /// /// 使布尔属性可编辑 /// [System.AttributeUsage(System.AttributeTargets.Field, Inherited = true, AllowMultiple = false)] public class EditablePropertyAttribute : UnityEngine.PropertyAttribute { } /// /// 将属性设置为只读 /// [System.AttributeUsage(System.AttributeTargets.Field, Inherited = true, AllowMultiple = false)] public class ReadOnlyAttribute : UnityEngine.PropertyAttribute { } /// @endcond /// /// Windows/Mac 统一窗口控制器 /// public class UniWindowController : MonoBehaviour { /// /// 与 UniWinCore.TransparentType 相同 /// public enum TransparentType : int { None = 0, Alpha = 1, ColorKey = 2, } /// /// 指定点击测试方法(即切换点击穿透) /// public enum HitTestType : int { None = 0, Opacity = 1, Raycast = 2, } /// /// 标识 OnStateChanged 事件发生时的类型 /// [Flags] public enum WindowStateEventType : int { None = 0, StyleChanged = 1, Resized = 2, // 以下规格可能会有变化 TopMostEnabled = 16 + 1 + 8, TopMostDisabled = 16 + 1, BottomMostEnabled = 32 + 1 + 8, BottomMostDisabled = 32 + 1, WallpaperModeEnabled = 64 + 1 + 8, WallpaperModeDisabled = 64 + 1, }; /// /// 鼠标按键 /// [Flags] public enum MouseButton : int { None = 0, Left = 1, Right = 2, Middle = 4, } /// /// 修饰键 /// [Flags] public enum ModifierKey : int { None = 0, Alt = 1, Control = 2, Shift = 4, Command = 8, } /// /// 获取当前 UniWindowController 实例 /// public static UniWindowController current => _current ? _current : FindOrCreateInstance(); private static UniWindowController _current; /// /// 底层类 /// private UniWinCore _uniWinCore = null; /// /// 此窗口是否接收鼠标事件 /// public bool isClickThrough { get { return _isClickThrough; } set { SetClickThrough(value); } } private bool _isClickThrough = false; /// /// 此窗口是否透明 /// public bool isTransparent { get { return _isTransparent; } set { SetTransparent(value); } } [SerializeField, EditableProperty, Tooltip("选中后启动时设置为透明")] private bool _isTransparent = false; /// /// 窗口透明度(0.0 到 1.0) /// public float alphaValue { get { return _alphaValue; } set { SetAlphaValue(value); } } [SerializeField, EditableProperty, Tooltip("窗口透明度"), Range(0f, 1f)] private float _alphaValue = 1.0f; /// /// 此窗口是否置顶 /// public bool isTopmost { get { return ((_uniWinCore == null) ? _isTopmost : _isTopmost = _uniWinCore.IsTopmost); } set { SetTopmost(value); } } [SerializeField, EditableProperty, Tooltip("选中后启动时置顶")] private bool _isTopmost = false; /// /// 此窗口是否置底 /// public bool isBottommost { get { return ((_uniWinCore == null) ? _isBottommost : _isBottommost = _uniWinCore.IsBottommost); } set { SetBottommost(value); } } [SerializeField, EditableProperty, Tooltip("选中后启动时置底")] private bool _isBottommost = false; /// /// 此窗口是否最大化 /// public bool isZoomed { get { return ((_uniWinCore == null) ? _isZoomed : _isZoomed = _uniWinCore.GetZoomed()); } set { SetZoomed(value); } } [SerializeField, EditableProperty, Tooltip("选中后启动时最大化")] private bool _isZoomed = false; /// /// 此窗口是否适配到显示器 /// public bool shouldFitMonitor { get { return _shouldFitMonitor; } set { FitToMonitor(value, _monitorToFit); } } [SerializeField, EditableProperty, Tooltip("选中后将窗口适配到显示器")] private bool _shouldFitMonitor = false; /// /// 适配窗口的目标显示器索引(0, 1, ...) /// public int monitorToFit { get { return _monitorToFit; } set { FitToMonitor(_shouldFitMonitor, value); } } private int _monitorToFit = 0; /// /// 启用/禁用接受文件拖放 /// public bool allowDropFiles { get { return _allowDropFiles; } set { SetAllowDrop(value); } } [SerializeField, EditableProperty, Tooltip("启用文件或文件夹拖放")] private bool _allowDropFiles = false; /// /// 是否启用点击穿透自动判定 /// 如果禁用,可以手动修改 isClickThrough /// public bool isHitTestEnabled = true; /// /// 点击穿透自动判定的方法 /// [Tooltip("选择方法")] public HitTestType hitTestType = HitTestType.Opacity; /// /// 点击穿透判定方法为不透明度时使用的阈值 /// 鼠标下方像素的 alpha 达到此值时判定为命中 /// [Tooltip("点击测试类型为 Opacity 时可用"), RangeAttribute(0f, 1f)] public float opacityThreshold = 0.1f; /// /// 点击穿透判定方法为 raycast 时的最远距离 /// private float raycastMaxDepth = 100.0f; /// /// 启用后,窗口透明时会自动将相机背景改为单色黑色透明 /// [Header("高级设置")] [Tooltip("窗口透明时更改相机背景")] public bool autoSwitchCameraBackground = true; /// /// 启用后,启动时如果是全屏则会强制退出全屏 /// /// 用于即使启动对话框设置了全屏,也能切换为窗口模式 /// 仅在启动时生效 /// 在 Mac 上,即使强制退出全屏,似乎仍会变成另一个画面,效果不大 /// [Tooltip("启动时强制窗口模式")] public bool forceWindowed = false; /// /// 相机实例 /// [Tooltip("未设置时使用主相机")] public Camera currentCamera; /// /// 透明方式的指定 /// [Header("仅限 Windows")] [Tooltip("选择透明方式。*仅 Windows 可用")] public TransparentType transparentType = TransparentType.Alpha; /// /// 当透明类型为 ColorKey 时使用的键色 /// [Tooltip("将在窗口下次变为透明时使用")] public Color32 keyColor = new Color32(0x01, 0x00, 0x01, 0x00); /// /// 在 macOS 上,是否允许将窗口放置在菜单栏上方 /// public bool isFreePositioningEnabled { get { return ((_uniWinCore == null) ? _isFreePositioningEnabled : _isFreePositioningEnabled = _uniWinCore.IsFreePositioningEnabled); } set { SetFreePositioning(value); } } [Header("仅限 macOS")] [Tooltip("禁用 constrainFrameRect() *仅 macOS 可用")] [SerializeField, EditableProperty] private bool _isFreePositioningEnabled = false; /// /// 鼠标指针是否在不透明像素或物体上 /// [Header("状态")] [SerializeField, ReadOnly, Tooltip("鼠标指针是否在不透明像素上?(只读)")] private bool onObject = true; /// /// 鼠标指针下方的像素颜色。(只读) /// [SerializeField, ReadOnly, Tooltip("鼠标指针下方的像素颜色。(只读)")] public Color pickedColor; /// /// 获取/设置窗口坐标 /// public Vector2 windowPosition { get { return (_uniWinCore != null ? _uniWinCore.GetWindowPosition() : Vector2.zero); } set { _uniWinCore?.SetWindowPosition(value); } } /// /// 获取/设置窗口大小 /// public Vector2 windowSize { get { return (_uniWinCore != null ? _uniWinCore.GetWindowSize() : Vector2.zero); } set { _uniWinCore?.SetWindowSize(value); } } /// /// 获取客户区域大小 /// public Vector2 clientSize { get { return (_uniWinCore != null ? _uniWinCore.GetClientSize() : Vector2.zero); } } /// /// 获取/设置鼠标光标坐标 /// public Vector2 cursorPosition { get { return UniWinCore.GetCursorPosition(); } set { UniWinCore.SetCursorPosition(value); } } /// /// 初始状态的窗口位置和大小 /// private Rect originalWindowRectangle; // 存储相机原有背景,以便在窗口透明时替换为 alpha 为零的黑色 private CameraClearFlags originalCameraClearFlags; private Color originalCameraBackground; /// /// 存储光标下方 1 像素颜色的纹理 /// private Texture2D colorPickerTexture = null; /// /// Raycast 使用的鼠标事件信息 /// private PointerEventData pointerEventData; /// /// Raycast 时的图层遮罩 /// private int hitTestLayerMask; /// /// 窗口样式改变时发生 /// public event OnStateChangedDelegate OnStateChanged; public delegate void OnStateChangedDelegate(WindowStateEventType type); public delegate void FilesDelegate(string[] files); /// /// 文件或文件夹被拖放后发生 /// public event FilesDelegate OnDropFiles; /// /// 显示器设置或分辨率改变时发生 /// public event OnMonitorChangedDelegate OnMonitorChanged; public delegate void OnMonitorChangedDelegate(); // 用于初始化 void Awake() { // 用作单例。如果已有实例,则销毁自身 if (this != current) { Destroy(this.gameObject); return; } else { _current = this; } // 强制退出全屏。在编辑器中不做任何操作 #if !UNITY_EDITOR if (forceWindowed && Screen.fullScreen) { Screen.fullScreen = false; } #endif if (!currentCamera) { // 寻找主相机 currentCamera = Camera.main; // 如果主相机未找到,则使用 Find 查找 //if (!currentCamera) //{ // currentCamera = GameObject.FindAnyObjectByType(); //} } // 记录相机原始背景 if (currentCamera) { originalCameraClearFlags = currentCamera.clearFlags; originalCameraBackground = currentCamera.backgroundColor; } // 鼠标事件信息 pointerEventData = new PointerEventData(EventSystem.current); // 使用 Ignore Raycast 之外的图层作为有效遮罩 hitTestLayerMask = ~LayerMask.GetMask("Ignore Raycast"); // 准备用于提取鼠标下方像素颜色的纹理 colorPickerTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); // 创建窗口控制实例 _uniWinCore = new UniWinCore(); } /// /// 适配到指定显示器 /// private void UpdateMonitorFitting() { if (!_shouldFitMonitor) return; int monitors = UniWinCore.GetMonitorCount(); int targetMonitorIndex = _monitorToFit; if (targetMonitorIndex < 0) { targetMonitorIndex = 0; } if (monitors <= targetMonitorIndex) { targetMonitorIndex = monitors - 1; } if (targetMonitorIndex >= 0) { _uniWinCore.FitToMonitor(targetMonitorIndex); } } /// /// 查找现有实例或创建新实例 /// /// private static UniWindowController FindOrCreateInstance() { #if UNITY_2022_1_OR_NEWER var instance = GameObject.FindAnyObjectByType(); #else var instance = GameObject.FindObjectOfType(); #endif // 目前禁止自动创建 // // 场景中未找到时创建新实例 // if (!instance) // { // var obj = new GameObject(nameof(UniWindowController)); // obj.AddComponent(); // } return instance; } void Start() { //// New Input System 存在兼容性问题,用于验证输出 // #if ENABLE_LEGACY_INPUT_MANAGER // Debug.Log("使用旧版输入管理器。"); // #elif ENABLE_INPUT_SYSTEM // Debug.Log("使用新版输入系统。"); // Debug.Log("后台运行 " + Mouse.current.canRunInBackground); // #else // Debug.Log("鼠标位置不可用。"); // #endif // 启动获取鼠标光标下颜色的协程 StartCoroutine(HitTestCoroutine()); // 获取初始窗口大小和位置 StoreOriginalWindowRectangle(); // 适配到所选显示器 OnMonitorChanged += UpdateMonitorFitting; UpdateMonitorFitting(); } void OnDestroy() { if (_uniWinCore != null) { _uniWinCore.Dispose(); } // 同时销毁实例 if (this == current) { _current = null; } } void StoreOriginalWindowRectangle() { if (_uniWinCore != null) { var size = _uniWinCore.GetWindowSize(); var pos = _uniWinCore.GetWindowPosition(); originalWindowRectangle = new Rect(pos, size); } } // 每一帧调用 Update void Update() { // 如果尚未获取自身窗口,则获取 if (_uniWinCore == null || !_uniWinCore.IsActive) { UpdateTargetWindow(); } else { _uniWinCore.Update(); } // 处理事件 UpdateEvents(); // 更新键盘、鼠标操作对下方窗口的穿透状态 UpdateClickThrough(); } /// /// 检查并处理 UniWinCore 事件 /// private void UpdateEvents() { if (_uniWinCore == null) return; if (_uniWinCore.ObserveDroppedFiles(out var droppedFiles)) { OnDropFiles?.Invoke(droppedFiles); } if (_uniWinCore.ObserveMonitorChanged()) { OnMonitorChanged?.Invoke(); } if (_uniWinCore.ObserveWindowStyleChanged(out var type)) { // // 指定了适配显示器时,最大化被取消的情况下 // if (shouldFitMonitor && !uniWinCore.GetZoomed()) // { // //StartCoroutine("ForceZoomed"); // 延迟强制最大化 // //SetZoomed(true); // 强制最大化 ←不一定生效 // //shouldFitMonitor = false; // 禁用适配 // } if (_shouldFitMonitor) StartCoroutine("ForceZoomed"); // 延迟强制最大化 OnStateChanged?.Invoke((WindowStateEventType)type); } } IEnumerator ForceZoomed() { yield return new WaitForSeconds(0.5f); if (_shouldFitMonitor && !_uniWinCore.GetZoomed()) SetZoomed(true); yield return null; } /// /// 指定相机。如果之前有相机,则恢复其背景 /// /// public void SetCamera(Camera newCamera) { // 如果相机已更改,恢复其设置 if (newCamera != currentCamera) { SetCameraBackground(false); } currentCamera = newCamera; // 记录相机的原始背景 if (currentCamera) { originalCameraClearFlags = currentCamera.clearFlags; originalCameraBackground = currentCamera.backgroundColor; SetCameraBackground(_isTransparent); } } /// /// 将鼠标/触摸操作穿透到下方窗口 /// /// void SetClickThrough(bool isThrough) { _uniWinCore?.EnableClickThrough(isThrough); _isClickThrough = isThrough; } /// /// 根据像素颜色切换操作接收状态 /// void UpdateClickThrough() { // 没有自动点击测试则结束 if (!isHitTestEnabled || hitTestType == HitTestType.None) return; // 鼠标光标隐藏状态视为在透明像素上 bool hit = (onObject); if (_isClickThrough) { // 如果当前是点击穿透状态,仅在命中时取消穿透 if (hit) { SetClickThrough(false); } } else { // 如果当前不是穿透状态,仅在透明且未命中时启用穿透 if (isTransparent && !hit) { SetClickThrough(true); } } } /// /// 在协程中重复进行光标下颜色或 Raycast 的点击测试 /// 使用 WaitForEndOfFrame() 所以采用协程 /// /// private IEnumerator HitTestCoroutine() { while (Application.isPlaying) { yield return new WaitForEndOfFrame(); // Windows 下,如果是单色透明则点击测试由 OS 负责,所以始终为命中 #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN if (transparentType == TransparentType.ColorKey) { onObject = true; } else #endif if (hitTestType == HitTestType.Opacity) { HitTestByOpaquePixel(); } else if (hitTestType == HitTestType.Raycast) { HitTestByRaycast(); } else { // 无点击测试时始终为 true onObject = true; } } yield return null; } /// /// 将屏幕上的鼠标坐标换算为 Unity 的屏幕坐标系 /// private Vector2 GetClientCursorPosition() { // New Input System 在没有焦点时无法获取鼠标坐标,因此自行计算 Vector2 mousePos = UniWinCore.GetCursorPosition(); Vector2 winPos = windowPosition; Rect clientRect = _uniWinCore.GetClientRectangle(); Vector2 unityPos = new Vector2( (mousePos.x - winPos.x - clientRect.x) * Screen.width / clientRect.width, (mousePos.y - winPos.y - clientRect.y) * Screen.height / clientRect.height ); // // 调试用 // // 与 Unity 获取的值进行比较 // #if ENABLE_LEGACY_INPUT_MANAGER // Vector2 position = Input.mousePosition; // #elif ENABLE_INPUT_SYSTEM // Vector2 position = Mouse.current.position.ReadValue(); // #endif // if (!position.Equals(unityPos)) // { // Debug.LogWarning("鼠标位置差异 : " + position + " / " + unityPos); // } // 在编辑器中始终使用 Unity 功能获取鼠标坐标 // Game 窗口可能不是唯一窗口,或 Scale 不同,无法简单计算 #if UNITY_EDITOR #if ENABLE_LEGACY_INPUT_MANAGER return Input.mousePosition; #elif UNITY_EDITOR && ENABLE_INPUT_SYSTEM return Mouse.current.position.ReadValue(); #else return unityPos; #endif #else return unityPos; #endif } /// /// 检查鼠标下方是否有不透明像素 /// private void HitTestByOpaquePixel() { Vector2 mousePos = GetClientCursorPosition(); // 检查鼠标坐标 if (GetOnOpaquePixel(mousePos)) { //Debug.Log("鼠标 " + mousePos); onObject = true; //activeFingerId = -1; // 取消触摸追踪 return; } else { onObject = false; } } /// /// 返回指定坐标的像素是否透明 /// /// 坐标[px]。必须在绘制范围内。 /// private bool GetOnOpaquePixel(Vector2 mousePos) { float w = Screen.width; float h = Screen.height; //Debug.Log(w + ", " + h); // 在屏幕外则视为透明 if ( mousePos.x < 0 || mousePos.x >= w || mousePos.y < 0 || mousePos.y >= h ) { return false; } // 如果不是透明状态,在范围内则视为不透明 if (!_isTransparent) return true; // 如果是 LayeredWindow,点击测试由 OS 负责,窗口内返回true if (transparentType == TransparentType.ColorKey) return true; // 根据指定坐标的绘制结果进行判断 try // 在 WaitForEndOfFrame 时机执行的话,不需要 try 应该也没问题 { // 参考 http://tsubakit1.hateblo.jp/entry/20131203/1386000440 colorPickerTexture.ReadPixels(new Rect(mousePos, Vector2.one), 0, 0); Color color = colorPickerTexture.GetPixels32()[0]; pickedColor = color; return (color.a >= opacityThreshold); // alpha 达到阈值则视为不透明 } catch (System.Exception ex) { Debug.LogError(ex.Message); return false; } } /// /// 检查鼠标下方是否有对象 /// private void HitTestByRaycast() { Vector2 position = GetClientCursorPosition(); // // 判断是否在 uGUI 上 var raycastResults = new List(); pointerEventData.position = position; EventSystem.current.RaycastAll(pointerEventData, raycastResults); foreach (var result in raycastResults) { // 考虑图层遮罩(Ignore Raycast 以外的命中) if (((1 << result.gameObject.layer) & hitTestLayerMask) > 0) { onObject = true; return; } } // 如果忽略图层限制直接命中,使用下面代码 // // 如果判定为在 uGUI 上,则结束 // if (EventSystem.current.IsPointerOverGameObject()) // { // onObject = true; // return; // } if (currentCamera && currentCamera.isActiveAndEnabled) { Ray ray = currentCamera.ScreenPointToRay(position); // 判断是否在 3D 对象上 if (Physics.Raycast(ray, out _, raycastMaxDepth)) { onObject = true; return; } // 判断是否在 2D 对象上 var rayHit2D = Physics2D.GetRayIntersection(ray); Debug.DrawRay(ray.origin, ray.direction, Color.blue, 2f, false); if (rayHit2D.collider != null) { onObject = true; return; } } else { // 如果相机无效,则获取主相机 currentCamera = Camera.main; } // 若均未命中,则判定为不在对象上 onObject = false; } /// /// 如果自己的窗口句柄不确定,则重新查找 /// private void UpdateTargetWindow() { if (_uniWinCore == null) { _uniWinCore = new UniWinCore(); } // 如果尚未获取窗口,则执行获取处理 if (!_uniWinCore.IsActive) { _uniWinCore.AttachMyWindow(); // 获取到窗口后设置初始值 if (_uniWinCore.IsActive) { _uniWinCore.SetTransparentType((UniWinCore.TransparentType)transparentType); _uniWinCore.SetKeyColor(keyColor); _uniWinCore.SetAlphaValue(_alphaValue); SetTransparent(_isTransparent); if (_isBottommost) { SetBottommost(_isBottommost); } else { SetTopmost(_isTopmost); } SetZoomed(_isZoomed); SetClickThrough(_isClickThrough); SetAllowDrop(_allowDropFiles); SetFreePositioning(_isFreePositioningEnabled); // 获取窗口时执行与显示器更改相同的处理 OnMonitorChanged?.Invoke(); } } else { #if UNITY_EDITOR // 在编辑器中,由于 Game 视图可能被关闭或停靠,如果发生变化则更改目标窗口 // 如果活动窗口与当前目标相同,则不执行任何操作 _uniWinCore.AttachMyActiveWindow(); #endif } } /// /// 窗口焦点变化时调用 /// /// private void OnApplicationFocus(bool focus) { if (focus) { UpdateTargetWindow(); // 获取焦点的瞬间,强制关闭点击穿透 if (_isTransparent && isHitTestEnabled && transparentType != TransparentType.ColorKey) { SetClickThrough(false); } } } /// /// 窗口变为透明状态时,自动将背景改为透明单色 /// /// void SetCameraBackground(bool transparent) { // 如果未指定相机或未启用自动切换,则不执行任何操作 if (!currentCamera || !autoSwitchCameraBackground) return; // 如果需要透明,则将相机背景改为透明色 if (transparent) { // 如果尚未透明化,则记忆当前相机信息 if (!isTransparent) { originalCameraClearFlags = currentCamera.clearFlags; originalCameraBackground = currentCamera.backgroundColor; } currentCamera.clearFlags = CameraClearFlags.SolidColor; if (transparentType == TransparentType.ColorKey) { currentCamera.backgroundColor = keyColor; } else { currentCamera.backgroundColor = Color.clear; } } else { currentCamera.clearFlags = originalCameraClearFlags; currentCamera.backgroundColor = originalCameraBackground; } } /// /// 切换透明化状态 /// /// private void SetTransparent(bool transparent) { SetCameraBackground(transparent); _isTransparent = transparent; #if !UNITY_EDITOR if (_uniWinCore != null) { _uniWinCore.EnableTransparent(transparent); } #endif UpdateClickThrough(); } /// /// 更改透明方式 /// /// public void SetTransparentType(TransparentType type) { if (_uniWinCore != null) { // 如果正在透明中,则先解除再重新透明 if (_isTransparent) { SetTransparent(false); _uniWinCore.SetTransparentType((UniWinCore.TransparentType)type); transparentType = type; SetTransparent(true); } else { _uniWinCore.SetTransparentType((UniWinCore.TransparentType)type); transparentType = type; } } } /// /// 设置窗口透明度 /// /// 0.0 到 1.0 private void SetAlphaValue(float alpha) { _alphaValue = alpha; _uniWinCore?.SetAlphaValue(_alphaValue); } /// /// 切换置顶 /// /// private void SetTopmost(bool topmost) { //if (_isTopmost == topmost) return; if (_uniWinCore == null) return; _uniWinCore.EnableTopmost(topmost); _isTopmost = _uniWinCore.IsTopmost; _isBottommost = _uniWinCore.IsBottommost; } /// /// 切换始终置底 /// /// private void SetBottommost(bool bottommost) { if (_uniWinCore == null) return; _uniWinCore.EnableBottommost(bottommost); _isBottommost = _uniWinCore.IsBottommost; _isTopmost = _uniWinCore.IsTopmost; } /// /// 最大化/还原 /// /// private void SetZoomed(bool zoomed) { if (_uniWinCore == null) return; _uniWinCore.SetZoomed(zoomed); _isZoomed = _uniWinCore.GetZoomed(); } private void SetAllowDrop(bool enabled) { if (_uniWinCore == null) return; _uniWinCore.SetAllowDrop(enabled); _allowDropFiles = enabled; } /// /// 在 macOS 上,允许将窗口放置在包含菜单栏上方在内的任意位置 /// /// private void SetFreePositioning(bool enabled) { if (_uniWinCore == null) return; _uniWinCore.EnableFreePositioning(enabled); _isFreePositioningEnabled = _uniWinCore.IsFreePositioningEnabled; } /// /// 获取连接的显示器数量 /// /// public static int GetMonitorCount() { //if (uniWinCore == null) return 0; return UniWinCore.GetMonitorCount(); } /// /// 获取显示器的位置和大小 /// /// /// public static Rect GetMonitorRect(int index) { if (UniWinCore.GetMonitorRectangle(index, out Vector2 position, out Vector2 size)) { return new Rect(position, size); } return Rect.zero; } /// /// 适配到指定显示器 /// /// private bool FitToMonitor(bool shouldFit, int monitorIndex) { if (_uniWinCore == null) { _shouldFitMonitor = shouldFit; _monitorToFit = monitorIndex; return false; } if (shouldFit) { if (!_shouldFitMonitor) { // 之前未适配的情况 _monitorToFit = monitorIndex; _shouldFitMonitor = shouldFit; UpdateMonitorFitting(); } else { if (_monitorToFit != monitorIndex) { // 适配的显示器发生变化的情况 _monitorToFit = monitorIndex; UpdateMonitorFitting(); } } } else { if (_shouldFitMonitor) { // 之前是适配状态,现在被取消的情况 _monitorToFit = monitorIndex; _shouldFitMonitor = shouldFit; UpdateMonitorFitting(); _uniWinCore.SetZoomed(false); //uniWinCore.SetWindowSize(originalWindowRectangle.size); //uniWinCore.SetWindowPosition(originalWindowRectangle.position); } else { // 未在适配中时,仅更改选择 _monitorToFit = monitorIndex; } } return true; } /// /// 获取鼠标光标位置 /// /// 光标位置 public static Vector2 GetCursorPosition() { return UniWinCore.GetCursorPosition(); } /// /// 设置鼠标光标位置 /// /// public static void SetCursorPosition(Vector2 position) { UniWinCore.SetCursorPosition(position); } /// /// 获取鼠标按键状态 /// /// public static MouseButton GetMouseButtons() { int buttons = UniWinCore.GetMouseButtons(); return (MouseButton)buttons; } /// /// 获取按下的修饰键 /// /// public static ModifierKey GetModifierKeys() { int mod = UniWinCore.GetModifierKeys(); return (ModifierKey)mod; } /// /// 退出时需要恢复窗口状态 /// void OnApplicationQuit() { if (Application.isPlaying) { if (_uniWinCore != null) { // 在编辑器中恢复窗口状态 // 在独立构建中会看到恢复过程,因此跳过 #if UNITY_EDITOR _uniWinCore.SetWindowSize(originalWindowRectangle.size); _uniWinCore.SetWindowPosition(originalWindowRectangle.position); _uniWinCore.DetachWindow(); #endif _uniWinCore.Dispose(); } } } /// /// 将焦点给予自身窗口 /// public void Focus() { if (_uniWinCore != null) { //uniWin.SetFocus(); } } /// /// 仅供调试用。用于获取各阶段参考信息的函数 /// /// [Obsolete] public int GetDebugInfo() { if (_uniWinCore != null) { return UniWinCore.GetDebugInfo(); } return 0; } } }