init uniWindowController 0.9.8

This commit is contained in:
2026-06-14 22:49:28 +08:00
commit bfce70eb6f
95 changed files with 30126 additions and 0 deletions
@@ -0,0 +1,6 @@
{
"name": "Lrss3.Xuniwindowcontroller",
"references": [],
"includePlatforms": [],
"excludePlatforms": []
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4df3d67771614d24192762dbffd67d98
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b9161ad40887a5746afa1d4eb780f60a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 186ab6f8e4a960342b3bac311f6b2eb5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 04ad9f4fa34d2ed43a2c2a2b15c9959e
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
DefaultValueInitialized: true
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>25C56</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>LibUniWinC</string>
<key>CFBundleIdentifier</key>
<string>com.kirurobo.LibUniWinC</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>LibUniWinC</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>0.9.8</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>25B74</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>26.1</string>
<key>DTSDKBuild</key>
<string>25B74</string>
<key>DTSDKName</key>
<string>macosx26.1</string>
<key>DTXcode</key>
<string>2610</string>
<key>DTXcodeBuild</key>
<string>17B55</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019-2025 kirurobo. All rights reserved.</string>
</dict>
</plist>
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5d40a6da1308b51499d69d09998a1672
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 52438bdad5ba3524daae6d7c7050491e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,27 @@
fileFormatVersion: 2
guid: 2587e857c903c7648bb243f26d8388c5
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1afe0de787d9c9b419c0083741340611
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,27 @@
fileFormatVersion: 2
guid: 94d4ed4ccee04184c93f70e00c6f442c
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5807c8581d7c4be4ca838592a3a5ce8e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4ecd974a2286b994b8432ec73c8e5bd2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+209
View File
@@ -0,0 +1,209 @@
using AOT;
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Kirurobo
{
/// <summary>
/// 提供打开原生文件对话框的静态方法
/// </summary>
public class FilePanel
{
protected class LibUniWinC
{
[DllImport("LibUniWinC", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenFilePanel(in PanelSettings settings, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder buffer, UInt32 bufferSize);
[DllImport("LibUniWinC", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenSavePanel(in PanelSettings settings, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder buffer, UInt32 bufferSize);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PanelSettings : IDisposable {
public Int32 structSize;
public Int32 flags;
public IntPtr lpszTitle;
public IntPtr lpszFilter;
public IntPtr lpszInitialFile;
public IntPtr lpszInitialDir;
public IntPtr lpszDefaultExt;
public PanelSettings(Settings settings)
{
this.structSize = 0;
//this.structSize = 4 * 2 + Marshal.SizeOf<IntPtr>() * 3;
this.flags = (Int32)settings.flags;
//this.lpTitleText = IntPtr.Zero;
//this.lpFilterText = IntPtr.Zero;
//this.lpDefaultPath = IntPtr.Zero;
this.lpszTitle = Marshal.StringToHGlobalUni(settings.title);
this.lpszFilter = Marshal.StringToHGlobalUni(Filter.Join(settings.filters));
this.lpszInitialFile = Marshal.StringToHGlobalUni(settings.initialFile);
this.lpszInitialDir = Marshal.StringToHGlobalUni(settings.initialDirectory);
//this.lpszDefaultExt = Marshal.StringToHGlobalUni(settings.defaultExtension);
this.lpszDefaultExt = IntPtr.Zero;
//this.structSize = Marshal.SizeOf(this);
this.structSize = Marshal.SizeOf(this);
}
public void Dispose()
{
if (this.lpszTitle != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszTitle);
this.lpszTitle = IntPtr.Zero;
}
if (this.lpszFilter!= IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszFilter);
this.lpszFilter= IntPtr.Zero;
}
if (this.lpszInitialFile!= IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszInitialFile);
this.lpszInitialFile= IntPtr.Zero;
}
if (this.lpszInitialDir != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszInitialDir);
this.lpszInitialDir = IntPtr.Zero;
}
if (this.lpszDefaultExt != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszDefaultExt);
this.lpszDefaultExt = IntPtr.Zero;
}
}
}
}
/// <summary>
/// 对话框的设置标志
/// </summary>
[Flags]
public enum Flag
{
None = 0,
FileMustExist = 1, // 仅 Windows
FolderMustExist = 2, // 仅 Windows
AllowMultipleSelection = 4,
CanCreateDirectories = 16,
OverwritePrompt = 256, // macOS 上始终启用
CreatePrompt = 512, // macOS 上始终启用
ShowHiddenFiles = 4096,
RetrieveLink = 8192,
}
/// <summary>
/// 文件对话框的参数
/// </summary>
public struct Settings
{
public string title;
public Filter[] filters;
public string initialDirectory;
public string initialFile;
public string defaultExtension; // 未实现
public Flag flags;
}
/// <summary>
/// 文件过滤器
/// </summary>
public class Filter
{
protected string title;
protected string[] extensions;
/// <summary>
///
/// </summary>
/// <param name="title">过滤器标题(macOS 上暂不可用)</param>
/// <param name="extensions">扩展名数组,如 ["png", "jpg", "txt"]</param>
public Filter(string title, params string[] extensions)
{
this.title = title;
this.extensions = extensions;
}
public override string ToString()
{
return title + "\t" + String.Join("\t", extensions);
}
/// <summary>
/// 返回由 Filter 数组转换后的字符串
/// </summary>
/// <param name="filters"></param>
/// <returns></returns>
public static string Join(Filter[] filters)
{
if (filters == null) return "";
string result = "";
bool isFirstItem = true;
foreach (var filter in filters) {
if (!isFirstItem) result += "\n";
result += filter.ToString();
isFirstItem = false;
}
return result;
}
}
/// <summary>
/// 用于传递文件或文件夹路径的 UTF-16 缓冲区字符数
/// 因为多个路径以换行符分隔,260 字符不够用。
/// </summary>
private const int pathBufferSize = 2560;
/// <summary>
/// 打开文件选择对话框
/// </summary>
/// <param name="settings"></param>
/// <param name="action"></param>
public static void OpenFilePanel(Settings settings, Action<string[]> action)
{
LibUniWinC.PanelSettings ps = new LibUniWinC.PanelSettings(settings);
StringBuilder sb = new StringBuilder(pathBufferSize);
if (LibUniWinC.OpenFilePanel(in ps, sb, (uint)sb.Capacity))
{
string[] files = UniWinCore.parsePaths(sb.ToString());
action.Invoke(files);
}
ps.Dispose(); // 通过传入 Settings 的构造函数分配了内存,因此需要释放
}
/// <summary>
/// 打开保存文件选择对话框
/// </summary>
/// <param name="settings"></param>
/// <param name="action"></param>
public static void SaveFilePanel(Settings settings, Action<string[]> action)
{
LibUniWinC.PanelSettings ps = new LibUniWinC.PanelSettings(settings);
StringBuilder sb = new StringBuilder(pathBufferSize);
if (LibUniWinC.OpenSavePanel(in ps, sb, (uint)sb.Capacity))
{
string[] files = UniWinCore.parsePaths(sb.ToString());
action.Invoke(files);
}
ps.Dispose(); // 通过传入 Settings 的构造函数分配了内存,因此需要释放
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ade63dbb28ba23c40bfc1795ae2b605c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+926
View File
@@ -0,0 +1,926 @@
/*
* UniWinCore.cs
*
* Author: Kirurobo http://twitter.com/kirurobo
* License: MIT 许可证
*/
using System;
using System.Runtime.InteropServices;
using AOT;
using UnityEngine;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Kirurobo
{
/// <summary>
/// LibUniWinC 的原生插件包装
/// </summary>
internal class UniWinCore : IDisposable
{
/// <summary>
/// 仅 Windows 下的透明方法类型
/// </summary>
public enum TransparentType : int
{
None = 0,
Alpha = 1,
ColorKey = 2,
}
/// <summary>
/// 状态变更事件类型(实验性)
/// </summary>
[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,
};
#region Native functions
protected class LibUniWinC
{
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate void StringCallback([MarshalAs(UnmanagedType.LPWStr)] string returnString);
[UnmanagedFunctionPointer((CallingConvention.Winapi))]
public delegate void IntCallback([MarshalAs(UnmanagedType.I4)] int value);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsActive();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsTransparent();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsBorderless();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsTopmost();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsBottommost();
[DllImport("LibUniWinC", CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsMaximized();
[DllImport("LibUniWinC", CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsFreePositioningEnabled();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AttachMyWindow();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AttachMyOwnerWindow();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AttachMyActiveWindow();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DetachWindow();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void Update();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetTransparent([MarshalAs(UnmanagedType.U1)] bool bEnabled);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetBorderless([MarshalAs(UnmanagedType.U1)] bool bEnabled);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetAlphaValue(float alpha);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetClickThrough([MarshalAs(UnmanagedType.U1)] bool bEnabled);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetTopmost([MarshalAs(UnmanagedType.U1)] bool bEnabled);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetBottommost([MarshalAs(UnmanagedType.U1)] bool bEnabled);
[DllImport("LibUniWinC", CallingConvention = CallingConvention.Winapi)]
public static extern void SetMaximized([MarshalAs(UnmanagedType.U1)] bool bZoomed);
[DllImport("LibUniWinC", CallingConvention = CallingConvention.Winapi)]
public static extern void EnableFreePositioning([MarshalAs(UnmanagedType.U1)] bool bEnabled);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetPosition(float x, float y);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetPosition(out float x, out float y);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetSize(float x, float y);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetSize(out float x, out float y);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetClientSize(out float width, out float height);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetClientRectangle(out float x, out float y, out float width, out float height);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegisterDropFilesCallback([MarshalAs(UnmanagedType.FunctionPtr)] StringCallback callback);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnregisterDropFilesCallback();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegisterMonitorChangedCallback([MarshalAs(UnmanagedType.FunctionPtr)] IntCallback callback);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnregisterMonitorChangedCallback();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegisterWindowStyleChangedCallback([MarshalAs(UnmanagedType.FunctionPtr)] IntCallback callback);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnregisterWindowStyleChangedCallback();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetAllowDrop([MarshalAs(UnmanagedType.U1)] bool enabled);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern int GetCurrentMonitor();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern int GetMonitorCount();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorRectangle(int index, out float x, out float y, out float width, out float height);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetCursorPosition(float x, float y);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPosition(out float x, out float y);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern int GetMouseButtons();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern int GetModifierKeys();
#region 仅适用于 Windows
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetTransparentType(int type);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern void SetKeyColor(uint colorref);
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
public static extern int GetDebugInfo();
[DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AttachWindowHandle(IntPtr hWnd);
#endregion
}
#endregion
static string[] lastDroppedFiles;
static bool wasDropped = false;
static bool wasMonitorChanged = false;
static bool wasWindowStyleChanged = false;
static WindowStateEventType windowStateEventType = WindowStateEventType.None;
#if UNITY_EDITOR
/// <summary>
/// 获取 Unity 编辑器窗口
/// </summary>
/// <returns></returns>
/// <seealso href="http://baba-s.hatenablog.com/entry/2017/09/17/135018"/>
public static EditorWindow GetGameView()
{
var assembly = typeof(EditorWindow).Assembly;
var type = assembly.GetType("UnityEditor.GameView");
var gameView = EditorWindow.GetWindow(type);
return gameView;
}
#endif
/// <summary>
/// 判断窗口是否已附加且可用
/// </summary>
/// <value><c>true</c> 如果此实例处于活动状态;否则为 <c>false</c>。</value>
public bool IsActive { get; private set; } = false;
/// <summary>
/// 判断附加窗口是否始终置顶
/// </summary>
public bool IsTopmost { get { return (IsActive && _isTopmost); } }
private bool _isTopmost = false;
/// <summary>
/// 判断附加窗口是否始终置底
/// </summary>
public bool IsBottommost { get { return (IsActive && _isBottommost); } }
private bool _isBottommost = false;
/// <summary>
/// 判断附加窗口是否透明
/// </summary>
public bool IsTransparent { get { return (IsActive && _isTransparent); } }
private bool _isTransparent = false;
/// <summary>
/// 判断附加窗口是否点击穿透(即不接收任何鼠标操作)
/// </summary>
public bool IsClickThrough { get { return (IsActive && _isClickThrough); } }
private bool _isClickThrough = false;
/// <summary>
/// 判断附加窗口是否无边框(无标题栏和边框)
/// </summary>
public bool IsBorderless { get { return (IsActive && _isBorderless); } }
private bool _isBorderless = false;
/// <summary>
/// 判断附加窗口是否可以自由定位(仅 macOS)
/// </summary>
public bool IsFreePositioningEnabled { get { return (IsActive && _isFreePositioningEnabled); } }
private bool _isFreePositioningEnabled = false;
/// <summary>
/// Windows 下的透明方法类型
/// </summary>
private TransparentType transparentType = TransparentType.Alpha;
/// <summary>
/// 当 transparentType 为 ColorKey 时用于透明的颜色
/// </summary>
private Color32 keyColor = new Color32(1, 0, 1, 0);
#region Constructor or destructor
/// <summary>
/// 窗口控制构造函数
/// </summary>
public UniWinCore()
{
IsActive = false;
}
/// <summary>
/// 析构函数
/// </summary>
~UniWinCore()
{
Dispose();
}
/// <summary>
/// 结束时的处理
/// </summary>
public void Dispose()
{
// 由于最后恢复窗口状态会引起注意,所以特意不恢复,因此注释掉
//DetachWindow();
// 替代 DetachWindow()
LibUniWinC.UnregisterDropFilesCallback();
LibUniWinC.UnregisterMonitorChangedCallback();
LibUniWinC.UnregisterWindowStyleChangedCallback();
}
#endregion
#region Callbacks
/// <summary>
/// 显示器或分辨率变化时的回调
/// 此处的处理保持最低限度,仅设置标志
/// </summary>
/// <param name="monitorCount"></param>
[MonoPInvokeCallback(typeof(LibUniWinC.IntCallback))]
private static void _monitorChangedCallback([MarshalAs(UnmanagedType.I4)] int monitorCount)
{
wasMonitorChanged = true;
}
/// <summary>
/// 窗口样式、最大化、最小化等调用的回调
/// 此处的处理保持最低限度,仅设置标志
/// </summary>
/// <param name="e"></param>
[MonoPInvokeCallback(typeof(LibUniWinC.IntCallback))]
private static void _windowStyleChangedCallback([MarshalAs(UnmanagedType.I4)] int e)
{
wasWindowStyleChanged = true;
windowStateEventType = (WindowStateEventType)e;
}
/// <summary>
/// ファイル、フォルダがドロップされた時に呼ばれるコールバック
/// 文字列を配列に直すことと、フラグを立てるまで行う
/// </summary>
/// <param name="paths"></param>
[MonoPInvokeCallback(typeof(LibUniWinC.StringCallback))]
private static void _dropFilesCallback([MarshalAs(UnmanagedType.LPWStr)] string paths)
{
// 将以 LF 分隔的字符串分割为路径数组
//char[] delimiters = { '\n', '\0' };
//string[] files = paths.Split(delimiters).Where(s => s != "").ToArray();
string[] files = parsePaths(paths);
if (files.Length > 0)
{
lastDroppedFiles = new string[files.Length];
files.CopyTo(lastDroppedFiles, 0);
wasDropped = true;
}
}
/// <summary>
/// 将双引号包围、LF(或null)分隔的字符串转换为数组并返回
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
internal static string[] parsePaths(string text)
{
System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();
bool inEscaped = false;
int len = text.Length;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++)
{
char c = text[i];
if (c == '"')
{
if (inEscaped)
{
if (((i + 1) < len) && text[i + 1] == '"')
{
i++;
sb.Append(c); // 连续双引号视为一个双引号
continue;
}
}
inEscaped = !inEscaped; // 非连续则切换是否在引号内
}
else if (c == '\n')
{
if (inEscaped)
{
// 如果在引号内,则作为路径的一部分
sb.Append(c);
}
else
{
// 如果不在引号内,则作为分隔符,移到下一个路径
if (sb.Length > 0)
{
list.Add(sb.ToString());
//sb.Clear(); // for .NET 4 or later
sb.Length = 0; // for .NET 2
}
}
}
else if (c == '\0')
{
// 空字符始终作为分隔符,移到下一个路径
if (sb.Length > 0)
{
list.Add(sb.ToString());
//sb.Clear(); // for .NET 4 or later
sb.Length = 0; // for .NET 2
}
}
else
{
sb.Append(c);
}
}
if (sb.Length > 0)
{
list.Add(sb.ToString());
}
// 移除空字符串元素
list.RemoveAll(v => v.Length == 0);
return list.ToArray();
}
#endregion
#region Find, attach or detach
/// <summary>
/// 将窗口状态恢复至最初并从操作对象中解除
/// </summary>
public void DetachWindow()
{
#if UNITY_EDITOR
// エディタの場合、ウィンドウスタイルでは常に最前面と得られていない可能性があるため、
// 最前面ではないのが本来と決め打ちで、デタッチ時無効化する
EnableTopmost(false);
#endif
LibUniWinC.DetachWindow();
}
/// <summary>
/// 查找自己的窗口(如果游戏视图是独立窗口则查找它)并作为操作对象
/// </summary>
/// <returns></returns>
public bool AttachMyWindow()
{
#if UNITY_EDITOR_WIN
// 似乎没有可靠的方法获取游戏视图,因此给予焦点后立即获取活动窗口
var gameView = GetGameView();
if (gameView)
{
gameView.Focus();
LibUniWinC.AttachMyActiveWindow();
}
#else
LibUniWinC.AttachMyWindow();
#endif
// 添加事件处理程序
LibUniWinC.RegisterDropFilesCallback(_dropFilesCallback);
LibUniWinC.RegisterMonitorChangedCallback(_monitorChangedCallback);
LibUniWinC.RegisterWindowStyleChangedCallback(_windowStyleChangedCallback);
IsActive = LibUniWinC.IsActive();
return IsActive;
}
public bool AttachWindowHandle(IntPtr hWnd)
{
LibUniWinC.AttachWindowHandle(hWnd);
IsActive = LibUniWinC.IsActive();
return IsActive;
}
/// <summary>
/// 选择自己进程中当前活动的窗口
/// 编辑器情况下,窗口会关闭或停靠,因此在聚焦时调用
/// </summary>
/// <returns></returns>
public bool AttachMyActiveWindow()
{
LibUniWinC.AttachMyActiveWindow();
IsActive = LibUniWinC.IsActive();
return IsActive;
}
#endregion
#region About window status
/// <summary>
/// 定期调用以维持窗口样式
/// </summary>
public void Update()
{
LibUniWinC.Update();
}
string GetDebubgWindowSizeInfo()
{
float x, y, cx, cy;
LibUniWinC.GetSize(out x, out y);
LibUniWinC.GetClientSize(out cx, out cy);
return $"W:{x},H:{y} CW:{cx},CH:{cy}";
}
/// <summary>
/// 设置/取消透明
/// </summary>
/// <param name="isTransparent"></param>
public void EnableTransparent(bool isTransparent)
{
// 编辑器无法透明或边框与正常不同,因此跳过
#if !UNITY_EDITOR
LibUniWinC.SetTransparent(isTransparent);
LibUniWinC.SetBorderless(isTransparent);
#endif
this._isTransparent = isTransparent;
}
/// <summary>
/// 设置窗口透明度
/// </summary>
/// <param name="alpha">0.0 - 1.0</param>
public void SetAlphaValue(float alpha)
{
// Windows 编辑器下,一旦半透明化后显示不会更新,因此禁用。Mac 则没问题
#if !UNITY_EDITOR_WIN
LibUniWinC.SetAlphaValue(alpha);
#endif
}
/// <summary>
/// 设置窗口 Z 顺序(是否置顶)。
/// </summary>
/// <param name="isTopmost">如果设为 <c>true</c> 则置顶。</param>
public void EnableTopmost(bool isTopmost)
{
LibUniWinC.SetTopmost(isTopmost);
this._isTopmost = isTopmost;
this._isBottommost = false; // 互斥
}
/// <summary>
/// 设置窗口 Z 顺序(是否置底)。
/// </summary>
/// <param name="isBottommost">如果设为 <c>true</c> 则置底。</param>
public void EnableBottommost(bool isBottommost)
{
LibUniWinC.SetBottommost(isBottommost);
this._isBottommost = isBottommost;
this._isTopmost = false; // 互斥
}
/// <summary>
/// 设置/取消点击穿透
/// </summary>
/// <param name="isThrough"></param>
public void EnableClickThrough(bool isThrough)
{
// 编辑器下点击穿透可能导致无法操作,因此跳过
#if !UNITY_EDITOR
LibUniWinC.SetClickThrough(isThrough);
#endif
this._isClickThrough = isThrough;
}
/// <summary>
/// 最大化窗口(Mac 上为缩放)
/// 最大化后可能还会调整大小,目前可能无法可靠工作
/// </summary>
public void SetZoomed(bool isZoomed)
{
LibUniWinC.SetMaximized(isZoomed);
}
/// <summary>
/// 获取窗口是否已最大化(Mac 上为缩放)
/// 最大化后可能还会调整大小,目前可能无法可靠工作
/// </summary>
public bool GetZoomed()
{
return LibUniWinC.IsMaximized();
}
/// <summary>
/// 设置窗口位置。
/// </summary>
/// <param name="position">位置。</param>
public void SetWindowPosition(Vector2 position)
{
LibUniWinC.SetPosition(position.x, position.y);
}
/// <summary>
/// 获取窗口位置。
/// </summary>
/// <returns>位置。</returns>
public Vector2 GetWindowPosition()
{
Vector2 pos = Vector2.zero;
LibUniWinC.GetPosition(out pos.x, out pos.y);
return pos;
}
/// <summary>
/// 设置窗口大小。
/// </summary>
/// <param name="size">x 为宽度,y 为高度</param>
public void SetWindowSize(Vector2 size)
{
LibUniWinC.SetSize(size.x, size.y);
}
/// <summary>
/// 获取窗口大小。
/// </summary>
/// <returns>x 为宽度,y 为高度</returns>
public Vector2 GetWindowSize()
{
Vector2 size = Vector2.zero;
LibUniWinC.GetSize(out size.x, out size.y);
return size;
}
/// <summary>
/// 获取客户区大小。
/// </summary>
/// <returns>x 为宽度,y 为高度</returns>
public Vector2 GetClientSize()
{
Vector2 size = Vector2.zero;
LibUniWinC.GetClientSize(out size.x, out size.y);
return size;
}
/// <summary>
/// 获取客户区矩形。
/// </summary>
/// <returns>x 为宽度,y 为高度</returns>
public Rect GetClientRectangle()
{
Vector2 pos = Vector2.zero;
Vector2 size = Vector2.zero;
LibUniWinC.GetClientRectangle(out pos.x, out pos.y, out size.x, out size.y);
return new Rect(pos.x, pos.y, size.x, size.y);
}
#endregion
#region File opening
public void SetAllowDrop(bool enabled)
{
LibUniWinC.SetAllowDrop(enabled);
}
#endregion
#region Event observers
/// <summary>
/// 检查文件拖放并取消拖放标志
/// </summary>
/// <param name="files"></param>
/// <returns>如果文件被拖放则返回 true</returns>
public bool ObserveDroppedFiles(out string[] files)
{
files = lastDroppedFiles;
if (!wasDropped || files == null) return false;
wasDropped = false;
return true;
}
/// <summary>
/// 检查显示器数量或分辨率变化,并取消标志
/// </summary>
/// <returns>如果已变化则返回 true</returns>
public bool ObserveMonitorChanged()
{
if (!wasMonitorChanged) return false;
wasMonitorChanged = false;
return true;
}
/// <summary>
/// 检查窗口样式是否已变更,并取消标志
/// </summary>
/// <returns>如果窗口样式已变更则返回 true</returns>
public bool ObserveWindowStyleChanged()
{
if (!wasWindowStyleChanged) return false;
windowStateEventType = WindowStateEventType.None;
wasWindowStyleChanged = false;
return true;
}
/// <summary>
/// 检查窗口样式是否已变更,并取消标志
/// </summary>
/// <returns>如果窗口样式已变更则返回 true</returns>
public bool ObserveWindowStyleChanged(out WindowStateEventType type)
{
if (!wasWindowStyleChanged)
{
type = WindowStateEventType.None;
return false;
}
type = windowStateEventType;
windowStateEventType = WindowStateEventType.None;
wasWindowStyleChanged = false;
return true;
}
#endregion
#region About mouse cursor
/// <summary>
/// 设置鼠标指针位置。
/// </summary>
/// <param name="position">位置。</param>
public static void SetCursorPosition(Vector2 position)
{
LibUniWinC.SetCursorPosition(position.x, position.y);
}
/// <summary>
/// 获取鼠标指针位置。
/// </summary>
/// <returns>位置。</returns>
public static Vector2 GetCursorPosition()
{
Vector2 pos = Vector2.zero;
LibUniWinC.GetCursorPosition(out pos.x, out pos.y);
return pos;
}
/// <summary>
/// Get pressed mouse buttons.
/// </summary>
/// <returns>Bit flags of pressed buttons</returns>
public static int GetMouseButtons()
{
return LibUniWinC.GetMouseButtons();
}
/// <summary>
/// 获取按下的修饰键。
/// </summary>
/// <returns>按下键的位标志</returns>
public static int GetModifierKeys()
{
return LibUniWinC.GetModifierKeys();
}
// 未实现
public static bool GetCursorVisible()
{
return true;
}
#endregion
#region for Windows only
/// <summary>
/// 指定透明方法(仅 Windows 支持)
/// </summary>
/// <param name="type"></param>
public void SetTransparentType(TransparentType type)
{
LibUniWinC.SetTransparentType((Int32)type);
transparentType = type;
}
/// <summary>
/// 单色透明时指定透明色(仅 Windows 支持)
/// </summary>
/// <param name="color"></param>
public void SetKeyColor(Color32 color)
{
LibUniWinC.SetKeyColor((UInt32)(color.b * 0x10000 + color.g * 0x100 + color.r));
keyColor = color;
}
#endregion
#region for macOS only
/// <summary>
/// 设置/取消窗口的自由配置(仅 macOS 支持)
/// </summary>
/// <param name="enabled"></param>
public void EnableFreePositioning(bool enabled)
{
LibUniWinC.EnableFreePositioning(enabled);
_isFreePositioningEnabled = LibUniWinC.IsFreePositioningEnabled();
}
#endregion
#region About monitors
/// <summary>
/// 获取窗口所在显示器的索引
/// </summary>
/// <returns>显示器索引</returns>
public int GetCurrentMonitor()
{
return LibUniWinC.GetCurrentMonitor();
}
/// <summary>
/// 获取已连接显示器的数量
/// </summary>
/// <returns>数量</returns>
public static int GetMonitorCount()
{
return LibUniWinC.GetMonitorCount();
}
/// <summary>
/// 获取显示器的位置和大小
/// </summary>
/// <param name="index"></param>
/// <param name="position"></param>
/// <param name="size"></param>
/// <returns></returns>
public static bool GetMonitorRectangle(int index, out Vector2 position, out Vector2 size)
{
return LibUniWinC.GetMonitorRectangle(index, out position.x, out position.y, out size.x, out size.y);
}
/// <summary>
/// 将窗口适配到指定显示器
/// </summary>
/// <param name="monitorIndex"></param>
/// <returns></returns>
public bool FitToMonitor(int monitorIndex)
{
float dx, dy, dw, dh;
if (LibUniWinC.GetMonitorRectangle(monitorIndex, out dx, out dy, out dw, out dh))
{
// 如果处于最大化状态则先恢复
if (LibUniWinC.IsMaximized()) LibUniWinC.SetMaximized(false);
// 指定显示器的中心坐标
float cx = dx + (dw / 2);
float cy = dy + (dh / 2);
// 将窗口中心移动到指定显示器中心
float ww, wh;
LibUniWinC.GetSize(out ww, out wh);
float wx = cx - (ww / 2);
float wy = cy - (wh / 2);
LibUniWinC.SetPosition(wx, wy);
// 最大化
LibUniWinC.SetMaximized(true);
//Debug.Log(String.Format("显示器 {4} : {0},{1} - {2},{3}", dx, dy, dw, dh, monitorIndex));
return true;
}
return false;
}
/// <summary>
/// 打印显示器列表
/// </summary>
[Obsolete]
public static void DebugMonitorInfo()
{
int monitors = LibUniWinC.GetMonitorCount();
int currentMonitorIndex = LibUniWinC.GetCurrentMonitor();
string message = "当前显示器: " + currentMonitorIndex + "\r\n";
for (int i = 0; i < monitors; i++)
{
float x, y, w, h;
bool result = LibUniWinC.GetMonitorRectangle(i, out x, out y, out w, out h);
message += String.Format(
"显示器 {0}: X:{1}, Y:{2} - W:{3}, H:{4}\r\n",
i, x, y, w, h
);
}
Debug.Log(message);
}
/// <summary>
/// 获取用于调试的信息
/// </summary>
/// <returns></returns>
[Obsolete]
public static int GetDebugInfo()
{
return LibUniWinC.GetDebugInfo();
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4930552cf3596b040954b14d8b7c47e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d327b245537480646bd85e511002d6ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+206
View File
@@ -0,0 +1,206 @@
/*
* UniWindowDragMove.cs
*
* 作者: Kirurobo http://twitter.com/kirurobo
* 许可证: MIT
*/
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
#if ENABLE_LEGACY_INPUT_MANAGER
#elif ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
namespace Kirurobo
{
public class UniWindowMoveHandle : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler, IPointerUpHandler
{
private UniWindowController _uniwinc;
/// <summary>
/// 窗口最大化时是否禁用移动
/// </summary>
[Tooltip("窗口已最大化(缩放)时禁用拖拽移动。")]
public bool disableOnZoomed = true;
/// <summary>
/// 拖动中则为 true
/// </summary>
public bool IsDragging
{
get { return _isDragging; }
}
private bool _isDragging = false;
/// <summary>
/// 是否进行拖动
/// </summary>
private bool IsEnabled
{
get { return enabled && (!disableOnZoomed || !IsZoomed); }
}
/// <summary>
/// 是否适配显示器或最大化
/// </summary>
private bool IsZoomed
{
get { return (_uniwinc && (_uniwinc.shouldFitMonitor || _uniwinc.isZoomed)); }
}
/// <summary>
/// 记录拖动前自动命中测试是否启用
/// </summary>
private bool _isHitTestEnabled;
/// <summary>
/// 拖动开始时窗口内坐标[像素]
/// </summary>
private Vector2 _dragStartedPosition;
// 首次帧更新前调用 Start
void Start()
{
// 获取场景中的 UniWindowController
_uniwinc = GameObject.FindAnyObjectByType<UniWindowController>();
if (_uniwinc) _isHitTestEnabled = _uniwinc.isHitTestEnabled;
//// 下面的代码似乎不需要,所以注释掉以免擅自更改
//Input.simulateMouseWithTouches = false;
}
/// <summary>
/// 拖动开始时的处理
/// </summary>
public void OnBeginDrag(PointerEventData eventData)
{
if (!IsEnabled)
{
return;
}
// 仅通过鼠标左键拖动
if (eventData.button != PointerEventData.InputButton.Left) return;
// Mac 上行为会有所不同
// 实际上仅在 Retina 支持启用时,但 eventData.position 的坐标系与窗口坐标系的缩放会不一致
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
_dragStartedPosition = _uniwinc.windowPosition - _uniwinc.cursorPosition;
#else
_dragStartedPosition = eventData.position;
#endif
// 如果 _isDragging 为 false,则判断即将开始拖动
if (!_isDragging)
{
// 拖动期间禁用命中测试
_isHitTestEnabled = _uniwinc.isHitTestEnabled;
_uniwinc.isHitTestEnabled = false;
_uniwinc.isClickThrough = false;
}
_isDragging = true;
}
/// <summary>
/// 拖动结束时的处理
/// </summary>
public void OnEndDrag(PointerEventData eventData)
{
EndDragging();
}
/// <summary>
/// 鼠标抬起时也视为拖动结束
/// </summary>
/// <param name="eventData"></param>
public void OnPointerUp(PointerEventData eventData)
{
EndDragging();
}
/// <summary>
/// 结束拖动
/// </summary>
private void EndDragging()
{
if (_isDragging)
{
_uniwinc.isHitTestEnabled = _isHitTestEnabled;
}
_isDragging = false;
}
/// <summary>
/// 非最大化时,通过鼠标拖动移动窗口
/// </summary>
public void OnDrag(PointerEventData eventData)
{
if (!_uniwinc || !_isDragging) return;
// 如果拖动移动已被禁用,则结束拖动
if (!IsEnabled)
{
EndDragging();
return;
}
// // 如果鼠标左键未按下,则结束拖动
// if (eventData.button != PointerEventData.InputButton.Left) return;
// [Shift]、[Ctrl]、[Alt]、[Command] 键按下期间不视为拖动
var modifiers = UniWindowController.GetModifierKeys();
if (modifiers != UniWindowController.ModifierKey.None) return;
// 如果鼠标按钮已松开,则结束拖动
var buttons = UniWindowController.GetMouseButtons();
if ((buttons & UniWindowController.MouseButton.Left) == UniWindowController.MouseButton.None) {
EndDragging();
return;
}
// #if ENABLE_LEGACY_INPUT_MANAGER
// // 在 Mac 上,如果在多显示器之间移动,EventSystem 的 OnEndDrag 可能无法正确调用,因此始终监控鼠标按钮
// if (!Input.Mouse.Button(0).IsPressed) {
// EndDragging();
// return;
// }
// if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)
// || Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)
// || Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) return;
// #elif ENABLE_INPUT_SYSTEM
// // 在 Mac 上,如果在多显示器之间移动,EventSystem 的 OnEndDrag 可能无法正确调用,因此始终监控鼠标按钮
// if (!Mouse.current.leftButton.isPressed) {
// EndDragging();
// return;
// }
// if (Keyboard.current[Key.LeftShift].isPressed || Keyboard.current[Key.RightShift].isPressed
// || Keyboard.current[Key.LeftCtrl].isPressed || Keyboard.current[Key.RightCtrl].isPressed
// || Keyboard.current[Key.LeftAlt].isPressed || Keyboard.current[Key.RightAlt].isPressed) return;
// #endif
// 如果是全屏则不移动窗口
// 在编辑器中可能会变为 true,因此仅在非编辑器环境下确认
#if !UNITY_EDITOR
if (Screen.fullScreen)
{
EndDragging();
return;
}
#endif
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
// 在 Mac 上,通过原生插件获取/设置光标位置
_uniwinc.windowPosition = _uniwinc.cursorPosition + _dragStartedPosition;
//Debug.Log("Drag start: " + _dragStartedPosition);
#else
// 在 Windows 上,为支持触控操作而使用 eventData.position
// 将窗口移动与起始位置一致的屏幕位置偏移量
_uniwinc.windowPosition += eventData.position - _dragStartedPosition;
#endif
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd641513c2924f7488734c8cac43310f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: